From b5d3909c60ca50cf5c8da1647fe2c3dbe55f0f68 Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Thu, 30 Nov 2023 23:02:09 +0500 Subject: [PATCH 01/10] Basic setup, PutNextRectangle places one rectangle --- .../CircularCloudLayouter.cs | 39 ++++++++++++++ cs/TagsCloudVisualization/ICloudLayouter.cs | 8 +++ .../TagsCloudVisualization.csproj | 10 ++++ .../GlobalUsings.cs | 1 + .../TagsCloudVisualizationTests.csproj | 25 +++++++++ cs/TagsCloudVisualizationTests/UnitTest1.cs | 52 +++++++++++++++++++ cs/tdd.sln | 12 +++++ 7 files changed, 147 insertions(+) create mode 100644 cs/TagsCloudVisualization/CircularCloudLayouter.cs create mode 100644 cs/TagsCloudVisualization/ICloudLayouter.cs create mode 100644 cs/TagsCloudVisualization/TagsCloudVisualization.csproj create mode 100644 cs/TagsCloudVisualizationTests/GlobalUsings.cs create mode 100644 cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj create mode 100644 cs/TagsCloudVisualizationTests/UnitTest1.cs diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs new file mode 100644 index 000000000..0b94184f3 --- /dev/null +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -0,0 +1,39 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public class CircularCloudLayouter : ICloudLayouter +{ + private readonly Point center; + public List Rectangles { get; } + + public CircularCloudLayouter(Point center) + { + this.center = center; + Rectangles = new List(); + } + + public Rectangle PutNextRectangle(Size rectangleSize) + { + if (rectangleSize.Height < 0 || rectangleSize.Width < 0) + throw new ArgumentException("Width and height must be positive."); + + if (Rectangles.Count == 0) + { + Rectangles.Add(new Rectangle( + center.X - rectangleSize.Width / 2, + center.Y - rectangleSize.Height / 2, + rectangleSize.Width, + rectangleSize.Height)); + return Rectangles[0]; + } + + var newRectangle = GetRectanglePosition(rectangleSize); + return newRectangle; + } + + public Rectangle GetRectanglePosition(Size rectangleSize) + { + return new Rectangle(); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/ICloudLayouter.cs b/cs/TagsCloudVisualization/ICloudLayouter.cs new file mode 100644 index 000000000..bf2b79e85 --- /dev/null +++ b/cs/TagsCloudVisualization/ICloudLayouter.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public interface ICloudLayouter +{ + Rectangle PutNextRectangle(Size rectangleSize); +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudVisualization.csproj b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj new file mode 100644 index 000000000..efffbd58e --- /dev/null +++ b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj @@ -0,0 +1,10 @@ + + + + net7.0 + enable + enable + 11 + + + diff --git a/cs/TagsCloudVisualizationTests/GlobalUsings.cs b/cs/TagsCloudVisualizationTests/GlobalUsings.cs new file mode 100644 index 000000000..cefced496 --- /dev/null +++ b/cs/TagsCloudVisualizationTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj b/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj new file mode 100644 index 000000000..4907a49f3 --- /dev/null +++ b/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/cs/TagsCloudVisualizationTests/UnitTest1.cs b/cs/TagsCloudVisualizationTests/UnitTest1.cs new file mode 100644 index 000000000..356e21f43 --- /dev/null +++ b/cs/TagsCloudVisualizationTests/UnitTest1.cs @@ -0,0 +1,52 @@ +using System.Drawing; +using FluentAssertions; +using TagsCloudVisualization; + +namespace TagsCloudVisualizationTests; + +public class CircularCloudLayouterTests +{ + private CircularCloudLayouter layouter = null!; + + [SetUp] + public void SetUp() + { + layouter = new CircularCloudLayouter(new Point(500, 500)); + } + + [TestCase(-200, -300)] + [TestCase(-200, 300)] + [TestCase(200, -300)] + public void PutNextRectangle_ThrowsArgumentException_WhenRectanlgeSizeContainsNegativeParameters(int width, + int height) + { + Assert.Throws(() => layouter.PutNextRectangle(new Size(width, height))); + } + + [TestCase(100, 120, 450, 440)] + [TestCase(100, 119, 450, 441)] + [TestCase(101, 120, 450, 440)] + public void PutNextRectangle_CorrectlyPlacesRectangle_OnFirstRectangle(int width, int height, int expectedX, + int expectedY) + { + var rectangle = layouter.PutNextRectangle(new Size(width, height)); + rectangle.Location.Should().Be(new Point(expectedX, expectedY)); + } + + [Test] + public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectangles() + { + var rectangles = new List(); + + for (var i = 1; i < 50; i++) + { + var newRectangle = layouter.PutNextRectangle(new Size(20, 10)); + + foreach (var rectangle in rectangles) + Assert.That(rectangle.IntersectsWith(newRectangle), Is.False); + + rectangles.Add(newRectangle); + Console.WriteLine(newRectangle.X + " " + newRectangle.Y); + } + } +} \ No newline at end of file diff --git a/cs/tdd.sln b/cs/tdd.sln index c8f523d63..5feb229a5 100644 --- a/cs/tdd.sln +++ b/cs/tdd.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BowlingGame", "BowlingGame\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualization", "TagsCloudVisualization\TagsCloudVisualization.csproj", "{EA3857A6-89CE-47CD-BC79-1465A23D805B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualizationTests", "TagsCloudVisualizationTests\TagsCloudVisualizationTests.csproj", "{33339EDF-0AC4-4F21-9DD2-A659FD897FF8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +25,14 @@ Global {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.Build.0 = Release|Any CPU + {EA3857A6-89CE-47CD-BC79-1465A23D805B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA3857A6-89CE-47CD-BC79-1465A23D805B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA3857A6-89CE-47CD-BC79-1465A23D805B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA3857A6-89CE-47CD-BC79-1465A23D805B}.Release|Any CPU.Build.0 = Release|Any CPU + {33339EDF-0AC4-4F21-9DD2-A659FD897FF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33339EDF-0AC4-4F21-9DD2-A659FD897FF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33339EDF-0AC4-4F21-9DD2-A659FD897FF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33339EDF-0AC4-4F21-9DD2-A659FD897FF8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 9cf07f429032c5b20e3d1eb6be2649c16db1ef44 Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Fri, 1 Dec 2023 11:37:01 +0500 Subject: [PATCH 02/10] CircularCloudLayouter implemented using the packing algorithm --- .../CircularCloudLayouter.cs | 46 ++++++++++++------- cs/TagsCloudVisualization/TagsCloudDrawer.cs | 6 +++ ...Test1.cs => CircularCloudLayouterTests.cs} | 13 +----- 3 files changed, 36 insertions(+), 29 deletions(-) create mode 100644 cs/TagsCloudVisualization/TagsCloudDrawer.cs rename cs/TagsCloudVisualizationTests/{UnitTest1.cs => CircularCloudLayouterTests.cs} (70%) diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 0b94184f3..98f1c8a54 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -4,36 +4,48 @@ namespace TagsCloudVisualization; public class CircularCloudLayouter : ICloudLayouter { + private readonly List placedRectangles = new(); private readonly Point center; - public List Rectangles { get; } - + private static readonly Random Random = new(); + public CircularCloudLayouter(Point center) { this.center = center; - Rectangles = new List(); } public Rectangle PutNextRectangle(Size rectangleSize) { if (rectangleSize.Height < 0 || rectangleSize.Width < 0) - throw new ArgumentException("Width and height must be positive."); - - if (Rectangles.Count == 0) + throw new ArgumentException("Height and width must be positive"); + + Rectangle newRectangle; + do { - Rectangles.Add(new Rectangle( - center.X - rectangleSize.Width / 2, - center.Y - rectangleSize.Height / 2, - rectangleSize.Width, - rectangleSize.Height)); - return Rectangles[0]; - } - - var newRectangle = GetRectanglePosition(rectangleSize); + var distanceFromCenter = NextNormalDistribution(10, 50); + var angle = NextNormalDistribution(0, 360); + + var x = center.X + (int)(distanceFromCenter * Math.Cos(angle)); + var y = center.Y + (int)(distanceFromCenter * Math.Sin(angle)); + newRectangle = new Rectangle(new Point(x, y), rectangleSize); + + } while (IntersectsWithAny(newRectangle)); + + placedRectangles.Add(newRectangle); return newRectangle; } - public Rectangle GetRectanglePosition(Size rectangleSize) + private bool IntersectsWithAny(Rectangle newRectangle) + { + return placedRectangles + .Any(rectangle => rectangle + .IntersectsWith(newRectangle)); + } + + private static int NextNormalDistribution(int mean, int stdDev) { - return new Rectangle(); + var u1 = 1.0 - Random.NextDouble(); + var u2 = 1.0 - Random.NextDouble(); + var randomNumber = Math.Sqrt(-2 * Math.Log(u1)) * Math.Sin(2 * Math.PI * u2); + return mean + (int)(stdDev * randomNumber); } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudDrawer.cs b/cs/TagsCloudVisualization/TagsCloudDrawer.cs new file mode 100644 index 000000000..2a2bc26de --- /dev/null +++ b/cs/TagsCloudVisualization/TagsCloudDrawer.cs @@ -0,0 +1,6 @@ +namespace TagsCloudVisualization; + +public class TagsCloudDrawer +{ + +} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/UnitTest1.cs b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs similarity index 70% rename from cs/TagsCloudVisualizationTests/UnitTest1.cs rename to cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs index 356e21f43..114a1491a 100644 --- a/cs/TagsCloudVisualizationTests/UnitTest1.cs +++ b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs @@ -22,17 +22,7 @@ public void PutNextRectangle_ThrowsArgumentException_WhenRectanlgeSizeContainsNe { Assert.Throws(() => layouter.PutNextRectangle(new Size(width, height))); } - - [TestCase(100, 120, 450, 440)] - [TestCase(100, 119, 450, 441)] - [TestCase(101, 120, 450, 440)] - public void PutNextRectangle_CorrectlyPlacesRectangle_OnFirstRectangle(int width, int height, int expectedX, - int expectedY) - { - var rectangle = layouter.PutNextRectangle(new Size(width, height)); - rectangle.Location.Should().Be(new Point(expectedX, expectedY)); - } - + [Test] public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectangles() { @@ -46,7 +36,6 @@ public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectang Assert.That(rectangle.IntersectsWith(newRectangle), Is.False); rectangles.Add(newRectangle); - Console.WriteLine(newRectangle.X + " " + newRectangle.Y); } } } \ No newline at end of file From bf7edf49a5081e7ec332ffb00858d62e6a38c62e Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Sat, 2 Dec 2023 13:25:42 +0500 Subject: [PATCH 03/10] Added performance test, slightly refactored the algorithm for placing rectangles in CircularCloudLayouter.cs --- .../CircularCloudLayouter.cs | 34 ++++++++------- cs/TagsCloudVisualization/ICloudLayouter.cs | 1 + cs/TagsCloudVisualization/TagsCloudDrawer.cs | 6 --- .../TagsCloudVisualization.csproj | 5 +++ .../CircularCloudLayouterTests.cs | 42 +++++++++++++++---- 5 files changed, 60 insertions(+), 28 deletions(-) delete mode 100644 cs/TagsCloudVisualization/TagsCloudDrawer.cs diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 98f1c8a54..8ae13b5ba 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -4,48 +4,54 @@ namespace TagsCloudVisualization; public class CircularCloudLayouter : ICloudLayouter { - private readonly List placedRectangles = new(); + public List PlacedRectangles { get; } = new(); private readonly Point center; private static readonly Random Random = new(); - + public CircularCloudLayouter(Point center) { this.center = center; } + private Point CalculateRectangleLocation(Size rectangleSize) + { + var distanceFromCenter = NextNormalDistribution(10, Math.Max(rectangleSize.Height, rectangleSize.Width) * 2); + var angle = NextNormalDistribution(0, 360); + + var x = center.X + (int) (distanceFromCenter * Math.Cos(angle)); + var y = center.Y + (int) (distanceFromCenter * Math.Sin(angle)); + + return new Point(x, y); + } + public Rectangle PutNextRectangle(Size rectangleSize) { if (rectangleSize.Height < 0 || rectangleSize.Width < 0) throw new ArgumentException("Height and width must be positive"); - + Rectangle newRectangle; do { - var distanceFromCenter = NextNormalDistribution(10, 50); - var angle = NextNormalDistribution(0, 360); - - var x = center.X + (int)(distanceFromCenter * Math.Cos(angle)); - var y = center.Y + (int)(distanceFromCenter * Math.Sin(angle)); - newRectangle = new Rectangle(new Point(x, y), rectangleSize); - + var location = CalculateRectangleLocation(rectangleSize); + newRectangle = new Rectangle(location, rectangleSize); } while (IntersectsWithAny(newRectangle)); - placedRectangles.Add(newRectangle); + PlacedRectangles.Add(newRectangle); return newRectangle; } private bool IntersectsWithAny(Rectangle newRectangle) { - return placedRectangles + return PlacedRectangles .Any(rectangle => rectangle .IntersectsWith(newRectangle)); } - private static int NextNormalDistribution(int mean, int stdDev) + private static double NextNormalDistribution(double mean, double stdDev) { var u1 = 1.0 - Random.NextDouble(); var u2 = 1.0 - Random.NextDouble(); var randomNumber = Math.Sqrt(-2 * Math.Log(u1)) * Math.Sin(2 * Math.PI * u2); - return mean + (int)(stdDev * randomNumber); + return mean + (int) (stdDev * randomNumber); } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/ICloudLayouter.cs b/cs/TagsCloudVisualization/ICloudLayouter.cs index bf2b79e85..1f5451e3f 100644 --- a/cs/TagsCloudVisualization/ICloudLayouter.cs +++ b/cs/TagsCloudVisualization/ICloudLayouter.cs @@ -4,5 +4,6 @@ namespace TagsCloudVisualization; public interface ICloudLayouter { + List PlacedRectangles { get; } Rectangle PutNextRectangle(Size rectangleSize); } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudDrawer.cs b/cs/TagsCloudVisualization/TagsCloudDrawer.cs deleted file mode 100644 index 2a2bc26de..000000000 --- a/cs/TagsCloudVisualization/TagsCloudDrawer.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TagsCloudVisualization; - -public class TagsCloudDrawer -{ - -} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudVisualization.csproj b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj index efffbd58e..dd1f80dc2 100644 --- a/cs/TagsCloudVisualization/TagsCloudVisualization.csproj +++ b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj @@ -5,6 +5,11 @@ enable enable 11 + Exe + + + + diff --git a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs index 114a1491a..2abed1093 100644 --- a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs @@ -1,5 +1,4 @@ using System.Drawing; -using FluentAssertions; using TagsCloudVisualization; namespace TagsCloudVisualizationTests; @@ -7,6 +6,13 @@ namespace TagsCloudVisualizationTests; public class CircularCloudLayouterTests { private CircularCloudLayouter layouter = null!; + private Random random = null!; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + random = new Random(); + } [SetUp] public void SetUp() @@ -22,20 +28,40 @@ public void PutNextRectangle_ThrowsArgumentException_WhenRectanlgeSizeContainsNe { Assert.Throws(() => layouter.PutNextRectangle(new Size(width, height))); } - - [Test] - public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectangles() + + [TestCase(true)] + [TestCase(false)] + public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectangles(bool randomRectangleSize) { var rectangles = new List(); - - for (var i = 1; i < 50; i++) + var rectSize = new Size(20, 10); + + for (var i = 1; i <= 50; i++) { - var newRectangle = layouter.PutNextRectangle(new Size(20, 10)); - + if (randomRectangleSize) + rectSize = new Size(random.Next(201), random.Next(201)); + + var newRectangle = layouter.PutNextRectangle(rectSize); + foreach (var rectangle in rectangles) Assert.That(rectangle.IntersectsWith(newRectangle), Is.False); rectangles.Add(newRectangle); } } + + [Timeout(1000)] + [TestCase(true)] + [TestCase(false)] + public void PutNextRectangle_HasSufficientPerformance_OnLargeAmountOfRectangles(bool randomRectangleSize) + { + var rectSize = new Size(20, 10); + + for (var i = 1; i <= 200; i++) + { + if (randomRectangleSize) + rectSize = new Size(random.Next(201), random.Next(201)); + layouter.PutNextRectangle(rectSize); + } + } } \ No newline at end of file From 0daac4faad2d01670addfb7cc8bcad6ec49fff8b Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Sat, 2 Dec 2023 14:41:08 +0500 Subject: [PATCH 04/10] Changed the algorithm for placing rectangles in CircularCloudLayouter.cs --- .../CircularCloudLayouter.cs | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 8ae13b5ba..68086447f 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -6,36 +6,60 @@ public class CircularCloudLayouter : ICloudLayouter { public List PlacedRectangles { get; } = new(); private readonly Point center; - private static readonly Random Random = new(); public CircularCloudLayouter(Point center) { this.center = center; } - private Point CalculateRectangleLocation(Size rectangleSize) + private Rectangle CalculateRectanglePosition(Size rectangleSize, int radiusIncrement, double angleIncrement) { - var distanceFromCenter = NextNormalDistribution(10, Math.Max(rectangleSize.Height, rectangleSize.Width) * 2); - var angle = NextNormalDistribution(0, 360); - - var x = center.X + (int) (distanceFromCenter * Math.Cos(angle)); - var y = center.Y + (int) (distanceFromCenter * Math.Sin(angle)); + var rectangle = new Rectangle(Point.Empty, rectangleSize); + + var radius = 0d; + var angle = 0d; + + do + { + var x = center.X + radius * Math.Cos(angle); + var y = center.Y + radius * Math.Sin(angle); + rectangle.Location = new Point((int) x, (int) y); + + angle += angleIncrement; + + if (!(angle >= 2 * Math.PI)) + continue; + + angle = 0; + radius += radiusIncrement; + } while (IntersectsWithAny(rectangle)); - return new Point(x, y); + return rectangle; } + private Rectangle PlaceRectangle(Size rectangleSize) + { + var newRectangle = new Rectangle(Point.Empty, rectangleSize); + + if (PlacedRectangles.Count == 0) + { + newRectangle.Location = new Point(center.X - newRectangle.Width / 2, center.Y - newRectangle.Height / 2); + return newRectangle; + } + + + const int radiusIncrement = 15; + const double angleIncrement = 0.2d; + + return CalculateRectanglePosition(rectangleSize, radiusIncrement, angleIncrement); + } + public Rectangle PutNextRectangle(Size rectangleSize) { if (rectangleSize.Height < 0 || rectangleSize.Width < 0) throw new ArgumentException("Height and width must be positive"); - Rectangle newRectangle; - do - { - var location = CalculateRectangleLocation(rectangleSize); - newRectangle = new Rectangle(location, rectangleSize); - } while (IntersectsWithAny(newRectangle)); - + var newRectangle = PlaceRectangle(rectangleSize); PlacedRectangles.Add(newRectangle); return newRectangle; } @@ -46,12 +70,4 @@ private bool IntersectsWithAny(Rectangle newRectangle) .Any(rectangle => rectangle .IntersectsWith(newRectangle)); } - - private static double NextNormalDistribution(double mean, double stdDev) - { - var u1 = 1.0 - Random.NextDouble(); - var u2 = 1.0 - Random.NextDouble(); - var randomNumber = Math.Sqrt(-2 * Math.Log(u1)) * Math.Sin(2 * Math.PI * u2); - return mean + (int) (stdDev * randomNumber); - } } \ No newline at end of file From 7f4724989e077fd45f1f3938ae48dd88d2af3b03 Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Sun, 3 Dec 2023 23:14:16 +0500 Subject: [PATCH 05/10] Small CircularCloudLayouter.cs algorithm refactor + more unit tests added in CircularCloudLayouterTests.cs --- .../CircularCloudLayouter.cs | 17 ++++-- cs/TagsCloudVisualization/ICloudLayouter.cs | 1 + .../CircularCloudLayouterTests.cs | 52 +++++++++++++++++-- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 68086447f..37f6d32c9 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -46,14 +46,25 @@ private Rectangle PlaceRectangle(Size rectangleSize) newRectangle.Location = new Point(center.X - newRectangle.Width / 2, center.Y - newRectangle.Height / 2); return newRectangle; } - - const int radiusIncrement = 15; - const double angleIncrement = 0.2d; + const int radiusIncrement = 1; + const double angleIncrement = 0.1d; return CalculateRectanglePosition(rectangleSize, radiusIncrement, angleIncrement); } + public Rectangle GetCloudBorders() + { + var left = PlacedRectangles.Min(r => r.Left); + var right = PlacedRectangles.Max(r => r.Right); + var top = PlacedRectangles.Min(r => r.Top); + var bottom = PlacedRectangles.Max(r => r.Bottom); + + var width = right - left; + var height = bottom - top; + return new Rectangle(left, top, width, height); + } + public Rectangle PutNextRectangle(Size rectangleSize) { if (rectangleSize.Height < 0 || rectangleSize.Width < 0) diff --git a/cs/TagsCloudVisualization/ICloudLayouter.cs b/cs/TagsCloudVisualization/ICloudLayouter.cs index 1f5451e3f..017f6cffb 100644 --- a/cs/TagsCloudVisualization/ICloudLayouter.cs +++ b/cs/TagsCloudVisualization/ICloudLayouter.cs @@ -6,4 +6,5 @@ public interface ICloudLayouter { List PlacedRectangles { get; } Rectangle PutNextRectangle(Size rectangleSize); + Rectangle GetCloudBorders(); } \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs index 2abed1093..8d90f64f7 100644 --- a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs @@ -35,7 +35,7 @@ public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectang { var rectangles = new List(); var rectSize = new Size(20, 10); - + for (var i = 1; i <= 50; i++) { if (randomRectangleSize) @@ -50,14 +50,58 @@ public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectang } } - [Timeout(1000)] + [TestCase(10, true)] + [TestCase(10, false)] + [TestCase(50, true)] + [TestCase(50, false)] + [TestCase(100, true)] + [TestCase(100, false)] + public void PutNextRectangle_HasCircularShape_OnDifferentInputData(int numberOfRectangles, bool randomRectangleSize) + { + PlaceRectangles(numberOfRectangles, randomRectangleSize); + + var borders = layouter.GetCloudBorders(); + + var heightToWidthRatio = (double) Math.Min(borders.Width, borders.Height) / Math.Max(borders.Width, borders.Height); + + Assert.That(heightToWidthRatio, Is.GreaterThan(0.8).Within(0.05)); + } + + + [TestCase(10, true)] + [TestCase(10, false)] + [TestCase(50, true)] + [TestCase(50, false)] + [TestCase(100, true)] + [TestCase(100, false)] + public void PutNextRectangle_IsDenseEnough_OnDifferentInputData(int numberOfRectangles, bool randomRectangleSize) + { + PlaceRectangles(numberOfRectangles, randomRectangleSize); + + var borders = layouter.GetCloudBorders(); + + var radius = Math.Max(borders.Width, borders.Height) / 2; + var circleSquare = Math.PI * radius * radius; + var rectanglesAccumulatedSquare = layouter.PlacedRectangles.Sum(r => r.Width * r.Height); + + var rectanglesToCircleSquareRatio = rectanglesAccumulatedSquare / circleSquare; + + Assert.That(rectanglesToCircleSquareRatio, Is.GreaterThan(0.7).Within(0.05)); + } + + [Timeout(3000)] [TestCase(true)] [TestCase(false)] public void PutNextRectangle_HasSufficientPerformance_OnLargeAmountOfRectangles(bool randomRectangleSize) + { + PlaceRectangles(200, randomRectangleSize); + } + + private void PlaceRectangles(int numberOfRectangles, bool randomRectangleSize) { var rectSize = new Size(20, 10); - - for (var i = 1; i <= 200; i++) + + for (var i = 1; i <= numberOfRectangles; i++) { if (randomRectangleSize) rectSize = new Size(random.Next(201), random.Next(201)); From bb8077743dc5c9f8ed99879f49c2c31c1ea8e20c Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Mon, 4 Dec 2023 00:08:36 +0500 Subject: [PATCH 06/10] TagsCloudDrawer.cs has been implemented --- cs/TagsCloudVisualization/Program.cs | 22 ++++++++++ cs/TagsCloudVisualization/TagsCloudDrawer.cs | 42 ++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 cs/TagsCloudVisualization/Program.cs create mode 100644 cs/TagsCloudVisualization/TagsCloudDrawer.cs diff --git a/cs/TagsCloudVisualization/Program.cs b/cs/TagsCloudVisualization/Program.cs new file mode 100644 index 000000000..d4a1338a6 --- /dev/null +++ b/cs/TagsCloudVisualization/Program.cs @@ -0,0 +1,22 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public static class Program +{ + public static void Main() + { + var random = new Random(); + + var layouter = new CircularCloudLayouter(new Point(-500, -500)); + for (var i = 1; i <= 100; i++) + { + layouter.PutNextRectangle(new Size(10,10)); + } + + var drawer = new TagsCloudDrawer(layouter); + + var bitmap = drawer.DrawRectangles(new Pen(Color.Red, 1), 10); + TagsCloudDrawer.SaveImage(bitmap, Directory.GetCurrentDirectory(), "image.jpeg"); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudDrawer.cs b/cs/TagsCloudVisualization/TagsCloudDrawer.cs new file mode 100644 index 000000000..6161b3117 --- /dev/null +++ b/cs/TagsCloudVisualization/TagsCloudDrawer.cs @@ -0,0 +1,42 @@ +using System.Drawing; +using System.Drawing.Imaging; + +namespace TagsCloudVisualization; + +public class TagsCloudDrawer +{ + private readonly ICloudLayouter layouter; + + public TagsCloudDrawer(ICloudLayouter layouter) + { + this.layouter = layouter; + } + + public Bitmap DrawRectangles(Pen pen, int scale) + { + var borders = layouter.GetCloudBorders(); + var bitmap = new Bitmap(borders.Width * scale, borders.Height * scale); + var graphics = Graphics.FromImage(bitmap); + + var rectanglesWithShift = layouter.PlacedRectangles + .Select(r => + new Rectangle( + (r.X - borders.X) * scale, + (r.Y - borders.Y) * scale, + r.Width * scale, + r.Height * scale)); + + foreach (var rectangle in rectanglesWithShift) + graphics.DrawRectangle(pen, rectangle); + + return bitmap; + } + + public static void SaveImage(Bitmap bitmap, string dirPath, string filename) + { + if (!Directory.Exists(dirPath)) + Directory.CreateDirectory(dirPath); + + bitmap.Save(Path.Combine(dirPath, filename), ImageFormat.Jpeg); + } +} \ No newline at end of file From 8aaf0c0b7f2dcd67d64217f677adac531acbb30b Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Mon, 4 Dec 2023 13:08:39 +0500 Subject: [PATCH 07/10] Added generated images, implemented tests for TagsCloudDrawer.cs --- .../Images/100 rectangles 10x10.jpeg | Bin 0 -> 93009 bytes .../100 rectangles with random size.jpeg | Bin 0 -> 506835 bytes .../Images/25 rectangles 50x10.jpeg | Bin 0 -> 101359 bytes cs/TagsCloudVisualization/Program.cs | 10 ++--- cs/TagsCloudVisualization/README.md | 6 +++ cs/TagsCloudVisualization/TagsCloudDrawer.cs | 21 ++++++---- .../TagsCloudDrawerTests.cs | 37 ++++++++++++++++++ .../TagsCloudVisualizationTests.csproj | 1 + 8 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 cs/TagsCloudVisualization/Images/100 rectangles 10x10.jpeg create mode 100644 cs/TagsCloudVisualization/Images/100 rectangles with random size.jpeg create mode 100644 cs/TagsCloudVisualization/Images/25 rectangles 50x10.jpeg create mode 100644 cs/TagsCloudVisualization/README.md create mode 100644 cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs diff --git a/cs/TagsCloudVisualization/Images/100 rectangles 10x10.jpeg b/cs/TagsCloudVisualization/Images/100 rectangles 10x10.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0d30ff8585751ffb4d40f525521598c556f4585c GIT binary patch literal 93009 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

yOXD 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 b|C%|ct3i=mX6&+v21L4jav98yvd8}e#*f{8 literal 0 HcmV?d00001 diff --git a/cs/TagsCloudVisualization/Images/25 rectangles 50x10.jpeg b/cs/TagsCloudVisualization/Images/25 rectangles 50x10.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..fc155e2b511d6464907535811c1b19a3e03f8bcd GIT binary patch literal 101359 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 c|MXsiT23r`74`8G&D>w@x4#-~a#s literal 0 HcmV?d00001 diff --git a/cs/TagsCloudVisualization/Program.cs b/cs/TagsCloudVisualization/Program.cs index d4a1338a6..7f8bc7afa 100644 --- a/cs/TagsCloudVisualization/Program.cs +++ b/cs/TagsCloudVisualization/Program.cs @@ -8,15 +8,13 @@ public static void Main() { var random = new Random(); - var layouter = new CircularCloudLayouter(new Point(-500, -500)); + var layouter = new CircularCloudLayouter(new Point(500, 500)); for (var i = 1; i <= 100; i++) - { - layouter.PutNextRectangle(new Size(10,10)); - } + layouter.PutNextRectangle(new Size(random.Next(201), random.Next(201))); var drawer = new TagsCloudDrawer(layouter); - var bitmap = drawer.DrawRectangles(new Pen(Color.Red, 1), 10); - TagsCloudDrawer.SaveImage(bitmap, Directory.GetCurrentDirectory(), "image.jpeg"); + var bitmap = drawer.DrawRectangles(new Pen(Color.Red, 3), 3); + TagsCloudDrawer.SaveImage(bitmap, ""); } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/README.md b/cs/TagsCloudVisualization/README.md new file mode 100644 index 000000000..454bbae36 --- /dev/null +++ b/cs/TagsCloudVisualization/README.md @@ -0,0 +1,6 @@ +![25 rectangles 50x10.jpeg](Images%2F25%20rectangles%2050x10.jpeg) +25 rectangles 50x10 +![100 rectangles 10x10.jpeg](Images%2F100%20rectangles%2010x10.jpeg) +100 rectangles 10x10 +![100 rectangles with random size.jpeg](Images%2F100%20rectangles%20with%20random%20size.jpeg) +100 rectangles with random size \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudDrawer.cs b/cs/TagsCloudVisualization/TagsCloudDrawer.cs index 6161b3117..9e2136c9c 100644 --- a/cs/TagsCloudVisualization/TagsCloudDrawer.cs +++ b/cs/TagsCloudVisualization/TagsCloudDrawer.cs @@ -14,10 +14,15 @@ public TagsCloudDrawer(ICloudLayouter layouter) public Bitmap DrawRectangles(Pen pen, int scale) { + if (scale <= 0) + throw new ArgumentException("Scale must be a positive number."); + if (pen is null) + throw new ArgumentException("Pen must not be null."); + var borders = layouter.GetCloudBorders(); var bitmap = new Bitmap(borders.Width * scale, borders.Height * scale); var graphics = Graphics.FromImage(bitmap); - + var rectanglesWithShift = layouter.PlacedRectangles .Select(r => new Rectangle( @@ -31,12 +36,14 @@ public Bitmap DrawRectangles(Pen pen, int scale) return bitmap; } - - public static void SaveImage(Bitmap bitmap, string dirPath, string filename) + + public static void SaveImage(Bitmap bitmap, string filename) { - if (!Directory.Exists(dirPath)) - Directory.CreateDirectory(dirPath); - - bitmap.Save(Path.Combine(dirPath, filename), ImageFormat.Jpeg); + if (string.IsNullOrWhiteSpace(filename) || filename.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) + throw new ArgumentException("The provided filename is not valid."); + + var currentDir = Directory.GetCurrentDirectory(); + + bitmap.Save(Path.Combine(currentDir, filename), ImageFormat.Jpeg); } } \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs b/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs new file mode 100644 index 000000000..17650fcfd --- /dev/null +++ b/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs @@ -0,0 +1,37 @@ +using System.Drawing; +using NSubstitute; +using TagsCloudVisualization; + +namespace TagsCloudVisualizationTests; + +public class TagsCloudDrawerTests +{ + private TagsCloudDrawer drawer = null!; + private Pen pen = null!; + + [SetUp] + public void SetUp() + { + var layouter = Substitute.For(); + drawer = new TagsCloudDrawer(layouter); + pen = new Pen(Color.Red, 1); + } + + [TestCase(false, -2)] + [TestCase(false, 0)] + [TestCase(true, 2)] + public void DrawRectangles_ThrowsArgumentException_OnInvalidArguments(bool penIsNull, int scale) + { + Assert.Throws(() => drawer.DrawRectangles((penIsNull ? null : pen)!, scale)); + } + + [TestCase("<>\\")] + [TestCase("name|123")] + [TestCase(null)] + [TestCase("")] + public void SaveImage_ThrowsArgumentException_OnInvalidFilename(string filename) + { + Assert.Throws(() => TagsCloudDrawer.SaveImage(new Bitmap(1, 1), filename)); + } + +} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj b/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj index 4907a49f3..1fa321d9d 100644 --- a/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj +++ b/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj @@ -12,6 +12,7 @@ + From bd6d36196d3d54f55c6e8d3ebdb4595d907415a4 Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Mon, 4 Dec 2023 14:21:01 +0500 Subject: [PATCH 08/10] Added image generating after failed test functionality, small refactoring --- cs/TagsCloudVisualization/Program.cs | 2 +- cs/TagsCloudVisualization/TagsCloudDrawer.cs | 17 ++++++++++++----- .../CircularCloudLayouterTests.cs | 14 +++++++++++++- .../TagsCloudDrawerTests.cs | 17 ++++++++++------- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/cs/TagsCloudVisualization/Program.cs b/cs/TagsCloudVisualization/Program.cs index 7f8bc7afa..acb5fdfcd 100644 --- a/cs/TagsCloudVisualization/Program.cs +++ b/cs/TagsCloudVisualization/Program.cs @@ -15,6 +15,6 @@ public static void Main() var drawer = new TagsCloudDrawer(layouter); var bitmap = drawer.DrawRectangles(new Pen(Color.Red, 3), 3); - TagsCloudDrawer.SaveImage(bitmap, ""); + TagsCloudDrawer.SaveImage(bitmap, Directory.GetCurrentDirectory(), "image.jpeg"); } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudDrawer.cs b/cs/TagsCloudVisualization/TagsCloudDrawer.cs index 9e2136c9c..a4c649571 100644 --- a/cs/TagsCloudVisualization/TagsCloudDrawer.cs +++ b/cs/TagsCloudVisualization/TagsCloudDrawer.cs @@ -37,13 +37,20 @@ public Bitmap DrawRectangles(Pen pen, int scale) return bitmap; } - public static void SaveImage(Bitmap bitmap, string filename) + public static void SaveImage(Bitmap bitmap, string dirPath, string filename) { if (string.IsNullOrWhiteSpace(filename) || filename.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) throw new ArgumentException("The provided filename is not valid."); - - var currentDir = Directory.GetCurrentDirectory(); - - bitmap.Save(Path.Combine(currentDir, filename), ImageFormat.Jpeg); + + try + { + Directory.CreateDirectory(dirPath); + } + catch (Exception) + { + throw new ArgumentException("The provided directory path is not valid."); + } + + bitmap.Save(Path.Combine(dirPath, filename), ImageFormat.Jpeg); } } \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs index 8d90f64f7..da27e5068 100644 --- a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs @@ -1,4 +1,5 @@ using System.Drawing; +using NUnit.Framework.Interfaces; using TagsCloudVisualization; namespace TagsCloudVisualizationTests; @@ -89,13 +90,24 @@ public void PutNextRectangle_IsDenseEnough_OnDifferentInputData(int numberOfRect Assert.That(rectanglesToCircleSquareRatio, Is.GreaterThan(0.7).Within(0.05)); } - [Timeout(3000)] + [Timeout(5000)] [TestCase(true)] [TestCase(false)] public void PutNextRectangle_HasSufficientPerformance_OnLargeAmountOfRectangles(bool randomRectangleSize) { PlaceRectangles(200, randomRectangleSize); } + + [TearDown] + public void GenerateImage_OnTestFail() + { + if (TestContext.CurrentContext.Result.Outcome == ResultState.Success) + return; + + var drawer = new TagsCloudDrawer(layouter); + var bitmap = drawer.DrawRectangles(new Pen(Color.Red, 1), 5); + TagsCloudDrawer.SaveImage(bitmap, @"..\..\..\FailedTests",$"{TestContext.CurrentContext.Test.Name}.jpeg"); + } private void PlaceRectangles(int numberOfRectangles, bool randomRectangleSize) { diff --git a/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs b/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs index 17650fcfd..f3d104bce 100644 --- a/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs +++ b/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs @@ -25,13 +25,16 @@ public void DrawRectangles_ThrowsArgumentException_OnInvalidArguments(bool penIs Assert.Throws(() => drawer.DrawRectangles((penIsNull ? null : pen)!, scale)); } - [TestCase("<>\\")] - [TestCase("name|123")] - [TestCase(null)] - [TestCase("")] - public void SaveImage_ThrowsArgumentException_OnInvalidFilename(string filename) + [TestCase("test", "<>\\")] + [TestCase("test", "name|123")] + [TestCase("test", null)] + [TestCase("test", "")] + [TestCase(null, "filename")] + [TestCase("","filename")] + [TestCase(" ","filename")] + [TestCase(@"\:\","filename")] + public void SaveImage_ThrowsArgumentException_OnInvalidParameters(string dirPath, string filename) { - Assert.Throws(() => TagsCloudDrawer.SaveImage(new Bitmap(1, 1), filename)); + Assert.Throws(() => TagsCloudDrawer.SaveImage(new Bitmap(1, 1), dirPath, filename)); } - } \ No newline at end of file From e382de7b4472ac9eb9c8b2730ca0245c8ff0214d Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Tue, 5 Dec 2023 17:59:07 +0500 Subject: [PATCH 09/10] refactored --- .../CircularCloudBuilder.cs | 68 +++++++++++++++++ .../CircularCloudLayouter.cs | 73 ++----------------- .../ICircularCloudBuilder.cs | 8 ++ cs/TagsCloudVisualization/ICloudLayouter.cs | 16 +++- cs/TagsCloudVisualization/IRectangleDrawer.cs | 8 ++ cs/TagsCloudVisualization/Program.cs | 7 +- cs/TagsCloudVisualization/RectangleDrawer.cs | 39 ++++++++++ cs/TagsCloudVisualization/TagsCloudDrawer.cs | 26 ++----- .../CircularCloudLayouterTests.cs | 71 ++++++++++-------- .../RectangleDrawerTests.cs | 32 ++++++++ .../SpiralBuilderTests.cs | 27 +++++++ .../TagsCloudDrawerTests.cs | 21 +----- .../TagsCloudVisualizationTests.csproj | 2 +- 13 files changed, 254 insertions(+), 144 deletions(-) create mode 100644 cs/TagsCloudVisualization/CircularCloudBuilder.cs create mode 100644 cs/TagsCloudVisualization/ICircularCloudBuilder.cs create mode 100644 cs/TagsCloudVisualization/IRectangleDrawer.cs create mode 100644 cs/TagsCloudVisualization/RectangleDrawer.cs create mode 100644 cs/TagsCloudVisualizationTests/RectangleDrawerTests.cs create mode 100644 cs/TagsCloudVisualizationTests/SpiralBuilderTests.cs diff --git a/cs/TagsCloudVisualization/CircularCloudBuilder.cs b/cs/TagsCloudVisualization/CircularCloudBuilder.cs new file mode 100644 index 000000000..5b201da26 --- /dev/null +++ b/cs/TagsCloudVisualization/CircularCloudBuilder.cs @@ -0,0 +1,68 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public class CircularCloudBuilder : ICircularCloudBuilder +{ + public Point Center { get; } + public int RadiusIncrement { get; } + public double AngleIncrement { get; } + + public CircularCloudBuilder(Point center, int radiusIncrement, double angleIncrement) + { + Center = center; + RadiusIncrement = radiusIncrement; + AngleIncrement = angleIncrement; + } + + private static void ValidateArguments(Size rectangleSize, List placedRectangles) + { + if (placedRectangles is null) + throw new ArgumentException("The list of placed rectangles cannot be null."); + if (rectangleSize.Height < 0 || rectangleSize.Width < 0) + throw new ArgumentException("Height and width must be positive"); + } + + private Rectangle PlaceFirstRectangle(Size rectangleSize) + { + var firstRectangleLocation = new Point(Center.X - rectangleSize.Width / 2, + Center.Y - rectangleSize.Height / 2); + return new Rectangle(firstRectangleLocation, rectangleSize); + } + + public Rectangle GetNextPosition(Size rectangleSize, List placedRectangles) + { + ValidateArguments(rectangleSize, placedRectangles); + + if (placedRectangles.Count == 0) + return PlaceFirstRectangle(rectangleSize); + + var rectangle = new Rectangle(Point.Empty, rectangleSize); + var radius = 0d; + var angle = 0d; + + do + { + var x = Center.X + radius * Math.Cos(angle); + var y = Center.Y + radius * Math.Sin(angle); + rectangle.Location = new Point((int) x, (int) y); + + angle += AngleIncrement; + + if (!(angle >= 2 * Math.PI)) + continue; + + angle = 0; + radius += RadiusIncrement; + } while (IntersectsWithAny(rectangle, placedRectangles)); + + return rectangle; + } + + private static bool IntersectsWithAny(Rectangle newRectangle, IEnumerable placedRectangles) + { + return placedRectangles + .Any(rectangle => rectangle + .IntersectsWith(newRectangle)); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 37f6d32c9..47c3e36f8 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -5,64 +5,11 @@ namespace TagsCloudVisualization; public class CircularCloudLayouter : ICloudLayouter { public List PlacedRectangles { get; } = new(); - private readonly Point center; + private readonly ICircularCloudBuilder circularCloudBuilder; - public CircularCloudLayouter(Point center) + public CircularCloudLayouter(ICircularCloudBuilder circularCloudBuilder) { - this.center = center; - } - - private Rectangle CalculateRectanglePosition(Size rectangleSize, int radiusIncrement, double angleIncrement) - { - var rectangle = new Rectangle(Point.Empty, rectangleSize); - - var radius = 0d; - var angle = 0d; - - do - { - var x = center.X + radius * Math.Cos(angle); - var y = center.Y + radius * Math.Sin(angle); - rectangle.Location = new Point((int) x, (int) y); - - angle += angleIncrement; - - if (!(angle >= 2 * Math.PI)) - continue; - - angle = 0; - radius += radiusIncrement; - } while (IntersectsWithAny(rectangle)); - - return rectangle; - } - - private Rectangle PlaceRectangle(Size rectangleSize) - { - var newRectangle = new Rectangle(Point.Empty, rectangleSize); - - if (PlacedRectangles.Count == 0) - { - newRectangle.Location = new Point(center.X - newRectangle.Width / 2, center.Y - newRectangle.Height / 2); - return newRectangle; - } - - const int radiusIncrement = 1; - const double angleIncrement = 0.1d; - - return CalculateRectanglePosition(rectangleSize, radiusIncrement, angleIncrement); - } - - public Rectangle GetCloudBorders() - { - var left = PlacedRectangles.Min(r => r.Left); - var right = PlacedRectangles.Max(r => r.Right); - var top = PlacedRectangles.Min(r => r.Top); - var bottom = PlacedRectangles.Max(r => r.Bottom); - - var width = right - left; - var height = bottom - top; - return new Rectangle(left, top, width, height); + this.circularCloudBuilder = circularCloudBuilder; } public Rectangle PutNextRectangle(Size rectangleSize) @@ -70,15 +17,9 @@ public Rectangle PutNextRectangle(Size rectangleSize) if (rectangleSize.Height < 0 || rectangleSize.Width < 0) throw new ArgumentException("Height and width must be positive"); - var newRectangle = PlaceRectangle(rectangleSize); - PlacedRectangles.Add(newRectangle); - return newRectangle; - } - - private bool IntersectsWithAny(Rectangle newRectangle) - { - return PlacedRectangles - .Any(rectangle => rectangle - .IntersectsWith(newRectangle)); + var nextRectangle = circularCloudBuilder.GetNextPosition(rectangleSize, PlacedRectangles); + + PlacedRectangles.Add(nextRectangle); + return nextRectangle; } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/ICircularCloudBuilder.cs b/cs/TagsCloudVisualization/ICircularCloudBuilder.cs new file mode 100644 index 000000000..e5d66e5b7 --- /dev/null +++ b/cs/TagsCloudVisualization/ICircularCloudBuilder.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public interface ICircularCloudBuilder +{ + public Rectangle GetNextPosition(Size rectangleSize, List placedRectangles); +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/ICloudLayouter.cs b/cs/TagsCloudVisualization/ICloudLayouter.cs index 017f6cffb..b5d08c914 100644 --- a/cs/TagsCloudVisualization/ICloudLayouter.cs +++ b/cs/TagsCloudVisualization/ICloudLayouter.cs @@ -6,5 +6,19 @@ public interface ICloudLayouter { List PlacedRectangles { get; } Rectangle PutNextRectangle(Size rectangleSize); - Rectangle GetCloudBorders(); + + Rectangle GetCloudBorders() + { + if (PlacedRectangles is null || PlacedRectangles.Count == 0) + throw new InvalidOperationException("The list of placed rectangles cannot be null or empty"); + + var left = PlacedRectangles.Min(r => r.Left); + var right = PlacedRectangles.Max(r => r.Right); + var top = PlacedRectangles.Min(r => r.Top); + var bottom = PlacedRectangles.Max(r => r.Bottom); + + var width = right - left; + var height = bottom - top; + return new Rectangle(left, top, width, height); + } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/IRectangleDrawer.cs b/cs/TagsCloudVisualization/IRectangleDrawer.cs new file mode 100644 index 000000000..e8d31a2f8 --- /dev/null +++ b/cs/TagsCloudVisualization/IRectangleDrawer.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public interface IRectangleDrawer +{ + public Bitmap DrawRectangles(List rectangles, Rectangle borders); +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/Program.cs b/cs/TagsCloudVisualization/Program.cs index acb5fdfcd..d41bee8b9 100644 --- a/cs/TagsCloudVisualization/Program.cs +++ b/cs/TagsCloudVisualization/Program.cs @@ -8,13 +8,14 @@ public static void Main() { var random = new Random(); - var layouter = new CircularCloudLayouter(new Point(500, 500)); + var layouter = new CircularCloudLayouter(new CircularCloudBuilder(new Point(500, 500), 1, 0.1d)); for (var i = 1; i <= 100; i++) layouter.PutNextRectangle(new Size(random.Next(201), random.Next(201))); - var drawer = new TagsCloudDrawer(layouter); + var rectangleDrawer = new RectangleDrawer(new Pen(Color.Red, 3), 3); + var drawer = new TagsCloudDrawer(layouter, rectangleDrawer); - var bitmap = drawer.DrawRectangles(new Pen(Color.Red, 3), 3); + var bitmap = drawer.DrawTagCloud(); TagsCloudDrawer.SaveImage(bitmap, Directory.GetCurrentDirectory(), "image.jpeg"); } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/RectangleDrawer.cs b/cs/TagsCloudVisualization/RectangleDrawer.cs new file mode 100644 index 000000000..98ec704fa --- /dev/null +++ b/cs/TagsCloudVisualization/RectangleDrawer.cs @@ -0,0 +1,39 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public class RectangleDrawer : IRectangleDrawer +{ + private readonly Pen pen; + private readonly int scale; + + public RectangleDrawer(Pen pen, int scale) + { + if (scale <= 0) + throw new ArgumentException("Scale must be a positive number."); + this.pen = pen ?? throw new ArgumentException("Pen must not be null."); + this.scale = scale; + } + + public Bitmap DrawRectangles(List rectangles, Rectangle borders) + { + if (rectangles is null) + throw new ArgumentException("The list of rectangles cannot be null"); + + var bitmap = new Bitmap(borders.Width * scale, borders.Height * scale); + var graphics = Graphics.FromImage(bitmap); + + var rectanglesWithShift = rectangles + .Select(r => + new Rectangle( + (r.X - borders.X) * scale, + (r.Y - borders.Y) * scale, + r.Width * scale, + r.Height * scale)); + + foreach (var rectangle in rectanglesWithShift) + graphics.DrawRectangle(pen, rectangle); + + return bitmap; + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudDrawer.cs b/cs/TagsCloudVisualization/TagsCloudDrawer.cs index a4c649571..72166b018 100644 --- a/cs/TagsCloudVisualization/TagsCloudDrawer.cs +++ b/cs/TagsCloudVisualization/TagsCloudDrawer.cs @@ -6,35 +6,19 @@ namespace TagsCloudVisualization; public class TagsCloudDrawer { private readonly ICloudLayouter layouter; + private readonly IRectangleDrawer drawer; - public TagsCloudDrawer(ICloudLayouter layouter) + public TagsCloudDrawer(ICloudLayouter layouter, IRectangleDrawer drawer) { this.layouter = layouter; + this.drawer = drawer; } - public Bitmap DrawRectangles(Pen pen, int scale) + public Bitmap DrawTagCloud() { - if (scale <= 0) - throw new ArgumentException("Scale must be a positive number."); - if (pen is null) - throw new ArgumentException("Pen must not be null."); - var borders = layouter.GetCloudBorders(); - var bitmap = new Bitmap(borders.Width * scale, borders.Height * scale); - var graphics = Graphics.FromImage(bitmap); - - var rectanglesWithShift = layouter.PlacedRectangles - .Select(r => - new Rectangle( - (r.X - borders.X) * scale, - (r.Y - borders.Y) * scale, - r.Width * scale, - r.Height * scale)); - - foreach (var rectangle in rectanglesWithShift) - graphics.DrawRectangle(pen, rectangle); - return bitmap; + return drawer.DrawRectangles(layouter.PlacedRectangles, borders); } public static void SaveImage(Bitmap bitmap, string dirPath, string filename) diff --git a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs index da27e5068..e939bd16a 100644 --- a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs @@ -1,12 +1,14 @@ using System.Drawing; +using Moq; using NUnit.Framework.Interfaces; using TagsCloudVisualization; namespace TagsCloudVisualizationTests; +[TestFixture] public class CircularCloudLayouterTests { - private CircularCloudLayouter layouter = null!; + private ICloudLayouter layouter = null!; private Random random = null!; [OneTimeSetUp] @@ -18,7 +20,7 @@ public void OneTimeSetUp() [SetUp] public void SetUp() { - layouter = new CircularCloudLayouter(new Point(500, 500)); + layouter = new CircularCloudLayouter(new CircularCloudBuilder(new Point(500, 500), 1, 0.1d)); } [TestCase(-200, -300)] @@ -30,9 +32,20 @@ public void PutNextRectangle_ThrowsArgumentException_WhenRectanlgeSizeContainsNe Assert.Throws(() => layouter.PutNextRectangle(new Size(width, height))); } - [TestCase(true)] - [TestCase(false)] - public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectangles(bool randomRectangleSize) + [Test] + public void GetCloudBorders_ThrowsInvalidOperationException_WhenListOfRectanglesIsNullOrEmpty( + [Values(false, true)] bool isNull) + { + var mockLayouter = new Mock(); + mockLayouter.Setup(t => t.GetCloudBorders()).CallBase(); + if (!isNull) + mockLayouter.Setup(l => l.PlacedRectangles).Returns(new List()); + Assert.Throws(() => mockLayouter.Object.GetCloudBorders()); + } + + [Test] + public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectangles( + [Values(false, true)] bool randomRectangleSize) { var rectangles = new List(); var rectSize = new Size(20, 10); @@ -51,34 +64,28 @@ public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectang } } - [TestCase(10, true)] - [TestCase(10, false)] - [TestCase(50, true)] - [TestCase(50, false)] - [TestCase(100, true)] - [TestCase(100, false)] - public void PutNextRectangle_HasCircularShape_OnDifferentInputData(int numberOfRectangles, bool randomRectangleSize) + [Test] + public void PutNextRectangle_HasCircularShape_OnDifferentInputData( + [Values(50, 100)] int numberOfRectangles, + [Values(true, false)] bool randomRectangleSize) { PlaceRectangles(numberOfRectangles, randomRectangleSize); var borders = layouter.GetCloudBorders(); - var heightToWidthRatio = (double) Math.Min(borders.Width, borders.Height) / Math.Max(borders.Width, borders.Height); - + var heightToWidthRatio = + (double) Math.Min(borders.Width, borders.Height) / Math.Max(borders.Width, borders.Height); + Assert.That(heightToWidthRatio, Is.GreaterThan(0.8).Within(0.05)); } - - - [TestCase(10, true)] - [TestCase(10, false)] - [TestCase(50, true)] - [TestCase(50, false)] - [TestCase(100, true)] - [TestCase(100, false)] - public void PutNextRectangle_IsDenseEnough_OnDifferentInputData(int numberOfRectangles, bool randomRectangleSize) + + [Test] + public void PutNextRectangle_IsDenseEnough_OnDifferentInputData( + [Values(50, 100)] int numberOfRectangles, + [Values(true, false)] bool randomRectangleSize) { PlaceRectangles(numberOfRectangles, randomRectangleSize); - + var borders = layouter.GetCloudBorders(); var radius = Math.Max(borders.Width, borders.Height) / 2; @@ -86,14 +93,14 @@ public void PutNextRectangle_IsDenseEnough_OnDifferentInputData(int numberOfRect var rectanglesAccumulatedSquare = layouter.PlacedRectangles.Sum(r => r.Width * r.Height); var rectanglesToCircleSquareRatio = rectanglesAccumulatedSquare / circleSquare; - + Assert.That(rectanglesToCircleSquareRatio, Is.GreaterThan(0.7).Within(0.05)); } [Timeout(5000)] - [TestCase(true)] - [TestCase(false)] - public void PutNextRectangle_HasSufficientPerformance_OnLargeAmountOfRectangles(bool randomRectangleSize) + [Test] + public void PutNextRectangle_HasSufficientPerformance_OnLargeAmountOfRectangles( + [Values(true, false)]bool randomRectangleSize) { PlaceRectangles(200, randomRectangleSize); } @@ -104,11 +111,11 @@ public void GenerateImage_OnTestFail() if (TestContext.CurrentContext.Result.Outcome == ResultState.Success) return; - var drawer = new TagsCloudDrawer(layouter); - var bitmap = drawer.DrawRectangles(new Pen(Color.Red, 1), 5); - TagsCloudDrawer.SaveImage(bitmap, @"..\..\..\FailedTests",$"{TestContext.CurrentContext.Test.Name}.jpeg"); + var drawer = new TagsCloudDrawer(layouter, new RectangleDrawer(new Pen(Color.Red, 1), 5)); + var bitmap = drawer.DrawTagCloud(); + TagsCloudDrawer.SaveImage(bitmap, @"..\..\..\FailedTests", $"{TestContext.CurrentContext.Test.Name}.jpeg"); } - + private void PlaceRectangles(int numberOfRectangles, bool randomRectangleSize) { var rectSize = new Size(20, 10); diff --git a/cs/TagsCloudVisualizationTests/RectangleDrawerTests.cs b/cs/TagsCloudVisualizationTests/RectangleDrawerTests.cs new file mode 100644 index 000000000..6c4d939d7 --- /dev/null +++ b/cs/TagsCloudVisualizationTests/RectangleDrawerTests.cs @@ -0,0 +1,32 @@ +using System.Drawing; +using TagsCloudVisualization; + +namespace TagsCloudVisualizationTests; + +[TestFixture] +public class RectangleDrawerTests +{ + private Pen pen = null!; + private RectangleDrawer drawer = null!; + + [SetUp] + public void SetUp() + { + pen = new Pen(Color.Red, 5); + drawer = new RectangleDrawer(pen, 5); + } + + [Test] + public void DrawRectangles_ThrowsArgumentException_WhenListOfRectanglesIsNull() + { + Assert.Throws(() => drawer.DrawRectangles(null!, new Rectangle(1,1,1,1))); + } + + [TestCase(false, -2)] + [TestCase(false, 0)] + [TestCase(true, 2)] + public void Constructor_ThrowsArgumentException_OnInvalidArguments(bool penIsNull, int scale) + { + Assert.Throws(() => new RectangleDrawer((penIsNull ? null : pen)!, scale)); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/SpiralBuilderTests.cs b/cs/TagsCloudVisualizationTests/SpiralBuilderTests.cs new file mode 100644 index 000000000..f19ecc8be --- /dev/null +++ b/cs/TagsCloudVisualizationTests/SpiralBuilderTests.cs @@ -0,0 +1,27 @@ +using System.Drawing; +using TagsCloudVisualization; + +namespace TagsCloudVisualizationTests; + +[TestFixture] +public class SpiralBuilderTests +{ + private CircularCloudBuilder builder = null!; + private List placedRectangles = null!; + + [SetUp] + public void SetUp() + { + builder = new CircularCloudBuilder(new Point(500, 500), 1, 0.1d); + placedRectangles = new List(); + } + + [TestCase(-200, 300, false)] + [TestCase(200, -300, false)] + [TestCase(200, 300, true)] + public void GetNextPosition_ThrowsArgumentException_OnIncorrectParameters(int width, int height, bool listIsNull) + { + Assert.Throws( () => builder.GetNextPosition(new Size(width, height), + (listIsNull ? null : placedRectangles)!)); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs b/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs index f3d104bce..3b72de9b6 100644 --- a/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs +++ b/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs @@ -1,30 +1,11 @@ using System.Drawing; -using NSubstitute; using TagsCloudVisualization; namespace TagsCloudVisualizationTests; +[TestFixture] public class TagsCloudDrawerTests { - private TagsCloudDrawer drawer = null!; - private Pen pen = null!; - - [SetUp] - public void SetUp() - { - var layouter = Substitute.For(); - drawer = new TagsCloudDrawer(layouter); - pen = new Pen(Color.Red, 1); - } - - [TestCase(false, -2)] - [TestCase(false, 0)] - [TestCase(true, 2)] - public void DrawRectangles_ThrowsArgumentException_OnInvalidArguments(bool penIsNull, int scale) - { - Assert.Throws(() => drawer.DrawRectangles((penIsNull ? null : pen)!, scale)); - } - [TestCase("test", "<>\\")] [TestCase("test", "name|123")] [TestCase("test", null)] diff --git a/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj b/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj index 1fa321d9d..5b459266f 100644 --- a/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj +++ b/cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj @@ -12,7 +12,7 @@ - + From 90ec034a56fead2ed7eb7124b848ea7eeb36d483 Mon Sep 17 00:00:00 2001 From: Alexander Gorbatov Date: Thu, 7 Dec 2023 13:34:42 +0500 Subject: [PATCH 10/10] refactored --- .../CircularCloudBuilder.cs | 66 +++++++------------ .../CircularCloudLayouter.cs | 28 ++++++-- .../CloudLayouterExtensions.cs | 28 ++++++++ .../ICircularCloudBuilder.cs | 2 +- cs/TagsCloudVisualization/ICloudLayouter.cs | 15 ----- cs/TagsCloudVisualization/IRectangleDrawer.cs | 8 --- cs/TagsCloudVisualization/ImageParameters.cs | 17 +++++ cs/TagsCloudVisualization/Program.cs | 11 ++-- cs/TagsCloudVisualization/RectangleDrawer.cs | 39 ----------- cs/TagsCloudVisualization/TagsCloudDrawer.cs | 56 ++++++++++++++-- .../CircularCloudBuilderTests.cs | 35 ++++++++++ .../CircularCloudLayouterTests.cs | 26 +++----- .../RectangleDrawerTests.cs | 32 --------- .../SpiralBuilderTests.cs | 27 -------- .../TagsCloudDrawerTests.cs | 34 +++++++++- 15 files changed, 223 insertions(+), 201 deletions(-) create mode 100644 cs/TagsCloudVisualization/CloudLayouterExtensions.cs delete mode 100644 cs/TagsCloudVisualization/IRectangleDrawer.cs create mode 100644 cs/TagsCloudVisualization/ImageParameters.cs delete mode 100644 cs/TagsCloudVisualization/RectangleDrawer.cs create mode 100644 cs/TagsCloudVisualizationTests/CircularCloudBuilderTests.cs delete mode 100644 cs/TagsCloudVisualizationTests/RectangleDrawerTests.cs delete mode 100644 cs/TagsCloudVisualizationTests/SpiralBuilderTests.cs diff --git a/cs/TagsCloudVisualization/CircularCloudBuilder.cs b/cs/TagsCloudVisualization/CircularCloudBuilder.cs index 5b201da26..0f3e7071f 100644 --- a/cs/TagsCloudVisualization/CircularCloudBuilder.cs +++ b/cs/TagsCloudVisualization/CircularCloudBuilder.cs @@ -4,65 +4,49 @@ namespace TagsCloudVisualization; public class CircularCloudBuilder : ICircularCloudBuilder { - public Point Center { get; } - public int RadiusIncrement { get; } - public double AngleIncrement { get; } + private readonly int radiusIncrement; + private readonly double angleIncrement; - public CircularCloudBuilder(Point center, int radiusIncrement, double angleIncrement) + public CircularCloudBuilder(int radiusIncrement, double angleIncrement) { - Center = center; - RadiusIncrement = radiusIncrement; - AngleIncrement = angleIncrement; + if (radiusIncrement == 0 || Math.Abs(angleIncrement - 0) < 1e-3) + throw new ArgumentException("Parameters for the algorithm must not be null"); + + this.radiusIncrement = radiusIncrement; + this.angleIncrement = angleIncrement; } private static void ValidateArguments(Size rectangleSize, List placedRectangles) { + if (rectangleSize.Height < 0 || rectangleSize.Width < 0) + throw new ArgumentException("Height and width must not be negative."); if (placedRectangles is null) throw new ArgumentException("The list of placed rectangles cannot be null."); - if (rectangleSize.Height < 0 || rectangleSize.Width < 0) - throw new ArgumentException("Height and width must be positive"); } - private Rectangle PlaceFirstRectangle(Size rectangleSize) - { - var firstRectangleLocation = new Point(Center.X - rectangleSize.Width / 2, - Center.Y - rectangleSize.Height / 2); - return new Rectangle(firstRectangleLocation, rectangleSize); - } - - public Rectangle GetNextPosition(Size rectangleSize, List placedRectangles) + public Rectangle GetNextPosition(Point center, Size rectangleSize, List placedRectangles) { ValidateArguments(rectangleSize, placedRectangles); - if (placedRectangles.Count == 0) - return PlaceFirstRectangle(rectangleSize); - - var rectangle = new Rectangle(Point.Empty, rectangleSize); var radius = 0d; var angle = 0d; - + var rectangle = new Rectangle(Point.Empty, rectangleSize); do { - var x = Center.X + radius * Math.Cos(angle); - var y = Center.Y + radius * Math.Sin(angle); + var x = center.X + radius * Math.Cos(angle); + var y = center.Y + radius * Math.Sin(angle); + + angle += angleIncrement; + + if (Math.Abs(angle) >= 2 * Math.PI) + { + angle = 0; + radius += radiusIncrement; + } + rectangle.Location = new Point((int) x, (int) y); - - angle += AngleIncrement; - - if (!(angle >= 2 * Math.PI)) - continue; - - angle = 0; - radius += RadiusIncrement; - } while (IntersectsWithAny(rectangle, placedRectangles)); - + } while (placedRectangles.AnyIntersectsWith(rectangle)); + return rectangle; } - - private static bool IntersectsWithAny(Rectangle newRectangle, IEnumerable placedRectangles) - { - return placedRectangles - .Any(rectangle => rectangle - .IntersectsWith(newRectangle)); - } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 47c3e36f8..1aa91c960 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -5,21 +5,37 @@ namespace TagsCloudVisualization; public class CircularCloudLayouter : ICloudLayouter { public List PlacedRectangles { get; } = new(); + private readonly Point center; private readonly ICircularCloudBuilder circularCloudBuilder; - public CircularCloudLayouter(ICircularCloudBuilder circularCloudBuilder) + public CircularCloudLayouter(Point center, ICircularCloudBuilder circularCloudBuilder) { this.circularCloudBuilder = circularCloudBuilder; + this.center = center; } - + + private Rectangle PlaceFirstRectangle(Size rectangleSize) + { + var firstRectangleLocation = new Point(center.X - rectangleSize.Width / 2, + center.Y - rectangleSize.Height / 2); + return new Rectangle(firstRectangleLocation, rectangleSize); + } + public Rectangle PutNextRectangle(Size rectangleSize) { if (rectangleSize.Height < 0 || rectangleSize.Width < 0) throw new ArgumentException("Height and width must be positive"); - var nextRectangle = circularCloudBuilder.GetNextPosition(rectangleSize, PlacedRectangles); - - PlacedRectangles.Add(nextRectangle); - return nextRectangle; + if (PlacedRectangles.Count == 0) + { + var firstRectangle = PlaceFirstRectangle(rectangleSize); + PlacedRectangles.Add(firstRectangle); + return firstRectangle; + } + + var rectangle = circularCloudBuilder.GetNextPosition(center, rectangleSize, PlacedRectangles); + + PlacedRectangles.Add(rectangle); + return rectangle; } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CloudLayouterExtensions.cs b/cs/TagsCloudVisualization/CloudLayouterExtensions.cs new file mode 100644 index 000000000..dfa480177 --- /dev/null +++ b/cs/TagsCloudVisualization/CloudLayouterExtensions.cs @@ -0,0 +1,28 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public static class CloudLayouterExtensions +{ + public static Rectangle GetBorders(this List placedRectangles) + { + if (placedRectangles is null || placedRectangles.Count == 0) + throw new InvalidOperationException("The list of placed rectangles cannot be null or empty"); + + var left = placedRectangles.Min(r => r.Left); + var right = placedRectangles.Max(r => r.Right); + var top = placedRectangles.Min(r => r.Top); + var bottom = placedRectangles.Max(r => r.Bottom); + + var width = right - left; + var height = bottom - top; + return new Rectangle(left, top, width, height); + } + + public static bool AnyIntersectsWith(this IEnumerable rectangles, Rectangle rectangle) + { + return rectangles + .Any(r => r + .IntersectsWith(rectangle)); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/ICircularCloudBuilder.cs b/cs/TagsCloudVisualization/ICircularCloudBuilder.cs index e5d66e5b7..5fb162eec 100644 --- a/cs/TagsCloudVisualization/ICircularCloudBuilder.cs +++ b/cs/TagsCloudVisualization/ICircularCloudBuilder.cs @@ -4,5 +4,5 @@ namespace TagsCloudVisualization; public interface ICircularCloudBuilder { - public Rectangle GetNextPosition(Size rectangleSize, List placedRectangles); + public Rectangle GetNextPosition(Point center, Size rectangleSize, List placedRectangles); } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/ICloudLayouter.cs b/cs/TagsCloudVisualization/ICloudLayouter.cs index b5d08c914..1f5451e3f 100644 --- a/cs/TagsCloudVisualization/ICloudLayouter.cs +++ b/cs/TagsCloudVisualization/ICloudLayouter.cs @@ -6,19 +6,4 @@ public interface ICloudLayouter { List PlacedRectangles { get; } Rectangle PutNextRectangle(Size rectangleSize); - - Rectangle GetCloudBorders() - { - if (PlacedRectangles is null || PlacedRectangles.Count == 0) - throw new InvalidOperationException("The list of placed rectangles cannot be null or empty"); - - var left = PlacedRectangles.Min(r => r.Left); - var right = PlacedRectangles.Max(r => r.Right); - var top = PlacedRectangles.Min(r => r.Top); - var bottom = PlacedRectangles.Max(r => r.Bottom); - - var width = right - left; - var height = bottom - top; - return new Rectangle(left, top, width, height); - } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/IRectangleDrawer.cs b/cs/TagsCloudVisualization/IRectangleDrawer.cs deleted file mode 100644 index e8d31a2f8..000000000 --- a/cs/TagsCloudVisualization/IRectangleDrawer.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Drawing; - -namespace TagsCloudVisualization; - -public interface IRectangleDrawer -{ - public Bitmap DrawRectangles(List rectangles, Rectangle borders); -} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/ImageParameters.cs b/cs/TagsCloudVisualization/ImageParameters.cs new file mode 100644 index 000000000..1a988434b --- /dev/null +++ b/cs/TagsCloudVisualization/ImageParameters.cs @@ -0,0 +1,17 @@ +namespace TagsCloudVisualization; + +public class ImageParameters +{ + public int OffsetX { get; set; } + public int OffsetY { get; set; } + public int Height { get; set; } + public int Width { get; set; } + + public ImageParameters(int offsetX, int offsetY, int height, int width) + { + OffsetX = offsetX; + OffsetY = offsetY; + Height = height; + Width = width; + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/Program.cs b/cs/TagsCloudVisualization/Program.cs index d41bee8b9..e56354725 100644 --- a/cs/TagsCloudVisualization/Program.cs +++ b/cs/TagsCloudVisualization/Program.cs @@ -6,15 +6,12 @@ public static class Program { public static void Main() { - var random = new Random(); - - var layouter = new CircularCloudLayouter(new CircularCloudBuilder(new Point(500, 500), 1, 0.1d)); + var layouter = new CircularCloudLayouter(new Point(0, 0), new CircularCloudBuilder(1, 0.1d)); for (var i = 1; i <= 100; i++) - layouter.PutNextRectangle(new Size(random.Next(201), random.Next(201))); + layouter.PutNextRectangle(new Size(20, 10)); + + var drawer = new TagsCloudDrawer(layouter, new Pen(Color.Red, 3), 3); - var rectangleDrawer = new RectangleDrawer(new Pen(Color.Red, 3), 3); - var drawer = new TagsCloudDrawer(layouter, rectangleDrawer); - var bitmap = drawer.DrawTagCloud(); TagsCloudDrawer.SaveImage(bitmap, Directory.GetCurrentDirectory(), "image.jpeg"); } diff --git a/cs/TagsCloudVisualization/RectangleDrawer.cs b/cs/TagsCloudVisualization/RectangleDrawer.cs deleted file mode 100644 index 98ec704fa..000000000 --- a/cs/TagsCloudVisualization/RectangleDrawer.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Drawing; - -namespace TagsCloudVisualization; - -public class RectangleDrawer : IRectangleDrawer -{ - private readonly Pen pen; - private readonly int scale; - - public RectangleDrawer(Pen pen, int scale) - { - if (scale <= 0) - throw new ArgumentException("Scale must be a positive number."); - this.pen = pen ?? throw new ArgumentException("Pen must not be null."); - this.scale = scale; - } - - public Bitmap DrawRectangles(List rectangles, Rectangle borders) - { - if (rectangles is null) - throw new ArgumentException("The list of rectangles cannot be null"); - - var bitmap = new Bitmap(borders.Width * scale, borders.Height * scale); - var graphics = Graphics.FromImage(bitmap); - - var rectanglesWithShift = rectangles - .Select(r => - new Rectangle( - (r.X - borders.X) * scale, - (r.Y - borders.Y) * scale, - r.Width * scale, - r.Height * scale)); - - foreach (var rectangle in rectanglesWithShift) - graphics.DrawRectangle(pen, rectangle); - - return bitmap; - } -} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagsCloudDrawer.cs b/cs/TagsCloudVisualization/TagsCloudDrawer.cs index 72166b018..731c196ad 100644 --- a/cs/TagsCloudVisualization/TagsCloudDrawer.cs +++ b/cs/TagsCloudVisualization/TagsCloudDrawer.cs @@ -6,21 +6,65 @@ namespace TagsCloudVisualization; public class TagsCloudDrawer { private readonly ICloudLayouter layouter; - private readonly IRectangleDrawer drawer; + private readonly Pen pen; + private readonly int scale; - public TagsCloudDrawer(ICloudLayouter layouter, IRectangleDrawer drawer) + + public TagsCloudDrawer(ICloudLayouter layouter, Pen pen, int scale) { this.layouter = layouter; - this.drawer = drawer; + if (scale <= 0) + throw new ArgumentException("Scale must be a positive number."); + this.pen = pen ?? throw new ArgumentException("Pen must not be null."); + this.scale = scale; + } + + private static ImageParameters AdjustImageParameters(Rectangle borders) + { + var width = borders.Width; + var height = borders.Height; + var offsetX = 0; + var offsetY = 0; + if (borders.Left < 0) + { + width += Math.Abs(borders.Left); + offsetX = borders.Left; + } + + if (borders.Top < 0) + { + height += Math.Abs(borders.Top); + offsetY = borders.Top; + } + + if (borders.Right > width) + width += borders.Right - width; + + if (borders.Bottom > height) + height += borders.Bottom - height; + + return new ImageParameters(offsetX, offsetY, height, width); } public Bitmap DrawTagCloud() { - var borders = layouter.GetCloudBorders(); + if (layouter.PlacedRectangles is null) + throw new ArgumentException("The list of rectangles cannot be null"); - return drawer.DrawRectangles(layouter.PlacedRectangles, borders); + var borders = layouter.PlacedRectangles.GetBorders(); + var adjustedSize = AdjustImageParameters(borders); + + var bitmap = new Bitmap(adjustedSize.Width * scale, adjustedSize.Height * scale); + var graphics = Graphics.FromImage(bitmap); + graphics.TranslateTransform(-adjustedSize.OffsetX * scale, -adjustedSize.OffsetY * scale); + + foreach (var rectangle in layouter.PlacedRectangles.Select(r => + new Rectangle(r.X * scale, r.Y * scale, r.Width * scale, r.Height * scale))) + graphics.DrawRectangle(pen, rectangle); + + return bitmap; } - + public static void SaveImage(Bitmap bitmap, string dirPath, string filename) { if (string.IsNullOrWhiteSpace(filename) || filename.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) diff --git a/cs/TagsCloudVisualizationTests/CircularCloudBuilderTests.cs b/cs/TagsCloudVisualizationTests/CircularCloudBuilderTests.cs new file mode 100644 index 000000000..fb35fe862 --- /dev/null +++ b/cs/TagsCloudVisualizationTests/CircularCloudBuilderTests.cs @@ -0,0 +1,35 @@ +using System.Drawing; +using TagsCloudVisualization; + +namespace TagsCloudVisualizationTests; + +[TestFixture] +public class CircularCloudBuilderTests +{ + private CircularCloudBuilder builder = null!; + private List placedRectangles = null!; + + [SetUp] + public void SetUp() + { + builder = new CircularCloudBuilder(1, 0.1d); + placedRectangles = new List(); + } + + [TestCase(0, 0.1)] + [TestCase(1, 0)] + public void Constructor_ThrowsArgumentException_OnInvalidArguments(int radiusIncrement, double angleIncrement) + { + Assert.Throws(() => new CircularCloudBuilder(radiusIncrement, angleIncrement)); + } + + [TestCase(-200, 300, false)] + [TestCase(200, -300, false)] + [TestCase(200, 300, true)] + public void GetNextPosition_ThrowsArgumentException_OnInvalidArguments(int width, int height, bool isListNull) + { + Assert.Throws(() => builder.GetNextPosition(new Point(500, 500), + new Size(width, height), + (isListNull ? null : placedRectangles)!)); + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs index e939bd16a..fd076ee2b 100644 --- a/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualizationTests/CircularCloudLayouterTests.cs @@ -1,5 +1,4 @@ using System.Drawing; -using Moq; using NUnit.Framework.Interfaces; using TagsCloudVisualization; @@ -20,7 +19,7 @@ public void OneTimeSetUp() [SetUp] public void SetUp() { - layouter = new CircularCloudLayouter(new CircularCloudBuilder(new Point(500, 500), 1, 0.1d)); + layouter = new CircularCloudLayouter(new Point(500, 500), new CircularCloudBuilder(1, 0.1d)); } [TestCase(-200, -300)] @@ -33,19 +32,14 @@ public void PutNextRectangle_ThrowsArgumentException_WhenRectanlgeSizeContainsNe } [Test] - public void GetCloudBorders_ThrowsInvalidOperationException_WhenListOfRectanglesIsNullOrEmpty( - [Values(false, true)] bool isNull) + public void GetCloudBorders_ThrowsInvalidOperationException_WhenListOfRectanglesIsEmpty() { - var mockLayouter = new Mock(); - mockLayouter.Setup(t => t.GetCloudBorders()).CallBase(); - if (!isNull) - mockLayouter.Setup(l => l.PlacedRectangles).Returns(new List()); - Assert.Throws(() => mockLayouter.Object.GetCloudBorders()); + Assert.Throws(() => new List().GetBorders()); } [Test] public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectangles( - [Values(false, true)] bool randomRectangleSize) + [Values] bool randomRectangleSize) { var rectangles = new List(); var rectSize = new Size(20, 10); @@ -67,11 +61,11 @@ public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectang [Test] public void PutNextRectangle_HasCircularShape_OnDifferentInputData( [Values(50, 100)] int numberOfRectangles, - [Values(true, false)] bool randomRectangleSize) + [Values] bool randomRectangleSize) { PlaceRectangles(numberOfRectangles, randomRectangleSize); - var borders = layouter.GetCloudBorders(); + var borders = layouter.PlacedRectangles.GetBorders(); var heightToWidthRatio = (double) Math.Min(borders.Width, borders.Height) / Math.Max(borders.Width, borders.Height); @@ -82,11 +76,11 @@ public void PutNextRectangle_HasCircularShape_OnDifferentInputData( [Test] public void PutNextRectangle_IsDenseEnough_OnDifferentInputData( [Values(50, 100)] int numberOfRectangles, - [Values(true, false)] bool randomRectangleSize) + [Values] bool randomRectangleSize) { PlaceRectangles(numberOfRectangles, randomRectangleSize); - var borders = layouter.GetCloudBorders(); + var borders = layouter.PlacedRectangles.GetBorders(); var radius = Math.Max(borders.Width, borders.Height) / 2; var circleSquare = Math.PI * radius * radius; @@ -100,7 +94,7 @@ public void PutNextRectangle_IsDenseEnough_OnDifferentInputData( [Timeout(5000)] [Test] public void PutNextRectangle_HasSufficientPerformance_OnLargeAmountOfRectangles( - [Values(true, false)]bool randomRectangleSize) + [Values] bool randomRectangleSize) { PlaceRectangles(200, randomRectangleSize); } @@ -111,7 +105,7 @@ public void GenerateImage_OnTestFail() if (TestContext.CurrentContext.Result.Outcome == ResultState.Success) return; - var drawer = new TagsCloudDrawer(layouter, new RectangleDrawer(new Pen(Color.Red, 1), 5)); + var drawer = new TagsCloudDrawer(layouter, new Pen(Color.Red, 1), 5); var bitmap = drawer.DrawTagCloud(); TagsCloudDrawer.SaveImage(bitmap, @"..\..\..\FailedTests", $"{TestContext.CurrentContext.Test.Name}.jpeg"); } diff --git a/cs/TagsCloudVisualizationTests/RectangleDrawerTests.cs b/cs/TagsCloudVisualizationTests/RectangleDrawerTests.cs deleted file mode 100644 index 6c4d939d7..000000000 --- a/cs/TagsCloudVisualizationTests/RectangleDrawerTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Drawing; -using TagsCloudVisualization; - -namespace TagsCloudVisualizationTests; - -[TestFixture] -public class RectangleDrawerTests -{ - private Pen pen = null!; - private RectangleDrawer drawer = null!; - - [SetUp] - public void SetUp() - { - pen = new Pen(Color.Red, 5); - drawer = new RectangleDrawer(pen, 5); - } - - [Test] - public void DrawRectangles_ThrowsArgumentException_WhenListOfRectanglesIsNull() - { - Assert.Throws(() => drawer.DrawRectangles(null!, new Rectangle(1,1,1,1))); - } - - [TestCase(false, -2)] - [TestCase(false, 0)] - [TestCase(true, 2)] - public void Constructor_ThrowsArgumentException_OnInvalidArguments(bool penIsNull, int scale) - { - Assert.Throws(() => new RectangleDrawer((penIsNull ? null : pen)!, scale)); - } -} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/SpiralBuilderTests.cs b/cs/TagsCloudVisualizationTests/SpiralBuilderTests.cs deleted file mode 100644 index f19ecc8be..000000000 --- a/cs/TagsCloudVisualizationTests/SpiralBuilderTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Drawing; -using TagsCloudVisualization; - -namespace TagsCloudVisualizationTests; - -[TestFixture] -public class SpiralBuilderTests -{ - private CircularCloudBuilder builder = null!; - private List placedRectangles = null!; - - [SetUp] - public void SetUp() - { - builder = new CircularCloudBuilder(new Point(500, 500), 1, 0.1d); - placedRectangles = new List(); - } - - [TestCase(-200, 300, false)] - [TestCase(200, -300, false)] - [TestCase(200, 300, true)] - public void GetNextPosition_ThrowsArgumentException_OnIncorrectParameters(int width, int height, bool listIsNull) - { - Assert.Throws( () => builder.GetNextPosition(new Size(width, height), - (listIsNull ? null : placedRectangles)!)); - } -} \ No newline at end of file diff --git a/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs b/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs index 3b72de9b6..165c106ee 100644 --- a/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs +++ b/cs/TagsCloudVisualizationTests/TagsCloudDrawerTests.cs @@ -1,4 +1,5 @@ using System.Drawing; +using Moq; using TagsCloudVisualization; namespace TagsCloudVisualizationTests; @@ -6,16 +7,43 @@ namespace TagsCloudVisualizationTests; [TestFixture] public class TagsCloudDrawerTests { + private Mock cloudLayouter = null!; + private TagsCloudDrawer drawer = null!; + private Pen pen = null!; + + [SetUp] + public void SetUp() + { + cloudLayouter = new Mock(); + pen = new Pen(Color.Red, 3); + drawer = new TagsCloudDrawer(cloudLayouter.Object, pen, 3); + } + [TestCase("test", "<>\\")] [TestCase("test", "name|123")] [TestCase("test", null)] [TestCase("test", "")] [TestCase(null, "filename")] - [TestCase("","filename")] - [TestCase(" ","filename")] - [TestCase(@"\:\","filename")] + [TestCase("", "filename")] + [TestCase(" ", "filename")] + [TestCase(@"\:\", "filename")] public void SaveImage_ThrowsArgumentException_OnInvalidParameters(string dirPath, string filename) { Assert.Throws(() => TagsCloudDrawer.SaveImage(new Bitmap(1, 1), dirPath, filename)); } + + [Test] + public void DrawTagCloud_ThrowsArgumentException_WhenListOfRectanglesIsNull() + { + Assert.Throws(() => drawer.DrawTagCloud()); + } + + [TestCase(false, -2)] + [TestCase(false, 0)] + [TestCase(true, 2)] + public void Constructor_ThrowsArgumentException_OnInvalidArguments(bool penIsNull, int scale) + { + Assert.Throws(() => + new TagsCloudDrawer(cloudLayouter.Object, (penIsNull ? null : pen)!, scale)); + } } \ No newline at end of file