From 8cae61117ebfed19d7799c105c226d9ca42e8abf Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Wed, 13 Nov 2024 06:37:32 +0500 Subject: [PATCH 01/25] AddNearestFinder --- .../BruteForceNearestFinder.cs | 50 ++++++++++++ .../Tests/BruteForceNearestFinderTests.cs | 76 +++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 cs/TagsCloudVisualization/BruteForceNearestFinder.cs create mode 100644 cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs diff --git a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs new file mode 100644 index 000000000..d89794fbd --- /dev/null +++ b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Threading.Tasks; + +namespace TagsCloudVisualization +{ + public class BruteForceNearestFinder + { + private List rectangles = new(); + + public void Insert(Rectangle r) + { + if (RectangleHasInсorrectSize(r)) + throw new ArgumentException($"Rectangle has incorrect size: width = {r.Width}, height = {r.Height}"); + rectangles.Add(r); + } + + public Rectangle? FindNearestByDirection(Rectangle r, Direction direction) + { + if (RectangleHasInсorrectSize(r)) + throw new ArgumentException($"Rectangle has incorrect size: width= {r.Width}, height={r.Height}"); + if (rectangles.Count == 0) + return null; + var calculator = GetMinDistanceCalculatorBy(direction); + return rectangles.Select(currentRectangle => (distance: calculator(currentRectangle, r), CurrentEl: currentRectangle)) + .Where(el => el.distance > 0) + .MinBy(el => el.distance).CurrentEl; + } + + private Func GetMinDistanceCalculatorBy(Direction direction) + { + switch (direction) + { + case Direction.Left: return (possibleNearest, rectangleForFind) => rectangleForFind.X - possibleNearest.X; + case Direction.Right: return (possibleNearest, rectangleForFind) => possibleNearest.X - rectangleForFind.X; + case Direction.Top: return (possibleNearest, rectangleForFind) => rectangleForFind.Y - possibleNearest.Y; + default: return (possibleNearest, rectangleForFind) => possibleNearest.Y - rectangleForFind.Y; + } + } + + private bool RectangleHasInсorrectSize(Rectangle r) + { + return r.Width <= 0 || r.Height <= 0; + } + } +} diff --git a/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs b/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs new file mode 100644 index 000000000..75a92ff9d --- /dev/null +++ b/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using FluentAssertions; +using System.Drawing; + +namespace TagsCloudVisualization.Tests +{ + public class BruteForceNearestFinderTests + { + private BruteForceNearestFinder finder; + [SetUp] + public void SetUp() + { + finder = new BruteForceNearestFinder(); + } + [Test] + public void FindNearest_ShouldReturnNull_BeforeAnyInsertions() + { + var rectangleForFind = new Rectangle(new Point(5, 7), new Size(4, 2)); + + finder.FindNearestByDirection(rectangleForFind, Direction.Top).Should().BeNull(); + } + + [TestCase(0, 4, TestName = "WhenWidthZero")] + [TestCase(3, 0, TestName = "WhenHeightZero")] + [TestCase(-3, 4, TestName = "WhenWidthIsNegative")] + [TestCase(3, -4, TestName = "WhenHeightNegative")] + [TestCase(-3, -4, TestName = "WhenWidthAndHeightNegative")] + [TestCase(0, 0, TestName = "WhenWidthAndHeightIsZero")] + public void Insert_ShouldThrow(int width, int height) + { + var rectangleForInsert = new Rectangle(new Point(2, 2), new Size(width, height)); + + ShouldThrow((finder, rectangle) => finder.Insert(rectangle), rectangleForInsert); + } + + [TestCase(0, 4, TestName = "WhenWidthZero")] + [TestCase(3, 0, TestName = "WhenHeightZero")] + [TestCase(-3, 4, TestName = "WhenWidthIsNegative")] + [TestCase(3, -4, TestName = "WhenHeightNegative")] + [TestCase(-3, -4, TestName = "WhenWidthAndHeightNegative")] + [TestCase(0, 0, TestName = "WhenWidthAndHeightIsZero")] + public void FindNearest_ShouldThrow(int width, int height) + { + var rectangleForFind = new Rectangle(new Point(2, 2), new Size(width, height)); + + ShouldThrow((finder, rectangle) => finder.FindNearestByDirection(rectangle, Direction.Top), rectangleForFind); + } + + public void ShouldThrow(Action callFinderMethod, Rectangle incorrectRectangle) + { + Action act = () => callFinderMethod(finder, incorrectRectangle); + + act.Should().Throw(); + } + + [TestCase(4, 10, Direction.Top)] + [TestCase(2, 7, Direction.Top, true)] + [TestCase(2, 7, Direction.Right)] + public void FindNearest_ShouldReturnNearestRectangleByDirection_ForArgumentRectangle(int x, int y, Direction direction, bool isFirstRectNearest = false) + { + var addedRectangle1 = new Rectangle(new Point(2, 2), new Size(3, 4)); + var addedRectangle2 = new Rectangle(new Point(5, 7), new Size(4, 2)); + var rectangleForFind = new Rectangle(new Point(x, y), new Size(2, 1)); + + finder.Insert(addedRectangle1); + finder.Insert(addedRectangle2); + + finder.FindNearestByDirection(rectangleForFind, direction).Should().Be(isFirstRectNearest ? addedRectangle1 : addedRectangle2); + } + } +} From 09cf278332f3af80ee2d86f91acf01ca6f220a91 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Wed, 13 Nov 2024 23:42:01 +0500 Subject: [PATCH 02/25] Add CircularLayer --- cs/TagsCloudVisualization/CircleLayer.cs | 193 ++++++++++++++++++ .../Tests/CircleLayerTests.cs | 183 +++++++++++++++++ 2 files changed, 376 insertions(+) create mode 100644 cs/TagsCloudVisualization/CircleLayer.cs create mode 100644 cs/TagsCloudVisualization/Tests/CircleLayerTests.cs diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs new file mode 100644 index 000000000..59ff44755 --- /dev/null +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -0,0 +1,193 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public class CircleLayer +{ + public enum Sector + { + Top_Right, + Bottom_Right, + Bottom_Left, + Top_Left + } + + public Point Center { get; } + public int Radius { get; } + + + private Sector currentSector; + private readonly List layerRectangles; + private CircleLayer prevLayer; + + public CircleLayer(Point center, int radius) + { + Center = center; + Radius = radius; + currentSector = Sector.Top_Right; + layerRectangles = new List(); + } + + public CircleLayer OnSuccessInsertRectangle(Rectangle inserted) + { + currentSector = currentSector == Sector.Top_Left ? Sector.Top_Right : currentSector + 1; + layerRectangles.Add(inserted); + if (currentSector == Sector.Top_Right) + return CreateNextLayer(); + return this; + } + + private CircleLayer CreateNextLayer() + { + var nextLayer = new CircleLayer(Center, CalculateRadiusForNextLayer()); + nextLayer.prevLayer = this; + return nextLayer; + } + + private int CalculateRadiusForNextLayer() + { + var prevSector = Sector.Top_Right - 1; + return layerRectangles.Select(r => CalculateDistanceBetweenCenterAndRectangleBySector(r, prevSector + 1)).Max(); + } + + private int CalculateDistanceBetweenCenterAndRectangleBySector(Rectangle r, Sector s) + { + switch (s) + { + case Sector.Top_Right: + return CalculateDistanceBetweenPoints(Center, new Point(r.X + r.Width, r.Y)); + case Sector.Bottom_Right: + return CalculateDistanceBetweenPoints(Center, new Point(r.X + r.Width, r.Y + r.Height)); + case Sector.Bottom_Left: + return CalculateDistanceBetweenPoints(Center, new Point(r.X - r.Width, r.Y + r.Height)); + default: + return CalculateDistanceBetweenPoints(Center, new Point(r.X - r.Width, r.Y)); + } + } + + private int CalculateDistanceBetweenPoints(Point p1, Point p2) + { + return (int)Math.Ceiling(Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y))); + } + + public Point CalculateTopLeftRectangleCornerPosition(Size rectangleSize) + { + var relevantForSectorPosition = GetPositionRelevantForSector(); + switch (currentSector) + { + case Sector.Top_Right: + return new Point(relevantForSectorPosition.X, relevantForSectorPosition.Y - rectangleSize.Height); + case Sector.Bottom_Right: + return relevantForSectorPosition; + case Sector.Bottom_Left: + return new Point(relevantForSectorPosition.X - rectangleSize.Width, relevantForSectorPosition.Y); + default: + return new Point(relevantForSectorPosition.X - rectangleSize.Width, + relevantForSectorPosition.Y - rectangleSize.Height); + } + } + + private Point GetPositionRelevantForSector() + { + switch (currentSector) + { + case Sector.Top_Right: + return new Point(Center.X, Center.Y - Radius); + case Sector.Bottom_Right: + return new Point(Center.X + Radius, Center.Y); + case Sector.Bottom_Left: + return new Point(Center.X, Center.Y + Radius); + default: + return new Point(Center.X - Radius, Center.Y); + } + } + + public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rectangle intersected) + { + return CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); + } + + private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangle forInsertion, Rectangle intersected) + { + var distanceForMoving = CalculateDistanceForMovingBySector(s, forInsertion, intersected); + var isMovingAxisIsX = IsMovingAxisIsXBySector(s); + var nearestForCenterCorner = + CalculateCornerNearestForCenterAfterMove(s, distanceForMoving, forInsertion); + var distanceForBringBackOnCircle = + CalculateDeltaForBringRectangleBackOnCircle(nearestForCenterCorner, isMovingAxisIsX); + distanceForMoving *= CalculateMoveMultiplierForMoveClockwise(isMovingAxisIsX, forInsertion); + distanceForBringBackOnCircle *= CalculateMoveMultiplierForMoveFromCenter(!isMovingAxisIsX, forInsertion); + return isMovingAxisIsX + ? new Point(forInsertion.X + distanceForMoving, forInsertion.Y + distanceForBringBackOnCircle) + : new Point(forInsertion.X + distanceForBringBackOnCircle, forInsertion.Y + distanceForMoving); + } + + private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCorner, bool isMovingAxisIsX) + { + Func getAxisForBringBackOnCircle = isMovingAxisIsX ? p => p.Y : p => p.X; + Func getStaticAxis = isMovingAxisIsX ? p => p.X : p => p.Y; + + var distanceOnStaticAxis = Math.Abs(getStaticAxis(nearestForCenterCorner) - getStaticAxis(Center)); + var distanceOnAxisForBringBackOnCircle = Math.Abs(getAxisForBringBackOnCircle(nearestForCenterCorner) - getAxisForBringBackOnCircle(Center)); + return (int)Math.Ceiling(Math.Sqrt(Radius * Radius - distanceOnStaticAxis * distanceOnStaticAxis)) - distanceOnAxisForBringBackOnCircle; + } + + private Point CalculateCornerNearestForCenterAfterMove(Sector s, int distanceForMoving, Rectangle r) + { + var isAxisForMoveIsX = IsMovingAxisIsXBySector(s); + var moveMultiplier = CalculateMoveMultiplierForMoveClockwise(isAxisForMoveIsX, r); + distanceForMoving *= moveMultiplier; + var nearestCorner = GetCornerNearestForCenterBySector(s, r); + return isAxisForMoveIsX + ? new Point(nearestCorner.X + distanceForMoving, nearestCorner.Y) + : new Point(nearestCorner.X, nearestCorner.Y + distanceForMoving); + } + + private int CalculateMoveMultiplierForMoveFromCenter(bool isAxisForMoveIsX, Rectangle r) + { + return isAxisForMoveIsX + ? r.Right < Center.X ? -1 : 1 + : r.Bottom < Center.Y ? -1 : 1; + } + private int CalculateMoveMultiplierForMoveClockwise(bool isAxisForMoveIsX, Rectangle r) + { + return isAxisForMoveIsX + ? r.Left > Center.X ? -1 : 1 + : r.Bottom > Center.Y ? -1 : 1; + } + + private int CalculateDistanceForMovingBySector (Sector s, Rectangle forInsertion, Rectangle intersected) + { + switch (s) + { + case Sector.Top_Right: + return Math.Abs(forInsertion.Top - intersected.Bottom); + case Sector.Bottom_Right: + return Math.Abs(forInsertion.Right - intersected.Left); + case Sector.Bottom_Left: + return Math.Abs(forInsertion.Bottom - intersected.Top); + default: + return Math.Abs(forInsertion.Left - intersected.Right); + } + } + + private Point GetCornerNearestForCenterBySector(Sector s, Rectangle r) + { + switch (s) + { + case Sector.Top_Right: + return new Point(r.Left, r.Bottom); + case Sector.Bottom_Right: + return new Point(r.Left, r.Top); + case Sector.Bottom_Left: + return new Point(r.Right, r.Top); + default: + return new Point(r.Right, r.Bottom); + } + } + + private bool IsMovingAxisIsXBySector(Sector s) + { + return s == Sector.Bottom_Right || s == Sector.Top_Left; + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs new file mode 100644 index 000000000..3b01e6828 --- /dev/null +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices.Marshalling; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using FluentAssertions; +using static TagsCloudVisualization.CircleLayer; + +namespace TagsCloudVisualization.Tests +{ + public class CircleLayerTests + { + private CircleLayer currentLayer; + private Size defaultRectangleSize; + [SetUp] + public void SetUp() + { + var startRadius = 5; + var center = new Point(5, 5); + currentLayer = new CircleLayer(center, startRadius); + defaultRectangleSize = new Size(3, 4); + } + + [Test] + public void CircleLayer_InsertFirstForLayerRectangle_InTopRightSectorStart() + { + var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); + + possibleRectangleLocation.Should().Be(GetCorrectRectangleLocationByExpectedSector(Sector.Top_Right)); + } + + [TestCase(1, Sector.Bottom_Right)] + [TestCase(2, Sector.Bottom_Left)] + [TestCase(3, Sector.Top_Left)] + public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int insertionsCount, Sector expected) + { + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, insertionsCount); + + var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); + + possibleRectangleLocation.Should().Be(GetCorrectRectangleLocationByExpectedSector(expected)); + } + + [Test] + public void CircleLayer_GetNewLayer_AfterInsertionsOnAllSectors() + { + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); + + var nextLayer = currentLayer.OnSuccessInsertRectangle(new Rectangle(new Point(0, 0), defaultRectangleSize)); + + nextLayer.Should().NotBeSameAs(currentLayer); + } + + [Test] + public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMaxDistanceFromCenterToInsertedRectangle() + { + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); + var nextRectangleLocation = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4)); + + var nextLayer = currentLayer.OnSuccessInsertRectangle(new Rectangle(nextRectangleLocation, new Size(2,2))); + + nextLayer.Radius.Should().Be(10); + } + + private CircleLayer GetLayerAfterFewInsertionsRectangleWithSameSize(CircleLayer layer, int insertionsCount) + { + for (var i = 0; i < insertionsCount; i++) + { + var location = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(i)); + var rectangleForInsert = new Rectangle(location, defaultRectangleSize); + layer.OnSuccessInsertRectangle(rectangleForInsert); + } + return layer; + } + + private Sector GetSectorByInsertionsCount(int count) + { + return (Sector)((count - 1) % 4); + } + + private Point GetCorrectRectangleLocationByExpectedSector(Sector s) + { + switch (s) + { + case Sector.Top_Right: + return new Point(currentLayer.Center.X, currentLayer.Center.Y - currentLayer.Radius - defaultRectangleSize.Height); + case Sector.Bottom_Right: + return new Point(currentLayer.Center.X + currentLayer.Radius, currentLayer.Center.Y); + case Sector.Bottom_Left: + return new Point(currentLayer.Center.X - defaultRectangleSize.Width, currentLayer.Center.Y + currentLayer.Radius); + default: + return new Point(currentLayer.Center.X - currentLayer.Radius - defaultRectangleSize.Width, + currentLayer.Center.Y - defaultRectangleSize.Height); + } + } + + [Test] + public void CircleLayer_RectangleWithNewPositionAfterIntersection_ShouldNotIntersectSameRectangle() + { + var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); + var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); + + var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); + + new Rectangle(newPosition, rectangleForInsertion.Size).IntersectsWith(intersectedRectangle).Should() + .BeFalse(); + } + + [Test] + public void GetPositionOnCircleWithoutIntersection_ShouldPlaceBottomLeftCornerOnCircle_WhenFoundIntersectionInTopRightSector_AndIntersectedRectangleCanPlaceWithIntCoordinate() + { + var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); + var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); + + var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); + var bottomLeftCorner = new Point(newPosition.X, newPosition.Y + intersectedRectangle.Height); + + CurrentLayerContainsPoint(bottomLeftCorner).Should().BeTrue(); + } + + [Test] + public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInTopRightSector() + { + var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); + var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); + var expected = new Point(9, 1); + + var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); + + newPosition.Should().Be(expected); + } + + + [Test] + public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInBottomRightSector() + { + var rectangleForInsertion = new Rectangle(new Point(8, 9), new Size(5, 1)); + var intersectedRectangle = new Rectangle(new Point(10, 5), new Size(8, 7)); + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 1); + var expected = new Point(5, 10); + + var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); + + newPosition.Should().Be(expected); + } + + + [Test] + public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInBottomLeftSector() + { + var rectangleForInsertion = new Rectangle(new Point(-3, 9), new Size(5, 3)); + var intersectedRectangle = new Rectangle(new Point(-7, 8), new Size(8, 7)); + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 2); + var expected = new Point(-5, 5); + + var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); + + newPosition.Should().Be(expected); + } + + [Test] + public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInTopLeftSector() + { + var rectangleForInsertion = new Rectangle(new Point(-3, -2), new Size(4, 3)); + var intersectedRectangle = new Rectangle(new Point(-7, 1), new Size(8, 7)); + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); + var expected = new Point(1, -3); + + var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); + + newPosition.Should().Be(expected); + } + + private bool CurrentLayerContainsPoint(Point p) + { + return (p.X - currentLayer.Center.X) * (p.X - currentLayer.Center.X) + + (p.Y - currentLayer.Center.Y) * (p.Y - currentLayer.Center.Y) == currentLayer.Radius * currentLayer.Radius; + } + } +} From 9bbe39abf0539205435e34e1ca48e90d8daa609d Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Wed, 13 Nov 2024 23:43:21 +0500 Subject: [PATCH 03/25] Add CircularCloudLayoter --- .../CircularCloudLayouter.cs | 80 +++++++++++++++++ .../Tests/CircularCloudLayouterTests.cs | 88 +++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 cs/TagsCloudVisualization/CircularCloudLayouter.cs create mode 100644 cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs new file mode 100644 index 000000000..9ce645209 --- /dev/null +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TagsCloudVisualization +{ + public class CircularCloudLayouter + { + private List rectanglesLocation = new (); + private readonly Point center; + private BruteForceNearestFinder nearestFinder; + + public CircleLayer CurrentLayer { get; private set; } + + public CircularCloudLayouter(Point center) + { + this.center = center; + nearestFinder = new BruteForceNearestFinder(); + } + public Rectangle PutNextRectangle(Size rectangleSize) + { + Rectangle resultRectangle; + if (IsFirstRectangle()) + { + CreateFirstLayer(rectangleSize); + resultRectangle = PutRectangleToCenter(rectangleSize); + } + else + { + var possiblePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); + resultRectangle = new Rectangle(possiblePosition, rectangleSize); + } + OnSuccessInsertion(resultRectangle); + return resultRectangle; + } + + private void OnSuccessInsertion(Rectangle r) + { + rectanglesLocation.Add(r); + nearestFinder.Insert(r); + if (IsNotFirstInsertion()) + CurrentLayer.OnSuccessInsertRectangle(r); + } + + private void CreateFirstLayer(Size firstRectangle) + { + var radius = Math.Ceiling(Math.Max(firstRectangle.Width, firstRectangle.Height) / 2.0); + CurrentLayer = new CircleLayer(center, (int)radius); + } + + private Rectangle PutRectangleToCenter(Size rectangleSize) + { + var rectangleX = center.X - rectangleSize.Width / 2; + var rectangleY = center.Y - rectangleSize.Height / 2; + + return new Rectangle(new Point(rectangleX, rectangleY), rectangleSize); + } + + private bool IsFirstRectangle() + { + return rectanglesLocation.Count == 0; + } + + private bool IsNotFirstInsertion() + { + return rectanglesLocation.Count > 1; + } + + public IEnumerable GetRectangles() + { + foreach (var rectangle in rectanglesLocation) + { + yield return rectangle; + } + } + } +} diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs new file mode 100644 index 000000000..d2243950e --- /dev/null +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using NUnit.Framework; +using FluentAssertions; + +namespace TagsCloudVisualization.Tests +{ + public class CircularCloudLayouterTests + { + private CircularCloudLayouter layouter; + private Point defaultCenter; + + [SetUp] + public void SetUp() + { + defaultCenter = new Point(5, 5); + layouter = new CircularCloudLayouter(defaultCenter); + } + + [Test] + public void GetRectangles_ShouldBeEmpty_BeforePutAnyRectangles() + { + layouter.GetRectangles() + .Should().BeEmpty(); + } + + [Test] + public void PutNextRectangle_ShouldAddRectangleToCenter_WhenRectangleFirst() + { + var firstRectangleSize = new Size(6, 4); + var expected = new Rectangle(new Point(2, 3), firstRectangleSize); + + var nextRectangle = layouter.PutNextRectangle(firstRectangleSize); + + nextRectangle + .Should().Be(expected); + } + + [Test] + public void PutNextRectangle_ShouldCreateFirstCircleLayer_AfterPutFirstRectangle() + { + var firstRectangleSize = new Size(6, 4); + + layouter.PutNextRectangle(firstRectangleSize); + + layouter.CurrentLayer.Should().NotBeNull(); + } + + [TestCase(6, 4, 3)] + [TestCase(4, 6, 3)] + [TestCase(2, 2, 1)] + [TestCase(5, 9, 5)] + public void PutNextRectangle_ShouldCreateFirstCircleLayer_WithRadiusEqualHalfMaxSizeOfFirstRectangleRoundToInt(int height, int width, int expected) + { + var firstRectangleSize = new Size(width, height); + + layouter.PutNextRectangle(firstRectangleSize); + + layouter.CurrentLayer.Radius.Should().Be(expected); + } + + [Test] + public void PutNextRectangle_ShouldAddRectangleToLayouter_AfterPut() + { + var firstRectangleSize = new Size(4, 4); + + layouter.PutNextRectangle(firstRectangleSize); + + layouter.GetRectangles() + .Should().NotBeEmpty().And.HaveCount(1); + } + + [Test] + public void PutNextRectangle_ShouldUseCircleLayer_ForChoosePositionForRectangle() + { + var firstRectangleSize = new Size(4, 4); + var expected = new Point(5, -1); + + layouter.PutNextRectangle(firstRectangleSize); + var secondRectangleLocation = layouter.PutNextRectangle(firstRectangleSize).Location; + + secondRectangleLocation.Should().Be(expected); + } + } +} From dfbc811cba5e4538ad3673b1c9eafaaa120cc1f4 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Wed, 13 Nov 2024 23:45:29 +0500 Subject: [PATCH 04/25] Add Direction --- cs/TagsCloudVisualization/Dicrection.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 cs/TagsCloudVisualization/Dicrection.cs diff --git a/cs/TagsCloudVisualization/Dicrection.cs b/cs/TagsCloudVisualization/Dicrection.cs new file mode 100644 index 000000000..6a144ce0d --- /dev/null +++ b/cs/TagsCloudVisualization/Dicrection.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TagsCloudVisualization +{ + public enum Direction + { + Left, + Right, + Top, + Bottom + } +} From 9f88c1501ad43055c5267d54f11924e1fd573746 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Thu, 14 Nov 2024 11:49:35 +0500 Subject: [PATCH 05/25] Fix calculation radius --- cs/TagsCloudVisualization/CircleLayer.cs | 52 ++++++++----- .../Tests/CircleLayerTests.cs | 73 ++++++++++++++----- 2 files changed, 90 insertions(+), 35 deletions(-) diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs index 59ff44755..c1fbd8b0c 100644 --- a/cs/TagsCloudVisualization/CircleLayer.cs +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -44,24 +44,38 @@ private CircleLayer CreateNextLayer() return nextLayer; } - private int CalculateRadiusForNextLayer() - { - var prevSector = Sector.Top_Right - 1; - return layerRectangles.Select(r => CalculateDistanceBetweenCenterAndRectangleBySector(r, prevSector + 1)).Max(); - } - + private int CalculateRadiusForNextLayer() //TODO: выбрать наиболее адекватный вариант перерасчёта радиуса + { + var prevSector = Sector.Top_Right; + return layerRectangles.Select(r => CalculateDistanceBetweenCenterAndRectangleBySector(r, prevSector++)).Min(); + } + + //private Sector GetSectorNextClockwise(Sector s) + //{ + // switch (s) + // { + // case Sector.Top_Right: + // return Sector.Bottom_Right; + // case Sector.Bottom_Right: + // return Sector.Bottom_Left; + // case Sector.Bottom_Left: + // return Sector.Top_Left; + // default: + // return CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Top)); + // } + //} private int CalculateDistanceBetweenCenterAndRectangleBySector(Rectangle r, Sector s) { switch (s) { case Sector.Top_Right: - return CalculateDistanceBetweenPoints(Center, new Point(r.X + r.Width, r.Y)); + return CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Top)); case Sector.Bottom_Right: - return CalculateDistanceBetweenPoints(Center, new Point(r.X + r.Width, r.Y + r.Height)); + return CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Bottom)); case Sector.Bottom_Left: - return CalculateDistanceBetweenPoints(Center, new Point(r.X - r.Width, r.Y + r.Height)); + return CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Bottom)); default: - return CalculateDistanceBetweenPoints(Center, new Point(r.X - r.Width, r.Y)); + return CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Top)); } } @@ -72,22 +86,22 @@ private int CalculateDistanceBetweenPoints(Point p1, Point p2) public Point CalculateTopLeftRectangleCornerPosition(Size rectangleSize) { - var relevantForSectorPosition = GetPositionRelevantForSector(); + var rectangleStartPositionOnCircle = GetStartSectorPointOnCircle(); switch (currentSector) { case Sector.Top_Right: - return new Point(relevantForSectorPosition.X, relevantForSectorPosition.Y - rectangleSize.Height); + return new Point(rectangleStartPositionOnCircle.X, rectangleStartPositionOnCircle.Y - rectangleSize.Height); case Sector.Bottom_Right: - return relevantForSectorPosition; + return rectangleStartPositionOnCircle; case Sector.Bottom_Left: - return new Point(relevantForSectorPosition.X - rectangleSize.Width, relevantForSectorPosition.Y); + return new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, rectangleStartPositionOnCircle.Y); default: - return new Point(relevantForSectorPosition.X - rectangleSize.Width, - relevantForSectorPosition.Y - rectangleSize.Height); + return new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, + rectangleStartPositionOnCircle.Y - rectangleSize.Height); } } - private Point GetPositionRelevantForSector() + private Point GetStartSectorPointOnCircle() { switch (currentSector) { @@ -107,6 +121,10 @@ public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rec return CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); } + //TODO: переписать везде где можно подсчёт координат на свойства прямоугольника Top, Bottom и так далее + //TODO: пересечения для разных расположений четырёхугольника + //TODO: подумать, как считать радиус окружности адекватно + private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangle forInsertion, Rectangle intersected) { var distanceForMoving = CalculateDistanceForMovingBySector(s, forInsertion, intersected); diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs index 3b01e6828..c14ee08ce 100644 --- a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -29,7 +29,7 @@ public void CircleLayer_InsertFirstForLayerRectangle_InTopRightSectorStart() { var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); - possibleRectangleLocation.Should().Be(GetCorrectRectangleLocationByExpectedSector(Sector.Top_Right)); + possibleRectangleLocation.Should().Be(GetCorrectRectangleLocationByExpectedSector(Sector.Top_Right, defaultRectangleSize)); } [TestCase(1, Sector.Bottom_Right)] @@ -41,7 +41,7 @@ public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int in var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); - possibleRectangleLocation.Should().Be(GetCorrectRectangleLocationByExpectedSector(expected)); + possibleRectangleLocation.Should().Be(GetCorrectRectangleLocationByExpectedSector(expected, defaultRectangleSize)); } [Test] @@ -55,24 +55,20 @@ public void CircleLayer_GetNewLayer_AfterInsertionsOnAllSectors() } [Test] - public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMaxDistanceFromCenterToInsertedRectangle() + public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterToInsertedRectangle() { currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); - var nextRectangleLocation = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4)); + var nextRectangleLocation = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4), defaultRectangleSize); var nextLayer = currentLayer.OnSuccessInsertRectangle(new Rectangle(nextRectangleLocation, new Size(2,2))); - nextLayer.Radius.Should().Be(10); + nextLayer.Radius.Should().Be(9); } private CircleLayer GetLayerAfterFewInsertionsRectangleWithSameSize(CircleLayer layer, int insertionsCount) { - for (var i = 0; i < insertionsCount; i++) - { - var location = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(i)); - var rectangleForInsert = new Rectangle(location, defaultRectangleSize); - layer.OnSuccessInsertRectangle(rectangleForInsert); - } + layer = GetLayerAfterFewInsertionsRectangleWithDifferentSize(layer, insertionsCount, + new Size[insertionsCount].Select(x => defaultRectangleSize).ToArray()); return layer; } @@ -81,19 +77,19 @@ private Sector GetSectorByInsertionsCount(int count) return (Sector)((count - 1) % 4); } - private Point GetCorrectRectangleLocationByExpectedSector(Sector s) + private Point GetCorrectRectangleLocationByExpectedSector(Sector s, Size size) { switch (s) { case Sector.Top_Right: - return new Point(currentLayer.Center.X, currentLayer.Center.Y - currentLayer.Radius - defaultRectangleSize.Height); + return new Point(currentLayer.Center.X, currentLayer.Center.Y - currentLayer.Radius - size.Height); case Sector.Bottom_Right: return new Point(currentLayer.Center.X + currentLayer.Radius, currentLayer.Center.Y); case Sector.Bottom_Left: - return new Point(currentLayer.Center.X - defaultRectangleSize.Width, currentLayer.Center.Y + currentLayer.Radius); + return new Point(currentLayer.Center.X - size.Width, currentLayer.Center.Y + currentLayer.Radius); default: - return new Point(currentLayer.Center.X - currentLayer.Radius - defaultRectangleSize.Width, - currentLayer.Center.Y - defaultRectangleSize.Height); + return new Point(currentLayer.Center.X - currentLayer.Radius - size.Width, + currentLayer.Center.Y - size.Height); } } @@ -164,8 +160,8 @@ public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePositio [Test] public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInTopLeftSector() { - var rectangleForInsertion = new Rectangle(new Point(-3, -2), new Size(4, 3)); - var intersectedRectangle = new Rectangle(new Point(-7, 1), new Size(8, 7)); + var rectangleForInsertion = new Rectangle(new (-3, -2), new (4, 3)); + var intersectedRectangle = new Rectangle(new (-7, 1), new (8, 7)); currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); var expected = new Point(1, -3); @@ -179,5 +175,46 @@ private bool CurrentLayerContainsPoint(Point p) return (p.X - currentLayer.Center.X) * (p.X - currentLayer.Center.X) + (p.Y - currentLayer.Center.Y) * (p.Y - currentLayer.Center.Y) == currentLayer.Radius * currentLayer.Radius; } + + [Test] + public void GetPositionOnCircleWithoutIntersection_ShouldMoveRectangleClockwiseAndChangeSector_UntilFindsNewPosition() + { + var fullLayer = GetLayerWithFullFirstLayerForIntersection(currentLayer); + var forInsertion = new Rectangle(new (11, 5), new (6,6)); + var intersected = new Rectangle(new(10, 5),new(5, 8)); + + var newPosition = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); + + newPosition.Should().Be(new Point(-1, 11)); + } + + [Test] + public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterToInsertedRectangle2() + { + var nextLayer = GetLayerWithFullFirstLayerForIntersection(currentLayer); + + nextLayer.Radius.Should().Be(10); + } + + private CircleLayer GetLayerWithFullFirstLayerForIntersection(CircleLayer layer) + { + var sizesForInsertions = new Size[] + { + new (8,1), new(5,8), new (4,4), new (4,4), new(4,4) + }; + return GetLayerAfterFewInsertionsRectangleWithDifferentSize(layer, sizesForInsertions.Length, + sizesForInsertions); + } + + private CircleLayer GetLayerAfterFewInsertionsRectangleWithDifferentSize(CircleLayer layer, int insertionsCount, Size[] sizes) + { + for (var i = 1; i <= insertionsCount; i++) + { + var location = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(i), sizes[i - 1]); + var rectangleForInsert = new Rectangle(location, sizes[i - 1]); + layer = layer.OnSuccessInsertRectangle(rectangleForInsert); + } + return layer; + } } } From ce6db8cee30531da5e4984ddb1530970acd6d8fa Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Thu, 14 Nov 2024 12:27:13 +0500 Subject: [PATCH 06/25] Add move to next sector when find position without intersection --- cs/TagsCloudVisualization/CircleLayer.cs | 53 +++++++++++-------- .../Tests/CircleLayerTests.cs | 25 +++++---- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs index c1fbd8b0c..991061eb8 100644 --- a/cs/TagsCloudVisualization/CircleLayer.cs +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -15,7 +15,6 @@ public enum Sector public Point Center { get; } public int Radius { get; } - private Sector currentSector; private readonly List layerRectangles; private CircleLayer prevLayer; @@ -32,11 +31,16 @@ public CircleLayer OnSuccessInsertRectangle(Rectangle inserted) { currentSector = currentSector == Sector.Top_Left ? Sector.Top_Right : currentSector + 1; layerRectangles.Add(inserted); - if (currentSector == Sector.Top_Right) + if (ShouldCreateNewCircle()) return CreateNextLayer(); return this; } + private bool ShouldCreateNewCircle() + { + return currentSector == Sector.Top_Right; + } + private CircleLayer CreateNextLayer() { var nextLayer = new CircleLayer(Center, CalculateRadiusForNextLayer()); @@ -50,20 +54,6 @@ private int CalculateRadiusForNextLayer() //TODO: выбрать наиболе return layerRectangles.Select(r => CalculateDistanceBetweenCenterAndRectangleBySector(r, prevSector++)).Min(); } - //private Sector GetSectorNextClockwise(Sector s) - //{ - // switch (s) - // { - // case Sector.Top_Right: - // return Sector.Bottom_Right; - // case Sector.Bottom_Right: - // return Sector.Bottom_Left; - // case Sector.Bottom_Left: - // return Sector.Top_Left; - // default: - // return CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Top)); - // } - //} private int CalculateDistanceBetweenCenterAndRectangleBySector(Rectangle r, Sector s) { switch (s) @@ -86,7 +76,7 @@ private int CalculateDistanceBetweenPoints(Point p1, Point p2) public Point CalculateTopLeftRectangleCornerPosition(Size rectangleSize) { - var rectangleStartPositionOnCircle = GetStartSectorPointOnCircle(); + var rectangleStartPositionOnCircle = GetStartSectorPointOnCircleBySector(currentSector); switch (currentSector) { case Sector.Top_Right: @@ -101,9 +91,9 @@ public Point CalculateTopLeftRectangleCornerPosition(Size rectangleSize) } } - private Point GetStartSectorPointOnCircle() + private Point GetStartSectorPointOnCircleBySector(Sector s) { - switch (currentSector) + switch (s) { case Sector.Top_Right: return new Point(Center.X, Center.Y - Radius); @@ -118,12 +108,31 @@ private Point GetStartSectorPointOnCircle() public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rectangle intersected) { - return CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); + var nextPosition = CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); + if (IsNextPositionMoveToAnotherSector(nextPosition, forInsertion.Size)) + { + if (ShouldCreateNewCircle()) + { + + } + + currentSector += 1; + nextPosition = CalculateTopLeftRectangleCornerPosition(forInsertion.Size); + } + return nextPosition; + } + + private bool IsNextPositionMoveToAnotherSector(Point next, Size forInsertionSize) + { + return IsRectangleIntersectSymmetryAxis(new Rectangle(next, forInsertionSize)); + } + + private bool IsRectangleIntersectSymmetryAxis(Rectangle r) + { + return (r.Left < Center.X && r.Right > Center.X) || (r.Bottom > Center.Y && r.Top < Center.Y); } //TODO: переписать везде где можно подсчёт координат на свойства прямоугольника Top, Bottom и так далее - //TODO: пересечения для разных расположений четырёхугольника - //TODO: подумать, как считать радиус окружности адекватно private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangle forInsertion, Rectangle intersected) { diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs index c14ee08ce..d0da6afb4 100644 --- a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using FluentAssertions; using static TagsCloudVisualization.CircleLayer; +using System.Reflection.Emit; namespace TagsCloudVisualization.Tests { @@ -176,6 +177,18 @@ private bool CurrentLayerContainsPoint(Point p) (p.Y - currentLayer.Center.Y) * (p.Y - currentLayer.Center.Y) == currentLayer.Radius * currentLayer.Radius; } + [Test] + public void CircleLayer_RadiusNextCircleLayer_ShouldBeCeilingToInt() + { + var sizes = new Size[] + { + new (8,1), new(7,8), new (4,4), new (4,4), new(4,4) + }; + var nextLayer = GetLayerAfterFewInsertionsRectangleWithDifferentSize(currentLayer, sizes.Length, sizes); + + nextLayer.Radius.Should().Be(10); + } + [Test] public void GetPositionOnCircleWithoutIntersection_ShouldMoveRectangleClockwiseAndChangeSector_UntilFindsNewPosition() { @@ -185,22 +198,14 @@ public void GetPositionOnCircleWithoutIntersection_ShouldMoveRectangleClockwiseA var newPosition = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); - newPosition.Should().Be(new Point(-1, 11)); - } - - [Test] - public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterToInsertedRectangle2() - { - var nextLayer = GetLayerWithFullFirstLayerForIntersection(currentLayer); - - nextLayer.Radius.Should().Be(10); + newPosition.Should().Be(new Point(-1, 12)); } private CircleLayer GetLayerWithFullFirstLayerForIntersection(CircleLayer layer) { var sizesForInsertions = new Size[] { - new (8,1), new(5,8), new (4,4), new (4,4), new(4,4) + new (1,1), new(5,8), new (4,4), new (4,4), new(4,4) }; return GetLayerAfterFewInsertionsRectangleWithDifferentSize(layer, sizesForInsertions.Length, sizesForInsertions); From 517ccb0a464716f6e1c401283b1f3bd73251c14a Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Thu, 14 Nov 2024 15:32:49 +0500 Subject: [PATCH 07/25] Add intersections and small fix --- .../BruteForceNearestFinder.cs | 8 +-- cs/TagsCloudVisualization/CircleLayer.cs | 34 ++++++++---- .../CircularCloudLayouter.cs | 27 +++++++++- .../Tests/CircleLayerTests.cs | 52 +++++++++++-------- .../Tests/CircularCloudLayouterTests.cs | 31 ++++++++--- 5 files changed, 110 insertions(+), 42 deletions(-) diff --git a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs index d89794fbd..62e62b5fd 100644 --- a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs +++ b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs @@ -26,9 +26,11 @@ public void Insert(Rectangle r) if (rectangles.Count == 0) return null; var calculator = GetMinDistanceCalculatorBy(direction); - return rectangles.Select(currentRectangle => (distance: calculator(currentRectangle, r), CurrentEl: currentRectangle)) - .Where(el => el.distance > 0) - .MinBy(el => el.distance).CurrentEl; + var nearestByDirection = rectangles.Select(currentRectangle => + (distance: calculator(currentRectangle, r), CurrentEl: currentRectangle)) + .Where(el => el.distance >= 0).ToList(); + + return nearestByDirection.Count > 0 ? nearestByDirection.MinBy(el => el.distance).CurrentEl : null; } private Func GetMinDistanceCalculatorBy(Direction direction) diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs index 991061eb8..b3ac472e2 100644 --- a/cs/TagsCloudVisualization/CircleLayer.cs +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -13,11 +13,10 @@ public enum Sector } public Point Center { get; } - public int Radius { get; } + public int Radius { get; private set; } private Sector currentSector; private readonly List layerRectangles; - private CircleLayer prevLayer; public CircleLayer(Point center, int radius) { @@ -29,7 +28,7 @@ public CircleLayer(Point center, int radius) public CircleLayer OnSuccessInsertRectangle(Rectangle inserted) { - currentSector = currentSector == Sector.Top_Left ? Sector.Top_Right : currentSector + 1; + currentSector = GetNextClockwiseSector(); layerRectangles.Add(inserted); if (ShouldCreateNewCircle()) return CreateNextLayer(); @@ -41,14 +40,18 @@ private bool ShouldCreateNewCircle() return currentSector == Sector.Top_Right; } + private Sector GetNextClockwiseSector() + { + return currentSector == Sector.Top_Left ? Sector.Top_Right : currentSector + 1; + } + private CircleLayer CreateNextLayer() { var nextLayer = new CircleLayer(Center, CalculateRadiusForNextLayer()); - nextLayer.prevLayer = this; return nextLayer; } - private int CalculateRadiusForNextLayer() //TODO: выбрать наиболее адекватный вариант перерасчёта радиуса + private int CalculateRadiusForNextLayer() { var prevSector = Sector.Top_Right; return layerRectangles.Select(r => CalculateDistanceBetweenCenterAndRectangleBySector(r, prevSector++)).Min(); @@ -111,17 +114,30 @@ public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rec var nextPosition = CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); if (IsNextPositionMoveToAnotherSector(nextPosition, forInsertion.Size)) { + currentSector = GetNextClockwiseSector(); if (ShouldCreateNewCircle()) { - + CreateNextLayerAndChangeCurrentOnNext(); } - - currentSector += 1; nextPosition = CalculateTopLeftRectangleCornerPosition(forInsertion.Size); } return nextPosition; } + private void CreateNextLayerAndChangeCurrentOnNext() + { + var nextLayer = CreateNextLayer(); + ChangeCurrentLayerBy(nextLayer); + } + + private void ChangeCurrentLayerBy(CircleLayer next) + { + Radius = next.Radius; + currentSector = next.currentSector; + layerRectangles.Clear(); + layerRectangles.AddRange(next.layerRectangles); + } + private bool IsNextPositionMoveToAnotherSector(Point next, Size forInsertionSize) { return IsRectangleIntersectSymmetryAxis(new Rectangle(next, forInsertionSize)); @@ -132,8 +148,6 @@ private bool IsRectangleIntersectSymmetryAxis(Rectangle r) return (r.Left < Center.X && r.Right > Center.X) || (r.Bottom > Center.Y && r.Top < Center.Y); } - //TODO: переписать везде где можно подсчёт координат на свойства прямоугольника Top, Bottom и так далее - private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangle forInsertion, Rectangle intersected) { var distanceForMoving = CalculateDistanceForMovingBySector(s, forInsertion, intersected); diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 9ce645209..d57db6bc3 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -32,6 +32,14 @@ public Rectangle PutNextRectangle(Size rectangleSize) { var possiblePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); resultRectangle = new Rectangle(possiblePosition, rectangleSize); + var intersected = GetRectangleIntersection(resultRectangle); + while (intersected != new Rectangle()) + { + possiblePosition = + CurrentLayer.GetRectanglePositionWithoutIntersection(resultRectangle, intersected.Value); + resultRectangle = new Rectangle(possiblePosition, rectangleSize); + intersected = GetRectangleIntersection(resultRectangle); + } } OnSuccessInsertion(resultRectangle); return resultRectangle; @@ -45,9 +53,26 @@ private void OnSuccessInsertion(Rectangle r) CurrentLayer.OnSuccessInsertRectangle(r); } + private Rectangle? GetRectangleIntersection(Rectangle forInsertion) + { + return rectanglesLocation + .FirstOrDefault(forInsertion.IntersectsWith); + } + + private Rectangle?[] GetNearestByAllDirectionsFor(Rectangle r) + { + return new [] + { + nearestFinder.FindNearestByDirection(r, Direction.Bottom), + nearestFinder.FindNearestByDirection(r, Direction.Top), + nearestFinder.FindNearestByDirection(r, Direction.Left), + nearestFinder.FindNearestByDirection(r, Direction.Right) + }; + } + private void CreateFirstLayer(Size firstRectangle) { - var radius = Math.Ceiling(Math.Max(firstRectangle.Width, firstRectangle.Height) / 2.0); + var radius = Math.Ceiling(Math.Sqrt(firstRectangle.Width* firstRectangle.Width + firstRectangle.Height* firstRectangle.Height) / 2.0); CurrentLayer = new CircleLayer(center, (int)radius); } diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs index d0da6afb4..52ecef60f 100644 --- a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -1,14 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Runtime.InteropServices.Marshalling; -using System.Text; -using System.Threading.Tasks; +using FluentAssertions; using NUnit.Framework; -using FluentAssertions; +using System.Drawing; using static TagsCloudVisualization.CircleLayer; -using System.Reflection.Emit; namespace TagsCloudVisualization.Tests { @@ -46,7 +39,7 @@ public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int in } [Test] - public void CircleLayer_GetNewLayer_AfterInsertionsOnAllSectors() + public void CircleLayer_ShouldCreateNewLayer_AfterInsertionsOnAllSectors() { currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); @@ -190,9 +183,14 @@ public void CircleLayer_RadiusNextCircleLayer_ShouldBeCeilingToInt() } [Test] - public void GetPositionOnCircleWithoutIntersection_ShouldMoveRectangleClockwiseAndChangeSector_UntilFindsNewPosition() + public void GetPositionOnCircleWithoutIntersection_ShouldChangeCornerPositiomForSector_WhenMoveRectangleClockwise() { - var fullLayer = GetLayerWithFullFirstLayerForIntersection(currentLayer); + var sizesForInsertions = new Size[] + { + new (1,1), new(5,8), new (4,4), new (4,4), new(4,4) + }; + var fullLayer = GetLayerAfterFewInsertionsRectangleWithDifferentSize(currentLayer, sizesForInsertions.Length, + sizesForInsertions); var forInsertion = new Rectangle(new (11, 5), new (6,6)); var intersected = new Rectangle(new(10, 5),new(5, 8)); @@ -201,16 +199,6 @@ public void GetPositionOnCircleWithoutIntersection_ShouldMoveRectangleClockwiseA newPosition.Should().Be(new Point(-1, 12)); } - private CircleLayer GetLayerWithFullFirstLayerForIntersection(CircleLayer layer) - { - var sizesForInsertions = new Size[] - { - new (1,1), new(5,8), new (4,4), new (4,4), new(4,4) - }; - return GetLayerAfterFewInsertionsRectangleWithDifferentSize(layer, sizesForInsertions.Length, - sizesForInsertions); - } - private CircleLayer GetLayerAfterFewInsertionsRectangleWithDifferentSize(CircleLayer layer, int insertionsCount, Size[] sizes) { for (var i = 1; i <= insertionsCount; i++) @@ -221,5 +209,25 @@ private CircleLayer GetLayerAfterFewInsertionsRectangleWithDifferentSize(CircleL } return layer; } + + + [Test] + public void GetPositionOnCircleWithoutIntersection_ShouldCreateNewCircle_IfNeedMoveRectangleFromLastSector() + { + var intersectedSize = new Size(4, 9); + var sizesForInsertions = new Size[] + { + new (1,1), new(1,8), new (4,2), intersectedSize, + new(1,1), new(1,1), new(1,1) + }; + var fullLayer = GetLayerAfterFewInsertionsRectangleWithDifferentSize(currentLayer, sizesForInsertions.Length, + sizesForInsertions); + var forInsertion = new Rectangle(new(-8,2), new(8, 3)); + var intersected = new Rectangle(new(-4, -4), intersectedSize); + + var newPosition = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); + + newPosition.Should().Be(new Point(5, -5)); + } } } diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs index d2243950e..0d3bd5741 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -10,6 +10,7 @@ namespace TagsCloudVisualization.Tests { public class CircularCloudLayouterTests { + private CircularCloudLayouter layouter; private Point defaultCenter; @@ -49,11 +50,11 @@ public void PutNextRectangle_ShouldCreateFirstCircleLayer_AfterPutFirstRectangle layouter.CurrentLayer.Should().NotBeNull(); } - [TestCase(6, 4, 3)] - [TestCase(4, 6, 3)] - [TestCase(2, 2, 1)] - [TestCase(5, 9, 5)] - public void PutNextRectangle_ShouldCreateFirstCircleLayer_WithRadiusEqualHalfMaxSizeOfFirstRectangleRoundToInt(int height, int width, int expected) + [TestCase(6, 4, 4)] + [TestCase(4, 6, 4)] + [TestCase(2, 2, 2)] + [TestCase(5, 9, 6)] + public void PutNextRectangle_ShouldCreateFirstCircleLayer_WithRadiusEqualHalfDiagonalFirstRectangleRoundToInt(int height, int width, int expected) { var firstRectangleSize = new Size(width, height); @@ -77,12 +78,30 @@ public void PutNextRectangle_ShouldAddRectangleToLayouter_AfterPut() public void PutNextRectangle_ShouldUseCircleLayer_ForChoosePositionForRectangle() { var firstRectangleSize = new Size(4, 4); - var expected = new Point(5, -1); + var expectedRadius = 7; + var expected = new Point(defaultCenter.X, defaultCenter.Y - expectedRadius); layouter.PutNextRectangle(firstRectangleSize); var secondRectangleLocation = layouter.PutNextRectangle(firstRectangleSize).Location; secondRectangleLocation.Should().Be(expected); } + + [Test] + public void PutNextRectangle_ShouldPutRectangleWithoutIntersection_WhenNeedOneMoveForDeleteIntersection() + { + var firstRectangleSize = new Size(6, 4); + var expected = new Point(9, 1); + + layouter.PutNextRectangle(firstRectangleSize); + layouter.PutNextRectangle(new Size(4, 4)); + layouter.PutNextRectangle(new Size(4, 4)); + layouter.PutNextRectangle(new Size(4, 4)); + layouter.PutNextRectangle(new Size(4, 4)); + + var rectangleLocation = layouter.PutNextRectangle(new Size(3, 3)).Location; + + rectangleLocation.Should().Be(expected); + } } } From 376106824ca1b2b6bd1d005b5e09a06cfc1cca13 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Fri, 15 Nov 2024 00:40:18 +0500 Subject: [PATCH 08/25] Refactoring: replace validation in layoter, fix tests --- .../BruteForceNearestFinder.cs | 25 ++++--------- .../CircularCloudLayouter.cs | 8 ++++ .../Tests/BruteForceNearestFinderTests.cs | 37 +------------------ .../Tests/CircularCloudLayouterTests.cs | 17 +++++++++ 4 files changed, 35 insertions(+), 52 deletions(-) diff --git a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs index 62e62b5fd..8dcc98af2 100644 --- a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs +++ b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs @@ -14,39 +14,30 @@ public class BruteForceNearestFinder public void Insert(Rectangle r) { - if (RectangleHasInсorrectSize(r)) - throw new ArgumentException($"Rectangle has incorrect size: width = {r.Width}, height = {r.Height}"); rectangles.Add(r); } public Rectangle? FindNearestByDirection(Rectangle r, Direction direction) { - if (RectangleHasInсorrectSize(r)) - throw new ArgumentException($"Rectangle has incorrect size: width= {r.Width}, height={r.Height}"); if (rectangles.Count == 0) return null; var calculator = GetMinDistanceCalculatorBy(direction); - var nearestByDirection = rectangles.Select(currentRectangle => - (distance: calculator(currentRectangle, r), CurrentEl: currentRectangle)) - .Where(el => el.distance >= 0).ToList(); + var nearestByDirection = rectangles + .Select(possibleNearest => (Distance: calculator(possibleNearest, r), CurrentEl: possibleNearest)) + .Where(el => el.Distance >= 0).ToList(); - return nearestByDirection.Count > 0 ? nearestByDirection.MinBy(el => el.distance).CurrentEl : null; + return nearestByDirection.Count > 0 ? nearestByDirection.MinBy(el => el.Distance).CurrentEl : null; } private Func GetMinDistanceCalculatorBy(Direction direction) { switch (direction) { - case Direction.Left: return (possibleNearest, rectangleForFind) => rectangleForFind.X - possibleNearest.X; - case Direction.Right: return (possibleNearest, rectangleForFind) => possibleNearest.X - rectangleForFind.X; - case Direction.Top: return (possibleNearest, rectangleForFind) => rectangleForFind.Y - possibleNearest.Y; - default: return (possibleNearest, rectangleForFind) => possibleNearest.Y - rectangleForFind.Y; + case Direction.Left: return (possibleNearest, rectangleForFind) => possibleNearest.Right - rectangleForFind.Left; + case Direction.Right: return (possibleNearest, rectangleForFind) => possibleNearest.Left - rectangleForFind.Right; + case Direction.Top: return (possibleNearest, rectangleForFind) => rectangleForFind.Top - possibleNearest.Bottom; + default: return (possibleNearest, rectangleForFind) => possibleNearest.Top - rectangleForFind.Bottom; } } - - private bool RectangleHasInсorrectSize(Rectangle r) - { - return r.Width <= 0 || r.Height <= 0; - } } } diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index d57db6bc3..2e1716910 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -22,6 +22,7 @@ public CircularCloudLayouter(Point center) } public Rectangle PutNextRectangle(Size rectangleSize) { + ValidateRectangleSize(rectangleSize); Rectangle resultRectangle; if (IsFirstRectangle()) { @@ -44,6 +45,13 @@ public Rectangle PutNextRectangle(Size rectangleSize) OnSuccessInsertion(resultRectangle); return resultRectangle; } + private void ValidateRectangleSize(Size s) + { + if (s.Width <= 0 || s.Height <= 0) + { + throw new ArgumentException($"Rectangle has incorrect size: width = {s.Width}, height = {s.Height}"); + } + } private void OnSuccessInsertion(Rectangle r) { diff --git a/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs b/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs index 75a92ff9d..7a6127852 100644 --- a/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs +++ b/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs @@ -25,43 +25,10 @@ public void FindNearest_ShouldReturnNull_BeforeAnyInsertions() finder.FindNearestByDirection(rectangleForFind, Direction.Top).Should().BeNull(); } - [TestCase(0, 4, TestName = "WhenWidthZero")] - [TestCase(3, 0, TestName = "WhenHeightZero")] - [TestCase(-3, 4, TestName = "WhenWidthIsNegative")] - [TestCase(3, -4, TestName = "WhenHeightNegative")] - [TestCase(-3, -4, TestName = "WhenWidthAndHeightNegative")] - [TestCase(0, 0, TestName = "WhenWidthAndHeightIsZero")] - public void Insert_ShouldThrow(int width, int height) - { - var rectangleForInsert = new Rectangle(new Point(2, 2), new Size(width, height)); - - ShouldThrow((finder, rectangle) => finder.Insert(rectangle), rectangleForInsert); - } - - [TestCase(0, 4, TestName = "WhenWidthZero")] - [TestCase(3, 0, TestName = "WhenHeightZero")] - [TestCase(-3, 4, TestName = "WhenWidthIsNegative")] - [TestCase(3, -4, TestName = "WhenHeightNegative")] - [TestCase(-3, -4, TestName = "WhenWidthAndHeightNegative")] - [TestCase(0, 0, TestName = "WhenWidthAndHeightIsZero")] - public void FindNearest_ShouldThrow(int width, int height) - { - var rectangleForFind = new Rectangle(new Point(2, 2), new Size(width, height)); - - ShouldThrow((finder, rectangle) => finder.FindNearestByDirection(rectangle, Direction.Top), rectangleForFind); - } - - public void ShouldThrow(Action callFinderMethod, Rectangle incorrectRectangle) - { - Action act = () => callFinderMethod(finder, incorrectRectangle); - - act.Should().Throw(); - } - [TestCase(4, 10, Direction.Top)] [TestCase(2, 7, Direction.Top, true)] [TestCase(2, 7, Direction.Right)] - public void FindNearest_ShouldReturnNearestRectangleByDirection_ForArgumentRectangle(int x, int y, Direction direction, bool isFirstRectNearest = false) + public void FindNearest_ShouldReturnNearestRectangleByDirection_ForArgumentRectangle(int x, int y, Direction direction, bool isFirstNearest = false) { var addedRectangle1 = new Rectangle(new Point(2, 2), new Size(3, 4)); var addedRectangle2 = new Rectangle(new Point(5, 7), new Size(4, 2)); @@ -70,7 +37,7 @@ public void FindNearest_ShouldReturnNearestRectangleByDirection_ForArgumentRecta finder.Insert(addedRectangle1); finder.Insert(addedRectangle2); - finder.FindNearestByDirection(rectangleForFind, direction).Should().Be(isFirstRectNearest ? addedRectangle1 : addedRectangle2); + finder.FindNearestByDirection(rectangleForFind, direction).Should().Be(isFirstNearest ? addedRectangle1 : addedRectangle2); } } } diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs index 0d3bd5741..e24dbb7bc 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -5,6 +5,8 @@ using System.Text; using NUnit.Framework; using FluentAssertions; +using System.Reflection; +using static System.Collections.Specialized.BitVector32; namespace TagsCloudVisualization.Tests { @@ -21,6 +23,21 @@ public void SetUp() layouter = new CircularCloudLayouter(defaultCenter); } + [TestCase(0, 4, TestName = "WhenWidthZero")] + [TestCase(3, 0, TestName = "WhenHeightZero")] + [TestCase(-3, 4, TestName = "WhenWidthIsNegative")] + [TestCase(3, -4, TestName = "WhenHeightNegative")] + [TestCase(-3, -4, TestName = "WhenWidthAndHeightNegative")] + [TestCase(0, 0, TestName = "WhenWidthAndHeightIsZero")] + public void Insert_ShouldThrow(int width, int height) + { + var inCorrectSize = new Size(width, height); + + Action act = () => layouter.PutNextRectangle(inCorrectSize); + + act.Should().Throw(); + } + [Test] public void GetRectangles_ShouldBeEmpty_BeforePutAnyRectangles() { From 4ec2ddcd873e1404bf9cba865291de98140cd7e9 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Fri, 15 Nov 2024 15:12:51 +0500 Subject: [PATCH 09/25] Remove usless saving --- .../BruteForceNearestFinder.cs | 13 +++---------- .../Tests/BruteForceNearestFinderTests.cs | 15 ++++++++++----- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs index 8dcc98af2..ac39afc01 100644 --- a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs +++ b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs @@ -10,16 +10,9 @@ namespace TagsCloudVisualization { public class BruteForceNearestFinder { - private List rectangles = new(); - - public void Insert(Rectangle r) - { - rectangles.Add(r); - } - - public Rectangle? FindNearestByDirection(Rectangle r, Direction direction) + public Rectangle? FindNearestByDirection(Rectangle r, Direction direction, IEnumerable rectangles) { - if (rectangles.Count == 0) + if (rectangles.FirstOrDefault() == default) return null; var calculator = GetMinDistanceCalculatorBy(direction); var nearestByDirection = rectangles @@ -33,7 +26,7 @@ private Func GetMinDistanceCalculatorBy(Direction dir { switch (direction) { - case Direction.Left: return (possibleNearest, rectangleForFind) => possibleNearest.Right - rectangleForFind.Left; + case Direction.Left: return (possibleNearest, rectangleForFind) => rectangleForFind.Left - possibleNearest.Right; case Direction.Right: return (possibleNearest, rectangleForFind) => possibleNearest.Left - rectangleForFind.Right; case Direction.Top: return (possibleNearest, rectangleForFind) => rectangleForFind.Top - possibleNearest.Bottom; default: return (possibleNearest, rectangleForFind) => possibleNearest.Top - rectangleForFind.Bottom; diff --git a/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs b/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs index 7a6127852..9610b9920 100644 --- a/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs +++ b/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs @@ -18,26 +18,31 @@ public void SetUp() finder = new BruteForceNearestFinder(); } [Test] - public void FindNearest_ShouldReturnNull_BeforeAnyInsertions() + public void FindNearest_ShouldReturnNull_OnEmptyRectangles() { var rectangleForFind = new Rectangle(new Point(5, 7), new Size(4, 2)); - finder.FindNearestByDirection(rectangleForFind, Direction.Top).Should().BeNull(); + finder.FindNearestByDirection(rectangleForFind, Direction.Top, Array.Empty()).Should().BeNull(); } [TestCase(4, 10, Direction.Top)] [TestCase(2, 7, Direction.Top, true)] [TestCase(2, 7, Direction.Right)] + [TestCase(0, 0, Direction.Right, true)] + [TestCase(0, 0, Direction.Bottom, true)] + [TestCase(7, 4, Direction.Bottom)] + [TestCase(10, 11, Direction.Left)] + [TestCase(7, 4, Direction.Left, true)] public void FindNearest_ShouldReturnNearestRectangleByDirection_ForArgumentRectangle(int x, int y, Direction direction, bool isFirstNearest = false) { var addedRectangle1 = new Rectangle(new Point(2, 2), new Size(3, 4)); var addedRectangle2 = new Rectangle(new Point(5, 7), new Size(4, 2)); var rectangleForFind = new Rectangle(new Point(x, y), new Size(2, 1)); + var rectangles = new[] { addedRectangle1, addedRectangle2 }; - finder.Insert(addedRectangle1); - finder.Insert(addedRectangle2); + var nearest = finder.FindNearestByDirection(rectangleForFind, direction, rectangles); - finder.FindNearestByDirection(rectangleForFind, direction).Should().Be(isFirstNearest ? addedRectangle1 : addedRectangle2); + nearest.Should().Be(isFirstNearest ? addedRectangle1 : addedRectangle2); } } } From fe8c9fee10ee03bc99457182c9a44f0645396529 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Fri, 15 Nov 2024 16:57:20 +0500 Subject: [PATCH 10/25] Refactoring: add RectangleStorage --- cs/TagsCloudVisualization/RectangleStorage.cs | 66 +++++++++++++++++++ .../Tests/RectangleStorageTests.cs | 51 ++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 cs/TagsCloudVisualization/RectangleStorage.cs create mode 100644 cs/TagsCloudVisualization/Tests/RectangleStorageTests.cs diff --git a/cs/TagsCloudVisualization/RectangleStorage.cs b/cs/TagsCloudVisualization/RectangleStorage.cs new file mode 100644 index 000000000..820b2ac9b --- /dev/null +++ b/cs/TagsCloudVisualization/RectangleStorage.cs @@ -0,0 +1,66 @@ +using System.Drawing; + +namespace TagsCloudVisualization; + +public class RectangleStorage +{ + private readonly List elements = new(); + + public int Add(Rectangle r) + { + elements.Add(r); + return elements.Count - 1; + } + + public IEnumerable GetAll() + { + foreach (var rectangle in elements) yield return rectangle; + } + + public RectangleWrapper GetById(int id) + { + return elements[id]; + } + + public class RectangleWrapper + { + public RectangleWrapper(Rectangle v) + { + Value = v; + } + + private Rectangle Value { get; set; } + + public Size Size + { + get => Value.Size; + set => Value = new Rectangle(Location, value); + } + + public Point Location + { + get => Value.Location; + set => Value = new Rectangle(value, Size); + } + + public int Top => Value.Top; + public int Bottom => Value.Bottom; + public int Left => Value.Left; + public int Right => Value.Right; + + public static implicit operator RectangleWrapper(Rectangle v) + { + return new RectangleWrapper(v); + } + + public static implicit operator Rectangle(RectangleWrapper r) + { + return r.Value; + } + + public override bool Equals(object? obj) + { + return (obj as RectangleWrapper)?.Value.Equals(Value) ?? (obj as Rectangle?)?.Equals(Value) ?? false; + } + } +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/Tests/RectangleStorageTests.cs b/cs/TagsCloudVisualization/Tests/RectangleStorageTests.cs new file mode 100644 index 000000000..d14c1328a --- /dev/null +++ b/cs/TagsCloudVisualization/Tests/RectangleStorageTests.cs @@ -0,0 +1,51 @@ +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.Tests +{ + public class RectangleStorageTests + { + private RectangleStorage structStorage; + private Rectangle defaulRectangle; + [SetUp] + public void SetUp() + { + structStorage = new RectangleStorage(); + defaulRectangle = new(new(2, 2), new(2, 2)); + } + + [Test] + public void GetRectangles_ShouldGetAllRectangle() + { + structStorage.Add(defaulRectangle); + structStorage.Add(defaulRectangle); + + structStorage.GetAll().Should().HaveCount(2); + } + + [Test] + public void AddRectangle_ShouldGetIdForRectangle() + { + var id = structStorage.Add(defaulRectangle); + + structStorage.GetById(id).Should().Be(defaulRectangle); + } + + [Test] + public void ChangeRectangle_ShouldChangeRectangleByIndex() + { + var id = structStorage.Add(defaulRectangle); + var rectangleForChange = structStorage.GetById(id); + + rectangleForChange.Size = new Size(1, 1); + + structStorage.GetById(id).Size.Should().Be(rectangleForChange.Size); + } + } +} From d661efd4dca07d2f6d8db4ca85796b6a53d61009 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Fri, 15 Nov 2024 17:54:55 +0500 Subject: [PATCH 11/25] Refactoring: add using storage --- cs/TagsCloudVisualization/CircleLayer.cs | 15 ++-- .../CircularCloudLayouter.cs | 90 ++++++++++--------- .../Tests/CircleLayerTests.cs | 12 ++- .../Tests/CircularCloudLayouterTests.cs | 13 +++ 4 files changed, 79 insertions(+), 51 deletions(-) diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs index b3ac472e2..1c192ea90 100644 --- a/cs/TagsCloudVisualization/CircleLayer.cs +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -16,20 +16,21 @@ public enum Sector public int Radius { get; private set; } private Sector currentSector; - private readonly List layerRectangles; + private readonly RectangleStorage storage; + private readonly List layerRectangles = new(); - public CircleLayer(Point center, int radius) + public CircleLayer(Point center, int radius, RectangleStorage storage) { Center = center; Radius = radius; currentSector = Sector.Top_Right; - layerRectangles = new List(); + this.storage = storage; } - public CircleLayer OnSuccessInsertRectangle(Rectangle inserted) + public CircleLayer OnSuccessInsertRectangle(int addedRectangleId) { currentSector = GetNextClockwiseSector(); - layerRectangles.Add(inserted); + layerRectangles.Add(addedRectangleId); if (ShouldCreateNewCircle()) return CreateNextLayer(); return this; @@ -47,14 +48,14 @@ private Sector GetNextClockwiseSector() private CircleLayer CreateNextLayer() { - var nextLayer = new CircleLayer(Center, CalculateRadiusForNextLayer()); + var nextLayer = new CircleLayer(Center, CalculateRadiusForNextLayer(), storage); return nextLayer; } private int CalculateRadiusForNextLayer() { var prevSector = Sector.Top_Right; - return layerRectangles.Select(r => CalculateDistanceBetweenCenterAndRectangleBySector(r, prevSector++)).Min(); + return layerRectangles.Select(id => CalculateDistanceBetweenCenterAndRectangleBySector(storage.GetById(id), prevSector++)).Min(); } private int CalculateDistanceBetweenCenterAndRectangleBySector(Rectangle r, Sector s) diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 2e1716910..e5cbfe590 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; +using System.Reflection.Metadata.Ecma335; using System.Text; using System.Threading.Tasks; @@ -9,7 +10,7 @@ namespace TagsCloudVisualization { public class CircularCloudLayouter { - private List rectanglesLocation = new (); + private readonly RectangleStorage storage = new (); private readonly Point center; private BruteForceNearestFinder nearestFinder; @@ -23,88 +24,97 @@ public CircularCloudLayouter(Point center) public Rectangle PutNextRectangle(Size rectangleSize) { ValidateRectangleSize(rectangleSize); - Rectangle resultRectangle; - if (IsFirstRectangle()) + Point firstRectanglePosition; + var isFirstRectangle = IsFirstRectangle(); + if (isFirstRectangle) { CreateFirstLayer(rectangleSize); - resultRectangle = PutRectangleToCenter(rectangleSize); + firstRectanglePosition = PutRectangleToCenter(rectangleSize); } else { - var possiblePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); - resultRectangle = new Rectangle(possiblePosition, rectangleSize); - var intersected = GetRectangleIntersection(resultRectangle); - while (intersected != new Rectangle()) - { - possiblePosition = - CurrentLayer.GetRectanglePositionWithoutIntersection(resultRectangle, intersected.Value); - resultRectangle = new Rectangle(possiblePosition, rectangleSize); - intersected = GetRectangleIntersection(resultRectangle); - } + firstRectanglePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); } - OnSuccessInsertion(resultRectangle); - return resultRectangle; + var id = SaveRectangle(firstRectanglePosition, rectangleSize); + var rectangleWithOptimalPosition = OptimiseRectanglePosition(id, isFirstRectangle); + return rectangleWithOptimalPosition; } - private void ValidateRectangleSize(Size s) + + public Rectangle OptimiseRectanglePosition(int id, bool isFirstRectangle) { - if (s.Width <= 0 || s.Height <= 0) + if (isFirstRectangle) return storage.GetById(id); + return PutRectangleOnCircleWithoutIntersection(id); + } + + private int SaveRectangle(Point firstLocation, Size rectangleSize) + { + var id = storage.Add(new Rectangle(firstLocation, rectangleSize)); + return id; + } + + public Rectangle PutRectangleOnCircleWithoutIntersection(int id) + { + var r = storage.GetById(id); + var intersected = GetRectangleIntersection(r); + while (intersected != new Rectangle()) { - throw new ArgumentException($"Rectangle has incorrect size: width = {s.Width}, height = {s.Height}"); + var possiblePosition = + CurrentLayer.GetRectanglePositionWithoutIntersection(r, intersected.Value); + r = new Rectangle(possiblePosition, r.Size); + intersected = GetRectangleIntersection(r); } + CurrentLayer.OnSuccessInsertRectangle(id); + return r; } - private void OnSuccessInsertion(Rectangle r) + private void ValidateRectangleSize(Size s) { - rectanglesLocation.Add(r); - nearestFinder.Insert(r); - if (IsNotFirstInsertion()) - CurrentLayer.OnSuccessInsertRectangle(r); + if (s.Width <= 0 || s.Height <= 0) + { + throw new ArgumentException($"Rectangle has incorrect size: width = {s.Width}, height = {s.Height}"); + } } private Rectangle? GetRectangleIntersection(Rectangle forInsertion) { - return rectanglesLocation - .FirstOrDefault(forInsertion.IntersectsWith); + return storage.GetAll() + .FirstOrDefault(r => forInsertion.IntersectsWith(r) && forInsertion != r); } private Rectangle?[] GetNearestByAllDirectionsFor(Rectangle r) { + var rectangles = this.storage.GetAll(); return new [] { - nearestFinder.FindNearestByDirection(r, Direction.Bottom), - nearestFinder.FindNearestByDirection(r, Direction.Top), - nearestFinder.FindNearestByDirection(r, Direction.Left), - nearestFinder.FindNearestByDirection(r, Direction.Right) + nearestFinder.FindNearestByDirection(r, Direction.Bottom, rectangles), + nearestFinder.FindNearestByDirection(r, Direction.Top, rectangles), + nearestFinder.FindNearestByDirection(r, Direction.Left, rectangles), + nearestFinder.FindNearestByDirection(r, Direction.Right, rectangles) }; } private void CreateFirstLayer(Size firstRectangle) { var radius = Math.Ceiling(Math.Sqrt(firstRectangle.Width* firstRectangle.Width + firstRectangle.Height* firstRectangle.Height) / 2.0); - CurrentLayer = new CircleLayer(center, (int)radius); + CurrentLayer = new CircleLayer(center, (int)radius, storage); } - private Rectangle PutRectangleToCenter(Size rectangleSize) + private Point PutRectangleToCenter(Size rectangleSize) { var rectangleX = center.X - rectangleSize.Width / 2; var rectangleY = center.Y - rectangleSize.Height / 2; - return new Rectangle(new Point(rectangleX, rectangleY), rectangleSize); + return new Point(rectangleX, rectangleY); } private bool IsFirstRectangle() { - return rectanglesLocation.Count == 0; - } - - private bool IsNotFirstInsertion() - { - return rectanglesLocation.Count > 1; + return storage.GetAll().FirstOrDefault() == default; } public IEnumerable GetRectangles() { - foreach (var rectangle in rectanglesLocation) + foreach (var rectangle in storage.GetAll()) { yield return rectangle; } diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs index 52ecef60f..ec53b8a54 100644 --- a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -9,12 +9,14 @@ public class CircleLayerTests { private CircleLayer currentLayer; private Size defaultRectangleSize; + private RectangleStorage storage; [SetUp] public void SetUp() { var startRadius = 5; var center = new Point(5, 5); - currentLayer = new CircleLayer(center, startRadius); + storage = new RectangleStorage(); + currentLayer = new CircleLayer(center, startRadius, storage); defaultRectangleSize = new Size(3, 4); } @@ -42,8 +44,9 @@ public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int in public void CircleLayer_ShouldCreateNewLayer_AfterInsertionsOnAllSectors() { currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); + var insertedRectangleId = storage.Add(new Rectangle(new Point(0, 0), defaultRectangleSize)); - var nextLayer = currentLayer.OnSuccessInsertRectangle(new Rectangle(new Point(0, 0), defaultRectangleSize)); + var nextLayer = currentLayer.OnSuccessInsertRectangle(insertedRectangleId); nextLayer.Should().NotBeSameAs(currentLayer); } @@ -53,8 +56,9 @@ public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterTo { currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); var nextRectangleLocation = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4), defaultRectangleSize); + var insertedRectangleId = storage.Add(new Rectangle(nextRectangleLocation, new Size(2, 2))); - var nextLayer = currentLayer.OnSuccessInsertRectangle(new Rectangle(nextRectangleLocation, new Size(2,2))); + var nextLayer = currentLayer.OnSuccessInsertRectangle(insertedRectangleId); nextLayer.Radius.Should().Be(9); } @@ -205,7 +209,7 @@ private CircleLayer GetLayerAfterFewInsertionsRectangleWithDifferentSize(CircleL { var location = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(i), sizes[i - 1]); var rectangleForInsert = new Rectangle(location, sizes[i - 1]); - layer = layer.OnSuccessInsertRectangle(rectangleForInsert); + layer = layer.OnSuccessInsertRectangle(storage.Add(rectangleForInsert)); } return layer; } diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs index e24dbb7bc..1b1d82917 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -120,5 +120,18 @@ public void PutNextRectangle_ShouldPutRectangleWithoutIntersection_WhenNeedOneMo rectangleLocation.Should().Be(expected); } + + [Test] + public void PutNextRectangle_ShouldTryMoveRectangleCloserToCenter_WhenItPossible() + { + var firstRectangleSize = new Size(6, 4); + var secondRectangleSize = new Size(4, 4); + var expectedSecondRectangleLocation = new Point(-1, 5); + + layouter.PutNextRectangle(firstRectangleSize); + var second = layouter.PutNextRectangle(secondRectangleSize); + + second.Location.Should().Be(expectedSecondRectangleLocation); + } } } From e4ca17df31c1ad5795c1aa0c35bd4859d321bb59 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Fri, 15 Nov 2024 18:09:48 +0500 Subject: [PATCH 12/25] Fix: changing rectangle should change rectangle in storage --- cs/TagsCloudVisualization/CircularCloudLayouter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index e5cbfe590..91ed8cac2 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -60,7 +60,7 @@ public Rectangle PutRectangleOnCircleWithoutIntersection(int id) { var possiblePosition = CurrentLayer.GetRectanglePositionWithoutIntersection(r, intersected.Value); - r = new Rectangle(possiblePosition, r.Size); + r.Location = possiblePosition; intersected = GetRectangleIntersection(r); } CurrentLayer.OnSuccessInsertRectangle(id); From 8dff1872a17d0c8f148acc589d64e5447e110228 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Fri, 15 Nov 2024 22:18:34 +0500 Subject: [PATCH 13/25] Add move closer to center --- .../CircularCloudLayouter.cs | 234 ++++++++++-------- .../Tests/CircularCloudLayouterTests.cs | 39 ++- 2 files changed, 163 insertions(+), 110 deletions(-) diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 91ed8cac2..3f4d311e4 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -1,123 +1,159 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Reflection.Metadata.Ecma335; -using System.Text; -using System.Threading.Tasks; - -namespace TagsCloudVisualization +using System.Drawing; + +namespace TagsCloudVisualization; + +public class CircularCloudLayouter { - public class CircularCloudLayouter + private readonly Point center; + private readonly RectangleStorage storage = new(); + private readonly BruteForceNearestFinder nearestFinder; + + public CircularCloudLayouter(Point center) { - private readonly RectangleStorage storage = new (); - private readonly Point center; - private BruteForceNearestFinder nearestFinder; + this.center = center; + nearestFinder = new BruteForceNearestFinder(); + } + + internal CircularCloudLayouter(Point center, RectangleStorage storage) : this(center) + { + this.storage = storage; + } - public CircleLayer CurrentLayer { get; private set; } + public CircleLayer CurrentLayer { get; private set; } - public CircularCloudLayouter(Point center) + public Rectangle PutNextRectangle(Size rectangleSize) + { + ValidateRectangleSize(rectangleSize); + Point firstRectanglePosition; + var isFirstRectangle = IsFirstRectangle(); + if (isFirstRectangle) { - this.center = center; - nearestFinder = new BruteForceNearestFinder(); + CreateFirstLayer(rectangleSize); + firstRectanglePosition = PutRectangleToCenter(rectangleSize); } - public Rectangle PutNextRectangle(Size rectangleSize) + else { - ValidateRectangleSize(rectangleSize); - Point firstRectanglePosition; - var isFirstRectangle = IsFirstRectangle(); - if (isFirstRectangle) - { - CreateFirstLayer(rectangleSize); - firstRectanglePosition = PutRectangleToCenter(rectangleSize); - } - else - { - firstRectanglePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); - } - var id = SaveRectangle(firstRectanglePosition, rectangleSize); - var rectangleWithOptimalPosition = OptimiseRectanglePosition(id, isFirstRectangle); - return rectangleWithOptimalPosition; + firstRectanglePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); } + var id = SaveRectangle(firstRectanglePosition, rectangleSize); + var rectangleWithOptimalPosition = OptimiseRectanglePosition(id, isFirstRectangle); + return rectangleWithOptimalPosition; + } - public Rectangle OptimiseRectanglePosition(int id, bool isFirstRectangle) - { - if (isFirstRectangle) return storage.GetById(id); - return PutRectangleOnCircleWithoutIntersection(id); - } + private int SaveRectangle(Point firstLocation, Size rectangleSize) + { + var id = storage.Add(new Rectangle(firstLocation, rectangleSize)); + return id; + } - private int SaveRectangle(Point firstLocation, Size rectangleSize) - { - var id = storage.Add(new Rectangle(firstLocation, rectangleSize)); - return id; - } + public Rectangle OptimiseRectanglePosition(int id, bool isFirstRectangle) + { + if (isFirstRectangle) return storage.GetById(id); + PutRectangleOnCircleWithoutIntersection(id); + return TryMoveRectangleCloserToCenter(id); + } - public Rectangle PutRectangleOnCircleWithoutIntersection(int id) + public Rectangle PutRectangleOnCircleWithoutIntersection(int id) + { + var r = storage.GetById(id); + var intersected = GetRectangleIntersection(r); + while (intersected != new Rectangle()) { - var r = storage.GetById(id); - var intersected = GetRectangleIntersection(r); - while (intersected != new Rectangle()) - { - var possiblePosition = - CurrentLayer.GetRectanglePositionWithoutIntersection(r, intersected.Value); - r.Location = possiblePosition; - intersected = GetRectangleIntersection(r); - } - CurrentLayer.OnSuccessInsertRectangle(id); - return r; + var possiblePosition = + CurrentLayer.GetRectanglePositionWithoutIntersection(r, intersected.Value); + r.Location = possiblePosition; + intersected = GetRectangleIntersection(r); } - private void ValidateRectangleSize(Size s) - { - if (s.Width <= 0 || s.Height <= 0) - { - throw new ArgumentException($"Rectangle has incorrect size: width = {s.Width}, height = {s.Height}"); - } - } + CurrentLayer.OnSuccessInsertRectangle(id); + return r; + } - private Rectangle? GetRectangleIntersection(Rectangle forInsertion) - { - return storage.GetAll() - .FirstOrDefault(r => forInsertion.IntersectsWith(r) && forInsertion != r); - } + public Rectangle TryMoveRectangleCloserToCenter(int id) + { + var rectangleForMoving = storage.GetById(id); + var directionsForMoving = GetDirectionsForMovingToCenter(rectangleForMoving); + var distancesForMove = directionsForMoving + .Select(d => (Nearest: nearestFinder.FindNearestByDirection(rectangleForMoving, d, storage.GetAll()), + Direction: d)) + .Where(tuple => tuple.Nearest != null) + .Select(t => ( + DistanceCalculator: nearestFinder.GetMinDistanceCalculatorBy(t.Direction), t.Nearest, t.Direction)) + .Select(t => (Distance: t.DistanceCalculator((Rectangle)t.Nearest, rectangleForMoving), t.Direction)) + .ToArray(); + rectangleForMoving.Location = MoveByDirections(rectangleForMoving.Location, distancesForMove); + return rectangleForMoving; + } - private Rectangle?[] GetNearestByAllDirectionsFor(Rectangle r) + private Point MoveByDirections(Point p, (int Distance, Direction Direction)[] t) + { + foreach (var moveInfo in t) { - var rectangles = this.storage.GetAll(); - return new [] - { - nearestFinder.FindNearestByDirection(r, Direction.Bottom, rectangles), - nearestFinder.FindNearestByDirection(r, Direction.Top, rectangles), - nearestFinder.FindNearestByDirection(r, Direction.Left, rectangles), - nearestFinder.FindNearestByDirection(r, Direction.Right, rectangles) - }; + var factorForDistanceByX = moveInfo.Direction == Direction.Left ? -1 : moveInfo.Direction == Direction.Right ? 1 : 0; + var factorForDistanceByY = moveInfo.Direction == Direction.Top ? -1 : moveInfo.Direction == Direction.Bottom ? 1 : 0; + p.X += moveInfo.Distance * factorForDistanceByX; + p.Y += moveInfo.Distance * factorForDistanceByY; } - private void CreateFirstLayer(Size firstRectangle) - { - var radius = Math.Ceiling(Math.Sqrt(firstRectangle.Width* firstRectangle.Width + firstRectangle.Height* firstRectangle.Height) / 2.0); - CurrentLayer = new CircleLayer(center, (int)radius, storage); - } + return p; + } - private Point PutRectangleToCenter(Size rectangleSize) - { - var rectangleX = center.X - rectangleSize.Width / 2; - var rectangleY = center.Y - rectangleSize.Height / 2; + private List GetDirectionsForMovingToCenter(Rectangle r) + { + var directions = new List(); + if (r.Bottom < center.Y) directions.Add(Direction.Bottom); + if (r.Left > center.X) directions.Add(Direction.Left); + if (r.Right < center.X) directions.Add(Direction.Right); + if (r.Top > center.Y) directions.Add(Direction.Top); + return directions; + } - return new Point(rectangleX, rectangleY); - } - - private bool IsFirstRectangle() - { - return storage.GetAll().FirstOrDefault() == default; - } + private void ValidateRectangleSize(Size s) + { + if (s.Width <= 0 || s.Height <= 0) + throw new ArgumentException($"Rectangle has incorrect size: width = {s.Width}, height = {s.Height}"); + } - public IEnumerable GetRectangles() + private Rectangle? GetRectangleIntersection(Rectangle forInsertion) + { + return storage.GetAll() + .FirstOrDefault(r => forInsertion.IntersectsWith(r) && forInsertion != r); + } + + private Rectangle?[] GetNearestByAllDirectionsFor(Rectangle r) + { + var rectangles = storage.GetAll(); + return new[] { - foreach (var rectangle in storage.GetAll()) - { - yield return rectangle; - } - } + nearestFinder.FindNearestByDirection(r, Direction.Bottom, rectangles), + nearestFinder.FindNearestByDirection(r, Direction.Top, rectangles), + nearestFinder.FindNearestByDirection(r, Direction.Left, rectangles), + nearestFinder.FindNearestByDirection(r, Direction.Right, rectangles) + }; + } + + private void CreateFirstLayer(Size firstRectangle) + { + var radius = Math.Ceiling(Math.Sqrt(firstRectangle.Width * firstRectangle.Width + + firstRectangle.Height * firstRectangle.Height) / 2.0); + CurrentLayer = new CircleLayer(center, (int)radius, storage); + } + + private Point PutRectangleToCenter(Size rectangleSize) + { + var rectangleX = center.X - rectangleSize.Width / 2; + var rectangleY = center.Y - rectangleSize.Height / 2; + + return new Point(rectangleX, rectangleY); + } + + private bool IsFirstRectangle() + { + return storage.GetAll().FirstOrDefault() == default; + } + + public IEnumerable GetRectangles() + { + foreach (var rectangle in storage.GetAll()) yield return rectangle; } -} +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs index 1b1d82917..75b5a0053 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -92,41 +92,58 @@ public void PutNextRectangle_ShouldAddRectangleToLayouter_AfterPut() } [Test] - public void PutNextRectangle_ShouldUseCircleLayer_ForChoosePositionForRectangle() + public void PutRectangleOnCircleWithoutIntersection_ShouldUseCircleLayer_ForChoosePositionForRectangle() { var firstRectangleSize = new Size(4, 4); var expectedRadius = 7; + var storage = new RectangleStorage(); + layouter = new CircularCloudLayouter(defaultCenter, storage); var expected = new Point(defaultCenter.X, defaultCenter.Y - expectedRadius); - layouter.PutNextRectangle(firstRectangleSize); - var secondRectangleLocation = layouter.PutNextRectangle(firstRectangleSize).Location; + var rPos = layouter.CurrentLayer.CalculateTopLeftRectangleCornerPosition(firstRectangleSize); + + ; + var secondRectangleLocation = layouter.PutRectangleOnCircleWithoutIntersection(storage.Add(new (rPos, firstRectangleSize))).Location; secondRectangleLocation.Should().Be(expected); } [Test] - public void PutNextRectangle_ShouldPutRectangleWithoutIntersection_WhenNeedOneMoveForDeleteIntersection() + public void PutRectangleOnCircleWithoutIntersection_ShouldPutRectangleWithoutIntersection_WhenNeedOneMoveForDeleteIntersection() { var firstRectangleSize = new Size(6, 4); var expected = new Point(9, 1); - + var storage = new RectangleStorage(); + layouter = new CircularCloudLayouter(defaultCenter, storage); + var sizes = new Size[] { new(4, 4), new(4, 4), new(4, 4), new(4, 4)}; layouter.PutNextRectangle(firstRectangleSize); - layouter.PutNextRectangle(new Size(4, 4)); - layouter.PutNextRectangle(new Size(4, 4)); - layouter.PutNextRectangle(new Size(4, 4)); - layouter.PutNextRectangle(new Size(4, 4)); + layouter = InsertionsWithoutCompress(4, layouter, sizes, storage); + var rectangleWithIntersection = + new Rectangle(layouter.CurrentLayer.CalculateTopLeftRectangleCornerPosition(new(3, 3)), new(3, 3)); - var rectangleLocation = layouter.PutNextRectangle(new Size(3, 3)).Location; + var rectangleLocation = layouter.PutRectangleOnCircleWithoutIntersection(storage.Add(rectangleWithIntersection)).Location; rectangleLocation.Should().Be(expected); } + private CircularCloudLayouter InsertionsWithoutCompress(int insertionsCount, CircularCloudLayouter l, Size[] sizes, RectangleStorage storage) + { + for (var i = 0; i < insertionsCount; i++) + { + var pos = l.CurrentLayer.CalculateTopLeftRectangleCornerPosition(sizes[i]); + var r = new Rectangle(pos, sizes[i]); + l.PutRectangleOnCircleWithoutIntersection(storage.Add(r)); + } + + return l; + } + [Test] public void PutNextRectangle_ShouldTryMoveRectangleCloserToCenter_WhenItPossible() { var firstRectangleSize = new Size(6, 4); var secondRectangleSize = new Size(4, 4); - var expectedSecondRectangleLocation = new Point(-1, 5); + var expectedSecondRectangleLocation = new Point(5, -1); layouter.PutNextRectangle(firstRectangleSize); var second = layouter.PutNextRectangle(secondRectangleSize); From 6f70c9ddca47af4fc8ab6ce975b788d5b1a184c5 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Sat, 16 Nov 2024 04:07:39 +0500 Subject: [PATCH 14/25] Fix radius recalculate and intersection multiplier, add visualisation --- cs/TagsCloudVisualization/CircleLayer.cs | 123 +++++++++--------- .../CircularCloudLayouter.cs | 12 +- .../CircularCloudVisualization.cs | 41 ++++++ .../Tests/CircleLayerTests.cs | 69 +++++----- .../Tests/CircularCloudLayouterTests.cs | 9 +- 5 files changed, 157 insertions(+), 97 deletions(-) create mode 100644 cs/TagsCloudVisualization/CircularCloudVisualization.cs diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs index 1c192ea90..16f76d21f 100644 --- a/cs/TagsCloudVisualization/CircleLayer.cs +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -1,4 +1,7 @@ -using System.Drawing; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; namespace TagsCloudVisualization; @@ -27,13 +30,12 @@ public CircleLayer(Point center, int radius, RectangleStorage storage) this.storage = storage; } - public CircleLayer OnSuccessInsertRectangle(int addedRectangleId) + public void OnSuccessInsertRectangle(int addedRectangleId) { currentSector = GetNextClockwiseSector(); layerRectangles.Add(addedRectangleId); - if (ShouldCreateNewCircle()) - return CreateNextLayer(); - return this; + if (ShouldCreateNewCircle()) + CreateNextLayerAndChangeCurrentOnNext(); } private bool ShouldCreateNewCircle() @@ -46,31 +48,42 @@ private Sector GetNextClockwiseSector() return currentSector == Sector.Top_Left ? Sector.Top_Right : currentSector + 1; } - private CircleLayer CreateNextLayer() + private void CreateNextLayerAndChangeCurrentOnNext() { var nextLayer = new CircleLayer(Center, CalculateRadiusForNextLayer(), storage); - return nextLayer; + ChangeCurrentLayerBy(nextLayer); + } + + private void ChangeCurrentLayerBy(CircleLayer next) + { + Radius = next.Radius; + currentSector = next.currentSector; + var rectanglesForNextRadius = RemoveRectangleInCircle(); + layerRectangles.Clear(); + layerRectangles.AddRange(rectanglesForNextRadius); } private int CalculateRadiusForNextLayer() { - var prevSector = Sector.Top_Right; - return layerRectangles.Select(id => CalculateDistanceBetweenCenterAndRectangleBySector(storage.GetById(id), prevSector++)).Min(); + return layerRectangles + .Select(id => CalculateDistanceBetweenCenterAndRectangle(storage.GetById(id))) + .Min(); } - private int CalculateDistanceBetweenCenterAndRectangleBySector(Rectangle r, Sector s) + private List RemoveRectangleInCircle() { - switch (s) - { - case Sector.Top_Right: - return CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Top)); - case Sector.Bottom_Right: - return CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Bottom)); - case Sector.Bottom_Left: - return CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Bottom)); - default: - return CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Top)); - } + return layerRectangles + .Where(id => CalculateDistanceBetweenCenterAndRectangle(storage.GetById(id)) > Radius) + .ToList(); + } + + private int CalculateDistanceBetweenCenterAndRectangle(Rectangle r) + { + var d1 = CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Top)); + var d2 = CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Bottom)); + var d3 = CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Bottom)); + var d4 = CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Top)); + return Math.Max(Math.Max(d1, d2), Math.Max(d3, d4)); } private int CalculateDistanceBetweenPoints(Point p1, Point p2) @@ -84,11 +97,13 @@ public Point CalculateTopLeftRectangleCornerPosition(Size rectangleSize) switch (currentSector) { case Sector.Top_Right: - return new Point(rectangleStartPositionOnCircle.X, rectangleStartPositionOnCircle.Y - rectangleSize.Height); - case Sector.Bottom_Right: + return new Point(rectangleStartPositionOnCircle.X, + rectangleStartPositionOnCircle.Y - rectangleSize.Height); + case Sector.Bottom_Right: return rectangleStartPositionOnCircle; case Sector.Bottom_Left: - return new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, rectangleStartPositionOnCircle.Y); + return new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, + rectangleStartPositionOnCircle.Y); default: return new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, rectangleStartPositionOnCircle.Y - rectangleSize.Height); @@ -99,13 +114,13 @@ private Point GetStartSectorPointOnCircleBySector(Sector s) { switch (s) { - case Sector.Top_Right: + case Sector.Top_Right: return new Point(Center.X, Center.Y - Radius); - case Sector.Bottom_Right: + case Sector.Bottom_Right: return new Point(Center.X + Radius, Center.Y); - case Sector.Bottom_Left: + case Sector.Bottom_Left: return new Point(Center.X, Center.Y + Radius); - default: + default: return new Point(Center.X - Radius, Center.Y); } } @@ -116,27 +131,11 @@ public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rec if (IsNextPositionMoveToAnotherSector(nextPosition, forInsertion.Size)) { currentSector = GetNextClockwiseSector(); - if (ShouldCreateNewCircle()) - { - CreateNextLayerAndChangeCurrentOnNext(); - } + if (ShouldCreateNewCircle()) CreateNextLayerAndChangeCurrentOnNext(); nextPosition = CalculateTopLeftRectangleCornerPosition(forInsertion.Size); } - return nextPosition; - } - - private void CreateNextLayerAndChangeCurrentOnNext() - { - var nextLayer = CreateNextLayer(); - ChangeCurrentLayerBy(nextLayer); - } - private void ChangeCurrentLayerBy(CircleLayer next) - { - Radius = next.Radius; - currentSector = next.currentSector; - layerRectangles.Clear(); - layerRectangles.AddRange(next.layerRectangles); + return nextPosition; } private bool IsNextPositionMoveToAnotherSector(Point next, Size forInsertionSize) @@ -149,7 +148,8 @@ private bool IsRectangleIntersectSymmetryAxis(Rectangle r) return (r.Left < Center.X && r.Right > Center.X) || (r.Bottom > Center.Y && r.Top < Center.Y); } - private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangle forInsertion, Rectangle intersected) + private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangle forInsertion, + Rectangle intersected) { var distanceForMoving = CalculateDistanceForMovingBySector(s, forInsertion, intersected); var isMovingAxisIsX = IsMovingAxisIsXBySector(s); @@ -159,8 +159,8 @@ private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangl CalculateDeltaForBringRectangleBackOnCircle(nearestForCenterCorner, isMovingAxisIsX); distanceForMoving *= CalculateMoveMultiplierForMoveClockwise(isMovingAxisIsX, forInsertion); distanceForBringBackOnCircle *= CalculateMoveMultiplierForMoveFromCenter(!isMovingAxisIsX, forInsertion); - return isMovingAxisIsX - ? new Point(forInsertion.X + distanceForMoving, forInsertion.Y + distanceForBringBackOnCircle) + return isMovingAxisIsX + ? new Point(forInsertion.X + distanceForMoving, forInsertion.Y + distanceForBringBackOnCircle) : new Point(forInsertion.X + distanceForBringBackOnCircle, forInsertion.Y + distanceForMoving); } @@ -170,8 +170,10 @@ private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCo Func getStaticAxis = isMovingAxisIsX ? p => p.X : p => p.Y; var distanceOnStaticAxis = Math.Abs(getStaticAxis(nearestForCenterCorner) - getStaticAxis(Center)); - var distanceOnAxisForBringBackOnCircle = Math.Abs(getAxisForBringBackOnCircle(nearestForCenterCorner) - getAxisForBringBackOnCircle(Center)); - return (int)Math.Ceiling(Math.Sqrt(Radius * Radius - distanceOnStaticAxis * distanceOnStaticAxis)) - distanceOnAxisForBringBackOnCircle; + var distanceOnAxisForBringBackOnCircle = Math.Abs(getAxisForBringBackOnCircle(nearestForCenterCorner) - + getAxisForBringBackOnCircle(Center)); + return (int)Math.Ceiling(Math.Sqrt(Radius * Radius - distanceOnStaticAxis * distanceOnStaticAxis)) - + distanceOnAxisForBringBackOnCircle; } private Point CalculateCornerNearestForCenterAfterMove(Sector s, int distanceForMoving, Rectangle r) @@ -180,25 +182,30 @@ private Point CalculateCornerNearestForCenterAfterMove(Sector s, int distanceFor var moveMultiplier = CalculateMoveMultiplierForMoveClockwise(isAxisForMoveIsX, r); distanceForMoving *= moveMultiplier; var nearestCorner = GetCornerNearestForCenterBySector(s, r); - return isAxisForMoveIsX - ? new Point(nearestCorner.X + distanceForMoving, nearestCorner.Y) + return isAxisForMoveIsX + ? new Point(nearestCorner.X + distanceForMoving, nearestCorner.Y) : new Point(nearestCorner.X, nearestCorner.Y + distanceForMoving); } private int CalculateMoveMultiplierForMoveFromCenter(bool isAxisForMoveIsX, Rectangle r) { - return isAxisForMoveIsX - ? r.Right < Center.X ? -1 : 1 - : r.Bottom < Center.Y ? -1 : 1; + return isAxisForMoveIsX + ? r.Right <= Center.X ? -1 : 1 + : r.Bottom <= Center.Y + ? -1 + : 1; } + private int CalculateMoveMultiplierForMoveClockwise(bool isAxisForMoveIsX, Rectangle r) { return isAxisForMoveIsX ? r.Left > Center.X ? -1 : 1 - : r.Bottom > Center.Y ? -1 : 1; + : r.Bottom > Center.Y + ? -1 + : 1; } - private int CalculateDistanceForMovingBySector (Sector s, Rectangle forInsertion, Rectangle intersected) + private int CalculateDistanceForMovingBySector(Sector s, Rectangle forInsertion, Rectangle intersected) { switch (s) { diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 3f4d311e4..d2122036d 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -1,4 +1,7 @@ -using System.Drawing; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; namespace TagsCloudVisualization; @@ -35,6 +38,7 @@ public Rectangle PutNextRectangle(Size rectangleSize) { firstRectanglePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); } + var id = SaveRectangle(firstRectanglePosition, rectangleSize); var rectangleWithOptimalPosition = OptimiseRectanglePosition(id, isFirstRectangle); return rectangleWithOptimalPosition; @@ -89,8 +93,10 @@ private Point MoveByDirections(Point p, (int Distance, Direction Direction)[] t) { foreach (var moveInfo in t) { - var factorForDistanceByX = moveInfo.Direction == Direction.Left ? -1 : moveInfo.Direction == Direction.Right ? 1 : 0; - var factorForDistanceByY = moveInfo.Direction == Direction.Top ? -1 : moveInfo.Direction == Direction.Bottom ? 1 : 0; + var factorForDistanceByX = moveInfo.Direction == Direction.Left ? -1 : + moveInfo.Direction == Direction.Right ? 1 : 0; + var factorForDistanceByY = moveInfo.Direction == Direction.Top ? -1 : + moveInfo.Direction == Direction.Bottom ? 1 : 0; p.X += moveInfo.Distance * factorForDistanceByX; p.Y += moveInfo.Distance * factorForDistanceByY; } diff --git a/cs/TagsCloudVisualization/CircularCloudVisualization.cs b/cs/TagsCloudVisualization/CircularCloudVisualization.cs new file mode 100644 index 000000000..3a92636e4 --- /dev/null +++ b/cs/TagsCloudVisualization/CircularCloudVisualization.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +namespace TagsCloudVisualization +{ + public class CircularCloudVisualization + { + private RectangleStorage rectangleStorage; + private const string FILE_PATH = "./Images/"; + + public CircularCloudVisualization(RectangleStorage rectangles) + { + rectangleStorage = rectangles; + } + + public void CreateImage() + { + var fileName = Path.Combine(Path.GetTempPath(), "testImage1010.png"); + using (var image = new Bitmap(1000, 1000)) + { + using (Graphics graphics = Graphics.FromImage(image)) + { + Pen pen = new Pen(Color.Black); + graphics.DrawRectangles(pen, rectangleStorage.GetAll().ToArray()); + } + image.Save(fileName, ImageFormat.Png); + } + } + + private void CreateFile(string filePath) + { + + } + } +} diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs index ec53b8a54..c9d60a008 100644 --- a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using NUnit.Framework; using System.Drawing; +using System.Linq; using static TagsCloudVisualization.CircleLayer; namespace TagsCloudVisualization.Tests @@ -39,17 +40,7 @@ public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int in possibleRectangleLocation.Should().Be(GetCorrectRectangleLocationByExpectedSector(expected, defaultRectangleSize)); } - - [Test] - public void CircleLayer_ShouldCreateNewLayer_AfterInsertionsOnAllSectors() - { - currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); - var insertedRectangleId = storage.Add(new Rectangle(new Point(0, 0), defaultRectangleSize)); - - var nextLayer = currentLayer.OnSuccessInsertRectangle(insertedRectangleId); - - nextLayer.Should().NotBeSameAs(currentLayer); - } + [Test] public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterToInsertedRectangle() @@ -58,14 +49,14 @@ public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterTo var nextRectangleLocation = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4), defaultRectangleSize); var insertedRectangleId = storage.Add(new Rectangle(nextRectangleLocation, new Size(2, 2))); - var nextLayer = currentLayer.OnSuccessInsertRectangle(insertedRectangleId); + currentLayer.OnSuccessInsertRectangle(insertedRectangleId); - nextLayer.Radius.Should().Be(9); + currentLayer.Radius.Should().Be(9); } private CircleLayer GetLayerAfterFewInsertionsRectangleWithSameSize(CircleLayer layer, int insertionsCount) { - layer = GetLayerAfterFewInsertionsRectangleWithDifferentSize(layer, insertionsCount, + layer = GetLayerAfterFewInsertionsRectangle(layer, insertionsCount, new Size[insertionsCount].Select(x => defaultRectangleSize).ToArray()); return layer; } @@ -104,7 +95,7 @@ public void CircleLayer_RectangleWithNewPositionAfterIntersection_ShouldNotInter } [Test] - public void GetPositionOnCircleWithoutIntersection_ShouldPlaceBottomLeftCornerOnCircle_WhenFoundIntersectionInTopRightSector_AndIntersectedRectangleCanPlaceWithIntCoordinate() + public void GetPositionWithoutIntersection_ShouldPlaceBottomLeftCornerOnCircle_WhenFoundIntersectionInTopRightSector() { var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); @@ -116,7 +107,7 @@ public void GetPositionOnCircleWithoutIntersection_ShouldPlaceBottomLeftCornerOn } [Test] - public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInTopRightSector() + public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInTopRightSector() { var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); @@ -129,7 +120,7 @@ public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePositio [Test] - public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInBottomRightSector() + public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInBottomRightSector() { var rectangleForInsertion = new Rectangle(new Point(8, 9), new Size(5, 1)); var intersectedRectangle = new Rectangle(new Point(10, 5), new Size(8, 7)); @@ -143,7 +134,7 @@ public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePositio [Test] - public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInBottomLeftSector() + public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInBottomLeftSector() { var rectangleForInsertion = new Rectangle(new Point(-3, 9), new Size(5, 3)); var intersectedRectangle = new Rectangle(new Point(-7, 8), new Size(8, 7)); @@ -156,7 +147,7 @@ public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePositio } [Test] - public void GetPositionOnCircleWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInTopLeftSector() + public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInTopLeftSector() { var rectangleForInsertion = new Rectangle(new (-3, -2), new (4, 3)); var intersectedRectangle = new Rectangle(new (-7, 1), new (8, 7)); @@ -181,19 +172,19 @@ public void CircleLayer_RadiusNextCircleLayer_ShouldBeCeilingToInt() { new (8,1), new(7,8), new (4,4), new (4,4), new(4,4) }; - var nextLayer = GetLayerAfterFewInsertionsRectangleWithDifferentSize(currentLayer, sizes.Length, sizes); + var nextLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizes.Length, sizes); nextLayer.Radius.Should().Be(10); } [Test] - public void GetPositionOnCircleWithoutIntersection_ShouldChangeCornerPositiomForSector_WhenMoveRectangleClockwise() + public void GetPositionWithoutIntersection_ShouldChangeCornerPositionForSector_WhenMoveRectangleClockwise() { var sizesForInsertions = new Size[] { new (1,1), new(5,8), new (4,4), new (4,4), new(4,4) }; - var fullLayer = GetLayerAfterFewInsertionsRectangleWithDifferentSize(currentLayer, sizesForInsertions.Length, + var fullLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizesForInsertions.Length, sizesForInsertions); var forInsertion = new Rectangle(new (11, 5), new (6,6)); var intersected = new Rectangle(new(10, 5),new(5, 8)); @@ -203,35 +194,55 @@ public void GetPositionOnCircleWithoutIntersection_ShouldChangeCornerPositiomFor newPosition.Should().Be(new Point(-1, 12)); } - private CircleLayer GetLayerAfterFewInsertionsRectangleWithDifferentSize(CircleLayer layer, int insertionsCount, Size[] sizes) + private CircleLayer GetLayerAfterFewInsertionsRectangle(CircleLayer layer, int insertionsCount, Size[] sizes) { for (var i = 1; i <= insertionsCount; i++) { var location = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(i), sizes[i - 1]); var rectangleForInsert = new Rectangle(location, sizes[i - 1]); - layer = layer.OnSuccessInsertRectangle(storage.Add(rectangleForInsert)); + layer.OnSuccessInsertRectangle(storage.Add(rectangleForInsert)); } return layer; } [Test] - public void GetPositionOnCircleWithoutIntersection_ShouldCreateNewCircle_IfNeedMoveRectangleFromLastSector() + public void GetPositionWithoutIntersection_ShouldCreateNewCircle_IfNeedMoveRectangleFromLastSector() { var intersectedSize = new Size(4, 9); - var sizesForInsertions = new Size[] + var sizesForInsertions = new [] { new (1,1), new(1,8), new (4,2), intersectedSize, new(1,1), new(1,1), new(1,1) }; - var fullLayer = GetLayerAfterFewInsertionsRectangleWithDifferentSize(currentLayer, sizesForInsertions.Length, + var fullLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizesForInsertions.Length, sizesForInsertions); - var forInsertion = new Rectangle(new(-8,2), new(8, 3)); + var forInsertion = new Rectangle(new(-10,2), new(8, 3)); var intersected = new Rectangle(new(-4, -4), intersectedSize); var newPosition = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); - newPosition.Should().Be(new Point(5, -5)); + newPosition.Should().Be(new Point(5, -7)); + } + + [Test] + public void GetPositionWithoutIntersection_WhenOneRectangleOnSimmetricalAxis() + { + var intersectedSize = new Size(50, 50); + var sizesForInsertions = new[] + { + new (1,1), new(1,8), intersectedSize, new(1,1), new(1,1), new(1,1) + }; + var fullLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizesForInsertions.Length, + sizesForInsertions); + var forInsertion = new Rectangle(new(4, 10), new(1, 1)); + var intersected = new Rectangle(new(-50, 10), intersectedSize); + + forInsertion.Location = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); + + + forInsertion.IntersectsWith(intersected).Should().BeFalse(); + forInsertion.Location.Should().Be(new Point(-2, 9)); } } } diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs index 75b5a0053..e8307e141 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -1,12 +1,7 @@ using System; -using System.Collections.Generic; using System.Drawing; -using System.Linq; -using System.Text; using NUnit.Framework; using FluentAssertions; -using System.Reflection; -using static System.Collections.Specialized.BitVector32; namespace TagsCloudVisualization.Tests { @@ -112,10 +107,10 @@ public void PutRectangleOnCircleWithoutIntersection_ShouldUseCircleLayer_ForChoo public void PutRectangleOnCircleWithoutIntersection_ShouldPutRectangleWithoutIntersection_WhenNeedOneMoveForDeleteIntersection() { var firstRectangleSize = new Size(6, 4); - var expected = new Point(9, 1); + var expected = new Point(14, 1); var storage = new RectangleStorage(); layouter = new CircularCloudLayouter(defaultCenter, storage); - var sizes = new Size[] { new(4, 4), new(4, 4), new(4, 4), new(4, 4)}; + var sizes = new Size[] { new(4, 7), new(4, 4), new(4, 4), new(4, 4)}; layouter.PutNextRectangle(firstRectangleSize); layouter = InsertionsWithoutCompress(4, layouter, sizes, storage); var rectangleWithIntersection = From a4217c3ffb43678f81759f711243c84ad55fb877 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Sat, 16 Nov 2024 04:09:22 +0500 Subject: [PATCH 15/25] Small fix --- .../BruteForceNearestFinder.cs | 2 +- cs/TagsCloudVisualization/RectangleStorage.cs | 3 ++- .../Tests/RectangleStorageTests.cs | 20 +++++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs index ac39afc01..1f94c93b4 100644 --- a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs +++ b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs @@ -22,7 +22,7 @@ public class BruteForceNearestFinder return nearestByDirection.Count > 0 ? nearestByDirection.MinBy(el => el.Distance).CurrentEl : null; } - private Func GetMinDistanceCalculatorBy(Direction direction) + public Func GetMinDistanceCalculatorBy(Direction direction) { switch (direction) { diff --git a/cs/TagsCloudVisualization/RectangleStorage.cs b/cs/TagsCloudVisualization/RectangleStorage.cs index 820b2ac9b..a5ea62ad9 100644 --- a/cs/TagsCloudVisualization/RectangleStorage.cs +++ b/cs/TagsCloudVisualization/RectangleStorage.cs @@ -1,4 +1,5 @@ -using System.Drawing; +using System.Collections.Generic; +using System.Drawing; namespace TagsCloudVisualization; diff --git a/cs/TagsCloudVisualization/Tests/RectangleStorageTests.cs b/cs/TagsCloudVisualization/Tests/RectangleStorageTests.cs index d14c1328a..9d0b90954 100644 --- a/cs/TagsCloudVisualization/Tests/RectangleStorageTests.cs +++ b/cs/TagsCloudVisualization/Tests/RectangleStorageTests.cs @@ -11,41 +11,41 @@ namespace TagsCloudVisualization.Tests { public class RectangleStorageTests { - private RectangleStorage structStorage; + private RectangleStorage storage; private Rectangle defaulRectangle; [SetUp] public void SetUp() { - structStorage = new RectangleStorage(); + storage = new RectangleStorage(); defaulRectangle = new(new(2, 2), new(2, 2)); } [Test] public void GetRectangles_ShouldGetAllRectangle() { - structStorage.Add(defaulRectangle); - structStorage.Add(defaulRectangle); + storage.Add(defaulRectangle); + storage.Add(defaulRectangle); - structStorage.GetAll().Should().HaveCount(2); + storage.GetAll().Should().HaveCount(2); } [Test] public void AddRectangle_ShouldGetIdForRectangle() { - var id = structStorage.Add(defaulRectangle); + var id = storage.Add(defaulRectangle); - structStorage.GetById(id).Should().Be(defaulRectangle); + storage.GetById(id).Should().Be(defaulRectangle); } [Test] public void ChangeRectangle_ShouldChangeRectangleByIndex() { - var id = structStorage.Add(defaulRectangle); - var rectangleForChange = structStorage.GetById(id); + var id = storage.Add(defaulRectangle); + var rectangleForChange = storage.GetById(id); rectangleForChange.Size = new Size(1, 1); - structStorage.GetById(id).Size.Should().Be(rectangleForChange.Size); + storage.GetById(id).Size.Should().Be(rectangleForChange.Size); } } } From 051cb3a23dd0797507665ff1115d3aadb5231578 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Sat, 16 Nov 2024 04:38:14 +0500 Subject: [PATCH 16/25] Refactoring: remove dublicate tests --- .../Tests/CircleLayerTests.cs | 157 +++++++----------- 1 file changed, 58 insertions(+), 99 deletions(-) diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs index c9d60a008..346c906ef 100644 --- a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -1,4 +1,5 @@ -using FluentAssertions; +using System.Collections.Generic; +using FluentAssertions; using NUnit.Framework; using System.Drawing; using System.Linq; @@ -43,7 +44,7 @@ public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int in [Test] - public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterToInsertedRectangle() + public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterToInsertedRectangles() { currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); var nextRectangleLocation = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4), defaultRectangleSize); @@ -54,10 +55,10 @@ public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterTo currentLayer.Radius.Should().Be(9); } - private CircleLayer GetLayerAfterFewInsertionsRectangleWithSameSize(CircleLayer layer, int insertionsCount) + private CircleLayer GetLayerAfterFewInsertionsRectangleWithSameSize(CircleLayer layer, int additionsCount) { - layer = GetLayerAfterFewInsertionsRectangle(layer, insertionsCount, - new Size[insertionsCount].Select(x => defaultRectangleSize).ToArray()); + layer = GetLayerAfterFewInsertionsRectangle(layer, additionsCount, + new Size[additionsCount].Select(x => defaultRectangleSize).ToArray()); return layer; } @@ -106,55 +107,35 @@ public void GetPositionWithoutIntersection_ShouldPlaceBottomLeftCornerOnCircle_W CurrentLayerContainsPoint(bottomLeftCorner).Should().BeTrue(); } - [Test] - public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInTopRightSector() + public static IEnumerable SimpleIntersectionInSector { - var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); - var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); - var expected = new Point(9, 1); - - var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); - - newPosition.Should().Be(expected); - } - - - [Test] - public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInBottomRightSector() - { - var rectangleForInsertion = new Rectangle(new Point(8, 9), new Size(5, 1)); - var intersectedRectangle = new Rectangle(new Point(10, 5), new Size(8, 7)); - currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 1); - var expected = new Point(5, 10); - - var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); - - newPosition.Should().Be(expected); - } - - - [Test] - public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInBottomLeftSector() - { - var rectangleForInsertion = new Rectangle(new Point(-3, 9), new Size(5, 3)); - var intersectedRectangle = new Rectangle(new Point(-7, 8), new Size(8, 7)); - currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 2); - var expected = new Point(-5, 5); - - var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); - - newPosition.Should().Be(expected); + get + { + yield return new TestCaseData( + new Rectangle(new Point(5, -1), new Size(5, 1)), + new Rectangle(new Point(8, -6), new Size(8, 7)), + new Point(9, 1), 0).SetName("WhenFoundIntersectionInTopRightSector"); + yield return new TestCaseData( + new Rectangle(new Point(8, 9), new Size(5, 1)), + new Rectangle(new Point(10, 5), new Size(8, 7)), + new Point(5, 10), 1).SetName("WhenFoundIntersectionInBottomRightSector"); + yield return new TestCaseData( + new Rectangle(new Point(-3, 9), new Size(5, 3)), + new Rectangle(new Point(-7, 8), new Size(8, 7)), + new Point(-5, 5), 2).SetName("WhenFoundIntersectionInBottomLeftSector"); + yield return new TestCaseData( + new Rectangle(new(-3, -2), new(4, 3)), + new Rectangle(new(-7, 1), new(8, 7)), + new Point(1, -3), 3).SetName("WhenFoundIntersectionInTopLeftSector"); + } } - [Test] - public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition_WhenFoundIntersectionInTopLeftSector() + [TestCaseSource("SimpleIntersectionInSector")] + public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition(Rectangle forInsertion, Rectangle intersected, Point expected, int additionsCount) { - var rectangleForInsertion = new Rectangle(new (-3, -2), new (4, 3)); - var intersectedRectangle = new Rectangle(new (-7, 1), new (8, 7)); - currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); - var expected = new Point(1, -3); + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, additionsCount); - var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); + var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); newPosition.Should().Be(expected); } @@ -177,23 +158,40 @@ public void CircleLayer_RadiusNextCircleLayer_ShouldBeCeilingToInt() nextLayer.Radius.Should().Be(10); } - [Test] - public void GetPositionWithoutIntersection_ShouldChangeCornerPositionForSector_WhenMoveRectangleClockwise() + public static IEnumerable GetDataForIntersectionTests { - var sizesForInsertions = new Size[] + get { - new (1,1), new(5,8), new (4,4), new (4,4), new(4,4) - }; - var fullLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizesForInsertions.Length, - sizesForInsertions); - var forInsertion = new Rectangle(new (11, 5), new (6,6)); - var intersected = new Rectangle(new(10, 5),new(5, 8)); + yield return new TestCaseData(new Size[] + { new(1, 1), new(5, 8), new(4, 4), new(4, 4), new(4, 4) }, + new Rectangle(new(11, 5), new(6, 6)), + new Rectangle(new(10, 5), new(5, 8)), + new Point(-1, 12)).SetName("ChangeCornerPositionForSector_WhenMoveRectangleClockwise"); + yield return new TestCaseData(new Size[] + { new (1,1), new(1,8), new (4,2), new (4, 9), + new(1,1), new(1,1), new(1,1)}, + new Rectangle(new(-10, 2), new(8, 3)), + new Rectangle(new(-4, -4), new (4, 9)), + new Point(5, -7)).SetName("CreateNewCircle_IfNeedMoveRectangleFromLastSector"); + yield return new TestCaseData(new Size[] + { new (1,1), new(1,8), new (50, 50), new(1,1), new(1,1), new(1,1)}, + new Rectangle(new(4, 10), new(1, 1)), + new Rectangle(new(-50, 10), new(50, 50)), + new Point(-2, 9)).SetName("GetCorrectPosition_WhenRectanglesSidesMatch"); + + } + } + + [TestCaseSource("GetDataForIntersectionTests")] + public void GetPositionWithoutIntersection_Should(Size[] sizes, Rectangle forInsertion, Rectangle intersected, Point expected) + { + var fullLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizes.Length, sizes); var newPosition = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); - newPosition.Should().Be(new Point(-1, 12)); + newPosition.Should().Be(expected); } - + private CircleLayer GetLayerAfterFewInsertionsRectangle(CircleLayer layer, int insertionsCount, Size[] sizes) { for (var i = 1; i <= insertionsCount; i++) @@ -205,44 +203,5 @@ private CircleLayer GetLayerAfterFewInsertionsRectangle(CircleLayer layer, int i return layer; } - - [Test] - public void GetPositionWithoutIntersection_ShouldCreateNewCircle_IfNeedMoveRectangleFromLastSector() - { - var intersectedSize = new Size(4, 9); - var sizesForInsertions = new [] - { - new (1,1), new(1,8), new (4,2), intersectedSize, - new(1,1), new(1,1), new(1,1) - }; - var fullLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizesForInsertions.Length, - sizesForInsertions); - var forInsertion = new Rectangle(new(-10,2), new(8, 3)); - var intersected = new Rectangle(new(-4, -4), intersectedSize); - - var newPosition = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); - - newPosition.Should().Be(new Point(5, -7)); - } - - [Test] - public void GetPositionWithoutIntersection_WhenOneRectangleOnSimmetricalAxis() - { - var intersectedSize = new Size(50, 50); - var sizesForInsertions = new[] - { - new (1,1), new(1,8), intersectedSize, new(1,1), new(1,1), new(1,1) - }; - var fullLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizesForInsertions.Length, - sizesForInsertions); - var forInsertion = new Rectangle(new(4, 10), new(1, 1)); - var intersected = new Rectangle(new(-50, 10), intersectedSize); - - forInsertion.Location = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); - - - forInsertion.IntersectsWith(intersected).Should().BeFalse(); - forInsertion.Location.Should().Be(new Point(-2, 9)); - } } } From 9879db0ea09cad4a43c784715be3666399595918 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Sun, 17 Nov 2024 19:29:52 +0500 Subject: [PATCH 17/25] Refactoring And Fix infinite cicle when find position for new rectangle --- cs/TagsCloudVisualization/CircleLayer.cs | 104 ++++-- .../CircularCloudLayouter.cs | 31 +- .../CircularCloudVisualization.cs | 75 +++- cs/TagsCloudVisualization/README.md | 3 + .../Tests/CircleLayerTests.cs | 338 +++++++++--------- .../Tests/CircularCloudLayouterTests.cs | 23 +- ...CircularCloudLayouterVisualizationTests.cs | 40 +++ 7 files changed, 376 insertions(+), 238 deletions(-) create mode 100644 cs/TagsCloudVisualization/README.md create mode 100644 cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs index 16f76d21f..36e5322a8 100644 --- a/cs/TagsCloudVisualization/CircleLayer.cs +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -18,6 +18,10 @@ public enum Sector public Point Center { get; } public int Radius { get; private set; } + public Sector CurrentSector + { + get => currentSector; + } private Sector currentSector; private readonly RectangleStorage storage; private readonly List layerRectangles = new(); @@ -66,18 +70,18 @@ private void ChangeCurrentLayerBy(CircleLayer next) private int CalculateRadiusForNextLayer() { return layerRectangles - .Select(id => CalculateDistanceBetweenCenterAndRectangle(storage.GetById(id))) + .Select(id => CalculateDistanceBetweenCenterAndRectangleFarCorner(storage.GetById(id))) .Min(); } private List RemoveRectangleInCircle() { return layerRectangles - .Where(id => CalculateDistanceBetweenCenterAndRectangle(storage.GetById(id)) > Radius) + .Where(id => CalculateDistanceBetweenCenterAndRectangleFarCorner(storage.GetById(id)) > Radius) .ToList(); } - private int CalculateDistanceBetweenCenterAndRectangle(Rectangle r) + private int CalculateDistanceBetweenCenterAndRectangleFarCorner(Rectangle r) { var d1 = CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Top)); var d2 = CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Bottom)); @@ -127,13 +131,14 @@ private Point GetStartSectorPointOnCircleBySector(Sector s) public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rectangle intersected) { - var nextPosition = CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); - if (IsNextPositionMoveToAnotherSector(nextPosition, forInsertion.Size)) + if (IsNextPositionMoveToAnotherSector(forInsertion.Location, forInsertion.Size)) { currentSector = GetNextClockwiseSector(); if (ShouldCreateNewCircle()) CreateNextLayerAndChangeCurrentOnNext(); - nextPosition = CalculateTopLeftRectangleCornerPosition(forInsertion.Size); + forInsertion.Location = CalculateTopLeftRectangleCornerPosition(forInsertion.Size); } + var nextPosition = CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); + return nextPosition; } @@ -151,12 +156,21 @@ private bool IsRectangleIntersectSymmetryAxis(Rectangle r) private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangle forInsertion, Rectangle intersected) { + bool isMovingAxisIsX = IsMovingAxisIsXBySector(s); var distanceForMoving = CalculateDistanceForMovingBySector(s, forInsertion, intersected); - var isMovingAxisIsX = IsMovingAxisIsXBySector(s); - var nearestForCenterCorner = - CalculateCornerNearestForCenterAfterMove(s, distanceForMoving, forInsertion); - var distanceForBringBackOnCircle = - CalculateDeltaForBringRectangleBackOnCircle(nearestForCenterCorner, isMovingAxisIsX); + + int distanceForBringBackOnCircle; + if (IsRectangleBetweenSectors(distanceForMoving, forInsertion.Location, isMovingAxisIsX)) + { + distanceForBringBackOnCircle = Radius; + } + else + { + var nearestForCenterCorner = + CalculateCornerNearestForCenterAfterMove(s, distanceForMoving, forInsertion); + distanceForBringBackOnCircle = + CalculateDeltaForBringRectangleBackOnCircle(nearestForCenterCorner, isMovingAxisIsX, forInsertion); + } distanceForMoving *= CalculateMoveMultiplierForMoveClockwise(isMovingAxisIsX, forInsertion); distanceForBringBackOnCircle *= CalculateMoveMultiplierForMoveFromCenter(!isMovingAxisIsX, forInsertion); return isMovingAxisIsX @@ -164,7 +178,15 @@ private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangl : new Point(forInsertion.X + distanceForBringBackOnCircle, forInsertion.Y + distanceForMoving); } - private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCorner, bool isMovingAxisIsX) + private bool IsRectangleBetweenSectors(int distanceForMoving, Point forInsertionLocation, bool isMovingAxisIsX) + { + var distanceToCenter = Math.Abs(isMovingAxisIsX + ? forInsertionLocation.X - Center.X + : forInsertionLocation.Y - Center.Y); + return distanceForMoving > distanceToCenter; + } + + private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCorner, bool isMovingAxisIsX, Rectangle r) { Func getAxisForBringBackOnCircle = isMovingAxisIsX ? p => p.Y : p => p.X; Func getStaticAxis = isMovingAxisIsX ? p => p.X : p => p.Y; @@ -172,8 +194,34 @@ private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCo var distanceOnStaticAxis = Math.Abs(getStaticAxis(nearestForCenterCorner) - getStaticAxis(Center)); var distanceOnAxisForBringBackOnCircle = Math.Abs(getAxisForBringBackOnCircle(nearestForCenterCorner) - getAxisForBringBackOnCircle(Center)); - return (int)Math.Ceiling(Math.Sqrt(Radius * Radius - distanceOnStaticAxis * distanceOnStaticAxis)) - - distanceOnAxisForBringBackOnCircle; + var distanceBetweenCornerAndCenter = CalculateDistanceBetweenPoints(Center, nearestForCenterCorner); + if (distanceBetweenCornerAndCenter > Radius) + { + + return CalculateMoveMultiplierForMoveToCenter(!isMovingAxisIsX, r) + * WhenRectangleOutsideCircle(distanceOnStaticAxis, distanceBetweenCornerAndCenter, + distanceOnAxisForBringBackOnCircle); + } + + return CalculateMoveMultiplierForMoveFromCenter(!isMovingAxisIsX, r) + * WhenRectangleInCircle(distanceOnStaticAxis, distanceOnAxisForBringBackOnCircle); + } + + private int WhenRectangleOutsideCircle(int distanceOnStaticAxis, int distanceBetweenCornerAndCenter, int distanceOnAxisForBringBackOnCircle) + { + var inCircleCathetusPart = Math.Sqrt(Math.Abs(Radius * Radius - distanceOnStaticAxis * distanceOnStaticAxis)); + return CalculatePartCathetus(distanceBetweenCornerAndCenter, inCircleCathetusPart, + distanceOnAxisForBringBackOnCircle); + } + + private int WhenRectangleInCircle(int distanceOnStaticAxis, int distanceOnAxisForBringBackOnCircle) + { + return CalculatePartCathetus(Radius, distanceOnStaticAxis, distanceOnAxisForBringBackOnCircle); + } + + private int CalculatePartCathetus(int hypotenuse, double a, int b) + { + return (int)Math.Ceiling(Math.Sqrt(Math.Abs(hypotenuse * hypotenuse - a * a))) - b; } private Point CalculateCornerNearestForCenterAfterMove(Sector s, int distanceForMoving, Rectangle r) @@ -189,20 +237,28 @@ private Point CalculateCornerNearestForCenterAfterMove(Sector s, int distanceFor private int CalculateMoveMultiplierForMoveFromCenter(bool isAxisForMoveIsX, Rectangle r) { - return isAxisForMoveIsX - ? r.Right <= Center.X ? -1 : 1 - : r.Bottom <= Center.Y - ? -1 - : 1; + if (r.Bottom < Center.Y && r.Left > Center.X) return isAxisForMoveIsX ? 1 : -1; + if (r.Bottom < Center.Y && r.Right < Center.X) return -1; + if (r.Top > Center.Y && r.Left > Center.X) return 1; + if (r.Top > Center.Y && r.Right < Center.X) return isAxisForMoveIsX ? -1 : 1; + return isAxisForMoveIsX ? r.Bottom < Center.Y ? -1 : 1 + : r.Left > Center.X ? 1 : -1; + } + + private int CalculateMoveMultiplierForMoveToCenter(bool isAxisForMoveIsX, Rectangle r) + { + return CalculateMoveMultiplierForMoveFromCenter(isAxisForMoveIsX, r) * -1; } private int CalculateMoveMultiplierForMoveClockwise(bool isAxisForMoveIsX, Rectangle r) { - return isAxisForMoveIsX - ? r.Left > Center.X ? -1 : 1 - : r.Bottom > Center.Y - ? -1 - : 1; + if (r.Bottom < Center.Y && r.Left > Center.X) return 1; + if (r.Bottom < Center.Y && r.Right < Center.X) return isAxisForMoveIsX ? 1 : -1; + if (r.Top > Center.Y && r.Left > Center.X) return isAxisForMoveIsX ? -1 : 1; + if (r.Top > Center.Y && r.Right < Center.X) return -1; + return isAxisForMoveIsX ? r.Bottom < Center.Y ? 1 : -1 + : r.Left > Center.X ? -1 : 1; + } private int CalculateDistanceForMovingBySector(Sector s, Rectangle forInsertion, Rectangle intersected) diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index d2122036d..1a5449582 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -61,10 +61,10 @@ public Rectangle PutRectangleOnCircleWithoutIntersection(int id) { var r = storage.GetById(id); var intersected = GetRectangleIntersection(r); - while (intersected != new Rectangle()) + + while (RectangleHasIntersection(intersected)) { - var possiblePosition = - CurrentLayer.GetRectanglePositionWithoutIntersection(r, intersected.Value); + var possiblePosition = CurrentLayer.GetRectanglePositionWithoutIntersection(r, intersected.Value); r.Location = possiblePosition; intersected = GetRectangleIntersection(r); } @@ -73,6 +73,12 @@ public Rectangle PutRectangleOnCircleWithoutIntersection(int id) return r; } + private bool RectangleHasIntersection(Rectangle? intersected) + { + return intersected != default && intersected.Value != default; + } + + public Rectangle TryMoveRectangleCloserToCenter(int id) { var rectangleForMoving = storage.GetById(id); @@ -83,7 +89,7 @@ public Rectangle TryMoveRectangleCloserToCenter(int id) .Where(tuple => tuple.Nearest != null) .Select(t => ( DistanceCalculator: nearestFinder.GetMinDistanceCalculatorBy(t.Direction), t.Nearest, t.Direction)) - .Select(t => (Distance: t.DistanceCalculator((Rectangle)t.Nearest, rectangleForMoving), t.Direction)) + .Select(t => (Distance: t.DistanceCalculator(t.Nearest.Value, rectangleForMoving), t.Direction)) .ToArray(); rectangleForMoving.Location = MoveByDirections(rectangleForMoving.Location, distancesForMove); return rectangleForMoving; @@ -126,18 +132,6 @@ private void ValidateRectangleSize(Size s) .FirstOrDefault(r => forInsertion.IntersectsWith(r) && forInsertion != r); } - private Rectangle?[] GetNearestByAllDirectionsFor(Rectangle r) - { - var rectangles = storage.GetAll(); - return new[] - { - nearestFinder.FindNearestByDirection(r, Direction.Bottom, rectangles), - nearestFinder.FindNearestByDirection(r, Direction.Top, rectangles), - nearestFinder.FindNearestByDirection(r, Direction.Left, rectangles), - nearestFinder.FindNearestByDirection(r, Direction.Right, rectangles) - }; - } - private void CreateFirstLayer(Size firstRectangle) { var radius = Math.Ceiling(Math.Sqrt(firstRectangle.Width * firstRectangle.Width + @@ -157,9 +151,4 @@ private bool IsFirstRectangle() { return storage.GetAll().FirstOrDefault() == default; } - - public IEnumerable GetRectangles() - { - foreach (var rectangle in storage.GetAll()) yield return rectangle; - } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CircularCloudVisualization.cs b/cs/TagsCloudVisualization/CircularCloudVisualization.cs index 3a92636e4..12ba286a9 100644 --- a/cs/TagsCloudVisualization/CircularCloudVisualization.cs +++ b/cs/TagsCloudVisualization/CircularCloudVisualization.cs @@ -6,36 +6,95 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using static TagsCloudVisualization.CircleLayer; namespace TagsCloudVisualization { public class CircularCloudVisualization { + private readonly Color BACKGROUND_COLOR = Color.White; + private readonly Color RECTANGLE_COLOR = Color.DarkBlue; + private readonly Size ImageSize; private RectangleStorage rectangleStorage; - private const string FILE_PATH = "./Images/"; - public CircularCloudVisualization(RectangleStorage rectangles) + public CircularCloudVisualization(RectangleStorage rectangles, Size size) { rectangleStorage = rectangles; + ImageSize = size; } public void CreateImage() { - var fileName = Path.Combine(Path.GetTempPath(), "testImage1010.png"); - using (var image = new Bitmap(1000, 1000)) + var filePath = Path.Combine(Path.GetTempPath(), "testImage1010.png"); + using (var image = new Bitmap(ImageSize.Width, ImageSize.Height)) { using (Graphics graphics = Graphics.FromImage(image)) { - Pen pen = new Pen(Color.Black); - graphics.DrawRectangles(pen, rectangleStorage.GetAll().ToArray()); + graphics.Clear(BACKGROUND_COLOR); + DrawGrid(graphics); + Pen pen = new Pen(RECTANGLE_COLOR); + var rectangles = rectangleStorage.GetAll(); + graphics.DrawRectangle(new Pen(Color.Brown),rectangles.First()); + graphics.DrawRectangles(pen, rectangles.Skip(1).ToArray()); } - image.Save(fileName, ImageFormat.Png); + image.Save(filePath, ImageFormat.Png); } } - private void CreateFile(string filePath) + private void DrawGrid(Graphics g, int cellsCount = 100, int cellSize = 10) { + Pen p = new Pen(Color.DarkGray); + for (int y = 0; y < cellsCount; ++y) + { + g.DrawLine(p, 0, y * cellSize, cellsCount * cellSize, y * cellSize); + } + + for (int x = 0; x < cellsCount; ++x) + { + g.DrawLine(p, x * cellSize, 0, x * cellSize, cellsCount * cellSize); + } + } + + public void CreateImageWithSaveEveryStep(CircularCloudLayouter layouter, Size[] sizes) + { + var startName = "testImageStep"; + var extension = ".png"; + using (var image = new Bitmap(ImageSize.Width, ImageSize.Height)) + { + using (Graphics graphics = Graphics.FromImage(image)) + { + graphics.Clear(BACKGROUND_COLOR); + DrawGrid(graphics); + Pen pen = new Pen(RECTANGLE_COLOR); + for (var i = 0; i < sizes.Length; i++) + { + var r = layouter.PutNextRectangle(sizes[i]); + var currentFileName = $"{startName}{i}{extension}"; + graphics.DrawRectangle(new Pen(GetColorBySector(layouter.CurrentLayer.CurrentSector)), r); + var filePath = Path.Combine(@"C:\Users\Resh\Desktop\ShporaHomeworks\TagCloud1\tdd\cs\TagsCloudVisualization\Images", currentFileName); + image.Save(filePath, ImageFormat.Png); + } + } + + } + } + + private Color GetColorBySector(CircleLayer.Sector s) + { + switch (s) + { + case Sector.Top_Right: + return Color.Chartreuse; + case Sector.Bottom_Right: + return Color.Brown; + case Sector.Bottom_Left: + return Color.DeepPink; + default: + return Color.DodgerBlue; + } } } } diff --git a/cs/TagsCloudVisualization/README.md b/cs/TagsCloudVisualization/README.md new file mode 100644 index 000000000..958981835 --- /dev/null +++ b/cs/TagsCloudVisualization/README.md @@ -0,0 +1,3 @@ +![Alt text](./Images/FIRSTa.png?raw=true "First") +![Alt text](./Images/sECOND.png?raw=true "Second") +![Alt text](./Images/third.png?raw=true "Third") diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs index 346c906ef..53fdac821 100644 --- a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -1,207 +1,215 @@ using System.Collections.Generic; -using FluentAssertions; -using NUnit.Framework; using System.Drawing; using System.Linq; +using FluentAssertions; +using NUnit.Framework; using static TagsCloudVisualization.CircleLayer; -namespace TagsCloudVisualization.Tests +namespace TagsCloudVisualization.Tests; + +public class CircleLayerTests { - public class CircleLayerTests + private CircleLayer currentLayer; + private Size defaultRectangleSize; + private RectangleStorage storage; + + public static IEnumerable SimpleIntersectionInSector { - private CircleLayer currentLayer; - private Size defaultRectangleSize; - private RectangleStorage storage; - [SetUp] - public void SetUp() - { - var startRadius = 5; - var center = new Point(5, 5); - storage = new RectangleStorage(); - currentLayer = new CircleLayer(center, startRadius, storage); - defaultRectangleSize = new Size(3, 4); + get + { + yield return new TestCaseData( + new Rectangle(new Point(5, -1), new Size(5, 1)), + new Rectangle(new Point(8, -6), new Size(8, 7)), + new Point(9, 1), 0).SetName("WhenFoundIntersectionInTopRightSector"); + yield return new TestCaseData( + new Rectangle(new Point(8, 9), new Size(5, 1)), + new Rectangle(new Point(10, 5), new Size(8, 7)), + new Point(5, 10), 1).SetName("WhenFoundIntersectionInBottomRightSector"); + yield return new TestCaseData( + new Rectangle(new Point(-3, 9), new Size(5, 3)), + new Rectangle(new Point(-7, 8), new Size(8, 7)), + new Point(-1, 5), 2).SetName("WhenFoundIntersectionInBottomLeftSector"); + yield return new TestCaseData( + new Rectangle(new Point(-3, -2), new Size(4, 3)), + new Rectangle(new Point(-7, 1), new Size(8, 7)), + new Point(1, -1), 3).SetName("WhenFoundIntersectionInTopLeftSector"); } + } - [Test] - public void CircleLayer_InsertFirstForLayerRectangle_InTopRightSectorStart() - { - var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); - - possibleRectangleLocation.Should().Be(GetCorrectRectangleLocationByExpectedSector(Sector.Top_Right, defaultRectangleSize)); + public static IEnumerable GetDataForIntersectionTests + { + get + { + yield return new TestCaseData(new Size[] + { new(1, 1), new(5, 8), new(4, 4), new(4, 4), new(4, 4) }, + new Rectangle(new Point(11, 5), new Size(6, 6)), + new Rectangle(new Point(10, 5), new Size(5, 8)), + new Point(4, 12)).SetName("ChangeCornerPositionForSector_WhenMoveRectangleClockwise"); + yield return new TestCaseData(new Size[] + { new(1, 1), new(1, 8), new(50, 50), new(1, 1), new(1, 1), new(1, 1) }, + new Rectangle(new Point(4, 10), new Size(1, 1)), + new Rectangle(new Point(-50, 10), new Size(50, 50)), + new Point(8, 11)).SetName("GetCorrectPosition_WhenRectanglesSidesMatch"); + yield return new TestCaseData(new Size[] + { new(6, 3), new(4, 2), new(1, 1), new(4, 4) }, + new Rectangle(new Point(5, -7), new Size(6, 5)), + new Rectangle(new Point(5, -3), new Size(6, 3)), + new Point(12, 0)).SetName("NotChangeSector_WhenRectangleForIntersectionBottomEqualCenterY_AfterMove"); ; } + } - [TestCase(1, Sector.Bottom_Right)] - [TestCase(2, Sector.Bottom_Left)] - [TestCase(3, Sector.Top_Left)] - public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int insertionsCount, Sector expected) - { - currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, insertionsCount); + [SetUp] + public void SetUp() + { + var startRadius = 5; + var center = new Point(5, 5); + storage = new RectangleStorage(); + currentLayer = new CircleLayer(center, startRadius, storage); + defaultRectangleSize = new Size(3, 4); + } - var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); + [Test] + public void CircleLayer_InsertFirstForLayerRectangle_InTopRightSectorStart() + { + var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); - possibleRectangleLocation.Should().Be(GetCorrectRectangleLocationByExpectedSector(expected, defaultRectangleSize)); - } - + possibleRectangleLocation.Should() + .Be(GetCorrectRectangleLocationByExpectedSector(Sector.Top_Right, defaultRectangleSize)); + } - [Test] - public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterToInsertedRectangles() - { - currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); - var nextRectangleLocation = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4), defaultRectangleSize); - var insertedRectangleId = storage.Add(new Rectangle(nextRectangleLocation, new Size(2, 2))); + [TestCase(1, Sector.Bottom_Right)] + [TestCase(2, Sector.Bottom_Left)] + [TestCase(3, Sector.Top_Left)] + [TestCase(4, Sector.Top_Right)] + [TestCase(0, Sector.Top_Right)] + public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int insertionsCount, Sector expected) + { + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, insertionsCount); - currentLayer.OnSuccessInsertRectangle(insertedRectangleId); + var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); - currentLayer.Radius.Should().Be(9); - } + possibleRectangleLocation.Should() + .Be(GetCorrectRectangleLocationByExpectedSector(expected, defaultRectangleSize)); + } - private CircleLayer GetLayerAfterFewInsertionsRectangleWithSameSize(CircleLayer layer, int additionsCount) - { - layer = GetLayerAfterFewInsertionsRectangle(layer, additionsCount, - new Size[additionsCount].Select(x => defaultRectangleSize).ToArray()); - return layer; - } - private Sector GetSectorByInsertionsCount(int count) - { - return (Sector)((count - 1) % 4); - } + [Test] + public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterToInsertedRectangles() + { + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); + var nextRectangleLocation = + GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4), defaultRectangleSize); + var insertedRectangleId = storage.Add(new Rectangle(nextRectangleLocation, new Size(2, 2))); - private Point GetCorrectRectangleLocationByExpectedSector(Sector s, Size size) - { - switch (s) - { - case Sector.Top_Right: - return new Point(currentLayer.Center.X, currentLayer.Center.Y - currentLayer.Radius - size.Height); - case Sector.Bottom_Right: - return new Point(currentLayer.Center.X + currentLayer.Radius, currentLayer.Center.Y); - case Sector.Bottom_Left: - return new Point(currentLayer.Center.X - size.Width, currentLayer.Center.Y + currentLayer.Radius); - default: - return new Point(currentLayer.Center.X - currentLayer.Radius - size.Width, - currentLayer.Center.Y - size.Height); - } - } + currentLayer.OnSuccessInsertRectangle(insertedRectangleId); - [Test] - public void CircleLayer_RectangleWithNewPositionAfterIntersection_ShouldNotIntersectSameRectangle() - { - var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); - var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); + currentLayer.Radius.Should().Be(9); + } + + private CircleLayer GetLayerAfterFewInsertionsRectangleWithSameSize(CircleLayer layer, int additionsCount) + { + layer = GetLayerAfterFewInsertionsRectangle(layer, additionsCount, + new Size[additionsCount].Select(x => defaultRectangleSize).ToArray()); + return layer; + } - var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); + private Sector GetSectorByInsertionsCount(int count) + { + return (Sector)((count - 1) % 4); + } - new Rectangle(newPosition, rectangleForInsertion.Size).IntersectsWith(intersectedRectangle).Should() - .BeFalse(); + private Point GetCorrectRectangleLocationByExpectedSector(Sector s, Size size) + { + switch (s) + { + case Sector.Top_Right: + return new Point(currentLayer.Center.X, currentLayer.Center.Y - currentLayer.Radius - size.Height); + case Sector.Bottom_Right: + return new Point(currentLayer.Center.X + currentLayer.Radius, currentLayer.Center.Y); + case Sector.Bottom_Left: + return new Point(currentLayer.Center.X - size.Width, currentLayer.Center.Y + currentLayer.Radius); + default: + return new Point(currentLayer.Center.X - currentLayer.Radius - size.Width, + currentLayer.Center.Y - size.Height); } + } - [Test] - public void GetPositionWithoutIntersection_ShouldPlaceBottomLeftCornerOnCircle_WhenFoundIntersectionInTopRightSector() - { - var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); - var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); + [Test] + public void CircleLayer_RectangleWithNewPositionAfterIntersection_ShouldNotIntersectSameRectangle() + { + var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); + var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); - var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); - var bottomLeftCorner = new Point(newPosition.X, newPosition.Y + intersectedRectangle.Height); + var newPosition = + currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); - CurrentLayerContainsPoint(bottomLeftCorner).Should().BeTrue(); - } + new Rectangle(newPosition, rectangleForInsertion.Size).IntersectsWith(intersectedRectangle).Should() + .BeFalse(); + } - public static IEnumerable SimpleIntersectionInSector - { - get - { - yield return new TestCaseData( - new Rectangle(new Point(5, -1), new Size(5, 1)), - new Rectangle(new Point(8, -6), new Size(8, 7)), - new Point(9, 1), 0).SetName("WhenFoundIntersectionInTopRightSector"); - yield return new TestCaseData( - new Rectangle(new Point(8, 9), new Size(5, 1)), - new Rectangle(new Point(10, 5), new Size(8, 7)), - new Point(5, 10), 1).SetName("WhenFoundIntersectionInBottomRightSector"); - yield return new TestCaseData( - new Rectangle(new Point(-3, 9), new Size(5, 3)), - new Rectangle(new Point(-7, 8), new Size(8, 7)), - new Point(-5, 5), 2).SetName("WhenFoundIntersectionInBottomLeftSector"); - yield return new TestCaseData( - new Rectangle(new(-3, -2), new(4, 3)), - new Rectangle(new(-7, 1), new(8, 7)), - new Point(1, -3), 3).SetName("WhenFoundIntersectionInTopLeftSector"); - } - } + [Test] + public void + GetPositionWithoutIntersection_ShouldPlaceBottomLeftCornerOnCircle_WhenFoundIntersectionInTopRightSector() + { + var rectangleForInsertion = new Rectangle(new Point(5, -1), new Size(5, 1)); + var intersectedRectangle = new Rectangle(new Point(8, -6), new Size(8, 7)); - [TestCaseSource("SimpleIntersectionInSector")] - public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition(Rectangle forInsertion, Rectangle intersected, Point expected, int additionsCount) - { - currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, additionsCount); + var newPosition = + currentLayer.GetRectanglePositionWithoutIntersection(rectangleForInsertion, intersectedRectangle); + var bottomLeftCorner = new Point(newPosition.X, newPosition.Y + intersectedRectangle.Height); - var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); + CurrentLayerContainsPoint(bottomLeftCorner).Should().BeTrue(); + } - newPosition.Should().Be(expected); - } + [TestCaseSource(nameof(SimpleIntersectionInSector))] + public void GetPositionWithoutIntersection_ReturnCorrectRectanglePosition(Rectangle forInsertion, + Rectangle intersected, Point expected, int additionsCount) + { + currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, additionsCount); - private bool CurrentLayerContainsPoint(Point p) - { - return (p.X - currentLayer.Center.X) * (p.X - currentLayer.Center.X) + - (p.Y - currentLayer.Center.Y) * (p.Y - currentLayer.Center.Y) == currentLayer.Radius * currentLayer.Radius; - } + var newPosition = currentLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); - [Test] - public void CircleLayer_RadiusNextCircleLayer_ShouldBeCeilingToInt() - { - var sizes = new Size[] - { - new (8,1), new(7,8), new (4,4), new (4,4), new(4,4) - }; - var nextLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizes.Length, sizes); + newPosition.Should().Be(expected); + } - nextLayer.Radius.Should().Be(10); - } + private bool CurrentLayerContainsPoint(Point p) + { + return (p.X - currentLayer.Center.X) * (p.X - currentLayer.Center.X) + + (p.Y - currentLayer.Center.Y) * (p.Y - currentLayer.Center.Y) == currentLayer.Radius * currentLayer.Radius; + } - public static IEnumerable GetDataForIntersectionTests + [Test] + public void CircleLayer_RadiusNextCircleLayer_ShouldBeCeilingToInt() + { + var sizes = new Size[] { - get - { - yield return new TestCaseData(new Size[] - { new(1, 1), new(5, 8), new(4, 4), new(4, 4), new(4, 4) }, - new Rectangle(new(11, 5), new(6, 6)), - new Rectangle(new(10, 5), new(5, 8)), - new Point(-1, 12)).SetName("ChangeCornerPositionForSector_WhenMoveRectangleClockwise"); - yield return new TestCaseData(new Size[] - { new (1,1), new(1,8), new (4,2), new (4, 9), - new(1,1), new(1,1), new(1,1)}, - new Rectangle(new(-10, 2), new(8, 3)), - new Rectangle(new(-4, -4), new (4, 9)), - new Point(5, -7)).SetName("CreateNewCircle_IfNeedMoveRectangleFromLastSector"); - yield return new TestCaseData(new Size[] - { new (1,1), new(1,8), new (50, 50), new(1,1), new(1,1), new(1,1)}, - new Rectangle(new(4, 10), new(1, 1)), - new Rectangle(new(-50, 10), new(50, 50)), - new Point(-2, 9)).SetName("GetCorrectPosition_WhenRectanglesSidesMatch"); - - } - } + new(8, 1), new(7, 8), new(4, 4), new(4, 4), new(4, 4) + }; + var nextLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizes.Length, sizes); - [TestCaseSource("GetDataForIntersectionTests")] - public void GetPositionWithoutIntersection_Should(Size[] sizes, Rectangle forInsertion, Rectangle intersected, Point expected) - { - var fullLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizes.Length, sizes); + nextLayer.Radius.Should().Be(10); + } - var newPosition = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); + [TestCaseSource(nameof(GetDataForIntersectionTests))] + public void GetPositionWithoutIntersection_Should(Size[] sizes, Rectangle forInsertion, Rectangle intersected, + Point expected) + { + var fullLayer = GetLayerAfterFewInsertionsRectangle(currentLayer, sizes.Length, sizes); - newPosition.Should().Be(expected); - } - - private CircleLayer GetLayerAfterFewInsertionsRectangle(CircleLayer layer, int insertionsCount, Size[] sizes) + var newPosition = fullLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected); + + newPosition.Should().Be(expected); + } + + private CircleLayer GetLayerAfterFewInsertionsRectangle(CircleLayer layer, int insertionsCount, Size[] sizes) + { + for (var i = 1; i <= insertionsCount; i++) { - for (var i = 1; i <= insertionsCount; i++) - { - var location = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(i), sizes[i - 1]); - var rectangleForInsert = new Rectangle(location, sizes[i - 1]); - layer.OnSuccessInsertRectangle(storage.Add(rectangleForInsert)); - } - return layer; + var location = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(i), sizes[i - 1]); + var rectangleForInsert = new Rectangle(location, sizes[i - 1]); + layer.OnSuccessInsertRectangle(storage.Add(rectangleForInsert)); } + return layer; } -} +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs index e8307e141..5deee02fc 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -7,7 +7,6 @@ namespace TagsCloudVisualization.Tests { public class CircularCloudLayouterTests { - private CircularCloudLayouter layouter; private Point defaultCenter; @@ -33,13 +32,6 @@ public void Insert_ShouldThrow(int width, int height) act.Should().Throw(); } - [Test] - public void GetRectangles_ShouldBeEmpty_BeforePutAnyRectangles() - { - layouter.GetRectangles() - .Should().BeEmpty(); - } - [Test] public void PutNextRectangle_ShouldAddRectangleToCenter_WhenRectangleFirst() { @@ -57,6 +49,8 @@ public void PutNextRectangle_ShouldCreateFirstCircleLayer_AfterPutFirstRectangle { var firstRectangleSize = new Size(6, 4); + layouter.CurrentLayer.Should().BeNull(); + layouter.PutNextRectangle(firstRectangleSize); layouter.CurrentLayer.Should().NotBeNull(); @@ -75,17 +69,6 @@ public void PutNextRectangle_ShouldCreateFirstCircleLayer_WithRadiusEqualHalfDia layouter.CurrentLayer.Radius.Should().Be(expected); } - [Test] - public void PutNextRectangle_ShouldAddRectangleToLayouter_AfterPut() - { - var firstRectangleSize = new Size(4, 4); - - layouter.PutNextRectangle(firstRectangleSize); - - layouter.GetRectangles() - .Should().NotBeEmpty().And.HaveCount(1); - } - [Test] public void PutRectangleOnCircleWithoutIntersection_ShouldUseCircleLayer_ForChoosePositionForRectangle() { @@ -104,7 +87,7 @@ public void PutRectangleOnCircleWithoutIntersection_ShouldUseCircleLayer_ForChoo } [Test] - public void PutRectangleOnCircleWithoutIntersection_ShouldPutRectangleWithoutIntersection_WhenNeedOneMoveForDeleteIntersection() + public void PutRectangleOnCircleWithoutIntersection_ShouldPutRectangleWithoutIntersection() { var firstRectangleSize = new Size(6, 4); var expected = new Point(14, 1); diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs new file mode 100644 index 000000000..1930633a2 --- /dev/null +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs @@ -0,0 +1,40 @@ +using System; +using System.Drawing; +using System.Linq; +using NUnit.Framework; + +namespace TagsCloudVisualization.Tests; + +public class CircularCloudLayouterVisualizationTests +{ + private readonly Size ImageSize = new(1000, 1000); + + [Test] + public void GenerateImage() + { + var center = new Point(ImageSize.Width / 2, ImageSize.Height / 2); + var visualizator = new CircularCloudVisualization(GenerateRectangles(center), ImageSize); + visualizator.CreateImage(); + } + + private RectangleStorage GenerateRectangles(Point center) + { + var rnd = new Random(); + var storage = new RectangleStorage(); + var layouter = new CircularCloudLayouter(center, storage); + for (var i = 0; i < 41; i++) layouter.PutNextRectangle(new Size(rnd.Next(50, 100), rnd.Next(50, 100))); + + return storage; + } + + [Test] + public void GenerateImageWithSaveEveryStep() + { + var rnd = new Random(); + var center = new Point(ImageSize.Width / 2, ImageSize.Height / 2); + var visualizator = new CircularCloudVisualization(new RectangleStorage(), ImageSize); + var layouter = new CircularCloudLayouter(center, new RectangleStorage()); + visualizator.CreateImageWithSaveEveryStep(layouter, + new Size[41].Select(x => new Size(15, 100)).ToArray()); + } +} \ No newline at end of file From badf496bc6d44f7565d6d8af7deb599e455b66f0 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Sun, 17 Nov 2024 19:31:05 +0500 Subject: [PATCH 18/25] Add results for random generated data --- cs/TagsCloudVisualization/Images/FIRSTa.png | Bin 0 -> 31431 bytes cs/TagsCloudVisualization/Images/sECOND.png | Bin 0 -> 38559 bytes cs/TagsCloudVisualization/Images/third.png | Bin 0 -> 46775 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 cs/TagsCloudVisualization/Images/FIRSTa.png create mode 100644 cs/TagsCloudVisualization/Images/sECOND.png create mode 100644 cs/TagsCloudVisualization/Images/third.png diff --git a/cs/TagsCloudVisualization/Images/FIRSTa.png b/cs/TagsCloudVisualization/Images/FIRSTa.png new file mode 100644 index 0000000000000000000000000000000000000000..774375a9637076c23f8ba0348f64167e133bc5c0 GIT binary patch literal 31431 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 TxyDiO-y8mm<_lh1@!tOf@j>Uh literal 0 HcmV?d00001 From 23112ec423e96054e469157ae5217cdb989c1dc2 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Sun, 17 Nov 2024 22:31:20 +0500 Subject: [PATCH 19/25] fix filepath --- cs/TagsCloudVisualization/CircularCloudVisualization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cs/TagsCloudVisualization/CircularCloudVisualization.cs b/cs/TagsCloudVisualization/CircularCloudVisualization.cs index 12ba286a9..d8ee7a020 100644 --- a/cs/TagsCloudVisualization/CircularCloudVisualization.cs +++ b/cs/TagsCloudVisualization/CircularCloudVisualization.cs @@ -74,7 +74,7 @@ public void CreateImageWithSaveEveryStep(CircularCloudLayouter layouter, Size[] var r = layouter.PutNextRectangle(sizes[i]); var currentFileName = $"{startName}{i}{extension}"; graphics.DrawRectangle(new Pen(GetColorBySector(layouter.CurrentLayer.CurrentSector)), r); - var filePath = Path.Combine(@"C:\Users\Resh\Desktop\ShporaHomeworks\TagCloud1\tdd\cs\TagsCloudVisualization\Images", currentFileName); + var filePath = Path.Combine(Path.GetTempPath(), currentFileName); image.Save(filePath, ImageFormat.Png); } } From 80508bb09e6c38dfec26fd483d89fc5e1e22a626 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Sun, 17 Nov 2024 23:44:36 +0500 Subject: [PATCH 20/25] Add third task --- .../CircularCloudVisualization.cs | 27 +++++++++++++++---- .../Tests/CircularCloudLayouterTests.cs | 27 ++++++++++++++++--- ...CircularCloudLayouterVisualizationTests.cs | 1 + 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/cs/TagsCloudVisualization/CircularCloudVisualization.cs b/cs/TagsCloudVisualization/CircularCloudVisualization.cs index d8ee7a020..3a9c3b908 100644 --- a/cs/TagsCloudVisualization/CircularCloudVisualization.cs +++ b/cs/TagsCloudVisualization/CircularCloudVisualization.cs @@ -18,16 +18,18 @@ public class CircularCloudVisualization private readonly Color RECTANGLE_COLOR = Color.DarkBlue; private readonly Size ImageSize; private RectangleStorage rectangleStorage; + private int sizeFactor; - public CircularCloudVisualization(RectangleStorage rectangles, Size size) + public CircularCloudVisualization(RectangleStorage rectangles, Size imageSize) { rectangleStorage = rectangles; - ImageSize = size; + ImageSize = imageSize; + this.sizeFactor = sizeFactor; } - public void CreateImage() + public void CreateImage(string file = null) { - var filePath = Path.Combine(Path.GetTempPath(), "testImage1010.png"); + var filePath = file ?? Path.Combine(Path.GetTempPath(), "testImage1010.png"); using (var image = new Bitmap(ImageSize.Width, ImageSize.Height)) { using (Graphics graphics = Graphics.FromImage(image)) @@ -35,7 +37,7 @@ public void CreateImage() graphics.Clear(BACKGROUND_COLOR); DrawGrid(graphics); Pen pen = new Pen(RECTANGLE_COLOR); - var rectangles = rectangleStorage.GetAll(); + var rectangles = NormalizeSizes(rectangleStorage.GetAll()); graphics.DrawRectangle(new Pen(Color.Brown),rectangles.First()); graphics.DrawRectangles(pen, rectangles.Skip(1).ToArray()); } @@ -96,5 +98,20 @@ private Color GetColorBySector(CircleLayer.Sector s) return Color.DodgerBlue; } } + + private Rectangle[] NormalizeSizes(IEnumerable source) + { + double XLength = source.Max(r => r.Right) - source.Min(r => r.Left); + double YLength = source.Max(r => r.Bottom) - source.Min(r => r.Top); + var boundShift = 10; + var factorX = ImageSize.Width > XLength + ? (int)Math.Floor((ImageSize.Width - boundShift) / XLength) + : 1; + var factorY = ImageSize.Height> YLength + ? (int)Math.Floor((ImageSize.Height - boundShift) / YLength) + : 1; + return source.Select(r => new Rectangle(new Point(r.X * factorX,r.Y * factorY), + new Size(r.Width * factorX, r.Height * factorY))).ToArray(); + } } } diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs index 5deee02fc..0a8d66f17 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -1,7 +1,11 @@ using System; using System.Drawing; +using System.Linq; using NUnit.Framework; using FluentAssertions; +using NUnit.Framework.Interfaces; +using System.IO; +using System.Reflection; namespace TagsCloudVisualization.Tests { @@ -9,12 +13,31 @@ public class CircularCloudLayouterTests { private CircularCloudLayouter layouter; private Point defaultCenter; + private RectangleStorage storage; [SetUp] public void SetUp() { defaultCenter = new Point(5, 5); - layouter = new CircularCloudLayouter(defaultCenter); + storage = new RectangleStorage(); + layouter = new CircularCloudLayouter(defaultCenter, storage); + } + + [TearDown] + public void TearDown() + { + if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) + { + var testObj = TestContext.CurrentContext.Test.Parent.Fixture as CircularCloudLayouterTests; + var info = typeof(CircularCloudLayouterTests) + .GetField("storage", BindingFlags.NonPublic | BindingFlags.Instance); + var st = info.GetValue(testObj); + + var visualizator = new CircularCloudVisualization(st as RectangleStorage, new Size(1000, 1000)); + var pathFile = Path.Combine(Directory.GetCurrentDirectory(), TestContext.CurrentContext.Test.Name); + visualizator.CreateImage(pathFile); + TestContext.Out.WriteLine($"Tag cloud visualization saved to file {pathFile}"); + } } [TestCase(0, 4, TestName = "WhenWidthZero")] @@ -74,7 +97,6 @@ public void PutRectangleOnCircleWithoutIntersection_ShouldUseCircleLayer_ForChoo { var firstRectangleSize = new Size(4, 4); var expectedRadius = 7; - var storage = new RectangleStorage(); layouter = new CircularCloudLayouter(defaultCenter, storage); var expected = new Point(defaultCenter.X, defaultCenter.Y - expectedRadius); layouter.PutNextRectangle(firstRectangleSize); @@ -91,7 +113,6 @@ public void PutRectangleOnCircleWithoutIntersection_ShouldPutRectangleWithoutInt { var firstRectangleSize = new Size(6, 4); var expected = new Point(14, 1); - var storage = new RectangleStorage(); layouter = new CircularCloudLayouter(defaultCenter, storage); var sizes = new Size[] { new(4, 7), new(4, 4), new(4, 4), new(4, 4)}; layouter.PutNextRectangle(firstRectangleSize); diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs index 1930633a2..7c8c0325c 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs @@ -1,5 +1,6 @@ using System; using System.Drawing; +using System.IO; using System.Linq; using NUnit.Framework; From 3eb403a025ca942a9adf25b3167de52eb02d2372 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Wed, 20 Nov 2024 22:25:08 +0500 Subject: [PATCH 21/25] Fix visualizer and add .csproj --- .../CircularCloudVisualizer.cs | 75 +++++++++++++++++++ .../TagsCloudVisualization.csproj | 23 ++++++ 2 files changed, 98 insertions(+) create mode 100644 cs/TagsCloudVisualization/CircularCloudVisualizer.cs create mode 100644 cs/TagsCloudVisualization/TagsCloudVisualization.csproj diff --git a/cs/TagsCloudVisualization/CircularCloudVisualizer.cs b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs new file mode 100644 index 000000000..66b9731c4 --- /dev/null +++ b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +namespace TagsCloudVisualization +{ + public class CircularCloudVisualizer + { + private readonly Color backgroundColor = Color.White; + private readonly Color rectangleColor = Color.DarkBlue; + private readonly Size imageSize; + private readonly RectangleStorage rectangleStorage; + + public CircularCloudVisualizer(RectangleStorage rectangles, Size imageSize) + { + rectangleStorage = rectangles; + this.imageSize = imageSize; + } + + public void CreateImage(string? filePath = null, bool withSaveSteps = false) + { + var rectangles = NormalizeSizes(rectangleStorage.GetAll()); + + using var image = new Bitmap(imageSize.Width, imageSize.Height); + using var graphics = Graphics.FromImage(image); + graphics.Clear(backgroundColor); + var pen = new Pen(rectangleColor); + + for (var i = 0; i < rectangles.Length; i++) + { + var nextRectangle = rectangles[i]; + graphics.DrawRectangle(pen, nextRectangle); + if (withSaveSteps) + { + SaveImage(image, $"{filePath}Step{1}"); + } + } + SaveImage(image, filePath); + } + + private void SaveImage(Bitmap image, string? filePath = null) + { + var rnd = new Random(); + filePath = filePath ?? Path.Combine(Path.GetTempPath(), $"testImage{rnd.Next()}.png"); + image.Save(filePath, ImageFormat.Png); + } + + private Rectangle[] NormalizeSizes(IEnumerable source) + { + var sourceToArray = source.ToArray(); + + var XLength = sourceToArray.Max(r => r.Right) - sourceToArray.Min(r => r.Left); + var YLength = sourceToArray.Max(r => r.Bottom) - sourceToArray.Min(r => r.Top); + + var factorX = GetNormalizeFactorByAxis(imageSize.Width, XLength); + var factorY = GetNormalizeFactorByAxis(imageSize.Height, YLength); + + return sourceToArray.Select(r => new Rectangle( + new Point(r.X * factorX, r.Y * factorY), + new Size(r.Width * factorX, r.Height * factorY))) + .ToArray(); + } + + private int GetNormalizeFactorByAxis(int imageSizeOnAxis, int rectanglesSizeOnAxis) + { + const int boundShift = 10; + double imageSizeOnAxisWithShiftForBounds = imageSizeOnAxis - boundShift; + var stretchFactor = (int)Math.Floor(imageSizeOnAxisWithShiftForBounds / rectanglesSizeOnAxis); + return imageSizeOnAxisWithShiftForBounds > rectanglesSizeOnAxis ? stretchFactor : 1; + } + } +} diff --git a/cs/TagsCloudVisualization/TagsCloudVisualization.csproj b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj new file mode 100644 index 000000000..1a44a26a4 --- /dev/null +++ b/cs/TagsCloudVisualization/TagsCloudVisualization.csproj @@ -0,0 +1,23 @@ + + + + Exe + net8.0 + disable + enable + TagsCloudVisualization.Program + + + + + + + + + + + + + + + From cb4cb27e3e4e606f0ad4c7d747f9671fa8c01edb Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Thu, 21 Nov 2024 02:27:39 +0500 Subject: [PATCH 22/25] Fix naming --- cs/TagsCloudVisualization/CircleLayer.cs | 215 +++++++++--------- .../CircularCloudLayouter.cs | 130 +++++------ .../CircularCloudVisualization.cs | 117 ---------- .../CircularCloudVisualizer.cs | 12 +- cs/TagsCloudVisualization/PointExtensions.cs | 17 ++ 5 files changed, 178 insertions(+), 313 deletions(-) delete mode 100644 cs/TagsCloudVisualization/CircularCloudVisualization.cs create mode 100644 cs/TagsCloudVisualization/PointExtensions.cs diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs index 36e5322a8..34c66b478 100644 --- a/cs/TagsCloudVisualization/CircleLayer.cs +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -9,59 +9,57 @@ public class CircleLayer { public enum Sector { - Top_Right, - Bottom_Right, - Bottom_Left, - Top_Left + TopRight, + BottomRight, + BottomLeft, + TopLeft } public Point Center { get; } public int Radius { get; private set; } - public Sector CurrentSector - { - get => currentSector; - } + private Sector currentSector; private readonly RectangleStorage storage; - private readonly List layerRectangles = new(); + private readonly List layerRectangles = []; - public CircleLayer(Point center, int radius, RectangleStorage storage) + private CircleLayer(Point center, int radius, RectangleStorage storage) { Center = center; Radius = radius; - currentSector = Sector.Top_Right; + currentSector = Sector.TopRight; this.storage = storage; } + public CircleLayer(Point center, RectangleStorage storage) : this(center, 0, storage) + { } + public void OnSuccessInsertRectangle(int addedRectangleId) { - currentSector = GetNextClockwiseSector(); + if (addedRectangleId != 0) + { + currentSector = GetNextClockwiseSector(); + } layerRectangles.Add(addedRectangleId); - if (ShouldCreateNewCircle()) + if (ShouldCreateNewLayer()) CreateNextLayerAndChangeCurrentOnNext(); } - private bool ShouldCreateNewCircle() + private bool ShouldCreateNewLayer() { - return currentSector == Sector.Top_Right; + return currentSector == Sector.TopRight; } private Sector GetNextClockwiseSector() { - return currentSector == Sector.Top_Left ? Sector.Top_Right : currentSector + 1; + return currentSector == Sector.TopLeft ? Sector.TopRight : currentSector + 1; } private void CreateNextLayerAndChangeCurrentOnNext() { var nextLayer = new CircleLayer(Center, CalculateRadiusForNextLayer(), storage); - ChangeCurrentLayerBy(nextLayer); - } - - private void ChangeCurrentLayerBy(CircleLayer next) - { - Radius = next.Radius; - currentSector = next.currentSector; + Radius = nextLayer.Radius; + currentSector = nextLayer.currentSector; var rectanglesForNextRadius = RemoveRectangleInCircle(); layerRectangles.Clear(); layerRectangles.AddRange(rectanglesForNextRadius); @@ -81,52 +79,55 @@ private List RemoveRectangleInCircle() .ToList(); } - private int CalculateDistanceBetweenCenterAndRectangleFarCorner(Rectangle r) + private int CalculateDistanceBetweenCenterAndRectangleFarCorner(Rectangle rectangle) { - var d1 = CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Top)); - var d2 = CalculateDistanceBetweenPoints(Center, new Point(r.Right, r.Bottom)); - var d3 = CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Bottom)); - var d4 = CalculateDistanceBetweenPoints(Center, new Point(r.Left, r.Top)); - return Math.Max(Math.Max(d1, d2), Math.Max(d3, d4)); + var distanceToCorners = new List { + Center.CalculateDistanceBetween(new Point(rectangle.Right, rectangle.Top)), + Center.CalculateDistanceBetween(new Point(rectangle.Right, rectangle.Bottom)), + Center.CalculateDistanceBetween(new Point(rectangle.Left, rectangle.Bottom)), + Center.CalculateDistanceBetween(new Point(rectangle.Left, rectangle.Top)) + }; + return distanceToCorners.Max(); } - private int CalculateDistanceBetweenPoints(Point p1, Point p2) + private Point PutToCenter(Size rectangleSize) { - return (int)Math.Ceiling(Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y))); - } + var rectangleX = Center.X - rectangleSize.Width / 2; + var rectangleY = Center.Y - rectangleSize.Height / 2; + return new Point(rectangleX, rectangleY); + } public Point CalculateTopLeftRectangleCornerPosition(Size rectangleSize) { - var rectangleStartPositionOnCircle = GetStartSectorPointOnCircleBySector(currentSector); - switch (currentSector) + if (Radius == 0) { - case Sector.Top_Right: - return new Point(rectangleStartPositionOnCircle.X, - rectangleStartPositionOnCircle.Y - rectangleSize.Height); - case Sector.Bottom_Right: - return rectangleStartPositionOnCircle; - case Sector.Bottom_Left: - return new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, - rectangleStartPositionOnCircle.Y); - default: - return new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, - rectangleStartPositionOnCircle.Y - rectangleSize.Height); + return PutToCenter(rectangleSize); } + var rectangleStartPositionOnCircle = GetStartSectorPointOnCircleBySector(currentSector); + return currentSector switch + { + Sector.TopRight => new Point(rectangleStartPositionOnCircle.X, + rectangleStartPositionOnCircle.Y -= rectangleSize.Height), + Sector.BottomRight => + rectangleStartPositionOnCircle, + Sector.BottomLeft => + new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, + rectangleStartPositionOnCircle.Y), + _ => + new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, + rectangleStartPositionOnCircle.Y - rectangleSize.Height), + }; } private Point GetStartSectorPointOnCircleBySector(Sector s) { - switch (s) + return s switch { - case Sector.Top_Right: - return new Point(Center.X, Center.Y - Radius); - case Sector.Bottom_Right: - return new Point(Center.X + Radius, Center.Y); - case Sector.Bottom_Left: - return new Point(Center.X, Center.Y + Radius); - default: - return new Point(Center.X - Radius, Center.Y); - } + Sector.TopRight => new Point(Center.X, Center.Y - Radius), + Sector.BottomRight => new Point(Center.X + Radius, Center.Y), + Sector.BottomLeft => new Point(Center.X, Center.Y + Radius), + _ => new Point(Center.X - Radius, Center.Y), + }; } public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rectangle intersected) @@ -134,12 +135,11 @@ public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rec if (IsNextPositionMoveToAnotherSector(forInsertion.Location, forInsertion.Size)) { currentSector = GetNextClockwiseSector(); - if (ShouldCreateNewCircle()) CreateNextLayerAndChangeCurrentOnNext(); + if (ShouldCreateNewLayer()) CreateNextLayerAndChangeCurrentOnNext(); forInsertion.Location = CalculateTopLeftRectangleCornerPosition(forInsertion.Size); } var nextPosition = CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); - return nextPosition; } @@ -148,16 +148,16 @@ private bool IsNextPositionMoveToAnotherSector(Point next, Size forInsertionSize return IsRectangleIntersectSymmetryAxis(new Rectangle(next, forInsertionSize)); } - private bool IsRectangleIntersectSymmetryAxis(Rectangle r) + private bool IsRectangleIntersectSymmetryAxis(Rectangle rectangle) { - return (r.Left < Center.X && r.Right > Center.X) || (r.Bottom > Center.Y && r.Top < Center.Y); + return (rectangle.Left < Center.X && rectangle.Right > Center.X) || (rectangle.Bottom > Center.Y && rectangle.Top < Center.Y); } - private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangle forInsertion, + private Point CalculateNewPositionWithoutIntersectionBySector(Sector whereIntersected, Rectangle forInsertion, Rectangle intersected) { - bool isMovingAxisIsX = IsMovingAxisIsXBySector(s); - var distanceForMoving = CalculateDistanceForMovingBySector(s, forInsertion, intersected); + bool isMovingAxisIsX = IsMovingAxisIsXBySector(whereIntersected); + var distanceForMoving = CalculateDistanceForMovingBySector(whereIntersected, forInsertion, intersected); int distanceForBringBackOnCircle; if (IsRectangleBetweenSectors(distanceForMoving, forInsertion.Location, isMovingAxisIsX)) @@ -167,7 +167,7 @@ private Point CalculateNewPositionWithoutIntersectionBySector(Sector s, Rectangl else { var nearestForCenterCorner = - CalculateCornerNearestForCenterAfterMove(s, distanceForMoving, forInsertion); + CalculateCornerNearestForCenterAfterMove(whereIntersected, distanceForMoving, forInsertion); distanceForBringBackOnCircle = CalculateDeltaForBringRectangleBackOnCircle(nearestForCenterCorner, isMovingAxisIsX, forInsertion); } @@ -186,7 +186,7 @@ private bool IsRectangleBetweenSectors(int distanceForMoving, Point forInsertion return distanceForMoving > distanceToCenter; } - private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCorner, bool isMovingAxisIsX, Rectangle r) + private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCorner, bool isMovingAxisIsX, Rectangle forInsertion) { Func getAxisForBringBackOnCircle = isMovingAxisIsX ? p => p.Y : p => p.X; Func getStaticAxis = isMovingAxisIsX ? p => p.X : p => p.Y; @@ -194,16 +194,15 @@ private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCo var distanceOnStaticAxis = Math.Abs(getStaticAxis(nearestForCenterCorner) - getStaticAxis(Center)); var distanceOnAxisForBringBackOnCircle = Math.Abs(getAxisForBringBackOnCircle(nearestForCenterCorner) - getAxisForBringBackOnCircle(Center)); - var distanceBetweenCornerAndCenter = CalculateDistanceBetweenPoints(Center, nearestForCenterCorner); + var distanceBetweenCornerAndCenter = Center.CalculateDistanceBetween(nearestForCenterCorner); if (distanceBetweenCornerAndCenter > Radius) { - - return CalculateMoveMultiplierForMoveToCenter(!isMovingAxisIsX, r) + return CalculateMoveMultiplierForMoveToCenter(!isMovingAxisIsX, forInsertion) * WhenRectangleOutsideCircle(distanceOnStaticAxis, distanceBetweenCornerAndCenter, distanceOnAxisForBringBackOnCircle); } - return CalculateMoveMultiplierForMoveFromCenter(!isMovingAxisIsX, r) + return CalculateMoveMultiplierForMoveFromCenter(!isMovingAxisIsX, forInsertion) * WhenRectangleInCircle(distanceOnStaticAxis, distanceOnAxisForBringBackOnCircle); } @@ -224,75 +223,67 @@ private int CalculatePartCathetus(int hypotenuse, double a, int b) return (int)Math.Ceiling(Math.Sqrt(Math.Abs(hypotenuse * hypotenuse - a * a))) - b; } - private Point CalculateCornerNearestForCenterAfterMove(Sector s, int distanceForMoving, Rectangle r) + private Point CalculateCornerNearestForCenterAfterMove(Sector whereIntersected, int distanceForMoving, Rectangle forMove) { - var isAxisForMoveIsX = IsMovingAxisIsXBySector(s); - var moveMultiplier = CalculateMoveMultiplierForMoveClockwise(isAxisForMoveIsX, r); + var isAxisForMoveIsX = IsMovingAxisIsXBySector(whereIntersected); + var moveMultiplier = CalculateMoveMultiplierForMoveClockwise(isAxisForMoveIsX, forMove); distanceForMoving *= moveMultiplier; - var nearestCorner = GetCornerNearestForCenterBySector(s, r); + var nearestCorner = GetCornerNearestForCenterBySector(whereIntersected, forMove); return isAxisForMoveIsX ? new Point(nearestCorner.X + distanceForMoving, nearestCorner.Y) : new Point(nearestCorner.X, nearestCorner.Y + distanceForMoving); } - private int CalculateMoveMultiplierForMoveFromCenter(bool isAxisForMoveIsX, Rectangle r) + private int CalculateMoveMultiplierForMoveFromCenter(bool isAxisForMoveIsX, Rectangle forMove) { - if (r.Bottom < Center.Y && r.Left > Center.X) return isAxisForMoveIsX ? 1 : -1; - if (r.Bottom < Center.Y && r.Right < Center.X) return -1; - if (r.Top > Center.Y && r.Left > Center.X) return 1; - if (r.Top > Center.Y && r.Right < Center.X) return isAxisForMoveIsX ? -1 : 1; - return isAxisForMoveIsX ? r.Bottom < Center.Y ? -1 : 1 - : r.Left > Center.X ? 1 : -1; + if (forMove.Bottom < Center.Y && forMove.Left > Center.X) return isAxisForMoveIsX ? 1 : -1; + if (forMove.Bottom < Center.Y && forMove.Right < Center.X) return -1; + if (forMove.Top > Center.Y && forMove.Left > Center.X) return 1; + if (forMove.Top > Center.Y && forMove.Right < Center.X) return isAxisForMoveIsX ? -1 : 1; + return isAxisForMoveIsX ? forMove.Bottom < Center.Y ? -1 : 1 + : forMove.Left > Center.X ? 1 : -1; } - private int CalculateMoveMultiplierForMoveToCenter(bool isAxisForMoveIsX, Rectangle r) + private int CalculateMoveMultiplierForMoveToCenter(bool isAxisForMoveIsX, Rectangle forMove) { - return CalculateMoveMultiplierForMoveFromCenter(isAxisForMoveIsX, r) * -1; + return CalculateMoveMultiplierForMoveFromCenter(isAxisForMoveIsX, forMove) * -1; } - private int CalculateMoveMultiplierForMoveClockwise(bool isAxisForMoveIsX, Rectangle r) + private int CalculateMoveMultiplierForMoveClockwise(bool isAxisForMoveIsX, Rectangle forMove) { - if (r.Bottom < Center.Y && r.Left > Center.X) return 1; - if (r.Bottom < Center.Y && r.Right < Center.X) return isAxisForMoveIsX ? 1 : -1; - if (r.Top > Center.Y && r.Left > Center.X) return isAxisForMoveIsX ? -1 : 1; - if (r.Top > Center.Y && r.Right < Center.X) return -1; - return isAxisForMoveIsX ? r.Bottom < Center.Y ? 1 : -1 - : r.Left > Center.X ? -1 : 1; + if (forMove.Bottom < Center.Y && forMove.Left > Center.X) return 1; + if (forMove.Bottom < Center.Y && forMove.Right < Center.X) return isAxisForMoveIsX ? 1 : -1; + if (forMove.Top > Center.Y && forMove.Left > Center.X) return isAxisForMoveIsX ? -1 : 1; + if (forMove.Top > Center.Y && forMove.Right < Center.X) return -1; + return isAxisForMoveIsX ? forMove.Bottom < Center.Y ? 1 : -1 + : forMove.Left > Center.X ? -1 : 1; } - private int CalculateDistanceForMovingBySector(Sector s, Rectangle forInsertion, Rectangle intersected) + private int CalculateDistanceForMovingBySector(Sector whereIntersected, Rectangle forInsertion, Rectangle intersected) { - switch (s) + return whereIntersected switch { - case Sector.Top_Right: - return Math.Abs(forInsertion.Top - intersected.Bottom); - case Sector.Bottom_Right: - return Math.Abs(forInsertion.Right - intersected.Left); - case Sector.Bottom_Left: - return Math.Abs(forInsertion.Bottom - intersected.Top); - default: - return Math.Abs(forInsertion.Left - intersected.Right); - } + Sector.TopRight => Math.Abs(forInsertion.Top - intersected.Bottom), + Sector.BottomRight => Math.Abs(forInsertion.Right - intersected.Left), + Sector.BottomLeft => Math.Abs(forInsertion.Bottom - intersected.Top), + _ => Math.Abs(forInsertion.Left - intersected.Right), + }; } - private Point GetCornerNearestForCenterBySector(Sector s, Rectangle r) + private Point GetCornerNearestForCenterBySector(Sector rectangleLocationSector, Rectangle forInsertion) { - switch (s) + return rectangleLocationSector switch { - case Sector.Top_Right: - return new Point(r.Left, r.Bottom); - case Sector.Bottom_Right: - return new Point(r.Left, r.Top); - case Sector.Bottom_Left: - return new Point(r.Right, r.Top); - default: - return new Point(r.Right, r.Bottom); - } + Sector.TopRight => new Point(forInsertion.Left, forInsertion.Bottom), + Sector.BottomRight => new Point(forInsertion.Left, forInsertion.Top), + Sector.BottomLeft => new Point(forInsertion.Right, forInsertion.Top), + _ => new Point(forInsertion.Right, forInsertion.Bottom), + }; } - private bool IsMovingAxisIsXBySector(Sector s) + private bool IsMovingAxisIsXBySector(Sector forInsertionRectangleSector) { - return s == Sector.Bottom_Right || s == Sector.Top_Left; + return forInsertionRectangleSector == Sector.BottomRight || forInsertionRectangleSector == Sector.TopLeft; } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 1a5449582..52d1f5357 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -11,36 +11,27 @@ public class CircularCloudLayouter private readonly RectangleStorage storage = new(); private readonly BruteForceNearestFinder nearestFinder; + public CircleLayer CurrentLayer { get; } + public CircularCloudLayouter(Point center) { this.center = center; nearestFinder = new BruteForceNearestFinder(); + CurrentLayer = new(center, storage); } internal CircularCloudLayouter(Point center, RectangleStorage storage) : this(center) { this.storage = storage; + CurrentLayer = new(center, storage); } - public CircleLayer CurrentLayer { get; private set; } - public Rectangle PutNextRectangle(Size rectangleSize) { ValidateRectangleSize(rectangleSize); - Point firstRectanglePosition; - var isFirstRectangle = IsFirstRectangle(); - if (isFirstRectangle) - { - CreateFirstLayer(rectangleSize); - firstRectanglePosition = PutRectangleToCenter(rectangleSize); - } - else - { - firstRectanglePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); - } - + var firstRectanglePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); var id = SaveRectangle(firstRectanglePosition, rectangleSize); - var rectangleWithOptimalPosition = OptimiseRectanglePosition(id, isFirstRectangle); + var rectangleWithOptimalPosition = OptimiseRectanglePosition(id); return rectangleWithOptimalPosition; } @@ -50,80 +41,83 @@ private int SaveRectangle(Point firstLocation, Size rectangleSize) return id; } - public Rectangle OptimiseRectanglePosition(int id, bool isFirstRectangle) + private Rectangle OptimiseRectanglePosition(int id) { - if (isFirstRectangle) return storage.GetById(id); PutRectangleOnCircleWithoutIntersection(id); return TryMoveRectangleCloserToCenter(id); } public Rectangle PutRectangleOnCircleWithoutIntersection(int id) { - var r = storage.GetById(id); - var intersected = GetRectangleIntersection(r); + var forOptimise = storage.GetById(id); + var intersected = GetRectangleIntersection(forOptimise); - while (RectangleHasIntersection(intersected)) + while (intersected != default && intersected.Value != default) { - var possiblePosition = CurrentLayer.GetRectanglePositionWithoutIntersection(r, intersected.Value); - r.Location = possiblePosition; - intersected = GetRectangleIntersection(r); + var possiblePosition = CurrentLayer.GetRectanglePositionWithoutIntersection(forOptimise, intersected.Value); + forOptimise.Location = possiblePosition; + intersected = GetRectangleIntersection(forOptimise); } CurrentLayer.OnSuccessInsertRectangle(id); - return r; + return forOptimise; } - private bool RectangleHasIntersection(Rectangle? intersected) + internal Rectangle TryMoveRectangleCloserToCenter(int id) { - return intersected != default && intersected.Value != default; + var rectangleForMoving = storage.GetById(id); + var toCenter = GetDirectionsForMovingToCenter(rectangleForMoving); + foreach (var direction in toCenter) + { + rectangleForMoving.Location = MoveToCenterByDirection(rectangleForMoving, direction); + } + return rectangleForMoving; } - public Rectangle TryMoveRectangleCloserToCenter(int id) + private Point MoveToCenterByDirection(Rectangle forMoving, Direction toCenter) { - var rectangleForMoving = storage.GetById(id); - var directionsForMoving = GetDirectionsForMovingToCenter(rectangleForMoving); - var distancesForMove = directionsForMoving - .Select(d => (Nearest: nearestFinder.FindNearestByDirection(rectangleForMoving, d, storage.GetAll()), - Direction: d)) - .Where(tuple => tuple.Nearest != null) - .Select(t => ( - DistanceCalculator: nearestFinder.GetMinDistanceCalculatorBy(t.Direction), t.Nearest, t.Direction)) - .Select(t => (Distance: t.DistanceCalculator(t.Nearest.Value, rectangleForMoving), t.Direction)) - .ToArray(); - rectangleForMoving.Location = MoveByDirections(rectangleForMoving.Location, distancesForMove); - return rectangleForMoving; + var nearest = nearestFinder.FindNearestByDirection(forMoving, toCenter, storage.GetAll()); + if (nearest == null) return forMoving.Location; + var distanceCalculator = nearestFinder.GetMinDistanceCalculatorBy(toCenter); + var distanceForMove = distanceCalculator(nearest.Value, forMoving); + return MoveByDirection(forMoving.Location, distanceForMove, toCenter); } - private Point MoveByDirections(Point p, (int Distance, Direction Direction)[] t) + private Point MoveByDirection(Point forMoving, int distance, Direction toCenter) { - foreach (var moveInfo in t) + var factorForDistanceByX = toCenter switch { - var factorForDistanceByX = moveInfo.Direction == Direction.Left ? -1 : - moveInfo.Direction == Direction.Right ? 1 : 0; - var factorForDistanceByY = moveInfo.Direction == Direction.Top ? -1 : - moveInfo.Direction == Direction.Bottom ? 1 : 0; - p.X += moveInfo.Distance * factorForDistanceByX; - p.Y += moveInfo.Distance * factorForDistanceByY; - } - - return p; + Direction.Left => -1, + Direction.Right => 1, + _ => 0 + }; + var factorForDistanceByY = toCenter switch + { + Direction.Top => -1, + Direction.Bottom => 1, + _ => 0 + }; + forMoving.X += distance * factorForDistanceByX; + forMoving.Y += distance * factorForDistanceByY; + + return forMoving; } - private List GetDirectionsForMovingToCenter(Rectangle r) + private List GetDirectionsForMovingToCenter(Rectangle forMoving) { var directions = new List(); - if (r.Bottom < center.Y) directions.Add(Direction.Bottom); - if (r.Left > center.X) directions.Add(Direction.Left); - if (r.Right < center.X) directions.Add(Direction.Right); - if (r.Top > center.Y) directions.Add(Direction.Top); + if (forMoving.Bottom < center.Y) directions.Add(Direction.Bottom); + if (forMoving.Left > center.X) directions.Add(Direction.Left); + if (forMoving.Right < center.X) directions.Add(Direction.Right); + if (forMoving.Top > center.Y) directions.Add(Direction.Top); return directions; } - private void ValidateRectangleSize(Size s) + private void ValidateRectangleSize(Size forInsertion) { - if (s.Width <= 0 || s.Height <= 0) - throw new ArgumentException($"Rectangle has incorrect size: width = {s.Width}, height = {s.Height}"); + if (forInsertion.Width <= 0 || forInsertion.Height <= 0) + throw new ArgumentException($"Rectangle has incorrect size: width = {forInsertion.Width}, height = {forInsertion.Height}"); } private Rectangle? GetRectangleIntersection(Rectangle forInsertion) @@ -131,24 +125,4 @@ private void ValidateRectangleSize(Size s) return storage.GetAll() .FirstOrDefault(r => forInsertion.IntersectsWith(r) && forInsertion != r); } - - private void CreateFirstLayer(Size firstRectangle) - { - var radius = Math.Ceiling(Math.Sqrt(firstRectangle.Width * firstRectangle.Width + - firstRectangle.Height * firstRectangle.Height) / 2.0); - CurrentLayer = new CircleLayer(center, (int)radius, storage); - } - - private Point PutRectangleToCenter(Size rectangleSize) - { - var rectangleX = center.X - rectangleSize.Width / 2; - var rectangleY = center.Y - rectangleSize.Height / 2; - - return new Point(rectangleX, rectangleY); - } - - private bool IsFirstRectangle() - { - return storage.GetAll().FirstOrDefault() == default; - } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CircularCloudVisualization.cs b/cs/TagsCloudVisualization/CircularCloudVisualization.cs deleted file mode 100644 index 3a9c3b908..000000000 --- a/cs/TagsCloudVisualization/CircularCloudVisualization.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; -using static TagsCloudVisualization.CircleLayer; - -namespace TagsCloudVisualization -{ - public class CircularCloudVisualization - { - private readonly Color BACKGROUND_COLOR = Color.White; - private readonly Color RECTANGLE_COLOR = Color.DarkBlue; - private readonly Size ImageSize; - private RectangleStorage rectangleStorage; - private int sizeFactor; - - public CircularCloudVisualization(RectangleStorage rectangles, Size imageSize) - { - rectangleStorage = rectangles; - ImageSize = imageSize; - this.sizeFactor = sizeFactor; - } - - public void CreateImage(string file = null) - { - var filePath = file ?? Path.Combine(Path.GetTempPath(), "testImage1010.png"); - using (var image = new Bitmap(ImageSize.Width, ImageSize.Height)) - { - using (Graphics graphics = Graphics.FromImage(image)) - { - graphics.Clear(BACKGROUND_COLOR); - DrawGrid(graphics); - Pen pen = new Pen(RECTANGLE_COLOR); - var rectangles = NormalizeSizes(rectangleStorage.GetAll()); - graphics.DrawRectangle(new Pen(Color.Brown),rectangles.First()); - graphics.DrawRectangles(pen, rectangles.Skip(1).ToArray()); - } - image.Save(filePath, ImageFormat.Png); - } - } - - private void DrawGrid(Graphics g, int cellsCount = 100, int cellSize = 10) - { - Pen p = new Pen(Color.DarkGray); - - for (int y = 0; y < cellsCount; ++y) - { - g.DrawLine(p, 0, y * cellSize, cellsCount * cellSize, y * cellSize); - } - - for (int x = 0; x < cellsCount; ++x) - { - g.DrawLine(p, x * cellSize, 0, x * cellSize, cellsCount * cellSize); - } - } - - public void CreateImageWithSaveEveryStep(CircularCloudLayouter layouter, Size[] sizes) - { - var startName = "testImageStep"; - var extension = ".png"; - using (var image = new Bitmap(ImageSize.Width, ImageSize.Height)) - { - using (Graphics graphics = Graphics.FromImage(image)) - { - graphics.Clear(BACKGROUND_COLOR); - DrawGrid(graphics); - Pen pen = new Pen(RECTANGLE_COLOR); - for (var i = 0; i < sizes.Length; i++) - { - var r = layouter.PutNextRectangle(sizes[i]); - var currentFileName = $"{startName}{i}{extension}"; - graphics.DrawRectangle(new Pen(GetColorBySector(layouter.CurrentLayer.CurrentSector)), r); - var filePath = Path.Combine(Path.GetTempPath(), currentFileName); - image.Save(filePath, ImageFormat.Png); - } - } - - } - } - - private Color GetColorBySector(CircleLayer.Sector s) - { - switch (s) - { - case Sector.Top_Right: - return Color.Chartreuse; - case Sector.Bottom_Right: - return Color.Brown; - case Sector.Bottom_Left: - return Color.DeepPink; - default: - return Color.DodgerBlue; - } - } - - private Rectangle[] NormalizeSizes(IEnumerable source) - { - double XLength = source.Max(r => r.Right) - source.Min(r => r.Left); - double YLength = source.Max(r => r.Bottom) - source.Min(r => r.Top); - var boundShift = 10; - var factorX = ImageSize.Width > XLength - ? (int)Math.Floor((ImageSize.Width - boundShift) / XLength) - : 1; - var factorY = ImageSize.Height> YLength - ? (int)Math.Floor((ImageSize.Height - boundShift) / YLength) - : 1; - return source.Select(r => new Rectangle(new Point(r.X * factorX,r.Y * factorY), - new Size(r.Width * factorX, r.Height * factorY))).ToArray(); - } - } -} diff --git a/cs/TagsCloudVisualization/CircularCloudVisualizer.cs b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs index 66b9731c4..43dacc87b 100644 --- a/cs/TagsCloudVisualization/CircularCloudVisualizer.cs +++ b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs @@ -44,7 +44,7 @@ public void CreateImage(string? filePath = null, bool withSaveSteps = false) private void SaveImage(Bitmap image, string? filePath = null) { var rnd = new Random(); - filePath = filePath ?? Path.Combine(Path.GetTempPath(), $"testImage{rnd.Next()}.png"); + filePath ??= Path.Combine(Path.GetTempPath(), $"testImage{rnd.Next()}.png"); image.Save(filePath, ImageFormat.Png); } @@ -52,11 +52,11 @@ private Rectangle[] NormalizeSizes(IEnumerable source) { var sourceToArray = source.ToArray(); - var XLength = sourceToArray.Max(r => r.Right) - sourceToArray.Min(r => r.Left); - var YLength = sourceToArray.Max(r => r.Bottom) - sourceToArray.Min(r => r.Top); + var xLength = sourceToArray.Max(r => r.Right) - sourceToArray.Min(r => r.Left); + var yLength = sourceToArray.Max(r => r.Bottom) - sourceToArray.Min(r => r.Top); - var factorX = GetNormalizeFactorByAxis(imageSize.Width, XLength); - var factorY = GetNormalizeFactorByAxis(imageSize.Height, YLength); + var factorX = GetNormalizeFactorByAxis(imageSize.Width, xLength); + var factorY = GetNormalizeFactorByAxis(imageSize.Height, yLength); return sourceToArray.Select(r => new Rectangle( new Point(r.X * factorX, r.Y * factorY), @@ -64,7 +64,7 @@ private Rectangle[] NormalizeSizes(IEnumerable source) .ToArray(); } - private int GetNormalizeFactorByAxis(int imageSizeOnAxis, int rectanglesSizeOnAxis) + private static int GetNormalizeFactorByAxis(int imageSizeOnAxis, int rectanglesSizeOnAxis) { const int boundShift = 10; double imageSizeOnAxisWithShiftForBounds = imageSizeOnAxis - boundShift; diff --git a/cs/TagsCloudVisualization/PointExtensions.cs b/cs/TagsCloudVisualization/PointExtensions.cs new file mode 100644 index 000000000..c038ba1af --- /dev/null +++ b/cs/TagsCloudVisualization/PointExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TagsCloudVisualization +{ + public static class PointExtensions + { + public static int CalculateDistanceBetween(this Point p1, Point p2) + { + return (int)Math.Ceiling(Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y))); + } + } +} From 9d33c50dc111e09f099713f70b31f66de1a90c64 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Thu, 21 Nov 2024 03:15:44 +0500 Subject: [PATCH 23/25] Replace DrawGrid to extensions --- .../CircularCloudVisualizer.cs | 1 + .../GraphicsExtensions.cs | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 cs/TagsCloudVisualization/GraphicsExtensions.cs diff --git a/cs/TagsCloudVisualization/CircularCloudVisualizer.cs b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs index 43dacc87b..73c24a51e 100644 --- a/cs/TagsCloudVisualization/CircularCloudVisualizer.cs +++ b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs @@ -27,6 +27,7 @@ public void CreateImage(string? filePath = null, bool withSaveSteps = false) using var image = new Bitmap(imageSize.Width, imageSize.Height); using var graphics = Graphics.FromImage(image); graphics.Clear(backgroundColor); + graphics.DrawGrid(); var pen = new Pen(rectangleColor); for (var i = 0; i < rectangles.Length; i++) diff --git a/cs/TagsCloudVisualization/GraphicsExtensions.cs b/cs/TagsCloudVisualization/GraphicsExtensions.cs new file mode 100644 index 000000000..9f27921ef --- /dev/null +++ b/cs/TagsCloudVisualization/GraphicsExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TagsCloudVisualization +{ + public static class GraphicsExtensions + { + public static void DrawGrid(this Graphics graphics, int cellsCount = 100, int cellSize = 10) + { + Pen p = new Pen(Color.DarkGray); + + for (int y = 0; y < cellsCount; ++y) + { + graphics.DrawLine(p, 0, y * cellSize, cellsCount * cellSize, y * cellSize); + } + + for (int x = 0; x < cellsCount; ++x) + { + graphics.DrawLine(p, x * cellSize, 0, x * cellSize, cellsCount * cellSize); + } + } + } +} From d4550a04641f662e777759c2280af88e555c0e25 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Thu, 21 Nov 2024 13:23:55 +0500 Subject: [PATCH 24/25] Remove storage --- cs/TagsCloudVisualization/CircleLayer.cs | 70 ++++++++++--------- .../CircularCloudLayouter.cs | 43 +++++------- .../CircularCloudVisualizer.cs | 6 +- cs/TagsCloudVisualization/RectangleStorage.cs | 67 ------------------ cs/TagsCloudVisualization/RectangleWrapper.cs | 46 ++++++++++++ .../Tests/CircleLayerTests.cs | 49 +++++++------ .../Tests/CircularCloudLayouterTests.cs | 35 +++++----- ...CircularCloudLayouterVisualizationTests.cs | 43 ++++++------ 8 files changed, 168 insertions(+), 191 deletions(-) delete mode 100644 cs/TagsCloudVisualization/RectangleStorage.cs create mode 100644 cs/TagsCloudVisualization/RectangleWrapper.cs diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs index 34c66b478..d93d94fb9 100644 --- a/cs/TagsCloudVisualization/CircleLayer.cs +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -20,10 +20,10 @@ public enum Sector private Sector currentSector; - private readonly RectangleStorage storage; + private readonly List storage; private readonly List layerRectangles = []; - private CircleLayer(Point center, int radius, RectangleStorage storage) + private CircleLayer(Point center, int radius, List storage) { Center = center; Radius = radius; @@ -31,16 +31,14 @@ private CircleLayer(Point center, int radius, RectangleStorage storage) this.storage = storage; } - public CircleLayer(Point center, RectangleStorage storage) : this(center, 0, storage) - { } + public CircleLayer(Point center, List storage) : this(center, 0, storage) + { + } - public void OnSuccessInsertRectangle(int addedRectangleId) + public void OnSuccessInsertRectangle() { - if (addedRectangleId != 0) - { - currentSector = GetNextClockwiseSector(); - } - layerRectangles.Add(addedRectangleId); + if (storage.Count != 1) currentSector = GetNextClockwiseSector(); + layerRectangles.Add(storage.Count - 1); if (ShouldCreateNewLayer()) CreateNextLayerAndChangeCurrentOnNext(); } @@ -68,20 +66,21 @@ private void CreateNextLayerAndChangeCurrentOnNext() private int CalculateRadiusForNextLayer() { return layerRectangles - .Select(id => CalculateDistanceBetweenCenterAndRectangleFarCorner(storage.GetById(id))) + .Select(ind => CalculateDistanceBetweenCenterAndRectangleFarCorner(storage[ind])) .Min(); } private List RemoveRectangleInCircle() { return layerRectangles - .Where(id => CalculateDistanceBetweenCenterAndRectangleFarCorner(storage.GetById(id)) > Radius) + .Where(i => CalculateDistanceBetweenCenterAndRectangleFarCorner(storage[i]) > Radius) .ToList(); } private int CalculateDistanceBetweenCenterAndRectangleFarCorner(Rectangle rectangle) { - var distanceToCorners = new List { + var distanceToCorners = new List + { Center.CalculateDistanceBetween(new Point(rectangle.Right, rectangle.Top)), Center.CalculateDistanceBetween(new Point(rectangle.Right, rectangle.Bottom)), Center.CalculateDistanceBetween(new Point(rectangle.Left, rectangle.Bottom)), @@ -97,12 +96,10 @@ private Point PutToCenter(Size rectangleSize) return new Point(rectangleX, rectangleY); } + public Point CalculateTopLeftRectangleCornerPosition(Size rectangleSize) { - if (Radius == 0) - { - return PutToCenter(rectangleSize); - } + if (Radius == 0) return PutToCenter(rectangleSize); var rectangleStartPositionOnCircle = GetStartSectorPointOnCircleBySector(currentSector); return currentSector switch { @@ -112,10 +109,10 @@ public Point CalculateTopLeftRectangleCornerPosition(Size rectangleSize) rectangleStartPositionOnCircle, Sector.BottomLeft => new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, - rectangleStartPositionOnCircle.Y), + rectangleStartPositionOnCircle.Y), _ => new Point(rectangleStartPositionOnCircle.X - rectangleSize.Width, - rectangleStartPositionOnCircle.Y - rectangleSize.Height), + rectangleStartPositionOnCircle.Y - rectangleSize.Height) }; } @@ -126,7 +123,7 @@ private Point GetStartSectorPointOnCircleBySector(Sector s) Sector.TopRight => new Point(Center.X, Center.Y - Radius), Sector.BottomRight => new Point(Center.X + Radius, Center.Y), Sector.BottomLeft => new Point(Center.X, Center.Y + Radius), - _ => new Point(Center.X - Radius, Center.Y), + _ => new Point(Center.X - Radius, Center.Y) }; } @@ -138,6 +135,7 @@ public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rec if (ShouldCreateNewLayer()) CreateNextLayerAndChangeCurrentOnNext(); forInsertion.Location = CalculateTopLeftRectangleCornerPosition(forInsertion.Size); } + var nextPosition = CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); return nextPosition; @@ -150,14 +148,16 @@ private bool IsNextPositionMoveToAnotherSector(Point next, Size forInsertionSize private bool IsRectangleIntersectSymmetryAxis(Rectangle rectangle) { - return (rectangle.Left < Center.X && rectangle.Right > Center.X) || (rectangle.Bottom > Center.Y && rectangle.Top < Center.Y); + return (rectangle.Left < Center.X && rectangle.Right > Center.X) || + (rectangle.Bottom > Center.Y && rectangle.Top < Center.Y); } private Point CalculateNewPositionWithoutIntersectionBySector(Sector whereIntersected, Rectangle forInsertion, Rectangle intersected) { - bool isMovingAxisIsX = IsMovingAxisIsXBySector(whereIntersected); - var distanceForMoving = CalculateDistanceForMovingBySector(whereIntersected, forInsertion, intersected); + var isMovingAxisIsX = IsMovingAxisIsXBySector(whereIntersected); + var distanceForMoving = + CalculateDistanceForMoveClockwiseToPositionWithoutIntersection(whereIntersected, forInsertion, intersected); int distanceForBringBackOnCircle; if (IsRectangleBetweenSectors(distanceForMoving, forInsertion.Location, isMovingAxisIsX)) @@ -171,6 +171,7 @@ private Point CalculateNewPositionWithoutIntersectionBySector(Sector whereInters distanceForBringBackOnCircle = CalculateDeltaForBringRectangleBackOnCircle(nearestForCenterCorner, isMovingAxisIsX, forInsertion); } + distanceForMoving *= CalculateMoveMultiplierForMoveClockwise(isMovingAxisIsX, forInsertion); distanceForBringBackOnCircle *= CalculateMoveMultiplierForMoveFromCenter(!isMovingAxisIsX, forInsertion); return isMovingAxisIsX @@ -186,7 +187,8 @@ private bool IsRectangleBetweenSectors(int distanceForMoving, Point forInsertion return distanceForMoving > distanceToCenter; } - private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCorner, bool isMovingAxisIsX, Rectangle forInsertion) + private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCorner, bool isMovingAxisIsX, + Rectangle forInsertion) { Func getAxisForBringBackOnCircle = isMovingAxisIsX ? p => p.Y : p => p.X; Func getStaticAxis = isMovingAxisIsX ? p => p.X : p => p.Y; @@ -196,17 +198,16 @@ private int CalculateDeltaForBringRectangleBackOnCircle(Point nearestForCenterCo getAxisForBringBackOnCircle(Center)); var distanceBetweenCornerAndCenter = Center.CalculateDistanceBetween(nearestForCenterCorner); if (distanceBetweenCornerAndCenter > Radius) - { return CalculateMoveMultiplierForMoveToCenter(!isMovingAxisIsX, forInsertion) * WhenRectangleOutsideCircle(distanceOnStaticAxis, distanceBetweenCornerAndCenter, - distanceOnAxisForBringBackOnCircle); - } + distanceOnAxisForBringBackOnCircle); return CalculateMoveMultiplierForMoveFromCenter(!isMovingAxisIsX, forInsertion) * WhenRectangleInCircle(distanceOnStaticAxis, distanceOnAxisForBringBackOnCircle); } - private int WhenRectangleOutsideCircle(int distanceOnStaticAxis, int distanceBetweenCornerAndCenter, int distanceOnAxisForBringBackOnCircle) + private int WhenRectangleOutsideCircle(int distanceOnStaticAxis, int distanceBetweenCornerAndCenter, + int distanceOnAxisForBringBackOnCircle) { var inCircleCathetusPart = Math.Sqrt(Math.Abs(Radius * Radius - distanceOnStaticAxis * distanceOnStaticAxis)); return CalculatePartCathetus(distanceBetweenCornerAndCenter, inCircleCathetusPart, @@ -223,7 +224,8 @@ private int CalculatePartCathetus(int hypotenuse, double a, int b) return (int)Math.Ceiling(Math.Sqrt(Math.Abs(hypotenuse * hypotenuse - a * a))) - b; } - private Point CalculateCornerNearestForCenterAfterMove(Sector whereIntersected, int distanceForMoving, Rectangle forMove) + private Point CalculateCornerNearestForCenterAfterMove(Sector whereIntersected, int distanceForMoving, + Rectangle forMove) { var isAxisForMoveIsX = IsMovingAxisIsXBySector(whereIntersected); var moveMultiplier = CalculateMoveMultiplierForMoveClockwise(isAxisForMoveIsX, forMove); @@ -257,17 +259,19 @@ private int CalculateMoveMultiplierForMoveClockwise(bool isAxisForMoveIsX, Recta if (forMove.Top > Center.Y && forMove.Right < Center.X) return -1; return isAxisForMoveIsX ? forMove.Bottom < Center.Y ? 1 : -1 : forMove.Left > Center.X ? -1 : 1; - } - private int CalculateDistanceForMovingBySector(Sector whereIntersected, Rectangle forInsertion, Rectangle intersected) + private int CalculateDistanceForMoveClockwiseToPositionWithoutIntersection( + Sector whereIntersected, + Rectangle forInsertion, + Rectangle intersected) { return whereIntersected switch { Sector.TopRight => Math.Abs(forInsertion.Top - intersected.Bottom), Sector.BottomRight => Math.Abs(forInsertion.Right - intersected.Left), Sector.BottomLeft => Math.Abs(forInsertion.Bottom - intersected.Top), - _ => Math.Abs(forInsertion.Left - intersected.Right), + _ => Math.Abs(forInsertion.Left - intersected.Right) }; } @@ -278,7 +282,7 @@ private Point GetCornerNearestForCenterBySector(Sector rectangleLocationSector, Sector.TopRight => new Point(forInsertion.Left, forInsertion.Bottom), Sector.BottomRight => new Point(forInsertion.Left, forInsertion.Top), Sector.BottomLeft => new Point(forInsertion.Right, forInsertion.Top), - _ => new Point(forInsertion.Right, forInsertion.Bottom), + _ => new Point(forInsertion.Right, forInsertion.Bottom) }; } diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 52d1f5357..88b741577 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -8,7 +8,7 @@ namespace TagsCloudVisualization; public class CircularCloudLayouter { private readonly Point center; - private readonly RectangleStorage storage = new(); + private readonly List storage = []; private readonly BruteForceNearestFinder nearestFinder; public CircleLayer CurrentLayer { get; } @@ -20,7 +20,7 @@ public CircularCloudLayouter(Point center) CurrentLayer = new(center, storage); } - internal CircularCloudLayouter(Point center, RectangleStorage storage) : this(center) + internal CircularCloudLayouter(Point center, List storage) : this(center) { this.storage = storage; CurrentLayer = new(center, storage); @@ -29,43 +29,37 @@ internal CircularCloudLayouter(Point center, RectangleStorage storage) : this(ce public Rectangle PutNextRectangle(Size rectangleSize) { ValidateRectangleSize(rectangleSize); + var firstRectanglePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); - var id = SaveRectangle(firstRectanglePosition, rectangleSize); - var rectangleWithOptimalPosition = OptimiseRectanglePosition(id); + var forInsertion = new Rectangle(firstRectanglePosition, rectangleSize); + var rectangleWithOptimalPosition = OptimiseRectanglePosition(forInsertion); + storage.Add(rectangleWithOptimalPosition); + CurrentLayer.OnSuccessInsertRectangle(); return rectangleWithOptimalPosition; } - private int SaveRectangle(Point firstLocation, Size rectangleSize) + private Rectangle OptimiseRectanglePosition(Rectangle forInsertion) { - var id = storage.Add(new Rectangle(firstLocation, rectangleSize)); - return id; + PutRectangleOnCircleWithoutIntersection(forInsertion); + return TryMoveRectangleCloserToCenter(forInsertion); } - private Rectangle OptimiseRectanglePosition(int id) + public Rectangle PutRectangleOnCircleWithoutIntersection(RectangleWrapper forOptimise) { - PutRectangleOnCircleWithoutIntersection(id); - return TryMoveRectangleCloserToCenter(id); - } - - public Rectangle PutRectangleOnCircleWithoutIntersection(int id) - { - var forOptimise = storage.GetById(id); var intersected = GetRectangleIntersection(forOptimise); - while (intersected != default && intersected.Value != default) + while (intersected != null && intersected.Value != default) { var possiblePosition = CurrentLayer.GetRectanglePositionWithoutIntersection(forOptimise, intersected.Value); forOptimise.Location = possiblePosition; intersected = GetRectangleIntersection(forOptimise); } - CurrentLayer.OnSuccessInsertRectangle(id); return forOptimise; } - internal Rectangle TryMoveRectangleCloserToCenter(int id) + internal Rectangle TryMoveRectangleCloserToCenter(RectangleWrapper rectangleForMoving) { - var rectangleForMoving = storage.GetById(id); var toCenter = GetDirectionsForMovingToCenter(rectangleForMoving); foreach (var direction in toCenter) { @@ -77,14 +71,14 @@ internal Rectangle TryMoveRectangleCloserToCenter(int id) private Point MoveToCenterByDirection(Rectangle forMoving, Direction toCenter) { - var nearest = nearestFinder.FindNearestByDirection(forMoving, toCenter, storage.GetAll()); + var nearest = nearestFinder.FindNearestByDirection(forMoving, toCenter, storage.Select(r => (Rectangle)r)); if (nearest == null) return forMoving.Location; var distanceCalculator = nearestFinder.GetMinDistanceCalculatorBy(toCenter); var distanceForMove = distanceCalculator(nearest.Value, forMoving); return MoveByDirection(forMoving.Location, distanceForMove, toCenter); } - private Point MoveByDirection(Point forMoving, int distance, Direction toCenter) + private static Point MoveByDirection(Point forMoving, int distance, Direction toCenter) { var factorForDistanceByX = toCenter switch { @@ -114,7 +108,7 @@ private List GetDirectionsForMovingToCenter(Rectangle forMoving) return directions; } - private void ValidateRectangleSize(Size forInsertion) + private static void ValidateRectangleSize(Size forInsertion) { if (forInsertion.Width <= 0 || forInsertion.Height <= 0) throw new ArgumentException($"Rectangle has incorrect size: width = {forInsertion.Width}, height = {forInsertion.Height}"); @@ -122,7 +116,8 @@ private void ValidateRectangleSize(Size forInsertion) private Rectangle? GetRectangleIntersection(Rectangle forInsertion) { - return storage.GetAll() - .FirstOrDefault(r => forInsertion.IntersectsWith(r) && forInsertion != r); + if (storage.Count == 0) return null; + return storage.Select(r => (Rectangle)r).FirstOrDefault(r => forInsertion != r + && forInsertion.IntersectsWith(r)); } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CircularCloudVisualizer.cs b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs index 73c24a51e..f6a28b540 100644 --- a/cs/TagsCloudVisualization/CircularCloudVisualizer.cs +++ b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs @@ -12,9 +12,9 @@ public class CircularCloudVisualizer private readonly Color backgroundColor = Color.White; private readonly Color rectangleColor = Color.DarkBlue; private readonly Size imageSize; - private readonly RectangleStorage rectangleStorage; + private readonly List rectangleStorage; - public CircularCloudVisualizer(RectangleStorage rectangles, Size imageSize) + public CircularCloudVisualizer(List rectangles, Size imageSize) { rectangleStorage = rectangles; this.imageSize = imageSize; @@ -22,7 +22,7 @@ public CircularCloudVisualizer(RectangleStorage rectangles, Size imageSize) public void CreateImage(string? filePath = null, bool withSaveSteps = false) { - var rectangles = NormalizeSizes(rectangleStorage.GetAll()); + var rectangles = NormalizeSizes(rectangleStorage.Select(r => (Rectangle)r)); using var image = new Bitmap(imageSize.Width, imageSize.Height); using var graphics = Graphics.FromImage(image); diff --git a/cs/TagsCloudVisualization/RectangleStorage.cs b/cs/TagsCloudVisualization/RectangleStorage.cs deleted file mode 100644 index a5ea62ad9..000000000 --- a/cs/TagsCloudVisualization/RectangleStorage.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Collections.Generic; -using System.Drawing; - -namespace TagsCloudVisualization; - -public class RectangleStorage -{ - private readonly List elements = new(); - - public int Add(Rectangle r) - { - elements.Add(r); - return elements.Count - 1; - } - - public IEnumerable GetAll() - { - foreach (var rectangle in elements) yield return rectangle; - } - - public RectangleWrapper GetById(int id) - { - return elements[id]; - } - - public class RectangleWrapper - { - public RectangleWrapper(Rectangle v) - { - Value = v; - } - - private Rectangle Value { get; set; } - - public Size Size - { - get => Value.Size; - set => Value = new Rectangle(Location, value); - } - - public Point Location - { - get => Value.Location; - set => Value = new Rectangle(value, Size); - } - - public int Top => Value.Top; - public int Bottom => Value.Bottom; - public int Left => Value.Left; - public int Right => Value.Right; - - public static implicit operator RectangleWrapper(Rectangle v) - { - return new RectangleWrapper(v); - } - - public static implicit operator Rectangle(RectangleWrapper r) - { - return r.Value; - } - - public override bool Equals(object? obj) - { - return (obj as RectangleWrapper)?.Value.Equals(Value) ?? (obj as Rectangle?)?.Equals(Value) ?? false; - } - } -} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/RectangleWrapper.cs b/cs/TagsCloudVisualization/RectangleWrapper.cs new file mode 100644 index 000000000..539083c01 --- /dev/null +++ b/cs/TagsCloudVisualization/RectangleWrapper.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Drawing; + +namespace TagsCloudVisualization; + +public class RectangleWrapper +{ + public RectangleWrapper(Rectangle value) + { + Value = value; + } + + private Rectangle Value { get; set; } + + public Size Size + { + get => Value.Size; + set => Value = new Rectangle(Location, value); + } + + public Point Location + { + get => Value.Location; + set => Value = new Rectangle(value, Size); + } + + public int Top => Value.Top; + public int Bottom => Value.Bottom; + public int Left => Value.Left; + public int Right => Value.Right; + + public static implicit operator RectangleWrapper(Rectangle value) + { + return new RectangleWrapper(value); + } + + public static implicit operator Rectangle(RectangleWrapper wrapper) + { + return wrapper.Value; + } + + public override bool Equals(object? obj) + { + return (obj as RectangleWrapper)?.Value.Equals(Value) ?? (obj as Rectangle?)?.Equals(Value) ?? false; + } +} diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs index 53fdac821..33717ca84 100644 --- a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -11,7 +11,7 @@ public class CircleLayerTests { private CircleLayer currentLayer; private Size defaultRectangleSize; - private RectangleStorage storage; + private List storage; public static IEnumerable SimpleIntersectionInSector { @@ -61,10 +61,12 @@ public static IEnumerable GetDataForIntersectionTests [SetUp] public void SetUp() { - var startRadius = 5; var center = new Point(5, 5); - storage = new RectangleStorage(); - currentLayer = new CircleLayer(center, startRadius, storage); + storage = new (); + currentLayer = new CircleLayer(center, storage); + var first = new Rectangle(currentLayer.CalculateTopLeftRectangleCornerPosition(new Size(8, 6)), new Size(8, 6)); + storage.Add(first); + currentLayer.OnSuccessInsertRectangle(first); defaultRectangleSize = new Size(3, 4); } @@ -74,14 +76,14 @@ public void CircleLayer_InsertFirstForLayerRectangle_InTopRightSectorStart() var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); possibleRectangleLocation.Should() - .Be(GetCorrectRectangleLocationByExpectedSector(Sector.Top_Right, defaultRectangleSize)); + .Be(GetCorrectRectangleLocationByExpectedSector(Sector.TopRight, defaultRectangleSize)); } - [TestCase(1, Sector.Bottom_Right)] - [TestCase(2, Sector.Bottom_Left)] - [TestCase(3, Sector.Top_Left)] - [TestCase(4, Sector.Top_Right)] - [TestCase(0, Sector.Top_Right)] + [TestCase(1, Sector.BottomRight)] + [TestCase(2, Sector.BottomLeft)] + [TestCase(3, Sector.TopLeft)] + [TestCase(4, Sector.TopRight)] + [TestCase(0, Sector.TopRight)] public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int insertionsCount, Sector expected) { currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, insertionsCount); @@ -99,9 +101,9 @@ public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterTo currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, 3); var nextRectangleLocation = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4), defaultRectangleSize); - var insertedRectangleId = storage.Add(new Rectangle(nextRectangleLocation, new Size(2, 2))); + storage.Add(new Rectangle(nextRectangleLocation, new Size(2, 2))); - currentLayer.OnSuccessInsertRectangle(insertedRectangleId); + currentLayer.OnSuccessInsertRectangle(storage.Last()); currentLayer.Radius.Should().Be(9); } @@ -118,20 +120,16 @@ private Sector GetSectorByInsertionsCount(int count) return (Sector)((count - 1) % 4); } - private Point GetCorrectRectangleLocationByExpectedSector(Sector s, Size size) + private Point GetCorrectRectangleLocationByExpectedSector(Sector expected, Size size) { - switch (s) + return expected switch { - case Sector.Top_Right: - return new Point(currentLayer.Center.X, currentLayer.Center.Y - currentLayer.Radius - size.Height); - case Sector.Bottom_Right: - return new Point(currentLayer.Center.X + currentLayer.Radius, currentLayer.Center.Y); - case Sector.Bottom_Left: - return new Point(currentLayer.Center.X - size.Width, currentLayer.Center.Y + currentLayer.Radius); - default: - return new Point(currentLayer.Center.X - currentLayer.Radius - size.Width, - currentLayer.Center.Y - size.Height); - } + Sector.TopRight => new Point(currentLayer.Center.X, currentLayer.Center.Y - currentLayer.Radius - size.Height), + Sector.BottomRight => new Point(currentLayer.Center.X + currentLayer.Radius, currentLayer.Center.Y), + Sector.BottomLeft => new Point(currentLayer.Center.X - size.Width, currentLayer.Center.Y + currentLayer.Radius), + _ => new Point(currentLayer.Center.X - currentLayer.Radius - size.Width, + currentLayer.Center.Y - size.Height), + }; } [Test] @@ -207,7 +205,8 @@ private CircleLayer GetLayerAfterFewInsertionsRectangle(CircleLayer layer, int i { var location = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(i), sizes[i - 1]); var rectangleForInsert = new Rectangle(location, sizes[i - 1]); - layer.OnSuccessInsertRectangle(storage.Add(rectangleForInsert)); + storage.Add(rectangleForInsert); + layer.OnSuccessInsertRectangle(storage.Last()); } return layer; diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs index 0a8d66f17..6801e89ef 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -6,6 +6,8 @@ using NUnit.Framework.Interfaces; using System.IO; using System.Reflection; +using System.Collections.Generic; +using System.Diagnostics; namespace TagsCloudVisualization.Tests { @@ -13,13 +15,13 @@ public class CircularCloudLayouterTests { private CircularCloudLayouter layouter; private Point defaultCenter; - private RectangleStorage storage; + private List storage; [SetUp] public void SetUp() { defaultCenter = new Point(5, 5); - storage = new RectangleStorage(); + storage = []; layouter = new CircularCloudLayouter(defaultCenter, storage); } @@ -28,12 +30,12 @@ public void TearDown() { if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) { - var testObj = TestContext.CurrentContext.Test.Parent.Fixture as CircularCloudLayouterTests; + var testObj = TestContext.CurrentContext.Test.Parent?.Fixture as CircularCloudLayouterTests; var info = typeof(CircularCloudLayouterTests) .GetField("storage", BindingFlags.NonPublic | BindingFlags.Instance); - var st = info.GetValue(testObj); + var st = info?.GetValue(testObj); - var visualizator = new CircularCloudVisualization(st as RectangleStorage, new Size(1000, 1000)); + var visualizator = new CircularCloudVisualizer(st as List ?? [], new Size(1000, 1000)); var pathFile = Path.Combine(Directory.GetCurrentDirectory(), TestContext.CurrentContext.Test.Name); visualizator.CreateImage(pathFile); TestContext.Out.WriteLine($"Tag cloud visualization saved to file {pathFile}"); @@ -68,14 +70,8 @@ public void PutNextRectangle_ShouldAddRectangleToCenter_WhenRectangleFirst() } [Test] - public void PutNextRectangle_ShouldCreateFirstCircleLayer_AfterPutFirstRectangle() + public void PutNextRectangle_ShouldCreateFirstCircleLayer_AfterCreation() { - var firstRectangleSize = new Size(6, 4); - - layouter.CurrentLayer.Should().BeNull(); - - layouter.PutNextRectangle(firstRectangleSize); - layouter.CurrentLayer.Should().NotBeNull(); } @@ -103,7 +99,7 @@ public void PutRectangleOnCircleWithoutIntersection_ShouldUseCircleLayer_ForChoo var rPos = layouter.CurrentLayer.CalculateTopLeftRectangleCornerPosition(firstRectangleSize); ; - var secondRectangleLocation = layouter.PutRectangleOnCircleWithoutIntersection(storage.Add(new (rPos, firstRectangleSize))).Location; + var secondRectangleLocation = layouter.PutRectangleOnCircleWithoutIntersection(new(new(rPos, firstRectangleSize))).Location; secondRectangleLocation.Should().Be(expected); } @@ -114,24 +110,25 @@ public void PutRectangleOnCircleWithoutIntersection_ShouldPutRectangleWithoutInt var firstRectangleSize = new Size(6, 4); var expected = new Point(14, 1); layouter = new CircularCloudLayouter(defaultCenter, storage); - var sizes = new Size[] { new(4, 7), new(4, 4), new(4, 4), new(4, 4)}; + var sizes = new Size[] { new(4, 7), new(4, 4), new(4, 4), new(4, 4) }; layouter.PutNextRectangle(firstRectangleSize); layouter = InsertionsWithoutCompress(4, layouter, sizes, storage); var rectangleWithIntersection = new Rectangle(layouter.CurrentLayer.CalculateTopLeftRectangleCornerPosition(new(3, 3)), new(3, 3)); - var rectangleLocation = layouter.PutRectangleOnCircleWithoutIntersection(storage.Add(rectangleWithIntersection)).Location; + var rectangleLocation = layouter.PutRectangleOnCircleWithoutIntersection(rectangleWithIntersection).Location; rectangleLocation.Should().Be(expected); } - private CircularCloudLayouter InsertionsWithoutCompress(int insertionsCount, CircularCloudLayouter l, Size[] sizes, RectangleStorage storage) + private static CircularCloudLayouter InsertionsWithoutCompress(int insertionsCount, CircularCloudLayouter l, Size[] sizes, List storage) { for (var i = 0; i < insertionsCount; i++) { var pos = l.CurrentLayer.CalculateTopLeftRectangleCornerPosition(sizes[i]); - var r = new Rectangle(pos, sizes[i]); - l.PutRectangleOnCircleWithoutIntersection(storage.Add(r)); + var forInsertion = new Rectangle(pos, sizes[i]); + storage.Add(forInsertion); + l.PutRectangleOnCircleWithoutIntersection(forInsertion); } return l; @@ -150,4 +147,4 @@ public void PutNextRectangle_ShouldTryMoveRectangleCloserToCenter_WhenItPossible second.Location.Should().Be(expectedSecondRectangleLocation); } } -} +} \ No newline at end of file diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs index 7c8c0325c..b08d35f44 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterVisualizationTests.cs @@ -1,41 +1,44 @@ using System; +using System.Collections.Generic; using System.Drawing; -using System.IO; -using System.Linq; using NUnit.Framework; namespace TagsCloudVisualization.Tests; public class CircularCloudLayouterVisualizationTests { - private readonly Size ImageSize = new(1000, 1000); + private Size imageSize; + private Point center; + private CircularCloudVisualizer visualizer; - [Test] - public void GenerateImage() + [SetUp] + public void SetUp() { - var center = new Point(ImageSize.Width / 2, ImageSize.Height / 2); - var visualizator = new CircularCloudVisualization(GenerateRectangles(center), ImageSize); - visualizator.CreateImage(); + imageSize = new(1000, 1000); + center = new Point(imageSize.Width / 2, imageSize.Height / 2); + visualizer = new CircularCloudVisualizer(GenerateRectangles(center, 100, 10, 100), imageSize); } - private RectangleStorage GenerateRectangles(Point center) + [Test] + public void GenerateImage() { - var rnd = new Random(); - var storage = new RectangleStorage(); - var layouter = new CircularCloudLayouter(center, storage); - for (var i = 0; i < 41; i++) layouter.PutNextRectangle(new Size(rnd.Next(50, 100), rnd.Next(50, 100))); - - return storage; + visualizer.CreateImage(); } [Test] public void GenerateImageWithSaveEveryStep() + { + visualizer.CreateImage(withSaveSteps: true); + } + + private static List GenerateRectangles(Point center, int maxSize, int minSize, int count) { var rnd = new Random(); - var center = new Point(ImageSize.Width / 2, ImageSize.Height / 2); - var visualizator = new CircularCloudVisualization(new RectangleStorage(), ImageSize); - var layouter = new CircularCloudLayouter(center, new RectangleStorage()); - visualizator.CreateImageWithSaveEveryStep(layouter, - new Size[41].Select(x => new Size(15, 100)).ToArray()); + var storage = new List(); + var layouter = new CircularCloudLayouter(center, storage); + for (var i = 0; i < count; i++) layouter.PutNextRectangle(new Size(rnd.Next(minSize, maxSize), + rnd.Next(minSize, maxSize))); + + return storage; } } \ No newline at end of file From 067800a75411e2fdbd86076764173a2ca3c77b67 Mon Sep 17 00:00:00 2001 From: SomEnaMeforme Date: Thu, 21 Nov 2024 19:55:27 +0500 Subject: [PATCH 25/25] Fix intelisense and add compressor --- .../BruteForceNearestFinder.cs | 17 ++- cs/TagsCloudVisualization/CircleLayer.cs | 27 ++--- .../CircularCloudLayouter.cs | 105 ++++-------------- .../CircularCloudVisualizer.cs | 17 ++- cs/TagsCloudVisualization/CloudCompressor.cs | 67 +++++++++++ .../GraphicsExtensions.cs | 2 +- cs/TagsCloudVisualization/PointExtensions.cs | 4 +- cs/TagsCloudVisualization/RectangleWrapper.cs | 46 -------- .../Tests/BruteForceNearestFinderTests.cs | 10 +- .../Tests/CircleLayerTests.cs | 20 ++-- .../Tests/CircularCloudLayouterTests.cs | 46 ++------ 11 files changed, 143 insertions(+), 218 deletions(-) create mode 100644 cs/TagsCloudVisualization/CloudCompressor.cs delete mode 100644 cs/TagsCloudVisualization/RectangleWrapper.cs diff --git a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs index 1f94c93b4..c8f3f7053 100644 --- a/cs/TagsCloudVisualization/BruteForceNearestFinder.cs +++ b/cs/TagsCloudVisualization/BruteForceNearestFinder.cs @@ -2,15 +2,12 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Runtime.InteropServices.ComTypes; -using System.Text; -using System.Threading.Tasks; namespace TagsCloudVisualization { public class BruteForceNearestFinder { - public Rectangle? FindNearestByDirection(Rectangle r, Direction direction, IEnumerable rectangles) + public Rectangle? FindNearestByDirection(Rectangle r, Direction direction, List rectangles) { if (rectangles.FirstOrDefault() == default) return null; @@ -24,13 +21,13 @@ public class BruteForceNearestFinder public Func GetMinDistanceCalculatorBy(Direction direction) { - switch (direction) + return direction switch { - case Direction.Left: return (possibleNearest, rectangleForFind) => rectangleForFind.Left - possibleNearest.Right; - case Direction.Right: return (possibleNearest, rectangleForFind) => possibleNearest.Left - rectangleForFind.Right; - case Direction.Top: return (possibleNearest, rectangleForFind) => rectangleForFind.Top - possibleNearest.Bottom; - default: return (possibleNearest, rectangleForFind) => possibleNearest.Top - rectangleForFind.Bottom; - } + Direction.Left => (possibleNearest, rectangleForFind) => rectangleForFind.Left - possibleNearest.Right, + Direction.Right => (possibleNearest, rectangleForFind) => possibleNearest.Left - rectangleForFind.Right, + Direction.Top => (possibleNearest, rectangleForFind) => rectangleForFind.Top - possibleNearest.Bottom, + _ => (possibleNearest, rectangleForFind) => possibleNearest.Top - rectangleForFind.Bottom, + }; } } } diff --git a/cs/TagsCloudVisualization/CircleLayer.cs b/cs/TagsCloudVisualization/CircleLayer.cs index d93d94fb9..edb6a94ef 100644 --- a/cs/TagsCloudVisualization/CircleLayer.cs +++ b/cs/TagsCloudVisualization/CircleLayer.cs @@ -20,10 +20,10 @@ public enum Sector private Sector currentSector; - private readonly List storage; + private readonly List storage; private readonly List layerRectangles = []; - private CircleLayer(Point center, int radius, List storage) + private CircleLayer(Point center, int radius, List storage) { Center = center; Radius = radius; @@ -31,12 +31,12 @@ private CircleLayer(Point center, int radius, List storage) this.storage = storage; } - public CircleLayer(Point center, List storage) : this(center, 0, storage) - { - } + public CircleLayer(Point center, List storage) : this(center, 0, storage) + { } public void OnSuccessInsertRectangle() { + if (storage.Count == 0) throw new InvalidOperationException("Rectangle was not added"); if (storage.Count != 1) currentSector = GetNextClockwiseSector(); layerRectangles.Add(storage.Count - 1); if (ShouldCreateNewLayer()) @@ -97,7 +97,7 @@ private Point PutToCenter(Size rectangleSize) return new Point(rectangleX, rectangleY); } - public Point CalculateTopLeftRectangleCornerPosition(Size rectangleSize) + public Point AddNextRectangle(Size rectangleSize) { if (Radius == 0) return PutToCenter(rectangleSize); var rectangleStartPositionOnCircle = GetStartSectorPointOnCircleBySector(currentSector); @@ -129,11 +129,11 @@ private Point GetStartSectorPointOnCircleBySector(Sector s) public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rectangle intersected) { - if (IsNextPositionMoveToAnotherSector(forInsertion.Location, forInsertion.Size)) + if (IsRectangleIntersectSymmetryAxis(new Rectangle(forInsertion.Location, forInsertion.Size))) { currentSector = GetNextClockwiseSector(); if (ShouldCreateNewLayer()) CreateNextLayerAndChangeCurrentOnNext(); - forInsertion.Location = CalculateTopLeftRectangleCornerPosition(forInsertion.Size); + forInsertion.Location = AddNextRectangle(forInsertion.Size); } var nextPosition = CalculateNewPositionWithoutIntersectionBySector(currentSector, forInsertion, intersected); @@ -141,11 +141,6 @@ public Point GetRectanglePositionWithoutIntersection(Rectangle forInsertion, Rec return nextPosition; } - private bool IsNextPositionMoveToAnotherSector(Point next, Size forInsertionSize) - { - return IsRectangleIntersectSymmetryAxis(new Rectangle(next, forInsertionSize)); - } - private bool IsRectangleIntersectSymmetryAxis(Rectangle rectangle) { return (rectangle.Left < Center.X && rectangle.Right > Center.X) || @@ -262,9 +257,7 @@ private int CalculateMoveMultiplierForMoveClockwise(bool isAxisForMoveIsX, Recta } private int CalculateDistanceForMoveClockwiseToPositionWithoutIntersection( - Sector whereIntersected, - Rectangle forInsertion, - Rectangle intersected) + Sector whereIntersected, Rectangle forInsertion, Rectangle intersected) { return whereIntersected switch { @@ -275,7 +268,7 @@ private int CalculateDistanceForMoveClockwiseToPositionWithoutIntersection( }; } - private Point GetCornerNearestForCenterBySector(Sector rectangleLocationSector, Rectangle forInsertion) + private static Point GetCornerNearestForCenterBySector(Sector rectangleLocationSector, Rectangle forInsertion) { return rectangleLocationSector switch { diff --git a/cs/TagsCloudVisualization/CircularCloudLayouter.cs b/cs/TagsCloudVisualization/CircularCloudLayouter.cs index 88b741577..af4fa6261 100644 --- a/cs/TagsCloudVisualization/CircularCloudLayouter.cs +++ b/cs/TagsCloudVisualization/CircularCloudLayouter.cs @@ -7,105 +7,48 @@ namespace TagsCloudVisualization; public class CircularCloudLayouter { - private readonly Point center; - private readonly List storage = []; - private readonly BruteForceNearestFinder nearestFinder; - + private readonly List storage; + private readonly CloudCompressor compressor; + public CircleLayer CurrentLayer { get; } - public CircularCloudLayouter(Point center) - { - this.center = center; - nearestFinder = new BruteForceNearestFinder(); - CurrentLayer = new(center, storage); - } + public CircularCloudLayouter(Point center) : this(center, []) + { } - internal CircularCloudLayouter(Point center, List storage) : this(center) + internal CircularCloudLayouter(Point center, List storage) { this.storage = storage; CurrentLayer = new(center, storage); + compressor = new(center, storage); } - public Rectangle PutNextRectangle(Size rectangleSize) + public Rectangle PutNextRectangle(Size nextRectangle) { - ValidateRectangleSize(rectangleSize); - - var firstRectanglePosition = CurrentLayer.CalculateTopLeftRectangleCornerPosition(rectangleSize); - var forInsertion = new Rectangle(firstRectanglePosition, rectangleSize); - var rectangleWithOptimalPosition = OptimiseRectanglePosition(forInsertion); + ValidateRectangleSize(nextRectangle); + + var inserted = PutRectangleWithoutIntersection(nextRectangle); + var rectangleWithOptimalPosition = compressor.CompressCloudAfterInsertion(inserted); + storage.Add(rectangleWithOptimalPosition); CurrentLayer.OnSuccessInsertRectangle(); - return rectangleWithOptimalPosition; - } - private Rectangle OptimiseRectanglePosition(Rectangle forInsertion) - { - PutRectangleOnCircleWithoutIntersection(forInsertion); - return TryMoveRectangleCloserToCenter(forInsertion); + return rectangleWithOptimalPosition; } - - public Rectangle PutRectangleOnCircleWithoutIntersection(RectangleWrapper forOptimise) + + public Rectangle PutRectangleWithoutIntersection(Size forInsertionSize) { - var intersected = GetRectangleIntersection(forOptimise); + var firstRectanglePosition = CurrentLayer.AddNextRectangle(forInsertionSize); + var forInsertion = new Rectangle(firstRectanglePosition, forInsertionSize); + var intersected = GetRectangleIntersection(forInsertion); while (intersected != null && intersected.Value != default) { - var possiblePosition = CurrentLayer.GetRectanglePositionWithoutIntersection(forOptimise, intersected.Value); - forOptimise.Location = possiblePosition; - intersected = GetRectangleIntersection(forOptimise); + var possiblePosition = CurrentLayer.GetRectanglePositionWithoutIntersection(forInsertion, intersected.Value); + forInsertion.Location = possiblePosition; + intersected = GetRectangleIntersection(forInsertion); } - return forOptimise; - } - - internal Rectangle TryMoveRectangleCloserToCenter(RectangleWrapper rectangleForMoving) - { - var toCenter = GetDirectionsForMovingToCenter(rectangleForMoving); - foreach (var direction in toCenter) - { - rectangleForMoving.Location = MoveToCenterByDirection(rectangleForMoving, direction); - } - return rectangleForMoving; - } - - - private Point MoveToCenterByDirection(Rectangle forMoving, Direction toCenter) - { - var nearest = nearestFinder.FindNearestByDirection(forMoving, toCenter, storage.Select(r => (Rectangle)r)); - if (nearest == null) return forMoving.Location; - var distanceCalculator = nearestFinder.GetMinDistanceCalculatorBy(toCenter); - var distanceForMove = distanceCalculator(nearest.Value, forMoving); - return MoveByDirection(forMoving.Location, distanceForMove, toCenter); - } - - private static Point MoveByDirection(Point forMoving, int distance, Direction toCenter) - { - var factorForDistanceByX = toCenter switch - { - Direction.Left => -1, - Direction.Right => 1, - _ => 0 - }; - var factorForDistanceByY = toCenter switch - { - Direction.Top => -1, - Direction.Bottom => 1, - _ => 0 - }; - forMoving.X += distance * factorForDistanceByX; - forMoving.Y += distance * factorForDistanceByY; - - return forMoving; - } - - private List GetDirectionsForMovingToCenter(Rectangle forMoving) - { - var directions = new List(); - if (forMoving.Bottom < center.Y) directions.Add(Direction.Bottom); - if (forMoving.Left > center.X) directions.Add(Direction.Left); - if (forMoving.Right < center.X) directions.Add(Direction.Right); - if (forMoving.Top > center.Y) directions.Add(Direction.Top); - return directions; + return forInsertion; } private static void ValidateRectangleSize(Size forInsertion) @@ -117,7 +60,7 @@ private static void ValidateRectangleSize(Size forInsertion) private Rectangle? GetRectangleIntersection(Rectangle forInsertion) { if (storage.Count == 0) return null; - return storage.Select(r => (Rectangle)r).FirstOrDefault(r => forInsertion != r + return storage.FirstOrDefault(r => forInsertion != r && forInsertion.IntersectsWith(r)); } } \ No newline at end of file diff --git a/cs/TagsCloudVisualization/CircularCloudVisualizer.cs b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs index f6a28b540..9b99d7525 100644 --- a/cs/TagsCloudVisualization/CircularCloudVisualizer.cs +++ b/cs/TagsCloudVisualization/CircularCloudVisualizer.cs @@ -12,9 +12,9 @@ public class CircularCloudVisualizer private readonly Color backgroundColor = Color.White; private readonly Color rectangleColor = Color.DarkBlue; private readonly Size imageSize; - private readonly List rectangleStorage; + private readonly List rectangleStorage; - public CircularCloudVisualizer(List rectangles, Size imageSize) + public CircularCloudVisualizer(List rectangles, Size imageSize) { rectangleStorage = rectangles; this.imageSize = imageSize; @@ -22,7 +22,8 @@ public CircularCloudVisualizer(List rectangles, Size imageSize public void CreateImage(string? filePath = null, bool withSaveSteps = false) { - var rectangles = NormalizeSizes(rectangleStorage.Select(r => (Rectangle)r)); + var rectangles = rectangleStorage.Select(r => (Rectangle)r).ToArray(); + rectangles = NormalizeSizes(rectangles); using var image = new Bitmap(imageSize.Width, imageSize.Height); using var graphics = Graphics.FromImage(image); @@ -36,7 +37,7 @@ public void CreateImage(string? filePath = null, bool withSaveSteps = false) graphics.DrawRectangle(pen, nextRectangle); if (withSaveSteps) { - SaveImage(image, $"{filePath}Step{1}"); + SaveImage(image, filePath); } } SaveImage(image, filePath); @@ -51,15 +52,13 @@ private void SaveImage(Bitmap image, string? filePath = null) private Rectangle[] NormalizeSizes(IEnumerable source) { - var sourceToArray = source.ToArray(); - - var xLength = sourceToArray.Max(r => r.Right) - sourceToArray.Min(r => r.Left); - var yLength = sourceToArray.Max(r => r.Bottom) - sourceToArray.Min(r => r.Top); + var xLength = source.Max(r => r.Right) - source.Min(r => r.Left); + var yLength = source.Max(r => r.Bottom) - source.Min(r => r.Top); var factorX = GetNormalizeFactorByAxis(imageSize.Width, xLength); var factorY = GetNormalizeFactorByAxis(imageSize.Height, yLength); - return sourceToArray.Select(r => new Rectangle( + return source.Select(r => new Rectangle( new Point(r.X * factorX, r.Y * factorY), new Size(r.Width * factorX, r.Height * factorY))) .ToArray(); diff --git a/cs/TagsCloudVisualization/CloudCompressor.cs b/cs/TagsCloudVisualization/CloudCompressor.cs new file mode 100644 index 000000000..24addd20e --- /dev/null +++ b/cs/TagsCloudVisualization/CloudCompressor.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Drawing; + +namespace TagsCloudVisualization +{ + internal class CloudCompressor + { + private readonly BruteForceNearestFinder nearestFinder = new (); + private readonly Point compressionPoint; + private readonly List cloud; + + public CloudCompressor(Point compressTo, List cloud) + { + compressionPoint = compressTo; + this.cloud = cloud; + } + + public Rectangle CompressCloudAfterInsertion(Rectangle insertionRectangle) + { + var toCompressionPoint = GetDirectionsForMovingForCompress(insertionRectangle); + foreach (var direction in toCompressionPoint) + { + insertionRectangle.Location = CalculateRectangleLocationAfterCompress(insertionRectangle, direction); + } + return insertionRectangle; + } + + private Point CalculateRectangleLocationAfterCompress(Rectangle forMoving, Direction toCenter) + { + var nearest = nearestFinder.FindNearestByDirection(forMoving, toCenter, cloud); + if (nearest == null) return forMoving.Location; + var distanceCalculator = nearestFinder.GetMinDistanceCalculatorBy(toCenter); + var distanceForMove = distanceCalculator(nearest.Value, forMoving); + return MoveByDirection(forMoving.Location, distanceForMove, toCenter); + } + + private static Point MoveByDirection(Point forMoving, int distance, Direction whereMoving) + { + var factorForDistanceByX = whereMoving switch + { + Direction.Left => -1, + Direction.Right => 1, + _ => 0 + }; + var factorForDistanceByY = whereMoving switch + { + Direction.Top => -1, + Direction.Bottom => 1, + _ => 0 + }; + forMoving.X += distance * factorForDistanceByX; + forMoving.Y += distance * factorForDistanceByY; + + return forMoving; + } + + private List GetDirectionsForMovingForCompress(Rectangle forMoving) + { + var directions = new List(); + if (forMoving.Bottom < compressionPoint.Y) directions.Add(Direction.Bottom); + if (forMoving.Left > compressionPoint.X) directions.Add(Direction.Left); + if (forMoving.Right < compressionPoint.X) directions.Add(Direction.Right); + if (forMoving.Top > compressionPoint.Y) directions.Add(Direction.Top); + return directions; + } + } +} diff --git a/cs/TagsCloudVisualization/GraphicsExtensions.cs b/cs/TagsCloudVisualization/GraphicsExtensions.cs index 9f27921ef..01fc09426 100644 --- a/cs/TagsCloudVisualization/GraphicsExtensions.cs +++ b/cs/TagsCloudVisualization/GraphicsExtensions.cs @@ -11,7 +11,7 @@ public static class GraphicsExtensions { public static void DrawGrid(this Graphics graphics, int cellsCount = 100, int cellSize = 10) { - Pen p = new Pen(Color.DarkGray); + Pen p = new (Color.DarkGray); for (int y = 0; y < cellsCount; ++y) { diff --git a/cs/TagsCloudVisualization/PointExtensions.cs b/cs/TagsCloudVisualization/PointExtensions.cs index c038ba1af..51bdf6af0 100644 --- a/cs/TagsCloudVisualization/PointExtensions.cs +++ b/cs/TagsCloudVisualization/PointExtensions.cs @@ -9,9 +9,9 @@ namespace TagsCloudVisualization { public static class PointExtensions { - public static int CalculateDistanceBetween(this Point p1, Point p2) + public static int CalculateDistanceBetween(this Point current, Point other) { - return (int)Math.Ceiling(Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y))); + return (int)Math.Ceiling(Math.Sqrt((current.X - other.X) * (current.X - other.X) + (current.Y - other.Y) * (current.Y - other.Y))); } } } diff --git a/cs/TagsCloudVisualization/RectangleWrapper.cs b/cs/TagsCloudVisualization/RectangleWrapper.cs deleted file mode 100644 index 539083c01..000000000 --- a/cs/TagsCloudVisualization/RectangleWrapper.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; -using System.Drawing; - -namespace TagsCloudVisualization; - -public class RectangleWrapper -{ - public RectangleWrapper(Rectangle value) - { - Value = value; - } - - private Rectangle Value { get; set; } - - public Size Size - { - get => Value.Size; - set => Value = new Rectangle(Location, value); - } - - public Point Location - { - get => Value.Location; - set => Value = new Rectangle(value, Size); - } - - public int Top => Value.Top; - public int Bottom => Value.Bottom; - public int Left => Value.Left; - public int Right => Value.Right; - - public static implicit operator RectangleWrapper(Rectangle value) - { - return new RectangleWrapper(value); - } - - public static implicit operator Rectangle(RectangleWrapper wrapper) - { - return wrapper.Value; - } - - public override bool Equals(object? obj) - { - return (obj as RectangleWrapper)?.Value.Equals(Value) ?? (obj as Rectangle?)?.Equals(Value) ?? false; - } -} diff --git a/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs b/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs index 9610b9920..8d2eb2c18 100644 --- a/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs +++ b/cs/TagsCloudVisualization/Tests/BruteForceNearestFinderTests.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using NUnit.Framework; using FluentAssertions; using System.Drawing; @@ -22,7 +18,7 @@ public void FindNearest_ShouldReturnNull_OnEmptyRectangles() { var rectangleForFind = new Rectangle(new Point(5, 7), new Size(4, 2)); - finder.FindNearestByDirection(rectangleForFind, Direction.Top, Array.Empty()).Should().BeNull(); + finder.FindNearestByDirection(rectangleForFind, Direction.Top, []).Should().BeNull(); } [TestCase(4, 10, Direction.Top)] @@ -38,7 +34,7 @@ public void FindNearest_ShouldReturnNearestRectangleByDirection_ForArgumentRecta var addedRectangle1 = new Rectangle(new Point(2, 2), new Size(3, 4)); var addedRectangle2 = new Rectangle(new Point(5, 7), new Size(4, 2)); var rectangleForFind = new Rectangle(new Point(x, y), new Size(2, 1)); - var rectangles = new[] { addedRectangle1, addedRectangle2 }; + var rectangles = new List { addedRectangle1, addedRectangle2 }; var nearest = finder.FindNearestByDirection(rectangleForFind, direction, rectangles); diff --git a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs index 33717ca84..01ddc6ea5 100644 --- a/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircleLayerTests.cs @@ -11,7 +11,7 @@ public class CircleLayerTests { private CircleLayer currentLayer; private Size defaultRectangleSize; - private List storage; + private List storage; public static IEnumerable SimpleIntersectionInSector { @@ -54,7 +54,7 @@ public static IEnumerable GetDataForIntersectionTests { new(6, 3), new(4, 2), new(1, 1), new(4, 4) }, new Rectangle(new Point(5, -7), new Size(6, 5)), new Rectangle(new Point(5, -3), new Size(6, 3)), - new Point(12, 0)).SetName("NotChangeSector_WhenRectangleForIntersectionBottomEqualCenterY_AfterMove"); ; + new Point(12, 0)).SetName("NotChangeSector_WhenRectangleForIntersectionBottomEqualCenterY_AfterMove"); } } @@ -62,18 +62,18 @@ public static IEnumerable GetDataForIntersectionTests public void SetUp() { var center = new Point(5, 5); - storage = new (); + storage = []; currentLayer = new CircleLayer(center, storage); - var first = new Rectangle(currentLayer.CalculateTopLeftRectangleCornerPosition(new Size(8, 6)), new Size(8, 6)); + var first = new Rectangle(currentLayer.AddNextRectangle(new Size(8, 6)), new Size(8, 6)); storage.Add(first); - currentLayer.OnSuccessInsertRectangle(first); + currentLayer.OnSuccessInsertRectangle(); defaultRectangleSize = new Size(3, 4); } [Test] public void CircleLayer_InsertFirstForLayerRectangle_InTopRightSectorStart() { - var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); + var possibleRectangleLocation = currentLayer.AddNextRectangle(defaultRectangleSize); possibleRectangleLocation.Should() .Be(GetCorrectRectangleLocationByExpectedSector(Sector.TopRight, defaultRectangleSize)); @@ -88,7 +88,7 @@ public void CircleLayer_InsertRectangleInNextSector_AfterSuccessInsertion(int in { currentLayer = GetLayerAfterFewInsertionsRectangleWithSameSize(currentLayer, insertionsCount); - var possibleRectangleLocation = currentLayer.CalculateTopLeftRectangleCornerPosition(defaultRectangleSize); + var possibleRectangleLocation = currentLayer.AddNextRectangle(defaultRectangleSize); possibleRectangleLocation.Should() .Be(GetCorrectRectangleLocationByExpectedSector(expected, defaultRectangleSize)); @@ -103,7 +103,7 @@ public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterTo GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(4), defaultRectangleSize); storage.Add(new Rectangle(nextRectangleLocation, new Size(2, 2))); - currentLayer.OnSuccessInsertRectangle(storage.Last()); + currentLayer.OnSuccessInsertRectangle(); currentLayer.Radius.Should().Be(9); } @@ -111,7 +111,7 @@ public void CircleLayer_RadiusNextCircleLayer_ShouldBeIntMinDistanceFromCenterTo private CircleLayer GetLayerAfterFewInsertionsRectangleWithSameSize(CircleLayer layer, int additionsCount) { layer = GetLayerAfterFewInsertionsRectangle(layer, additionsCount, - new Size[additionsCount].Select(x => defaultRectangleSize).ToArray()); + new Size[additionsCount].Select(_ => defaultRectangleSize).ToArray()); return layer; } @@ -206,7 +206,7 @@ private CircleLayer GetLayerAfterFewInsertionsRectangle(CircleLayer layer, int i var location = GetCorrectRectangleLocationByExpectedSector(GetSectorByInsertionsCount(i), sizes[i - 1]); var rectangleForInsert = new Rectangle(location, sizes[i - 1]); storage.Add(rectangleForInsert); - layer.OnSuccessInsertRectangle(storage.Last()); + layer.OnSuccessInsertRectangle(); } return layer; diff --git a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs index 6801e89ef..cb447038b 100644 --- a/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs +++ b/cs/TagsCloudVisualization/Tests/CircularCloudLayouterTests.cs @@ -1,13 +1,11 @@ using System; using System.Drawing; -using System.Linq; using NUnit.Framework; using FluentAssertions; using NUnit.Framework.Interfaces; using System.IO; using System.Reflection; using System.Collections.Generic; -using System.Diagnostics; namespace TagsCloudVisualization.Tests { @@ -15,7 +13,7 @@ public class CircularCloudLayouterTests { private CircularCloudLayouter layouter; private Point defaultCenter; - private List storage; + private List storage; [SetUp] public void SetUp() @@ -35,9 +33,9 @@ public void TearDown() .GetField("storage", BindingFlags.NonPublic | BindingFlags.Instance); var st = info?.GetValue(testObj); - var visualizator = new CircularCloudVisualizer(st as List ?? [], new Size(1000, 1000)); + var visualizer = new CircularCloudVisualizer(st as List ?? [], new Size(1000, 1000)); var pathFile = Path.Combine(Directory.GetCurrentDirectory(), TestContext.CurrentContext.Test.Name); - visualizator.CreateImage(pathFile); + visualizer.CreateImage(pathFile); TestContext.Out.WriteLine($"Tag cloud visualization saved to file {pathFile}"); } } @@ -88,50 +86,28 @@ public void PutNextRectangle_ShouldCreateFirstCircleLayer_WithRadiusEqualHalfDia layouter.CurrentLayer.Radius.Should().Be(expected); } - [Test] - public void PutRectangleOnCircleWithoutIntersection_ShouldUseCircleLayer_ForChoosePositionForRectangle() - { - var firstRectangleSize = new Size(4, 4); - var expectedRadius = 7; - layouter = new CircularCloudLayouter(defaultCenter, storage); - var expected = new Point(defaultCenter.X, defaultCenter.Y - expectedRadius); - layouter.PutNextRectangle(firstRectangleSize); - var rPos = layouter.CurrentLayer.CalculateTopLeftRectangleCornerPosition(firstRectangleSize); - - ; - var secondRectangleLocation = layouter.PutRectangleOnCircleWithoutIntersection(new(new(rPos, firstRectangleSize))).Location; - - secondRectangleLocation.Should().Be(expected); - } - [Test] public void PutRectangleOnCircleWithoutIntersection_ShouldPutRectangleWithoutIntersection() { - var firstRectangleSize = new Size(6, 4); var expected = new Point(14, 1); - layouter = new CircularCloudLayouter(defaultCenter, storage); - var sizes = new Size[] { new(4, 7), new(4, 4), new(4, 4), new(4, 4) }; - layouter.PutNextRectangle(firstRectangleSize); - layouter = InsertionsWithoutCompress(4, layouter, sizes, storage); - var rectangleWithIntersection = - new Rectangle(layouter.CurrentLayer.CalculateTopLeftRectangleCornerPosition(new(3, 3)), new(3, 3)); - var rectangleLocation = layouter.PutRectangleOnCircleWithoutIntersection(rectangleWithIntersection).Location; + var sizes = new Size[] { new (6, 4), new(4, 7), new(4, 4), new(4, 4), new(4, 4) }; + layouter = InsertionsWithoutCompress(layouter, sizes, storage); + var rectangleLocation = layouter.PutRectangleWithoutIntersection(new(3, 3)).Location; rectangleLocation.Should().Be(expected); } - private static CircularCloudLayouter InsertionsWithoutCompress(int insertionsCount, CircularCloudLayouter l, Size[] sizes, List storage) + private static CircularCloudLayouter InsertionsWithoutCompress(CircularCloudLayouter layouter, Size[] sizes, List storage) { - for (var i = 0; i < insertionsCount; i++) + for (var i = 0; i < sizes.Length; i++) { - var pos = l.CurrentLayer.CalculateTopLeftRectangleCornerPosition(sizes[i]); - var forInsertion = new Rectangle(pos, sizes[i]); + var forInsertion = layouter.PutRectangleWithoutIntersection(sizes[i]); storage.Add(forInsertion); - l.PutRectangleOnCircleWithoutIntersection(forInsertion); + layouter.CurrentLayer.OnSuccessInsertRectangle(); } - return l; + return layouter; } [Test]