Skip to content
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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions cs/TagsCloudVisualization/CircularCloudLayouter.cs
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется, что в этом классе смешались две ответственности - он и располагает прямоугольники, и умеет с файловой системой работать. Давай оставим один лейаутер и отдельную зависимость, которая будет уметь сохранять файл

{
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);

Choose a reason for hiding this comment

The 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);
}
}
29 changes: 29 additions & 0 deletions cs/TagsCloudVisualization/LayoutGenerator.cs
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);
}
}
9 changes: 9 additions & 0 deletions cs/TagsCloudVisualization/Program.cs
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)));
16 changes: 16 additions & 0 deletions cs/TagsCloudVisualization/README.md
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)
32 changes: 32 additions & 0 deletions cs/TagsCloudVisualization/RectangleLayouter.cs
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;
}
119 changes: 119 additions & 0 deletions cs/TagsCloudVisualization/RectangleShifter.cs
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)))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Перед вызовом метода ShiftRectangleToCenter ты в цикле генерировал точки, пока не добрался до возможности вставить прямоугольник без пересечения с другими прямоугольниками. То есть к моменту вызова текущего метода мы точно уверены, что прямоугольник ни с чем уже не пересекается и ветка внутри if точно не будет выполнена. Так что, если у тебя нет планов на этот код, можешь его смело грохать))

{
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;

Choose a reason for hiding this comment

The 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);
}
}
41 changes: 41 additions & 0 deletions cs/TagsCloudVisualization/SpiralGenerator.cs
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;
}
}
18 changes: 18 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudVisualization.csproj
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" />

Choose a reason for hiding this comment

The 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>
Binary file added cs/TagsCloudVisualization/layout1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cs/TagsCloudVisualization/layout2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cs/TagsCloudVisualization/layout3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading