-
Notifications
You must be signed in to change notification settings - Fork 307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Мажирин Александр #252
Open
Kexogg
wants to merge
10
commits into
kontur-courses:master
Choose a base branch
from
Kexogg:homework
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Мажирин Александр #252
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
684cfb8
create project
Kexogg b6d5899
Renderer + tests
Kexogg 0427e65
add background and random color for rects
Kexogg df3fff6
add position generator
Kexogg 0ba6961
layouter & entrypoint
Kexogg 89d210a
tests & samples
Kexogg 51eb51b
refactor & fix typo in project name
Kexogg 6460a80
Resolve PR comments
Kexogg 4eb7c92
Resolve PR comments
Kexogg 79caa7d
address PR comments
Kexogg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
57 changes: 57 additions & 0 deletions
57
cs/TagsCloudVisualization/Layouter/CircularCloudLayouter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using SkiaSharp; | ||
|
||
namespace TagsCloudVisualization.Layouter | ||
{ | ||
public class CircularCloudLayouter : ICircularCloudLayouter | ||
{ | ||
private readonly List<SKRect> rectangles; | ||
private readonly SKPoint center; | ||
private double angle; | ||
private const double Step = 0.1; | ||
|
||
public CircularCloudLayouter(SKPoint center) | ||
{ | ||
rectangles = new List<SKRect>(); | ||
this.center = center; | ||
} | ||
|
||
public SKRect PutNextRectangle(SKSize rectangleSize) | ||
{ | ||
if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) | ||
throw new ArgumentException("Rectangle size must be positive", nameof(rectangleSize)); | ||
|
||
SKRect rectangle; | ||
|
||
do | ||
{ | ||
var centerOfRectangle = GetNextPosition(); | ||
var rectanglePosition = new SKPoint(centerOfRectangle.X - rectangleSize.Width / 2, | ||
centerOfRectangle.Y - rectangleSize.Height / 2); | ||
rectangle = new SKRect( | ||
rectanglePosition.X, | ||
rectanglePosition.Y, | ||
rectanglePosition.X + rectangleSize.Width, | ||
rectanglePosition.Y + rectangleSize.Height); | ||
} while (rectangles.Any(r => r.IntersectsWith(rectangle))); | ||
|
||
rectangles.Add(rectangle); | ||
return rectangle; | ||
} | ||
|
||
public SKRect[] GetRectangles() | ||
{ | ||
return rectangles.ToArray(); | ||
} | ||
|
||
private SKPoint GetNextPosition() | ||
{ | ||
var radius = Step * angle; | ||
var x = (float)(center.X + radius * Math.Cos(angle)); | ||
var y = (float)(center.Y + radius * Math.Sin(angle)); | ||
|
||
angle += Step; | ||
|
||
return new SKPoint(x, y); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using SkiaSharp; | ||
|
||
namespace TagsCloudVisualization.Layouter; | ||
|
||
public interface ICircularCloudLayouter | ||
{ | ||
SKRect PutNextRectangle(SKSize rectangleSize); | ||
SKRect[] GetRectangles(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using SkiaSharp; | ||
using TagsCloudVisualization.Layouter; | ||
using TagsCloudVisualization.Renderer; | ||
|
||
internal class Program | ||
{ | ||
private static void Main() | ||
{ | ||
Directory.CreateDirectory("results"); | ||
|
||
RenderCloud(GenerateRandomCloud(10), "results/cloud_10.png"); | ||
RenderCloud(GenerateRandomCloud(50), "results/cloud_50.png"); | ||
RenderCloud(GenerateRandomCloud(100), "results/cloud_100.png"); | ||
} | ||
|
||
private static SKRect[] GenerateRandomCloud(int count) | ||
{ | ||
var layouter = new CircularCloudLayouter(new SKPoint(500, 500)); | ||
var rectangleSizes = Enumerable.Range(0, count) | ||
.Select(_ => new SKSize(new Random().Next(10, 100), new Random().Next(10, 100))); | ||
return rectangleSizes.Select(layouter.PutNextRectangle).ToArray(); | ||
} | ||
|
||
private static void RenderCloud(SKRect[] rectangles, string path) | ||
{ | ||
var renderer = new Renderer(new SKSize(1000, 1000)); | ||
renderer.DrawRectangles(rectangles); | ||
var image = renderer.GetEncodedImage(); | ||
using var stream = File.OpenWrite(path); | ||
image.SaveTo(stream); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using SkiaSharp; | ||
|
||
namespace TagsCloudVisualization.Renderer; | ||
|
||
public interface IRenderer | ||
{ | ||
void DrawRectangles(IEnumerable<SKRect> rectangles); | ||
|
||
SKData GetEncodedImage(); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using SkiaSharp; | ||
|
||
namespace TagsCloudVisualization.Renderer; | ||
|
||
public class Renderer : IRenderer | ||
{ | ||
private readonly SKBitmap bitmap; | ||
private readonly SKPaint paint; | ||
|
||
public Renderer(SKSize size) | ||
{ | ||
bitmap = new SKBitmap((int)size.Width, (int)size.Height); | ||
paint = new SKPaint | ||
{ | ||
Color = SKColors.Black, | ||
IsStroke = true, | ||
TextSize = 24 | ||
}; | ||
using var canvas = new SKCanvas(bitmap); | ||
canvas.Clear(SKColors.LightGray); | ||
} | ||
|
||
public void DrawRectangles(IEnumerable<SKRect> rectangles) | ||
{ | ||
using var canvas = new SKCanvas(bitmap); | ||
foreach (var rectangle in rectangles) | ||
{ | ||
ValidateRectangle(rectangle); | ||
canvas.DrawRect(rectangle, paint); | ||
paint.Color = new SKColor((byte)(paint.Color.Red + 21), (byte)(paint.Color.Green + 43), | ||
(byte)(paint.Color.Blue + 67)); | ||
} | ||
} | ||
|
||
private void ValidateRectangle(SKRect rectangle) | ||
{ | ||
if (rectangle.Left < 0 || rectangle.Top < 0 || rectangle.Right > bitmap.Width || | ||
rectangle.Bottom > bitmap.Height) | ||
throw new ArgumentException("Rectangle is out of bounds"); | ||
if (rectangle.Left >= rectangle.Right || rectangle.Top >= rectangle.Bottom) | ||
throw new ArgumentException("Rectangle is invalid"); | ||
} | ||
|
||
public SKData GetEncodedImage() | ||
{ | ||
using var image = SKImage.FromBitmap(bitmap); | ||
return image.Encode(SKEncodedImageFormat.Png, 100); | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<GenerateProgramFile>false</GenerateProgramFile> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="SkiaSharp" Version="2.88.9"/> | ||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.9"/> | ||
</ItemGroup> | ||
|
||
</Project> |
112 changes: 112 additions & 0 deletions
112
cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
using NUnit.Framework.Interfaces; | ||
using TagsCloudVisualization.Layouter; | ||
using TagsCloudVisualization.Renderer; | ||
|
||
namespace TagsCloudVisualizationTests; | ||
|
||
[TestFixture] | ||
public class CircularCloudLayouterTests | ||
{ | ||
private CircularCloudLayouter layouter; | ||
private static readonly SKPoint Center = new(500, 500); | ||
private static readonly float Density = 0.65f; | ||
|
||
[SetUp] | ||
public void SetUp() | ||
{ | ||
layouter = new CircularCloudLayouter(Center); | ||
} | ||
|
||
[TearDown] | ||
public void TearDown() | ||
{ | ||
if (TestContext.CurrentContext.Result.Outcome.Status != TestStatus.Failed) return; | ||
SaveImage(); | ||
} | ||
|
||
private void SaveImage() | ||
{ | ||
var filename = "tests/layouter_" + TestContext.CurrentContext.Test.ID + ".png"; | ||
|
||
var renderer = new Renderer(new SKSize(Center.X * 2, Center.Y * 2)); | ||
renderer.DrawRectangles(layouter.GetRectangles()); | ||
var imageData = renderer.GetEncodedImage(); | ||
Directory.CreateDirectory("tests"); | ||
var path = Path.Combine(Directory.GetCurrentDirectory(), filename); | ||
using var stream = new FileStream(path, FileMode.Create); | ||
imageData.SaveTo(stream); | ||
Console.WriteLine($"Tag cloud visualization saved to file {path}"); | ||
} | ||
|
||
private IEnumerable<SKSize> GetRandomSizes(int count) | ||
{ | ||
var random = new Random(); | ||
for (var i = 0; i < count; i++) | ||
{ | ||
var size = new SKSize(random.Next(10, 100), random.Next(10, 100)); | ||
yield return size; | ||
} | ||
} | ||
|
||
[Test] | ||
public void PutNextRectangle_ShouldThrowArgumentException_WhenSizeNotPositive() | ||
{ | ||
Action action = () => layouter.PutNextRectangle(new SKSize(0, 100)); | ||
action.Should().Throw<ArgumentException>(); | ||
} | ||
|
||
[Test] | ||
public void PutNextRectangle_ShouldReturnRectangle() | ||
{ | ||
var rectangle = layouter.PutNextRectangle(new SKSize(100, 100)); | ||
|
||
rectangle.Should().BeEquivalentTo(new SKRect(Center.X - 50, Center.Y - 50, Center.X + 50, Center.Y + 50)); | ||
} | ||
|
||
[Test] | ||
public void PutNextRectangle_ShouldNotIntersectRectangles() | ||
{ | ||
var rectangles = new List<SKRect>(); | ||
for (var i = 0; i < 10; i++) rectangles.Add(layouter.PutNextRectangle(new SKSize(10, 10))); | ||
|
||
for (var i = 0; i < rectangles.Count - 1; i++) | ||
for (var j = i + 1; j < rectangles.Count; j++) | ||
rectangles[i].IntersectsWith(rectangles[j]).Should().BeFalse(); | ||
} | ||
|
||
[Test] | ||
[Repeat(3)] | ||
public void PutNextRectangle_ShouldGenerateDenseLayout() | ||
{ | ||
var sizes = GetRandomSizes(150); | ||
var rectangles = sizes.Select(size => layouter.PutNextRectangle(size)).ToArray(); | ||
var totalRectArea = rectangles.Sum(rect => rect.Width * rect.Height); | ||
var boundingCircleRadius = rectangles.Max(DistanceToCenter); | ||
var boundingCircleArea = Math.PI * boundingCircleRadius * boundingCircleRadius; | ||
var density = totalRectArea / boundingCircleArea; | ||
density.Should().BeGreaterOrEqualTo(Density); | ||
} | ||
|
||
[Test] | ||
[Repeat(3)] | ||
public void PutNextRectangle_ShouldPlaceRectanglesInCircle() | ||
{ | ||
var sizes = GetRandomSizes(150); | ||
var rectangles = sizes.Select(size => layouter.PutNextRectangle(size)).ToArray(); | ||
|
||
|
||
var presumedAverageSide = rectangles.Average(size => (size.Width + size.Height) / 2); | ||
var totalAreaOfRectangles = rectangles.Sum(rect => rect.Width * rect.Height); | ||
var circleRadius = Math.Sqrt(totalAreaOfRectangles / Density / Math.PI); | ||
var expectedMaxDistanceFromCenter = circleRadius + presumedAverageSide / 2; | ||
var maxDistanceFromCenter = (double)rectangles.Max(DistanceToCenter); | ||
|
||
maxDistanceFromCenter.Should().BeLessOrEqualTo(expectedMaxDistanceFromCenter); | ||
} | ||
|
||
private static float DistanceToCenter(SKRect rect) | ||
{ | ||
var rectCenter = new SKPoint(rect.MidX, rect.MidY); | ||
return SKPoint.Distance(Center, rectCenter); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
global using NUnit.Framework; | ||
global using FluentAssertions; | ||
global using SkiaSharp; | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
using NUnit.Framework.Interfaces; | ||
using TagsCloudVisualization.Renderer; | ||
|
||
namespace TagsCloudVisualizationTests; | ||
|
||
[TestFixture] | ||
public class RendererTests | ||
{ | ||
private const string DefaultFileName = "image.png"; | ||
private Renderer renderer; | ||
|
||
[SetUp] | ||
public void SetUp() | ||
{ | ||
renderer = new Renderer(new SKSize(100, 100)); | ||
} | ||
|
||
[TearDown] | ||
public void TearDown() | ||
{ | ||
if (File.Exists(DefaultFileName)) | ||
File.Delete(DefaultFileName); | ||
|
||
if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) | ||
{ | ||
SaveImage(); | ||
} | ||
} | ||
|
||
private void SaveImage() | ||
{ | ||
var filename = "tests/renderer_" + TestContext.CurrentContext.Test.ID + ".png"; | ||
Directory.CreateDirectory("tests"); | ||
var path = Path.Combine(Directory.GetCurrentDirectory(), filename); | ||
using var stream = new FileStream(path, FileMode.Create); | ||
var imageData = renderer.GetEncodedImage(); | ||
imageData.SaveTo(stream); | ||
Console.WriteLine($"Attempted to save result to file {path}"); | ||
} | ||
|
||
|
||
[Test] | ||
public void CreateImage_ShouldCreateImage() | ||
{ | ||
var image = renderer.GetEncodedImage(); | ||
image.Should().NotBeNull(); | ||
} | ||
|
||
[TestCase(0, 200)] | ||
[TestCase(-1, 50)] | ||
public void CreateRectangles_ShouldThrowException_WhenRectanglesAreOutOfBounds(int topLeft, int bottomRight) | ||
{ | ||
var action = () => renderer.DrawRectangles([new SKRect(topLeft, topLeft, bottomRight, bottomRight)]); | ||
action.Should().ThrowExactly<ArgumentException>(); | ||
} | ||
|
||
[TestCase(0, 0, 0, 0)] | ||
[TestCase(50, 50, 0, 0)] | ||
[TestCase(50, 0, 50, 0)] | ||
public void CreateRectangles_ShouldThrowException_WhenRectanglesAreInvalid(int left, int top, | ||
int right, int bottom) | ||
{ | ||
Assert.Throws<ArgumentException>(() => | ||
renderer.DrawRectangles([new SKRect(left, top, right, bottom)])); | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
|
||
<IsPackable>false</IsPackable> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/> | ||
<PackageReference Include="NUnit" Version="4.2.2"/> | ||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/> | ||
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/> | ||
<PackageReference Include="coverlet.collector" Version="6.0.0"/> | ||
<PackageReference Include="FluentAssertions" Version="6.12.2"/> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\TagsCloudVisualization\TagsCloudVisualization.csproj"/> | ||
</ItemGroup> | ||
|
||
</Project> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В целом можно, но не рекомендуется. При появлении большего числа файлов имена зависимостей станут повторяться и станет неудобно указывать полные имена классов