-
Notifications
You must be signed in to change notification settings - Fork 321
Копытов Михаил #266
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?
Копытов Михаил #266
Changes from 3 commits
9a0d66a
c036e09
c6b65dd
aa7daee
9628372
78ca294
2a715f6
7885a4a
00d9f22
cc95652
cdd8d79
dbca6ef
accc25a
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,68 @@ | ||
| using System.Drawing; | ||
|
|
||
| namespace TagsCloudVisualization; | ||
|
|
||
| public class CircularCloudLayouter | ||
| { | ||
| private List<Rectangle> Rects { get; } | ||
|
|
||
| private Size imageSize; | ||
|
|
||
| private readonly PointGenerator pointGenerator; | ||
|
|
||
| private readonly Random random = new Random(); | ||
|
|
||
| public CircularCloudLayouter(Point center, int imageWidth = 1920, int imageHeight = 1080) | ||
| { | ||
| if (imageWidth <= 0 || imageHeight <= 0) | ||
| throw new ArgumentException("Image width or height must be positive"); | ||
| if (center.X <= 0 || center.Y <= 0) | ||
| throw new ArgumentException("Center should be greater than 0"); | ||
| if (center.X >= imageWidth || center.Y >= imageHeight) | ||
| throw new ArgumentException("Center must be inside image bounds"); | ||
| Rects = new List<Rectangle>(); | ||
| imageSize = new Size(imageWidth, imageHeight); | ||
| pointGenerator = new PointGenerator(center, imageSize); | ||
| } | ||
|
|
||
|
|
||
| public Rectangle PutNextRectangle(Size rectangleSize) | ||
| { | ||
| if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) | ||
| throw new ArgumentException("Rectangle size must be positive"); | ||
|
|
||
| var points = pointGenerator.GetPointsOnSpiral(); | ||
| foreach (var point in points) | ||
| { | ||
| var rectangle = new Rectangle(point, rectangleSize); | ||
| if (Rects.Any(rectangle.IntersectsWith)) | ||
| continue; | ||
| Rects.Add(rectangle); | ||
| return rectangle; | ||
| } | ||
|
|
||
| throw new InvalidOperationException($"Failed to find place for the {Rects.Count} rectangle"); | ||
| } | ||
|
|
||
| public IEnumerable<Rectangle> GetCircularCloudRectangles(int count, Size rectangleSize, double coefficient = 0) | ||
| { | ||
| if (count <= 0) | ||
| throw new ArgumentException("Count must be positive"); | ||
| if (coefficient == 0) | ||
| { | ||
| for (var i = 0; i < count; i++) | ||
| yield return PutNextRectangle(rectangleSize); | ||
| yield break; | ||
| } | ||
|
|
||
| if (coefficient is < 0 or > 1) | ||
|
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. Что за коэффициент? Из названия не понятно? |
||
| throw new ArgumentException("Coefficient must be between 0 and 1"); | ||
| for (var i = 0; i < count; i++) | ||
| { | ||
| var rnd = random.NextDouble() + coefficient; | ||
| var rndWidth = (int)Math.Round(rectangleSize.Width * rnd); | ||
| var rndHeight = (int)Math.Round(rectangleSize.Height * rnd); | ||
| yield return PutNextRectangle(new Size(rndWidth, rndHeight)); | ||
| } | ||
| } | ||
|
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. Лучше вынести из лейаутера во внешний провайдер прямоугольников (или позиций прямоугольников) |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| using System.Drawing; | ||
| using System.Drawing.Imaging; | ||
| using System.Diagnostics; | ||
|
|
||
| namespace TagsCloudVisualization; | ||
|
|
||
| public static class ImageSaver | ||
| { | ||
| public static void ImageSave(Bitmap bitmap, string fileName) | ||
|
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. ImageSave -> SaveImag |
||
| { | ||
| var projectDir = Directory.GetParent(Environment.CurrentDirectory)!.Parent!.Parent!.FullName; | ||
| var imagesDir = Path.Combine(projectDir, "Image"); | ||
| var path = Path.Combine(imagesDir, fileName); | ||
| bitmap.Save(path, ImageFormat.Png); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| using System.Collections; | ||
| using System.Drawing; | ||
|
|
||
| namespace TagsCloudVisualization; | ||
|
|
||
| public class PointGenerator | ||
|
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. Необходимо вынести интерфейс |
||
| { | ||
| private readonly Point center; | ||
| private readonly Size imageSize; | ||
|
|
||
| public PointGenerator(Point center, Size imageSize) | ||
| { | ||
| if (center.X < 0 || center.Y < 0) | ||
| throw new ArgumentException("Center coordinates must be non-negative"); | ||
| if (imageSize.Width <= 0 || imageSize.Height <= 0) | ||
| throw new ArgumentException("Image size must be positive"); | ||
|
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.imageSize = imageSize; | ||
| } | ||
|
|
||
| public IEnumerable<Point> GetPointsOnSpiral(double angle = double.Pi / 24, double radius = 10) | ||
| { | ||
| if (radius <= 0) | ||
| throw new ArgumentException("Radius must be positive"); | ||
|
|
||
| var currentAngle = 0.0; | ||
| var currentRadius = 0.0; | ||
| var x = 0; | ||
| var y = 0; | ||
| while (x < imageSize.Width || y < imageSize.Height) | ||
|
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 vector = radius * currentAngle / (2 * Math.PI); | ||
| x = center.X + (int)Math.Round(Math.Cos(currentAngle) * vector); | ||
| y = center.Y + (int)Math.Round(Math.Sin(currentAngle) * vector); | ||
| yield return new Point(x, y); | ||
| currentAngle += angle; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| using System.Drawing; | ||
|
|
||
|
|
||
| namespace TagsCloudVisualization; | ||
|
|
||
| public static class Program | ||
| { | ||
| static void Main() | ||
| { | ||
| var imageCenter = new Point(1920/2, 1080/2); | ||
| var rectangleSize = new Size(200, 85); | ||
| var imageSize = new Size(1920, 1080); | ||
|
|
||
| var circularCloudLayouter = new CircularCloudLayouter(imageCenter); | ||
| var rectangles = circularCloudLayouter.GetCircularCloudRectangles(40,rectangleSize, 0.5); | ||
| var visualizer = new Render(imageSize); | ||
| var image = visualizer.CreateRectangleCloud(rectangles); | ||
| ImageSaver.ImageSave(image, "cloud.png"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
|  | ||
|  |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| using System.Drawing; | ||
|
|
||
| namespace TagsCloudVisualization; | ||
|
|
||
| public class Render(Size imageSize) | ||
|
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. Render -> Renderer или TagCloudRenderer. Необходимо вынести интерфейс ICloudRenderer |
||
| { | ||
| public Bitmap CreateRectangleCloud(IEnumerable<Rectangle> rectangles) | ||
| { | ||
| var bitmap = new Bitmap(imageSize.Width, imageSize.Height); | ||
|
|
||
| using var graphics = Graphics.FromImage(bitmap); | ||
| graphics.Clear(Color.FromArgb(0, 34, 43)); | ||
| var pen = new Pen(Color.FromArgb(212,85,0), 2); | ||
| foreach (var rect in rectangles) | ||
| graphics.DrawRectangle(pen, rect); | ||
|
|
||
| return bitmap; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net9.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="System.Drawing.Common" Version="10.0.0" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <Folder Include="Image\" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
|
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. Здесь стоит добавить тест на то что прямоугольники распологаются по форме круга, а также на их плотность расположения |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| using System.Drawing; | ||
| using FluentAssertions; | ||
| using TagsCloudVisualization; | ||
|
|
||
| namespace TagsCloudVisualizationTest; | ||
|
|
||
| public class CircularCloudLayouterTest | ||
| { | ||
| private Point validCenter; | ||
|
|
||
| [SetUp] | ||
| public void Setup() | ||
| { | ||
| validCenter = new Point(1920 / 2, 1080 / 2); | ||
| } | ||
|
|
||
|
|
||
| [TestCase(-1920, 1080)] | ||
| [TestCase(1920, -1080)] | ||
| public void CircularCloudLayouter_ShouldThrowException_WhenInvalidWindowSize(int imageWidth, int imageHeight) | ||
| { | ||
| var act = () => new CircularCloudLayouter(validCenter, imageWidth, imageHeight); | ||
|
|
||
| act.Should().Throw<ArgumentException>() | ||
| .WithMessage("Image width or height must be positive"); | ||
| } | ||
|
|
||
| [TestCase(-500, 500)] | ||
| [TestCase(500, -500)] | ||
| public void CircularCloudLayouter_ShouldThrowException_WhenInvalidCenter(int centerX, int centerY) | ||
| { | ||
| var invalidCenter = new Point(centerX, centerY); | ||
| var act = () => new CircularCloudLayouter(invalidCenter); | ||
|
|
||
| act.Should().Throw<ArgumentException>() | ||
| .WithMessage("Center should be greater than 0"); | ||
| ; | ||
|
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. Лишнее |
||
| } | ||
|
|
||
|
|
||
| [TestCase(1930, 500)] | ||
| [TestCase(500, 1090)] | ||
| public void CircularCloudLayouter_ShouldThrowException_WhenCenterIsOutsideImage(int centerX, int centerY) | ||
| { | ||
| var center = new Point(centerX, centerY); | ||
| var act = () => new CircularCloudLayouter(center); | ||
|
|
||
| act.Should().Throw<ArgumentException>() | ||
| .WithMessage("Center must be inside image bounds"); | ||
| ; | ||
|
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. Лишнее |
||
| } | ||
|
|
||
| [TestCase(-100, 100)] | ||
| [TestCase(100, -100)] | ||
| [TestCase(0, 100)] | ||
| [TestCase(100, 0)] | ||
| public void PutNextRectangle_ShouldThrowException_WhenInvalidRectangleSize(int rectangleWidth, int rectangleHeight) | ||
| { | ||
| var circularCloud = new CircularCloudLayouter(validCenter); | ||
| var act = () => circularCloud.PutNextRectangle(new Size(rectangleWidth, rectangleHeight)); | ||
|
|
||
| act.Should().Throw<ArgumentException>() | ||
| .WithMessage("Rectangle size must be positive"); | ||
| } | ||
|
|
||
| [Test] | ||
| public void PutNextRectangle_ShouldThrowException_WhenNoPlaceForNextRectangle() | ||
| { | ||
| var center = new Point(200, 200); | ||
| var circularCloud = new CircularCloudLayouter(center, 400, 400); | ||
| var rectangles = new List<Rectangle>(); | ||
| var rectangleSize = new Size(40, 40); | ||
| for (var i = 0; i < 111; i++) | ||
| { | ||
| var rect = circularCloud.PutNextRectangle(rectangleSize); | ||
| rectangles.Add(rect); | ||
| } | ||
|
|
||
| var act = () => circularCloud.PutNextRectangle(rectangleSize); | ||
| act.Should().Throw<InvalidOperationException>() | ||
| .WithMessage($"Failed to find place for the * rectangle"); | ||
| } | ||
|
|
||
| [Test] | ||
| public void PutNextRectangle_ShouldNotProduceIntersectingRectangles_WhenCalledMultipleTimes() | ||
| { | ||
| var circularCloud = new CircularCloudLayouter(validCenter); | ||
| var rectangles = new List<Rectangle>(); | ||
| var rectangleSize = new Size(20, 10); | ||
| for (var i = 0; i < 50; i++) | ||
| { | ||
| var rect = circularCloud.PutNextRectangle(rectangleSize); | ||
| rectangles.Add(rect); | ||
| } | ||
|
|
||
| for (var i = 0; i < rectangles.Count; i++) | ||
| { | ||
| for (var j = i + 1; j < rectangles.Count; j++) | ||
| { | ||
| rectangles[i].IntersectsWith(rectangles[j]) | ||
| .Should().BeFalse($"rect #{i} should not intersect rect #{j}"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| [TestCase(-1)] | ||
| [TestCase(0)] | ||
| public void GetCircularCloudRectangles_ShouldThrowException_WhenCountIsNotPositive(int count) | ||
| { | ||
| var circularCloud = new CircularCloudLayouter(validCenter); | ||
| var rectangleSize = new Size(40, 40); | ||
| var act = () => circularCloud.GetCircularCloudRectangles(count, rectangleSize).ToList(); | ||
|
|
||
| act.Should().Throw<ArgumentException>() | ||
| .WithMessage("Count must be positive"); | ||
| } | ||
|
|
||
| [Test] | ||
| public void GetCircularCloudRectangles_ShouldNotProduceIntersectingRectangles_WhenCalledMultipleTimes() | ||
| { | ||
| var circularCloud = new CircularCloudLayouter(validCenter); | ||
| var rectangleSize = new Size(20, 10); | ||
| var rectangles = circularCloud | ||
| .GetCircularCloudRectangles(100, rectangleSize) | ||
| .ToList(); | ||
|
|
||
| for (var i = 0; i < rectangles.Count; i++) | ||
| { | ||
| for (var j = i + 1; j < rectangles.Count; j++) | ||
| { | ||
| rectangles[i].IntersectsWith(rectangles[j]) | ||
| .Should().BeFalse($"rect #{i} should not intersect rect #{j}"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| [TestCase(-1)] | ||
| [TestCase(2)] | ||
| public void GetCircularCloudRectangles_ShouldThrowException_WhenInvalidCoefficient(double coefficient) | ||
| { | ||
| var circularCloud = new CircularCloudLayouter(validCenter); | ||
| var rectangleSize = new Size(20, 10); | ||
| var act = () => circularCloud | ||
| .GetCircularCloudRectangles(100, rectangleSize, coefficient) | ||
| .ToList(); | ||
| act.Should().Throw<ArgumentException>() | ||
| .WithMessage("Coefficient must be between 0 and 1"); | ||
| } | ||
|
|
||
| [TestCase(0.1)] | ||
| [TestCase(0.2)] | ||
| [TestCase(0.5)] | ||
| [TestCase(0.9)] | ||
| public void GetCircularCloudRectangles_ShouldNotProduceIntersectingRectangles_WhenValidCoefficient(double coefficient) | ||
| { | ||
| var circularCloud = new CircularCloudLayouter(validCenter); | ||
| var rectangleSize = new Size(20, 10); | ||
| var rectangles = circularCloud | ||
| .GetCircularCloudRectangles(100, rectangleSize) | ||
| .ToList(); | ||
|
|
||
| for (var i = 0; i < rectangles.Count; i++) | ||
| { | ||
| for (var j = i + 1; j < rectangles.Count; j++) | ||
| { | ||
| rectangles[i].IntersectsWith(rectangles[j]) | ||
| .Should().BeFalse($"rect #{i} should not intersect rect #{j}"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
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.
Rects -> Rectangles