-
Notifications
You must be signed in to change notification settings - Fork 307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Савенко Дмитрий #258
base: master
Are you sure you want to change the base?
Савенко Дмитрий #258
Changes from 4 commits
42176a5
dabe017
558d45e
24d230c
ac52e03
4d69018
2291eb9
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,55 @@ | ||
using System.Drawing; | ||
using System.Drawing.Imaging; | ||
using System.IO; | ||
using TagsCloudVisualization; | ||
|
||
public class CircularCloudLayouter | ||
{ | ||
private readonly RectangleLayouter rectangleLayouter; | ||
|
||
public CircularCloudLayouter(Point center) | ||
{ | ||
rectangleLayouter = new RectangleLayouter(center); | ||
} | ||
|
||
public Rectangle PutNextRectangle(Size rectangleSize) | ||
{ | ||
return rectangleLayouter.PutNextRectangle(rectangleSize); | ||
} | ||
|
||
public List<Rectangle> GetRectangles() | ||
{ | ||
return rectangleLayouter.GetRectangles(); | ||
} | ||
|
||
public void SaveImage(string filename, Size imageSize) | ||
{ | ||
using var bitmap = new Bitmap(imageSize.Width, imageSize.Height); | ||
using var graphics = Graphics.FromImage(bitmap); | ||
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. Круто, что про юзинги не забыл |
||
graphics.Clear(Color.Black); | ||
|
||
var random = new Random(); | ||
foreach (var rectangle in GetRectangles()) | ||
{ | ||
var color = Color.FromArgb( | ||
random.Next(128, 255), | ||
random.Next(128, 255), | ||
random.Next(128, 255) | ||
); | ||
|
||
using var brush = new SolidBrush(Color.FromArgb(180, color)); | ||
using var pen = new Pen(color, 2f); | ||
|
||
var rect = new Rectangle( | ||
rectangle.X + imageSize.Width / 2, | ||
rectangle.Y + imageSize.Height / 2, | ||
rectangle.Width, | ||
rectangle.Height); | ||
|
||
graphics.FillRectangle(brush, rect); | ||
graphics.DrawRectangle(pen, rect); | ||
} | ||
|
||
bitmap.Save(filename, ImageFormat.Png); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public class LayoutGenerator | ||
{ | ||
private readonly Point center; | ||
private readonly Size imageSize; | ||
|
||
public LayoutGenerator(Point center, Size imageSize) | ||
{ | ||
this.center = center; | ||
this.imageSize = imageSize; | ||
} | ||
|
||
public void GenerateLayout(string outputFileName, int rectangleCount, Func<Random, Size> sizeGenerator) | ||
{ | ||
var layouter = new CircularCloudLayouter(center); | ||
var random = new Random(); | ||
|
||
for (var i = 0; i < rectangleCount; i++) | ||
{ | ||
var size = sizeGenerator(random); | ||
layouter.PutNextRectangle(size); | ||
} | ||
|
||
layouter.SaveImage(outputFileName, imageSize); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using System.Drawing; | ||
using TagsCloudVisualization; | ||
|
||
|
||
var generator = new LayoutGenerator(new Point(0, 0), new Size(800, 600)); | ||
|
||
generator.GenerateLayout("layout1.png", 100, random => new Size(random.Next(20, 100), random.Next(10, 50))); | ||
generator.GenerateLayout("layout2.png", 150, random => new Size(50, 20)); | ||
generator.GenerateLayout("layout3.png", 200, random => new Size(random.Next(10, 30), random.Next(10, 30))); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Circular Cloud Layouter | ||
|
||
|
||
### Раскладка 1 | ||
|
||
![Layout 1](layout1.png) | ||
|
||
|
||
### Раскладка 2 | ||
|
||
![Layout 2](layout2.png) | ||
|
||
|
||
### Раскладка 3 | ||
|
||
![Layout 3](layout3.png) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public class RectangleLayouter(Point center) | ||
{ | ||
private readonly SpiralGenerator spiralGenerator = new(center); | ||
private readonly RectangleShifter rectangleShifter = new(center); | ||
private readonly List<Rectangle> rectangles = new(); | ||
|
||
public Rectangle PutNextRectangle(Size rectangleSize) | ||
{ | ||
if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) | ||
throw new ArgumentException("Размеры прямоугольника должны быть положительными."); | ||
|
||
Rectangle newRectangle; | ||
do | ||
{ | ||
var point = spiralGenerator.GetNextPoint(); | ||
var location = new Point( | ||
point.X - rectangleSize.Width / 2, | ||
point.Y - rectangleSize.Height / 2); | ||
newRectangle = new Rectangle(location, rectangleSize); | ||
} while (rectangles.Any(r => r.IntersectsWith(newRectangle))); | ||
|
||
newRectangle = rectangleShifter.ShiftRectangleToCenter(newRectangle, rectangles); | ||
rectangles.Add(newRectangle); | ||
return newRectangle; | ||
} | ||
|
||
public List<Rectangle> GetRectangles() => rectangles; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public class RectangleShifter | ||
{ | ||
private readonly Point center; | ||
|
||
public RectangleShifter(Point center) | ||
{ | ||
this.center = center; | ||
} | ||
|
||
public Rectangle ShiftRectangleToCenter(Rectangle rectangle, List<Rectangle> rectangles) | ||
{ | ||
if (rectangles.Any(r => r.IntersectsWith(rectangle))) | ||
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. Перед вызовом метода |
||
{ | ||
rectangle = FindNearestNonIntersectingPosition(rectangle, rectangles); | ||
if (rectangle == Rectangle.Empty) | ||
throw new InvalidOperationException("Невозможно разместить прямоугольник без пересечений."); | ||
} | ||
|
||
return MoveRectangleTowardsCenter(rectangle, rectangles); | ||
} | ||
|
||
private Rectangle MoveRectangleTowardsCenter(Rectangle rectangle, List<Rectangle> rectangles) | ||
{ | ||
var shiftedRectangle = rectangle; | ||
var directionToCenter = GetDirectionToCenter(shiftedRectangle); | ||
|
||
while (directionToCenter != Point.Empty) | ||
{ | ||
var nextLocation = new Point( | ||
shiftedRectangle.Location.X + Math.Sign(directionToCenter.X), | ||
shiftedRectangle.Location.Y + Math.Sign(directionToCenter.Y)); | ||
|
||
var nextRectangle = new Rectangle(nextLocation, shiftedRectangle.Size); | ||
|
||
if (rectangles.Any(r => r.IntersectsWith(nextRectangle))) | ||
break; | ||
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. На каждой итерации цикла сдвига ближе к центру эта проверка затрачивает серьезные ресурсы. Конечно, можно сейчас подзапариться за оптимизацию, но хочется делать это только после того как будет уверенность, что этот метод вообще нужен Ты проверял, он правда более плотное облако генерирует? Может быть даже без шифтера получалось достаточно плотное облако? |
||
|
||
shiftedRectangle = nextRectangle; | ||
directionToCenter = GetDirectionToCenter(shiftedRectangle); | ||
} | ||
|
||
return shiftedRectangle; | ||
} | ||
|
||
|
||
private Rectangle FindNearestNonIntersectingPosition(Rectangle rectangle, List<Rectangle> rectangles) | ||
{ | ||
var queue = new Queue<Point>(); | ||
var visited = new HashSet<Point>(); | ||
queue.Enqueue(rectangle.Location); | ||
visited.Add(rectangle.Location); | ||
|
||
var directions = GetDirections(); | ||
|
||
while (queue.Count > 0) | ||
{ | ||
var currentLocation = queue.Dequeue(); | ||
var currentRectangle = new Rectangle(currentLocation, rectangle.Size); | ||
|
||
if (IsNonIntersecting(currentRectangle, rectangles)) | ||
return currentRectangle; | ||
|
||
EnqueueAdjacentPositions(queue, visited, currentLocation, directions); | ||
} | ||
|
||
return Rectangle.Empty; | ||
} | ||
|
||
private Point[] GetDirections() | ||
{ | ||
return new[] | ||
{ | ||
new Point(-1, 0), | ||
new Point(1, 0), | ||
new Point(0, -1), | ||
new Point(0, 1) | ||
}; | ||
} | ||
|
||
private bool IsNonIntersecting(Rectangle currentRectangle, List<Rectangle> rectangles) | ||
{ | ||
return !rectangles.Any(r => r.IntersectsWith(currentRectangle)); | ||
} | ||
|
||
private void EnqueueAdjacentPositions(Queue<Point> queue, HashSet<Point> visited, Point currentLocation, Point[] directions) | ||
{ | ||
foreach (var dir in directions) | ||
{ | ||
var nextLocation = new Point(currentLocation.X + dir.X, currentLocation.Y + dir.Y); | ||
|
||
if (visited.Contains(nextLocation)) | ||
{ | ||
continue; | ||
} | ||
|
||
queue.Enqueue(nextLocation); | ||
visited.Add(nextLocation); | ||
} | ||
} | ||
|
||
private Point GetDirectionToCenter(Rectangle rectangle) | ||
{ | ||
var rectangleCenter = new Point( | ||
rectangle.Left + rectangle.Width / 2, | ||
rectangle.Top + rectangle.Height / 2); | ||
|
||
var directionX = center.X - rectangleCenter.X; | ||
var directionY = center.Y - rectangleCenter.Y; | ||
|
||
if (directionX == 0 && directionY == 0) | ||
return Point.Empty; | ||
|
||
return new Point(directionX, directionY); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public class SpiralGenerator | ||
{ | ||
private readonly Point center; | ||
private double phi; | ||
private readonly double spiralStep; | ||
private readonly double deltaPhi; | ||
private Point? lastGeneratedPoint; | ||
|
||
public SpiralGenerator(Point center, double spiralStep = 1, double deltaPhiDegrees = (Math.PI / 180)) | ||
{ | ||
if (spiralStep <= 0) | ||
throw new ArgumentException("Шаг спирали должен быть больше 0"); | ||
|
||
this.center = center; | ||
phi = 0; | ||
this.spiralStep = spiralStep; | ||
deltaPhi = deltaPhiDegrees; | ||
} | ||
|
||
public Point GetNextPoint() | ||
{ | ||
Point newPoint; | ||
|
||
do | ||
{ | ||
var radius = spiralStep * phi; | ||
var x = (int)Math.Round(radius * Math.Cos(phi)) + center.X; | ||
var y = (int)Math.Round(radius * Math.Sin(phi)) + center.Y; | ||
phi += deltaPhi; | ||
|
||
newPoint = new Point(x, y); | ||
} while (newPoint == lastGeneratedPoint); | ||
|
||
lastGeneratedPoint = newPoint; | ||
return newPoint; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="5.10.3" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> | ||
<PackageReference Include="NUnit" Version="4.2.2" /> | ||
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. Эти пакеты не нужны в проекте без тестов |
||
<PackageReference Include="System.Drawing.Common" Version="9.0.0-rc.2.24474.1" /> | ||
</ItemGroup> | ||
|
||
</Project> |
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.
Кажется, что в этом классе смешались две ответственности - он и располагает прямоугольники, и умеет с файловой системой работать. Давай оставим один лейаутер и отдельную зависимость, которая будет уметь сохранять файл