-
Notifications
You must be signed in to change notification settings - Fork 321
Шагов Владислав #272
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
base: master
Are you sure you want to change the base?
Шагов Владислав #272
Changes from 2 commits
1f4d810
3b4c72f
dc954f1
70d43ab
2ead8d9
389b344
f671a30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| using System.Drawing; | ||
|
|
||
| namespace TagsCloudVisualization; | ||
|
|
||
| public class CircularCloudLayouter | ||
| { | ||
| public readonly Point Center; | ||
| public readonly List<Rectangle> PlacedRectangles = []; | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| private readonly Spiral spiral; | ||
|
|
||
| public CircularCloudLayouter(Point center) | ||
| { | ||
| Center = center; | ||
| spiral = new Spiral(center); | ||
| } | ||
|
|
||
| public Rectangle PutNextRectangle(Size rectangleSize) | ||
| { | ||
| if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) | ||
| throw new ArgumentException("Rectangle size must be positive", nameof(rectangleSize)); | ||
|
|
||
| var rect = FindFreeRectangle(rectangleSize); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Свободный от чего? Мы там вроде место под прямоугольник ищем, нет? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| rect = ShiftToCenter(rect); | ||
|
|
||
| PlacedRectangles.Add(rect); | ||
| return rect; | ||
| } | ||
|
|
||
| private Rectangle FindFreeRectangle(Size size) | ||
| { | ||
| while (true) | ||
| { | ||
| var point = spiral.GetNextPoint(); | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| var rect = CreateRectangleByCenter(point, size); | ||
|
|
||
| if (!IntersectsWithOthers(rect)) | ||
| return rect; | ||
| } | ||
| } | ||
|
|
||
| private Rectangle ShiftToCenter(Rectangle rect) | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| { | ||
| rect = ShiftAxis(rect, | ||
| dx: rect.X < Center.X ? 1 : -1, | ||
| dy: 0); | ||
|
|
||
| rect = ShiftAxis(rect, | ||
| dx: 0, | ||
| dy: rect.Y < Center.Y ? 1 : -1); | ||
|
|
||
| return rect; | ||
| } | ||
|
|
||
| private Rectangle ShiftAxis(Rectangle rect, int dx, int dy) | ||
This comment was marked as resolved.
Sorry, something went wrong.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. можно сделать шаги побольше и если пересекся то уже маленькими шагами идти |
||
| { | ||
| while (true) | ||
| { | ||
| var shifted = rect with { X = rect.X + dx, Y = rect.Y + dy }; | ||
|
|
||
| if (IntersectsWithOthers(shifted) || TouchesCorner(rect, shifted) || CrossedTheCenter(rect, dx, dy, shifted)) | ||
| return rect; | ||
|
|
||
| rect = shifted; | ||
| } | ||
| } | ||
|
|
||
| private bool TouchesCorner(Rectangle oldRect, Rectangle newRect) | ||
| { | ||
| return PlacedRectangles.Any(r => !oldRect.IntersectsWith(r) && newRect.IntersectsWith(r)); | ||
| } | ||
|
|
||
| private bool CrossedTheCenter(Rectangle oldRect, int dx, int dy, Rectangle shifted) | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| { | ||
| var oldCx = oldRect.X + oldRect.Width / 2; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| var oldCy = oldRect.Y + oldRect.Height / 2; | ||
|
|
||
| var newCx = shifted.X + shifted.Width / 2; | ||
| var newCy = shifted.Y + shifted.Height / 2; | ||
|
|
||
| if (dx != 0) | ||
| return Math.Abs(newCx - Center.X) > Math.Abs(oldCx - Center.X); | ||
|
|
||
| if (dy != 0) | ||
| return Math.Abs(newCy - Center.Y) > Math.Abs(oldCy - Center.Y); | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| private bool IntersectsWithOthers(Rectangle rect) | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| => PlacedRectangles.Any(r => r.IntersectsWith(rect with{ Width = rect.Width+1, Height = rect.Height+1})); | ||
|
|
||
| private static Rectangle CreateRectangleByCenter(Point center, Size size) | ||
| => new( | ||
| center.X - size.Width / 2, | ||
| center.Y - size.Height / 2, | ||
| size.Width, | ||
| size.Height); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| using System.Drawing; | ||
| using System.Runtime.Versioning; | ||
|
|
||
| namespace TagsCloudVisualization; | ||
|
|
||
| [SupportedOSPlatform("windows")] | ||
|
|
||
This comment was marked as duplicate.
Sorry, something went wrong. |
||
| public static class CloudVisualizer | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ответственность этого класса - создать изборажение по заданным параметрам и сохранить в файл. При этом за пользователя очень много чего выбирается по-умолчанию:
Хорошенько подумай, что можно захотеть настраивать в изображении! Далее - недавно вы, кажется, проходили fluent interface'ы, помнишь такие? Давай применим новые знания и навыки: во-первых, давай создадим класс-билдер для |
||
| { | ||
| public static void GenerateWordsCloud(string fileName, List<Size> rectSizes) | ||
This comment was marked as resolved.
Sorry, something went wrong.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ну пока что rectangle)
This comment was marked as duplicate.
Sorry, something went wrong. |
||
| { | ||
| var center = new Point(0, 0); | ||
| var layouter = new CircularCloudLayouter(center); | ||
|
|
||
| foreach (var size in rectSizes) | ||
| { | ||
| layouter.PutNextRectangle(size); | ||
| } | ||
|
|
||
| SaveLayoutImage(layouter.PlacedRectangles, fileName); | ||
| } | ||
|
|
||
| private static void SaveLayoutImage(List<Rectangle> rectangles, string fileName, int padding = 50) | ||
| { | ||
| if (rectangles.Count == 0) | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| return; | ||
|
|
||
| var minX = rectangles.Min(r => r.Left); | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| var maxX = rectangles.Max(r => r.Right); | ||
| var minY = rectangles.Min(r => r.Top); | ||
| var maxY = rectangles.Max(r => r.Bottom); | ||
|
|
||
| var width = (maxX - minX) + padding * 2; | ||
| var height = (maxY - minY) + padding * 2; | ||
|
|
||
| using var bitmap = new Bitmap(width, height); | ||
| using var g = Graphics.FromImage(bitmap); | ||
|
|
||
| g.Clear(Color.Black); | ||
|
|
||
| var random = new Random(); | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
|
|
||
| foreach (var rect in rectangles) | ||
| { | ||
| var shifted = rect with { X = rect.Left - minX + padding, Y = rect.Top - minY + padding }; | ||
| if (rect == rectangles[0]) | ||
| { | ||
| g.FillRectangle(Brushes.Red, shifted); | ||
| continue; | ||
| } | ||
| var color = Color.FromArgb( | ||
| random.Next(60, 93), | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| 0, | ||
| random.Next(164, 255)); | ||
|
|
||
| using var brush = new SolidBrush(color); | ||
| g.FillRectangle(brush, shifted); | ||
| g.DrawRectangle(Pens.Black, shifted); | ||
| } | ||
|
|
||
| bitmap.Save(fileName); | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
|  | ||
|
|
||
|  | ||
|
|
||
|  |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| using System.Drawing; | ||
|
|
||
| namespace TagsCloudVisualization; | ||
|
|
||
| public class Spiral | ||
| { | ||
| private readonly Point center; | ||
| private double angle; | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| private readonly double angleStep; | ||
| private readonly double radiusStep; | ||
|
|
||
| public Spiral(Point center, double angleStep = 0.1, double radiusStep = 0.5) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. В чём здесь измеряются углы?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. радианы There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Это было "по коду непонятно в чём измеряются углы" :) |
||
| { | ||
| this.center = center; | ||
| this.angleStep = angleStep; | ||
| this.radiusStep = radiusStep; | ||
| angle = 0; | ||
| } | ||
|
|
||
| public Point GetNextPoint() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Хм, выглядит, как бесконечная коллекция. Не требую в обязательном порядке, но мне кажется было бы круто попробовать реализовать не |
||
| { | ||
| var radius = radiusStep * angle; | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| var x = center.X + (int)(radius * Math.Cos(angle)); | ||
| var y = center.Y + (int)(radius * Math.Sin(angle)); | ||
|
|
||
| angle += angleStep; | ||
| return new Point(x, y); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net9.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="NUnit" Version="4.4.0" /> | ||
| <PackageReference Include="System.Drawing.Common" Version="10.0.0" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| using System.Drawing; | ||
| using System.Runtime.Versioning; | ||
| using FluentAssertions; | ||
| using TagsCloudVisualization; | ||
|
|
||
| namespace TagsCloudVisualizationTests; | ||
|
|
||
| [SupportedOSPlatform("windows")] | ||
|
|
||
| [TestFixture] | ||
| public class CloudVisualizerTests | ||
| { | ||
| private string outputDir; | ||
|
|
||
| [SetUp] | ||
| public void SetUp() | ||
| { | ||
| outputDir = Path.Combine(TestContext.CurrentContext.WorkDirectory, "visualizer_tests"); | ||
| Directory.CreateDirectory(outputDir); | ||
| } | ||
|
|
||
| [Test] | ||
| public void GenerateWordsCloud_ShouldCreateFile_WhenSizesProvided() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Добавь тестов на всякие проблемки с созданием файла. |
||
| { | ||
| var file = Path.Combine(outputDir, "cloud.png"); | ||
| var sizes = new List<Size> | ||
| { | ||
| new(50, 30), | ||
| new(20, 20), | ||
| new(10, 40) | ||
| }; | ||
|
|
||
| CloudVisualizer.GenerateWordsCloud(file, sizes); | ||
|
|
||
| File.Exists(file).Should().BeTrue("visualizer must create output file"); | ||
| } | ||
|
|
||
| [Test] | ||
| public void GenerateWordsCloud_ShouldNotThrow_WhenGivenEmptyList() | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| { | ||
| var file = Path.Combine(outputDir, "empty.png"); | ||
|
|
||
| var act = () => CloudVisualizer.GenerateWordsCloud(file, []); | ||
|
|
||
| act.Should().NotThrow("empty list shouldn't cause failures"); | ||
| File.Exists(file).Should().BeFalse("empty input should not produce image"); | ||
| } | ||
|
|
||
| [Test] | ||
| public void GenerateWordsCloud_ShouldProduceNonEmptyImage() | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| { | ||
| var file = Path.Combine(outputDir, "nonempty.png"); | ||
| var sizes = new List<Size> { new(100, 50), new(30, 30) }; | ||
|
|
||
| CloudVisualizer.GenerateWordsCloud(file, sizes); | ||
|
|
||
| var fileInfo = new FileInfo(file); | ||
|
|
||
| fileInfo.Exists.Should().BeTrue(); | ||
| fileInfo.Length.Should().BeGreaterThan(1000, "image should not be empty"); | ||
| } | ||
|
|
||
| [Test] | ||
| public void GenerateWordsCloud_ShouldProduceValidPng() | ||
| { | ||
| var file = Path.Combine(outputDir, "valid.png"); | ||
| var sizes = new List<Size> { new(80, 40) }; | ||
|
|
||
| CloudVisualizer.GenerateWordsCloud(file, sizes); | ||
|
|
||
| using var bmp = new Bitmap(file); | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
|
|
||
| bmp.Width.Should().BeGreaterThan(0); | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| bmp.Height.Should().BeGreaterThan(0); | ||
| } | ||
|
|
||
| [Test] | ||
| public void GenerateWordsCloud_ShouldUseAllSizes() | ||
| { | ||
| var file = Path.Combine(outputDir, "count_check.png"); | ||
| var sizes = new List<Size> | ||
| { | ||
| new(10,10), | ||
| new(20,20), | ||
| new(30,30) | ||
| }; | ||
|
|
||
| CloudVisualizer.GenerateWordsCloud(file, sizes); | ||
|
|
||
| var layouter = new CircularCloudLayouter(new Point(0, 0)); | ||
| foreach (var size in sizes) | ||
| layouter.PutNextRectangle(size); | ||
|
|
||
| layouter.PlacedRectangles.Count.Should().Be(sizes.Count); | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| } | ||
|
|
||
| [Test] | ||
| public void GenerateWordsCloud_ShouldHandleLargeAmountOfRects() | ||
| { | ||
| var file = Path.Combine(outputDir, "many.png"); | ||
|
|
||
| var sizes = Enumerable | ||
| .Range(1, 200) | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| .Select(i => new Size(i % 40 + 10, i % 30 + 10)) | ||
| .ToList(); | ||
|
|
||
| var act = () => CloudVisualizer.GenerateWordsCloud(file, sizes); | ||
|
|
||
| act.Should().NotThrow(); | ||
| File.Exists(file).Should().BeTrue(); | ||
| } | ||
|
|
||
| [Test, Explicit] | ||
| public void VizualizeExamples() | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| { | ||
| var file1 = Path.Combine(outputDir, "example1.png"); | ||
| var file2 = Path.Combine(outputDir, "example2.png"); | ||
| var file3 = Path.Combine(outputDir, "example3.png"); | ||
| var sizes1 = Enumerable | ||
| .Range(1, 500) | ||
| .Select(i => new Size(i % 40 + 10, i % 30 + 10)) | ||
| .ToList(); | ||
| var sizes2 = Enumerable | ||
| .Range(1, 100) | ||
| .Select(i => new Size(i % 60 + 10, i % 20 + 10)) | ||
| .ToList(); | ||
| var sizes3 = Enumerable | ||
| .Range(1, 50) | ||
| .Select(i => new Size(i % 40 + 10, i % 70 + 10)) | ||
| .ToList(); | ||
| CloudVisualizer.GenerateWordsCloud(file1, sizes1); | ||
| CloudVisualizer.GenerateWordsCloud(file2, sizes2); | ||
| CloudVisualizer.GenerateWordsCloud(file3, sizes3); | ||
| } | ||
| } | ||

This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.