-
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
Леонид Рыбин #251
base: master
Are you sure you want to change the base?
Леонид Рыбин #251
Changes from 12 commits
df81772
1856ed4
db6b107
d75b674
60dfdc1
3629ea1
84833ea
ff4b438
12c99e0
f055c7c
2f1575c
437d628
1f5c3bc
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,21 @@ | ||
using System.Drawing; | ||
using TagsCloudVisualization.Layouters; | ||
|
||
namespace TagsCloudVisualization.Extensions; | ||
|
||
public static class ICircularCloudLayouterExtensions | ||
{ | ||
private const int MinRectangleSize = 40; | ||
private const int MaxRectangleSize = 70; | ||
|
||
public static Rectangle[] GenerateCloud(this ICloudLayouter layouter, int rectanglesNumber = 1000, int minRectangleSize = MinRectangleSize, int maxRectangleSize = MaxRectangleSize) | ||
{ | ||
var random = new Random(); | ||
return Enumerable.Range(1, rectanglesNumber) | ||
.Select(_ => new Size( | ||
random.Next(minRectangleSize, maxRectangleSize), | ||
random.Next(minRectangleSize, maxRectangleSize))) | ||
.Select(size => layouter.PutNextRectangle(size)) | ||
.ToArray(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using System.Drawing; | ||
using TagsCloudVisualization.PointGenerators; | ||
|
||
namespace TagsCloudVisualization.Layouters; | ||
|
||
public class CircularCloudLayouter : ICloudLayouter | ||
{ | ||
private const double DefaultRadius = 1; | ||
private const double DefaultAngleOffset = 10; | ||
private readonly Point center; | ||
public readonly List<Rectangle> rectangles; | ||
private readonly CircularSpiralPointGenerator pointsGenerator; | ||
|
||
public CircularCloudLayouter(Point center) : this(center, DefaultRadius, DefaultAngleOffset) {} | ||
|
||
public CircularCloudLayouter(Point center, double radius, double angleOffset) | ||
{ | ||
if (center.X < 0 || center.Y < 0) | ||
throw new ArgumentException("X or Y must be positive"); | ||
|
||
this.center = center; | ||
rectangles = new List<Rectangle>(); | ||
|
||
pointsGenerator = new CircularSpiralPointGenerator(radius, angleOffset, center); | ||
} | ||
|
||
public Rectangle PutNextRectangle(Size rectangleSize) | ||
{ | ||
if (rectangleSize.Height <= 0 || rectangleSize.Width <= 0) | ||
throw new ArgumentException("Height or Width is negative!"); | ||
|
||
Rectangle rectangle; | ||
|
||
do | ||
{ | ||
var rectangleCenterPos = pointsGenerator.GetPoint(); | ||
rectangle = CreateRectangle(rectangleCenterPos, rectangleSize); | ||
} | ||
while (rectangles.Any(rectangle.IntersectsWith)); | ||
|
||
rectangles.Add(rectangle); | ||
|
||
return rectangle; | ||
} | ||
|
||
private static Rectangle CreateRectangle(Point center, Size rectangleSize) | ||
{ | ||
var x = center.X - rectangleSize.Width / 2; | ||
var y = center.Y - rectangleSize.Height / 2; | ||
return new Rectangle(x, y, rectangleSize.Width, rectangleSize.Height); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.Layouters; | ||
|
||
public interface ICloudLayouter | ||
{ | ||
public Rectangle PutNextRectangle(Size rectangleSize); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.PointGenerators; | ||
|
||
public class CircularSpiralPointGenerator : IPointGenerator | ||
{ | ||
private double angleOffset; | ||
private double radius; | ||
private double angle = 0; | ||
private Point center; | ||
|
||
public CircularSpiralPointGenerator(double radius, double angleOffset, Point center) | ||
This comment was marked as resolved.
Sorry, something went wrong. 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. В |
||
{ | ||
if (radius <= 0) | ||
throw new ArgumentException("radius must be greater than 0"); | ||
if (angleOffset <= 0) | ||
throw new ArgumentException("angleOffset must be greater than 0"); | ||
|
||
this.radius = radius; | ||
this.angleOffset = angleOffset * Math.PI / 180; | ||
this.center = center; | ||
} | ||
|
||
public Point GetPoint() | ||
{ | ||
var radiusVector = (radius / (2 * Math.PI)) * angle; | ||
|
||
var x = (int)Math.Round( | ||
radiusVector * Math.Cos(angle) + center.X); | ||
var y = (int)Math.Round( | ||
radiusVector * Math.Sin(angle) + center.Y); | ||
|
||
angle += angleOffset; | ||
|
||
return new Point(x, y); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.PointGenerators; | ||
|
||
public interface IPointGenerator | ||
{ | ||
public Point GetPoint(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System.Drawing; | ||
using System.Drawing.Imaging; | ||
using TagsCloudVisualization.Extensions; | ||
using TagsCloudVisualization.Layouters; | ||
using TagsCloudVisualization.Visualizers; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
internal class Program | ||
{ | ||
private const int ImageWidth = 1920; | ||
private const int ImageHeight = 1080; | ||
|
||
private const int RectanglesNumber = 100; | ||
|
||
private const string ImagesDirectory = "images"; | ||
|
||
public static void Main(string[] args) | ||
{ | ||
var center = new Point(ImageWidth / 2, ImageHeight / 2); | ||
var cloudLayouter = new CircularCloudLayouter(center); | ||
|
||
var rectangles = cloudLayouter.GenerateCloud(RectanglesNumber); | ||
|
||
var visualizer = new Visualizer(); | ||
var bitmap = visualizer.CreateBitmap(rectangles, new Size(ImageWidth, ImageHeight)); | ||
Directory.CreateDirectory(ImagesDirectory); | ||
|
||
bitmap.Save(GetPathToImages(), ImageFormat.Jpeg); | ||
} | ||
|
||
private static string GetPathToImages() | ||
{ | ||
var filename = $"{RectanglesNumber}_TagCloud.jpg"; | ||
return Path.Combine(ImagesDirectory, filename); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
### 50 Прямоугольников | ||
![result](https://i.imgur.com/nbMnER4.jpeg) | ||
--- | ||
### 100 Прямоугольников | ||
![result1](https://i.imgur.com/l3GB61c.jpeg) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Folder Include="Images\" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.Visualizers; | ||
|
||
public interface IVisualizer | ||
{ | ||
public Bitmap CreateBitmap(IEnumerable<Rectangle> rectangles, Size bitmapSize); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.Visualizers; | ||
|
||
public class Visualizer : IVisualizer | ||
{ | ||
public Bitmap CreateBitmap(IEnumerable<Rectangle> rectangles, Size bitmapSize) | ||
{ | ||
var bitmap = new Bitmap(bitmapSize.Width, bitmapSize.Height); | ||
|
||
using var graphics = Graphics.FromImage(bitmap); | ||
foreach (var rectangle in rectangles) | ||
{ | ||
var pen = new Pen(Color.Blue); | ||
graphics.DrawRectangle(pen, rectangle); | ||
} | ||
|
||
return bitmap; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
using FluentAssertions; | ||
using NUnit.Framework.Interfaces; | ||
using System.Drawing; | ||
using System.Drawing.Imaging; | ||
using TagsCloudVisualization.Extensions; | ||
using TagsCloudVisualization.Layouters; | ||
using TagsCloudVisualization.Visualizers; | ||
|
||
namespace TagsCloudVisualizationTests; | ||
|
||
[TestFixture] | ||
public class CircularCloudLayouterTests | ||
{ | ||
private Rectangle[] rectangles; | ||
private const string ImagesDirectory = "TestImages"; | ||
|
||
private static readonly Point OptimalCenter = new(0, 0); | ||
private readonly double OptimalRadius = 1; | ||
private readonly double OptimalAngleOffset = 0.5; | ||
|
||
[TearDown] | ||
public void TearDown() | ||
{ | ||
var currentContext = TestContext.CurrentContext; | ||
if (currentContext.Result.Outcome.Status != TestStatus.Failed) | ||
return; | ||
|
||
var visualizer = new Visualizer(); | ||
var bitmap = visualizer.CreateBitmap(rectangles, GetLayoutSize()); | ||
Directory.CreateDirectory(ImagesDirectory); | ||
bitmap.Save(Path.Combine(ImagesDirectory, currentContext.Test.Name + ".jpg"), ImageFormat.Jpeg); | ||
|
||
TestContext.Out | ||
.WriteLine($"Tag cloud visualization saved to file " + | ||
$"{Path.Combine(ImagesDirectory, currentContext.Test.Name + ".jpg")}"); | ||
} | ||
|
||
[Test] | ||
public void CircularCloudLayouter_WhenCorrectArgs_NotThrowArgumentException() | ||
{ | ||
var act = () => new CircularCloudLayouter(new Point(5, 15)); | ||
|
||
act.Should().NotThrow<ArgumentException>(); | ||
} | ||
|
||
[TestCase(-5, -5, TestName = "WithNegativeXAndY")] | ||
[TestCase(-5, 5, TestName = "WithNegativeX")] | ||
[TestCase(5, -5, TestName = "WithNegativeY")] | ||
public void CircularCloudLayouter_WhenIncorrectArgs_ThrowArgumentException(int x, int y) | ||
{ | ||
var act = () => new CircularCloudLayouter(new Point(x, y)); | ||
|
||
act.Should().Throw<ArgumentException>(); | ||
} | ||
|
||
[TestCase(-5, -5, TestName = "WithNegativeWidthAndHeight")] | ||
[TestCase(-5, 5, TestName = "WithNegativeWidth")] | ||
[TestCase(5, -5, TestName = "WithNegativeHeight")] | ||
[TestCase(0, 0, TestName = "WithZeroWidthAndHeight")] | ||
public void PutNextRectangle_WhenIncorrectArgs_ThrowArgumentException(int width, int height) | ||
{ | ||
var circularCloudLayouter = new CircularCloudLayouter(new Point(600, 600), 1, 90); | ||
|
||
var act = () => circularCloudLayouter.PutNextRectangle(new Size(width, height)); | ||
|
||
act.Should().Throw<ArgumentException>(); | ||
} | ||
|
||
[Test] | ||
public void PutNextRectangle_ShouldReturnRectanglesWithoutIntersections() | ||
{ | ||
var rectanglesNumber = 100; | ||
var circularCloudLayouter = new CircularCloudLayouter(OptimalCenter); | ||
|
||
rectangles = circularCloudLayouter.GenerateCloud(rectanglesNumber); | ||
|
||
IsIntersectionBetweenRectangles(rectangles).Should().BeFalse(); | ||
} | ||
|
||
private static bool IsIntersectionBetweenRectangles(Rectangle[] rectangles) | ||
{ | ||
for (var i = 0; i < rectangles.Length; i++) | ||
{ | ||
for (var j = i + 1; j < rectangles.Length; j++) | ||
{ | ||
if (rectangles[i].IntersectsWith(rectangles[j])) | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
[Test] | ||
public void TagsCloud_ShouldBeShapeOfCircularCloud_WhenOptimalParameters() | ||
{ | ||
var rectanglesNumber = 1000; | ||
var circularCloudLayouter = new CircularCloudLayouter(OptimalCenter, OptimalRadius, OptimalAngleOffset); | ||
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. Так везде надо |
||
|
||
rectangles = circularCloudLayouter.GenerateCloud(rectanglesNumber, 10, 25); | ||
var layoutSize = GetLayoutSize(); | ||
var diametr = Math.Max(layoutSize.Height, layoutSize.Width); | ||
var circleArea = Math.PI* Math.Pow(diametr, 2) / 4; | ||
var rectanglesArea = (double)rectangles | ||
.Select(rectangle => rectangle.Height * rectangle.Width) | ||
.Sum(); | ||
var accuracy = circleArea / rectanglesArea; | ||
|
||
accuracy.Should().BeApproximately(1, 0.35); | ||
} | ||
|
||
private Size GetLayoutSize() | ||
{ | ||
var layoutWidth = rectangles.Max(rectangle => rectangle.Right) - | ||
rectangles.Min(rectangle => rectangle.Left); | ||
var layoutHeight = rectangles.Max(rectangle => rectangle.Top) - | ||
rectangles.Min(rectangle => rectangle.Bottom); | ||
return new Size(layoutWidth, layoutHeight); | ||
} | ||
|
||
[Test] | ||
public void TagsCloud_ShouldBeDense_WhenOptimalParameters() | ||
{ | ||
var rectanglesNumber = 1000; | ||
var circularCloudLayouter = new CircularCloudLayouter(OptimalCenter, OptimalRadius, OptimalAngleOffset); | ||
rectangles = circularCloudLayouter.GenerateCloud(rectanglesNumber, 10, 10); | ||
|
||
var expectedRectanglesArea = (double)rectanglesNumber * 10 * 10; | ||
var rectanglesArea = (double)rectangles | ||
.Select(rectangle => rectangle.Height * rectangle.Width) | ||
.Sum(); | ||
var accuracy = expectedRectanglesArea / rectanglesArea; | ||
|
||
accuracy.Should().BeApproximately(1, 0.2); | ||
} | ||
} | ||
This comment was marked as resolved.
Sorry, something went wrong. |
This comment was marked as resolved.
Sorry, something went wrong.