-
Notifications
You must be signed in to change notification settings - Fork 306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Рушкова Ольга2 #261
base: master
Are you sure you want to change the base?
Рушкова Ольга2 #261
Changes from 26 commits
8cae611
09cf278
9bbe39a
dfbc811
9f88c15
ce6db8c
517ccb0
3761068
4ec2ddc
fe8c9fe
d661efd
e4ca17d
8dff187
6f70c9d
a4217c3
051cb3a
9879db0
badf496
23112ec
80508bb
3eb403a
cb4cb27
9d33c50
d4550a0
067800a
4dfb06b
09726a2
9fd115b
adc2df3
066293b
0f532ed
c8f2b68
175d3ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Drawing; | ||
using System.Linq; | ||
|
||
namespace TagsCloudVisualization | ||
{ | ||
public static class BruteForceNearestFinder | ||
{ | ||
public static Rectangle? FindNearestByDirection(Rectangle r, Direction direction, List<Rectangle> rectangles) | ||
{ | ||
if (rectangles.FirstOrDefault() == default) | ||
return null; | ||
var calculator = GetMinDistanceCalculatorBy(direction); | ||
var nearestByDirection = rectangles | ||
.Select(possibleNearest => (Distance: calculator(possibleNearest, r), Nearest: possibleNearest )) | ||
.Where(el => el.Distance >= 0) | ||
.ToList(); | ||
|
||
return nearestByDirection.Count > 0 ? nearestByDirection.MinBy(el => el.Distance).Nearest : null; | ||
} | ||
|
||
public static Func<Rectangle, Rectangle, int> GetMinDistanceCalculatorBy(Direction direction) | ||
{ | ||
return direction switch | ||
{ | ||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. лучше все-таки явно енам тут обрабатывать, чтобы спецэффектов не получить, если я енам расширю |
||
}; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using System; | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public class CircleLayer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Неинформативное название. Что такое Layer? звучит как название модели. А судя по телу, это все-таки сервис |
||
{ | ||
public Point Center { get; } | ||
public int Radius { get; private set; } | ||
|
||
private const int DeltaRadius = 5; | ||
private int currentPositionDegrees; | ||
|
||
public CircleLayer(Point center) | ||
{ | ||
Center = center; | ||
Radius = 5; | ||
} | ||
|
||
private void Expand() | ||
{ | ||
Radius += DeltaRadius; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Лучше не выносить. Чтобы лишний раз по коду не бегать |
||
|
||
public Point GetNextRectanglePosition() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Протечка абстракций - сервис ничего не знает про прямоугольники, но мы его заставляем знать про это неявно |
||
{ | ||
currentPositionDegrees = GetNextPosition(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А зачем? Мы же и так возвращаем currentPositionDegrees) |
||
var positionAngleInRadians = currentPositionDegrees * Math.PI / 180.0; | ||
return new Point(Center.X + (int)Math.Ceiling(Radius * Math.Cos(positionAngleInRadians)), | ||
Center.Y + (int)Math.Ceiling(Radius * Math.Sin(positionAngleInRadians))); | ||
} | ||
|
||
private int GetNextPosition() | ||
{ | ||
if (currentPositionDegrees++ > 360) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Я даже не обратил внимания, что тут инкремент. Давай явно вынесем увеличение аргумента |
||
{ | ||
currentPositionDegrees = 0; | ||
Expand(); | ||
} | ||
return currentPositionDegrees; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Drawing; | ||
using System.Linq; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public class CircularCloudLayouter | ||
{ | ||
private readonly List<Rectangle> storage; | ||
private readonly CloudCompressor compressor; | ||
|
||
public CircleLayer CurrentLayer { get; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Почему свойство? |
||
|
||
public CircularCloudLayouter(Point center) : this(center, []) | ||
{ } | ||
|
||
internal CircularCloudLayouter(Point center, List<Rectangle> storage) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Протечка абстракций |
||
{ | ||
this.storage = storage; | ||
CurrentLayer = new(center); | ||
compressor = new(center, storage); | ||
} | ||
|
||
public Rectangle PutNextRectangle(Size nextRectangle) | ||
{ | ||
ValidateRectangleSize(nextRectangle); | ||
|
||
var inserted = PutRectangleWithoutIntersection(nextRectangle); | ||
var rectangleWithOptimalPosition = compressor.CompressCloudAfterInsertion(inserted); | ||
|
||
storage.Add(rectangleWithOptimalPosition); | ||
|
||
return rectangleWithOptimalPosition; | ||
} | ||
|
||
public Rectangle PutRectangleWithoutIntersection(Size forInsertionSize) | ||
{ | ||
var firstRectanglePosition = CurrentLayer.GetNextRectanglePosition(); | ||
var forInsertion = new Rectangle(firstRectanglePosition, forInsertionSize); | ||
var intersected = GetRectangleIntersection(forInsertion); | ||
|
||
while (intersected != null && intersected.Value != default) | ||
{ | ||
var possiblePosition = CurrentLayer.GetNextRectanglePosition(); | ||
forInsertion.Location = possiblePosition; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Лучше не менять поля у существующих объектов без острой необходимости. Будет сложнее понять что поломалось. Модель легковесная, еще и структура, почему не создать новую? |
||
intersected = GetRectangleIntersection(forInsertion); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do whlie |
||
return 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}"); | ||
} | ||
|
||
private Rectangle? GetRectangleIntersection(Rectangle forInsertion) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А если несколько пересечений? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GetOrDefault тогда уж There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Так ли нам важен прямоугольник, с которым пересекается? Или сам факт все таки? |
||
{ | ||
if (storage.Count == 0) return null; | ||
return storage.FirstOrDefault(r => forInsertion != r | ||
&& forInsertion.IntersectsWith(r)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
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 List<Rectangle> rectangleStorage; | ||
|
||
public CircularCloudVisualizer(List<Rectangle> rectangles, Size imageSize) | ||
{ | ||
rectangleStorage = rectangles; | ||
this.imageSize = imageSize; | ||
} | ||
|
||
public void CreateImage(string? filePath = null, bool withSaveSteps = false) | ||
{ | ||
var rectangles = rectangleStorage.ToArray(); | ||
|
||
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++) | ||
{ | ||
var nextRectangle = rectangles[i]; | ||
graphics.DrawRectangle(pen, nextRectangle); | ||
Comment on lines
+36
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Прямоугольники рисуются 1к1 к размеру изображения. Из-за этого получим, что при достаточно большом количестве прямоугольников визуализация поломается |
||
if (withSaveSteps) | ||
{ | ||
SaveImage(image, filePath); | ||
} | ||
} | ||
SaveImage(image, filePath); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Логику по рисованию и сохранению лучше разделить по SRP.
|
||
|
||
private void SaveImage(Bitmap image, string? filePath = null) | ||
{ | ||
var rnd = new Random(); | ||
filePath ??= Path.Combine(Path.GetTempPath(), $"testImage{rnd.Next()}.png"); | ||
image.Save(filePath, ImageFormat.Png); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
using System.Collections.Generic; | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization | ||
{ | ||
internal class CloudCompressor | ||
{ | ||
private readonly BruteForceNearestFinder nearestFinder = new (); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BruteForceNearestFinder - статический класс, не может быть полем. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Соответственно проект не соберется даже |
||
private readonly Point compressionPoint; | ||
private readonly List<Rectangle> cloud; | ||
|
||
public CloudCompressor(Point compressTo, List<Rectangle> 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<Direction> GetDirectionsForMovingForCompress(Rectangle forMoving) | ||
{ | ||
var directions = new List<Direction>(); | ||
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; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Кажется, облако не получается достаточно плотным и круглым как раз из-за не очень оптимально выбранного алгоритма сжатия. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Я бы предложил двигать максимально по вектору, как достаточно простое и похожее решение. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace TagsCloudVisualization | ||
{ | ||
public enum Direction | ||
{ | ||
Left, | ||
Right, | ||
Top, | ||
Bottom | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 (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); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 current, Point other) | ||
{ | ||
return (int)Math.Ceiling(Math.Sqrt((current.X - other.X) * (current.X - other.X) + (current.Y - other.Y) * (current.Y - other.Y))); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>disable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<StartupObject>TagsCloudVisualization.Program</StartupObject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="6.12.2" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> | ||
<PackageReference Include="NUnit" Version="4.2.2" /> | ||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" /> | ||
<PackageReference Include="System.Drawing.Common" Version="9.0.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Folder Include="Images\ImagesTest\" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System.Collections.Generic; | ||
using NUnit.Framework; | ||
using FluentAssertions; | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.Tests | ||
{ | ||
public class BruteForceNearestFinderTests | ||
{ | ||
[Test] | ||
public void FindNearest_ShouldReturnNull_OnEmptyRectangles() | ||
{ | ||
var rectangleForFind = new Rectangle(new Point(5, 7), new Size(4, 2)); | ||
|
||
BruteForceNearestFinder.FindNearestByDirection(rectangleForFind, Direction.Top, []).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 List<Rectangle> { addedRectangle1, addedRectangle2 }; | ||
|
||
var nearest = BruteForceNearestFinder.FindNearestByDirection(rectangleForFind, direction, rectangles); | ||
|
||
nearest.Should().Be(isFirstNearest ? addedRectangle1 : addedRectangle2); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Чет пересуложненное возвращаемое значение. Почему мы не можем сразу давать результат по дирекшну?