-
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
Черных Илья #243
base: master
Are you sure you want to change the base?
Черных Илья #243
Changes from 22 commits
9590cf2
ef5b9d0
c3361d0
9e42a71
bd1809c
0747ad9
f792052
c17e0f9
09efbed
2b2988d
5e32c12
7aa664f
42db42a
07fd1a3
0bd2619
2e4a6c9
f92ef27
5c9d9b7
353a7f5
21dfe52
66bfc9e
492bdc1
19ccfbf
ad8646b
e736a26
66c278f
61330d3
c35b934
d9abf43
3274439
d3f04e6
44309c3
938e8d8
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,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="7.0.0-alpha.5" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> | ||
<PackageReference Include="NUnit" Version="4.2.2" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\TagsCloudVisualization\TagsCloudVisualization.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
using System.Drawing; | ||
using System.Drawing.Imaging; | ||
using FluentAssertions; | ||
using NUnit.Framework; | ||
using NUnit.Framework.Interfaces; | ||
using TagsCloudVisualization; | ||
using TagsCloudVisualization.LayoutRectanglesInCloudAlgorithms; | ||
|
||
namespace CircularCloudLayouterTests; | ||
|
||
[TestFixture] | ||
public class CircularLayoutRectanglesInCloudAlgorithmTests | ||
{ | ||
private ICircularCloudLayouter cloudLayouter; | ||
private List<Rectangle> addedRectangles; | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
cloudLayouter = new CircularCloudLayouter(new Point(0, 0), new CircularLayoutRectanglesInCloudAlgorithm()); | ||
addedRectangles = []; | ||
} | ||
|
||
[TearDown] | ||
public void TearDown() | ||
{ | ||
if (TestContext.CurrentContext.Result.Outcome.Status != TestStatus.Failed) | ||
return; | ||
|
||
var pathImageStored = TestContext.CurrentContext.TestDirectory + @"\imageFailedTests"; | ||
|
||
if (!Directory.Exists(pathImageStored)) | ||
{ | ||
Directory.CreateDirectory(pathImageStored); | ||
} | ||
|
||
var testName = TestContext.CurrentContext.Test.Name; | ||
|
||
var bitmap = VisualizationCircularCloudLayout.DrawLayout(addedRectangles, 10); | ||
|
||
VisualizationCircularCloudLayout.SaveLayout(bitmap, pathImageStored, $"{testName}.png", ImageFormat.Png); | ||
|
||
Console.WriteLine($@"Tag cloud visualization saved to file {pathImageStored}\{testName}.png"); | ||
} | ||
|
||
[Test] | ||
public void PutNextRectangle_ShouldRectangleInCenter_WhenAddFirstRectangle() | ||
{ | ||
var rectangleSize = new Size(10, 15); | ||
|
||
var addedRectangle = cloudLayouter.PutNextRectangle(rectangleSize); | ||
var expectedRectangleStartPoint = new Point(-addedRectangle.Width / 2, - addedRectangle.Height / 2); | ||
|
||
addedRectangle.Location.Should().BeEquivalentTo(expectedRectangleStartPoint); | ||
} | ||
|
||
[TestCase(10, 5, 15)] | ||
[TestCase(50, 30, 100)] | ||
[TestCase(100, 5, 50)] | ||
public void PutNextRectangle_ShouldAddedRectanglesDoNotIntersect(int countRectangles, int minSideLength, | ||
int maxSideLength) | ||
{ | ||
var rectangleSizes = GenerateRectangleSizes(countRectangles, minSideLength, maxSideLength); | ||
|
||
addedRectangles.AddRange(rectangleSizes.Select(t => cloudLayouter.PutNextRectangle(t))); | ||
|
||
for (var i = 0; i < addedRectangles.Count-1; i++) | ||
{ | ||
addedRectangles | ||
.Skip(i + 1) | ||
.Any(addedRectangle => addedRectangle.IntersectsWith(addedRectangles[i])) | ||
.Should() | ||
.BeFalse(); | ||
} | ||
} | ||
|
||
[TestCase(10, 5, 15)] | ||
[TestCase(50, 30, 100)] | ||
[TestCase(100, 5, 50)] | ||
public void CircleShape_ShouldBeCloseToCircle_WhenAddMultipleRectangles(int countRectangles, int minSideLength, | ||
int maxSideLength) | ||
{ | ||
var rectangleSizes = GenerateRectangleSizes(countRectangles, minSideLength, maxSideLength); | ||
|
||
addedRectangles.AddRange(rectangleSizes.Select(t => cloudLayouter.PutNextRectangle(t))); | ||
|
||
var distances = addedRectangles | ||
.Select(rectangle => CalculateDistanceBetweenCenterRectangleAndCenterCloud(rectangle, new Point(0, 0))) | ||
.ToArray(); | ||
|
||
for (var i = 1; i < distances.Length; i++) | ||
{ | ||
var distanceBetweenRectangles = CalculateDistanceBetweenRectangles(addedRectangles[i], addedRectangles[i - 1]); | ||
distances[i].Should().BeApproximately(distances[i - 1], distanceBetweenRectangles); | ||
} | ||
} | ||
|
||
|
||
private static List<Size> GenerateRectangleSizes(int countRectangles, int minSideLength, int maxSideLength) | ||
{ | ||
var random = new Random(); | ||
|
||
var generatedSizes = Enumerable.Range(0, countRectangles) | ||
.Select(_ => new Size( | ||
random.Next(minSideLength, maxSideLength), | ||
random.Next(minSideLength, maxSideLength)) | ||
) | ||
.ToList(); | ||
|
||
return generatedSizes; | ||
} | ||
|
||
private static double CalculateDistanceBetweenCenterRectangleAndCenterCloud(Rectangle rectangle, Point center) | ||
{ | ||
var centerRectangle = new Point(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2); | ||
|
||
return CalculateDistanceBetweenPoints(centerRectangle, center); | ||
} | ||
|
||
private static double CalculateDistanceBetweenRectangles(Rectangle rectangle1, Rectangle rectangle2) | ||
{ | ||
var centerRectangle1 = new Point(rectangle1.X + rectangle1.Width / 2, rectangle1.Y + rectangle1.Height / 2); | ||
var centerRectangle2 = new Point(rectangle2.X + rectangle2.Width / 2, rectangle2.Y + rectangle2.Height / 2); | ||
|
||
return CalculateDistanceBetweenPoints(centerRectangle1, centerRectangle2); | ||
} | ||
|
||
private static double CalculateDistanceBetweenPoints(Point point1, Point point2) | ||
{ | ||
return Math.Sqrt(Math.Pow(point1.X - point2.X, 2) + Math.Pow(point1.Y - point2.Y, 2)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System.Drawing; | ||
using TagsCloudVisualization.LayoutRectanglesInCloudAlgorithms; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public class CircularCloudLayouter : ICircularCloudLayouter | ||
{ | ||
private readonly Point center; | ||
private readonly ILayoutRectanglesInCloudAlgorithm layoutRectanglesInCloudAlgorithm; | ||
|
||
public CircularCloudLayouter(Point center, ILayoutRectanglesInCloudAlgorithm layoutRectanglesInCloudAlgorithm) | ||
{ | ||
this.center = center; | ||
this.layoutRectanglesInCloudAlgorithm = layoutRectanglesInCloudAlgorithm; | ||
} | ||
|
||
public Rectangle PutNextRectangle(Size rectangleSize) | ||
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. Сейчас твой метод кладет прямоугольники всегда одним и тем же способом, но в идеале было бы хорошо алгоритм раскладки вынести куда-то в сторону и передавать его в конструктор, чтобы разные экземпляры CircularCloudLayouter могли раскладывать разными способами. Конкретно в этой задаче способ нужен только один, но на будущее лучше сделать класс гибче, и чтобы он почти ничего не знал о том, по какому алгоритму выбираются точки для прямоугольников |
||
{ | ||
var rectangle = layoutRectanglesInCloudAlgorithm.PutNextRectangle(rectangleSize, center); | ||
|
||
return rectangle; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public interface ICircularCloudLayouter | ||
{ | ||
Rectangle PutNextRectangle(Size rectangleSize); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.LayoutRectanglesInCloudAlgorithms; | ||
|
||
public class CircularLayoutRectanglesInCloudAlgorithm : ILayoutRectanglesInCloudAlgorithm | ||
{ | ||
private readonly List<Rectangle> addedRectangles = []; | ||
private double currentAngleOfCircle; | ||
private double currentRadiusOfCircle; | ||
private const double OneDegree = Math.PI / 180; | ||
private const double FullCircleRotation = 2 * Math.PI; | ||
|
||
public Rectangle PutNextRectangle(Size rectangleSize, Point cloudCenter) | ||
{ | ||
Rectangle rectangle; | ||
|
||
do | ||
{ | ||
var x = cloudCenter.X + (int)(currentRadiusOfCircle * Math.Cos(currentAngleOfCircle)) - rectangleSize.Width / 2; | ||
var y = cloudCenter.Y + (int)(currentRadiusOfCircle * Math.Sin(currentAngleOfCircle)) - rectangleSize.Height / 2; | ||
rectangle = new Rectangle(new Point(x, y), rectangleSize); | ||
|
||
// увеличиваем угол на 1 градус | ||
currentAngleOfCircle += OneDegree; | ||
|
||
// проверяем не прошли ли целый круг | ||
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 (currentAngleOfCircle > FullCircleRotation) | ||
{ | ||
currentAngleOfCircle = 0; | ||
currentRadiusOfCircle++; | ||
} | ||
} while (IntersectWithAddedRectangles(rectangle)); | ||
|
||
addedRectangles.Add(rectangle); | ||
|
||
return rectangle; | ||
} | ||
|
||
private bool IntersectWithAddedRectangles(Rectangle rectangle) | ||
{ | ||
return addedRectangles.Any(addedRectangle => addedRectangle.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. В этом классе нужно оставить только вычисление точек, а логику с прямоугольниками унести в CircularCloudLayouter |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.LayoutRectanglesInCloudAlgorithms; | ||
|
||
public interface ILayoutRectanglesInCloudAlgorithm | ||
{ | ||
Rectangle PutNextRectangle(Size rectangleSize, Point cloudCenter); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
![Визуализация десяти прямоугольников](images/visualization10Rectangles.png) | ||
![Визуализация пятидесяти прямоугольников](images/visualization50Rectangles.png) | ||
![Визуализация ста прямоугольников](images/visualization100Rectangles.png) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Folder Include="images\" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using System.Drawing; | ||
using System.Drawing.Imaging; | ||
|
||
namespace TagsCloudVisualization; | ||
#pragma warning disable CA1416 | ||
|
||
public class VisualizationCircularCloudLayout | ||
{ | ||
public static Bitmap DrawLayout(List<Rectangle> rectangles, int paddingFromBorders) | ||
{ | ||
var minX = rectangles.Min(rectangle => rectangle.Left); | ||
var minY = rectangles.Min(rectangle => rectangle.Top); | ||
var maxX = rectangles.Max(rectangle => rectangle.Right); | ||
var maxY = rectangles.Max(rectangle => rectangle.Bottom); | ||
var width = maxX - minX + paddingFromBorders; | ||
var height = maxY - minY + paddingFromBorders; | ||
|
||
var random = new Random(); | ||
|
||
var bitmap = new Bitmap(width, height); | ||
using var graphics = Graphics.FromImage(bitmap); | ||
|
||
graphics.Clear(Color.White); | ||
|
||
foreach (var rectangle in rectangles) | ||
{ | ||
var shiftedRectangle = rectangle with | ||
{ | ||
X = rectangle.X - minX + paddingFromBorders, | ||
Y = rectangle.Y - minY + paddingFromBorders | ||
}; | ||
|
||
var randomColor = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256)); | ||
|
||
var brush = new SolidBrush(randomColor); | ||
graphics.FillRectangle(brush, shiftedRectangle); | ||
graphics.DrawRectangle(Pens.Black, shiftedRectangle); | ||
} | ||
|
||
return bitmap; | ||
} | ||
|
||
public static void SaveLayout(Bitmap bitmap, string filePath, string fileName, ImageFormat imageFormat) | ||
{ | ||
bitmap.Save(Path.Combine(filePath, fileName), imageFormat); | ||
} | ||
} |
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.
У тебя ту довольно много статических методов, которые можно вынести в отдельный статический класс-хелпер