From 096337b264bd4908399737a47ea1eaaee2da9d3c Mon Sep 17 00:00:00 2001 From: RRAleshev Date: Mon, 4 Dec 2023 20:35:54 +0500 Subject: [PATCH 1/3] Prechecking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Реализованы базовые требования. Сделана сохранение изображений при ошибке. Добавлен README.md и изображения с примерами. --- .../CircularCloudLayouter.cs | 99 ++++++++++++++ .../Examples/LandscapeCloud200Rectangles.png | Bin 0 -> 30420 bytes .../Examples/PortraitCloud200Rectangles.png | Bin 0 -> 33061 bytes .../Examples/SquareCloud100Rectangles.png | Bin 0 -> 16096 bytes cs/TagsCloudVisualization/Program.cs | 32 +++++ cs/TagsCloudVisualization/README.md | 5 + .../TagCloudVisualisationTest.cs | 122 ++++++++++++++++++ .../TagsCloudVisualization.csproj | 19 +++ .../TagsCloudVisualization.sln | 25 ++++ 9 files changed, 302 insertions(+) create mode 100644 cs/TagsCloudVisualization/CircularCloudLayouter.cs create mode 100644 cs/TagsCloudVisualization/Examples/LandscapeCloud200Rectangles.png create mode 100644 cs/TagsCloudVisualization/Examples/PortraitCloud200Rectangles.png create mode 100644 cs/TagsCloudVisualization/Examples/SquareCloud100Rectangles.png create mode 100644 cs/TagsCloudVisualization/Program.cs create mode 100644 cs/TagsCloudVisualization/README.md create mode 100644 cs/TagsCloudVisualization/TagCloudVisualisationTest.cs create mode 100644 cs/TagsCloudVisualization/TagsCloudVisualization.csproj create mode 100644 cs/TagsCloudVisualization/TagsCloudVisualization.sln diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs new file mode 100644 index 000000000..327f99430 --- /dev/null +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using NUnit.Framework; + +namespace TagsCloudVisualization +{ + public class CircularCloudLayouter + { + private readonly Point center; + private List cloud; + + //Spiral coeffs + int i = 0; + float it = (float)Math.PI / 21; + float ri = 50; + + public CircularCloudLayouter(Point center) + { + if (center.X <= 0 || center.Y <= 0) + throw new ArgumentException("Central point coordinates should be in positive"); + cloud = new List(); + this.center = center; + } + + public Rectangle PutNextRectangle(Size rectSize) + { + if (rectSize.Width <= 0 || rectSize.Height <= 0) + throw new ArgumentException("Size width and height should be positive"); + + if (!cloud.Any()) + return new Rectangle(center, rectSize); + + Rectangle rect; + + while (true) + { + bool findPlace = true; + var point = GetNextPoint(); + + rect = new Rectangle(new Point(point.X - rectSize.Width / 2, point.Y - rectSize.Height / 2), rectSize); + foreach (var previous in cloud) + { + if (rect.IntersectsWith(previous)) + { + findPlace = false; + break; + } + } + + if (findPlace) + break; + } + + return rect; + } + + public List CreateCloud(List rectangleSizes) + { + cloud = new List(); + + foreach (var rectangleSize in rectangleSizes) + { + cloud.Add(PutNextRectangle(rectangleSize)); + } + + return cloud; + } + + public Image CreateImage() + { + Bitmap image = new Bitmap(center.X * 2, center.Y * 2); + Graphics gr = Graphics.FromImage(image); + Pen pen = new Pen(Color.White); + + gr.Clear(Color.Black); + gr.DrawRectangles(pen, cloud.ToArray()); + + return image; + } + + private Point GetNextPoint() + { + float r = (float)Math.Sqrt(ri * i); + float t = it * i; + float x = (float)(r * Math.Cos(t) + center.X); + float y = (float)(r * Math.Sin(t) + center.Y); + i++; + + //Console.WriteLine("{0} {1}", x, y); + + return new Point((int)x, (int)y); + } + } +} diff --git a/cs/TagsCloudVisualization/Examples/LandscapeCloud200Rectangles.png b/cs/TagsCloudVisualization/Examples/LandscapeCloud200Rectangles.png new file mode 100644 index 0000000000000000000000000000000000000000..bc022b5212e4adfb9fe63e5deb7f34d2c2aea90e GIT binary patch literal 30420 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 L0tEUYOtf58?vM=?wWnOp&0x= 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 Rhi84^?Z5WSzrOzA{{S@QxtIU| literal 0 HcmV?d00001 diff --git a/cs/TagsCloudVisualization/Examples/SquareCloud100Rectangles.png b/cs/TagsCloudVisualization/Examples/SquareCloud100Rectangles.png new file mode 100644 index 0000000000000000000000000000000000000000..b32f8644209785eaa2c764b77e510055e81fa54d GIT binary patch literal 16096 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 + var rnd = new Random(); + + for (int i = 0; i < 200; i++) + { + sizes.Add(new Size(rnd.Next(1, 50), rnd.Next(1, 50))); + } + + var c = layouter.CreateCloud(sizes); + + var image = layouter.CreateImage(); + + image.Save("cloud.png"); + Console.WriteLine("Image saved to file cloud.png"); + } + } +} + diff --git a/cs/TagsCloudVisualization/README.md b/cs/TagsCloudVisualization/README.md new file mode 100644 index 000000000..a96cd4d44 --- /dev/null +++ b/cs/TagsCloudVisualization/README.md @@ -0,0 +1,5 @@ +# Примеры + +![](Examples/LandscapeCloud200Rectangles.png) +![](Examples/PortraitCloud200Rectangles.png) +![](IExamples/SquareCloud100Rectangles.png) \ No newline at end of file diff --git a/cs/TagsCloudVisualization/TagCloudVisualisationTest.cs b/cs/TagsCloudVisualization/TagCloudVisualisationTest.cs new file mode 100644 index 000000000..8bd0c3a27 --- /dev/null +++ b/cs/TagsCloudVisualization/TagCloudVisualisationTest.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using FluentAssertions; +using System.Drawing; +using static System.Net.Mime.MediaTypeNames; + +namespace TagsCloudVisualization +{ + [TestFixture] + public class TagCloudVisualisationTest + { + //[SetUp] + //public void GenerateRandomSizes() + //{ + + //} + + [TestCase(-1, -1)] + [TestCase(1, -1)] + [TestCase(-1, 1)] + [TestCase(0, 0)] + public void CircularCloudLayouterConstructor_ThrowExceptionOnIncorrectCentralPoins(int x, int y) + { + Action a = () => new CircularCloudLayouter(new Point(x, y)); + a.Should().Throw(); + } + + [TestCase(1)] + [TestCase(123)] + public void CreateCloud_ReturnCorrectNumberOfRectangles(int rectCount) + { + var layouter = new CircularCloudLayouter(new Point(50, 50)); + + var sizes = new List(); + + for (int i = 0; i < rectCount; i++) + { + sizes.Add(new Size(1, 1)); + } + + var rects = layouter.CreateCloud(sizes); + + rects.Count().Should().Be(rectCount); + } + + [Test] + public void PutNextRectangle_ShouldReturnRectangle() + { + var layouter = new CircularCloudLayouter(new Point(50, 50)); + layouter.PutNextRectangle(new Size(1, 2)).Should().BeOfType(typeof(Rectangle)); + } + + [TestCase(1, 1)] + [TestCase(20, 1)] + [TestCase(256, 255)] + [TestCase(1, 20)] + public void PutNextRectangle_ShouldReturnRectangleOfCorrectSize(int width, int height) + { + var layouter = new CircularCloudLayouter(new Point(500, 500)); + var rect = layouter.PutNextRectangle(new Size(width, height)); + rect.Width.Should().Be(width); + rect.Height.Should().Be(height); + } + + [TestCase(-1, 1)] + [TestCase(1, -1)] + [TestCase(0, 1)] + [TestCase(1, 0)] + public void PutNextRectangle_ShouldThrowExceptionOnIncorrectSize(int width, int height) + { + var layouter = new CircularCloudLayouter(new Point(50, 50)); + Action a = () => layouter.PutNextRectangle(new Size(width, height)); + a.Should().Throw(); + } + + [TestCase(10)] + [TestCase(20)] + [TestCase(200)] + public void CreateCloud_RectanglesShouldNotIntersect(int rectCount) + { + var layouter = new CircularCloudLayouter(new Point(500, 500)); + var sizes = new List(); + var rnd = new Random(); + + for (int i = 0; i < rectCount; i++) + { + sizes.Add(new Size(rnd.Next(1, 100), rnd.Next(1, 100))); + } + + var cloud = layouter.CreateCloud(sizes); + + for (int i = 0; i < cloud.Count; i++) + { + for (int j = i; j < cloud.Count; j++) + { + if (i == j) continue; + var isIntersect = cloud[i].IntersectsWith(cloud[j]); + if (isIntersect) + { + var img = layouter.CreateImage(); + + //mark intersection + Graphics gr = Graphics.FromImage(img); + Pen pen = new Pen(Color.Red); + gr.DrawRectangle(pen, cloud[i]); + gr.DrawRectangle(pen, cloud[j]); + string filename = "error - " + DateTime.Now.ToString("H - mm - ss") + ".png"; + img.Save(filename); + Console.WriteLine("Tag cloud visualization saved to file {0}", filename); + } + + isIntersect.Should().BeFalse(); + } + } + } + + } +} diff --git a/cs/TagsCloudVisualization/TagsCloudVisualization.csproj b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj new file mode 100644 index 000000000..11493b3da --- /dev/null +++ b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj @@ -0,0 +1,19 @@ + + + + Exe + net6.0 + enable + enable + false + + + + + + + + + + + diff --git a/cs/TagsCloudVisualization/TagsCloudVisualization.sln b/cs/TagsCloudVisualization/TagsCloudVisualization.sln new file mode 100644 index 000000000..5bec6eddf --- /dev/null +++ b/cs/TagsCloudVisualization/TagsCloudVisualization.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34202.233 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualization", "TagsCloudVisualization.csproj", "{85DD6D7B-7811-4117-8B54-A4ECC712D5C8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85DD6D7B-7811-4117-8B54-A4ECC712D5C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85DD6D7B-7811-4117-8B54-A4ECC712D5C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85DD6D7B-7811-4117-8B54-A4ECC712D5C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85DD6D7B-7811-4117-8B54-A4ECC712D5C8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5BEAA608-2B32-428E-815C-F8F19E0DF0B8} + EndGlobalSection +EndGlobal From 7a2699b60d931eb17d0c20028212d551c697cb3c Mon Sep 17 00:00:00 2001 From: artBETEP <100296058+artBETEP@users.noreply.github.com> Date: Mon, 4 Dec 2023 20:40:43 +0500 Subject: [PATCH 2/3] Update README.md --- cs/TagsCloudVisualization/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cs/TagsCloudVisualization/README.md b/cs/TagsCloudVisualization/README.md index a96cd4d44..0fc10a25c 100644 --- a/cs/TagsCloudVisualization/README.md +++ b/cs/TagsCloudVisualization/README.md @@ -2,4 +2,4 @@ ![](Examples/LandscapeCloud200Rectangles.png) ![](Examples/PortraitCloud200Rectangles.png) -![](IExamples/SquareCloud100Rectangles.png) \ No newline at end of file +![](Examples/SquareCloud100Rectangles.png) From c250f91449d79bf65f09c5e799aaa1069b340c1e Mon Sep 17 00:00:00 2001 From: RRAleshev Date: Sun, 10 Dec 2023 15:38:59 +0500 Subject: [PATCH 3/3] Refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Создание точек вынесено в отдельный класс с интерфейсом IPointsProvider При падении тестов на раскладку сохраняется файл с изображением Поправлены замечания --- .../CircularCloudLayouter.cs | 99 +++++++++---------- cs/TagsCloudVisualization/IPointsProvider.cs | 11 +++ cs/TagsCloudVisualization/Program.cs | 8 +- cs/TagsCloudVisualization/README.md | 2 +- .../SpiralPointsProvider.cs | 39 ++++++++ .../TagCloudVisualisationTest.cs | 66 ++++++------- 6 files changed, 127 insertions(+), 98 deletions(-) create mode 100644 cs/TagsCloudVisualization/IPointsProvider.cs create mode 100644 cs/TagsCloudVisualization/SpiralPointsProvider.cs diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 327f99430..0d48696cd 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -2,98 +2,87 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using FluentAssertions; -using NUnit.Framework; namespace TagsCloudVisualization { public class CircularCloudLayouter { private readonly Point center; - private List cloud; - - //Spiral coeffs - int i = 0; - float it = (float)Math.PI / 21; - float ri = 50; + public ICollection Cloud { get; private set; } + private IPointsProvider pointsProvider; public CircularCloudLayouter(Point center) { if (center.X <= 0 || center.Y <= 0) throw new ArgumentException("Central point coordinates should be in positive"); - cloud = new List(); + this.center = center; + } - public Rectangle PutNextRectangle(Size rectSize) + public Rectangle PutNextRectangle(Size rectangleSize) { - if (rectSize.Width <= 0 || rectSize.Height <= 0) + if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) throw new ArgumentException("Size width and height should be positive"); - if (!cloud.Any()) - return new Rectangle(center, rectSize); + if (Cloud == null || !Cloud.Any()) + return new Rectangle(center, rectangleSize); - Rectangle rect; + Rectangle rectangle; + bool placingIsCorrect; - while (true) + var enumerator = pointsProvider.Points().GetEnumerator(); + enumerator.MoveNext(); + + do { - bool findPlace = true; - var point = GetNextPoint(); - - rect = new Rectangle(new Point(point.X - rectSize.Width / 2, point.Y - rectSize.Height / 2), rectSize); - foreach (var previous in cloud) - { - if (rect.IntersectsWith(previous)) - { - findPlace = false; - break; - } - } - - if (findPlace) - break; - } + var point = enumerator.Current; + enumerator.MoveNext(); + + rectangle = new Rectangle(new Point(point.X - rectangleSize.Width / 2, + point.Y - rectangleSize.Height / 2), + rectangleSize); + placingIsCorrect = PlacedCorrectly(rectangle, Cloud, new Size(center.X * 2, center.Y * 2)); - return rect; + } while (!placingIsCorrect); + + return rectangle; } - public List CreateCloud(List rectangleSizes) + public void LayoutRectancles(List rectangleSizes) { - cloud = new List(); - - foreach (var rectangleSize in rectangleSizes) - { - cloud.Add(PutNextRectangle(rectangleSize)); - } + Cloud = new List(); + pointsProvider = new SpiralPointsProvider(center); - return cloud; + foreach (var size in rectangleSizes) + Cloud.Add(PutNextRectangle(size)); } - public Image CreateImage() + public Image ToImage() { - Bitmap image = new Bitmap(center.X * 2, center.Y * 2); - Graphics gr = Graphics.FromImage(image); - Pen pen = new Pen(Color.White); + var image = new Bitmap(center.X * 2, center.Y * 2); + var gr = Graphics.FromImage(image); + var pen = new Pen(Color.White); gr.Clear(Color.Black); - gr.DrawRectangles(pen, cloud.ToArray()); + gr.DrawRectangles(pen, Cloud.ToArray()); return image; } - private Point GetNextPoint() + public bool PlacedCorrectly(Rectangle rectangle, ICollection rectanglesCloud, Size canvasSize) { - float r = (float)Math.Sqrt(ri * i); - float t = it * i; - float x = (float)(r * Math.Cos(t) + center.X); - float y = (float)(r * Math.Sin(t) + center.Y); - i++; + if (rectangle.Top < 0 || rectangle.Left < 0 || rectangle.Bottom > canvasSize.Height || + rectangle.Right > canvasSize.Width) + return false; - //Console.WriteLine("{0} {1}", x, y); + foreach (var previous in Cloud) + { + if (rectangle.IntersectsWith(previous)) + return false; + } - return new Point((int)x, (int)y); + return true; } } } diff --git a/cs/TagsCloudVisualization/IPointsProvider.cs b/cs/TagsCloudVisualization/IPointsProvider.cs new file mode 100644 index 000000000..851671efc --- /dev/null +++ b/cs/TagsCloudVisualization/IPointsProvider.cs @@ -0,0 +1,11 @@ +using System; +using System.Drawing; + +namespace TagsCloudVisualization +{ + internal interface IPointsProvider + { + public IEnumerable Points(); + public void Reset(); + } +} diff --git a/cs/TagsCloudVisualization/Program.cs b/cs/TagsCloudVisualization/Program.cs index 07b5bd4a7..902a00ab1 100644 --- a/cs/TagsCloudVisualization/Program.cs +++ b/cs/TagsCloudVisualization/Program.cs @@ -10,19 +10,19 @@ class Program { public static void Main() { - var layouter = new CircularCloudLayouter(new Point(600, 300)); + var layouter = new CircularCloudLayouter(new Point(300, 300)); var sizes = new List(); var rnd = new Random(); for (int i = 0; i < 200; i++) { - sizes.Add(new Size(rnd.Next(1, 50), rnd.Next(1, 50))); + sizes.Add(new Size(rnd.Next(5, 50), rnd.Next(5, 50))); } - var c = layouter.CreateCloud(sizes); + layouter.LayoutRectancles(sizes); - var image = layouter.CreateImage(); + var image = layouter.ToImage(); image.Save("cloud.png"); Console.WriteLine("Image saved to file cloud.png"); diff --git a/cs/TagsCloudVisualization/README.md b/cs/TagsCloudVisualization/README.md index a96cd4d44..c1280b61f 100644 --- a/cs/TagsCloudVisualization/README.md +++ b/cs/TagsCloudVisualization/README.md @@ -2,4 +2,4 @@ ![](Examples/LandscapeCloud200Rectangles.png) ![](Examples/PortraitCloud200Rectangles.png) -![](IExamples/SquareCloud100Rectangles.png) \ No newline at end of file +![](Examples/SquareCloud100Rectangles.png) \ No newline at end of file diff --git a/cs/TagsCloudVisualization/SpiralPointsProvider.cs b/cs/TagsCloudVisualization/SpiralPointsProvider.cs new file mode 100644 index 000000000..0ca6a7c5e --- /dev/null +++ b/cs/TagsCloudVisualization/SpiralPointsProvider.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; + +namespace TagsCloudVisualization +{ + public class SpiralPointsProvider : IPointsProvider + { + private int pointNumber = 0; + private readonly float angleStep = (float)Math.PI / 21; + private readonly float SpiralRadius = 50; + private readonly Point Center; + + public SpiralPointsProvider(Point center) + { + Center = center; + } + + public IEnumerable Points() + { + while (pointNumber < 10000000) // Limit number of returned points for safety reason + { + var r = Math.Sqrt(SpiralRadius * pointNumber); + var angle = angleStep * pointNumber; + var x = r * Math.Cos(angle) + Center.X; + var y = r * Math.Sin(angle) + Center.Y; + pointNumber++; + yield return new Point((int)x, (int)y); + } + throw new ArgumentException("Reach end of placing points"); + } + + public void Reset() + { + pointNumber = 0; + } + } +} diff --git a/cs/TagsCloudVisualization/TagCloudVisualisationTest.cs b/cs/TagsCloudVisualization/TagCloudVisualisationTest.cs index 8bd0c3a27..bba6622ae 100644 --- a/cs/TagsCloudVisualization/TagCloudVisualisationTest.cs +++ b/cs/TagsCloudVisualization/TagCloudVisualisationTest.cs @@ -1,23 +1,16 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NUnit.Framework; using FluentAssertions; using System.Drawing; -using static System.Net.Mime.MediaTypeNames; +using NUnit.Framework.Interfaces; +using System.Security.Claims; namespace TagsCloudVisualization { [TestFixture] public class TagCloudVisualisationTest { - //[SetUp] - //public void GenerateRandomSizes() - //{ - - //} + private CircularCloudLayouter layouter; [TestCase(-1, -1)] [TestCase(1, -1)] @@ -31,9 +24,9 @@ public void CircularCloudLayouterConstructor_ThrowExceptionOnIncorrectCentralPoi [TestCase(1)] [TestCase(123)] - public void CreateCloud_ReturnCorrectNumberOfRectangles(int rectCount) + public void LayoutRectangles_ReturnCorrectNumberOfRectangles(int rectCount) { - var layouter = new CircularCloudLayouter(new Point(50, 50)); + layouter = new CircularCloudLayouter(new Point(500, 500)); var sizes = new List(); @@ -42,15 +35,15 @@ public void CreateCloud_ReturnCorrectNumberOfRectangles(int rectCount) sizes.Add(new Size(1, 1)); } - var rects = layouter.CreateCloud(sizes); + layouter.LayoutRectancles(sizes); - rects.Count().Should().Be(rectCount); + layouter.Cloud.Count().Should().Be(rectCount); } [Test] public void PutNextRectangle_ShouldReturnRectangle() { - var layouter = new CircularCloudLayouter(new Point(50, 50)); + layouter = new CircularCloudLayouter(new Point(50, 50)); layouter.PutNextRectangle(new Size(1, 2)).Should().BeOfType(typeof(Rectangle)); } @@ -60,7 +53,7 @@ public void PutNextRectangle_ShouldReturnRectangle() [TestCase(1, 20)] public void PutNextRectangle_ShouldReturnRectangleOfCorrectSize(int width, int height) { - var layouter = new CircularCloudLayouter(new Point(500, 500)); + layouter = new CircularCloudLayouter(new Point(500, 500)); var rect = layouter.PutNextRectangle(new Size(width, height)); rect.Width.Should().Be(width); rect.Height.Should().Be(height); @@ -72,7 +65,7 @@ public void PutNextRectangle_ShouldReturnRectangleOfCorrectSize(int width, int h [TestCase(1, 0)] public void PutNextRectangle_ShouldThrowExceptionOnIncorrectSize(int width, int height) { - var layouter = new CircularCloudLayouter(new Point(50, 50)); + layouter = new CircularCloudLayouter(new Point(50, 50)); Action a = () => layouter.PutNextRectangle(new Size(width, height)); a.Should().Throw(); } @@ -80,43 +73,40 @@ public void PutNextRectangle_ShouldThrowExceptionOnIncorrectSize(int width, int [TestCase(10)] [TestCase(20)] [TestCase(200)] - public void CreateCloud_RectanglesShouldNotIntersect(int rectCount) + public void LayoutRectangles_RectanglesShouldNotIntersect(int rectCount) { - var layouter = new CircularCloudLayouter(new Point(500, 500)); + layouter = new CircularCloudLayouter(new Point(500, 500)); var sizes = new List(); var rnd = new Random(); for (int i = 0; i < rectCount; i++) { - sizes.Add(new Size(rnd.Next(1, 100), rnd.Next(1, 100))); + sizes.Add(new Size(rnd.Next(1, 50), rnd.Next(1, 50))); } - var cloud = layouter.CreateCloud(sizes); + layouter.LayoutRectancles(sizes); - for (int i = 0; i < cloud.Count; i++) + foreach (var rectangleA in layouter.Cloud) { - for (int j = i; j < cloud.Count; j++) + foreach (var rectangleB in layouter.Cloud) { - if (i == j) continue; - var isIntersect = cloud[i].IntersectsWith(cloud[j]); - if (isIntersect) - { - var img = layouter.CreateImage(); - - //mark intersection - Graphics gr = Graphics.FromImage(img); - Pen pen = new Pen(Color.Red); - gr.DrawRectangle(pen, cloud[i]); - gr.DrawRectangle(pen, cloud[j]); - string filename = "error - " + DateTime.Now.ToString("H - mm - ss") + ".png"; - img.Save(filename); - Console.WriteLine("Tag cloud visualization saved to file {0}", filename); - } + if (rectangleA == rectangleB) continue; + var isIntersect = rectangleA.IntersectsWith(rectangleB); isIntersect.Should().BeFalse(); } } } + [TearDown] + public void SaveImageOnTestFails() + { + if (TestContext.CurrentContext.Result.Outcome == ResultState.Failure) + { + var img = layouter.ToImage(); + string filename = TestContext.CurrentContext.Test.Name + "_Failed_" + DateTime.Now.ToString("H - mm - ss") + ".png"; + img.Save(filename); + } + } } }