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

Шевелев Георгий #226

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4562f97
Наметил примерно конструкцию проекта и тесты
Crazy1beatch Dec 4, 2023
8ddfe06
Добавил Архимедову спираль
Crazy1beatch Dec 5, 2023
5b716cf
Интегрировал спираль в layouter и подправил тесты
Crazy1beatch Dec 5, 2023
a528b58
Добавил отрисовку облака тегов, подправил расположение прямоугольников
Crazy1beatch Dec 5, 2023
f220213
Вынес тесты в отдельный проект
Crazy1beatch Dec 5, 2023
b05e987
Добавил readme по требованию тз
Crazy1beatch Dec 5, 2023
9e8abff
Поменял раскрасску на прямоугольники сплошной заливки
Crazy1beatch Dec 6, 2023
eff096f
Добавил тест на проверку плотности закраски и поправил неймспейсы
Crazy1beatch Dec 6, 2023
44ae382
Фикс чистокода и IDisposable
Crazy1beatch Dec 8, 2023
796eb5a
Убрал слова из общего словаря
Crazy1beatch Dec 8, 2023
aa61ae1
Фикс чистокода тестов, и соотношения сторон прямоугольников тегов
Crazy1beatch Dec 8, 2023
b22d6cd
Запретил использование отрицательных размеров картинки
Crazy1beatch Dec 8, 2023
1e1caf0
Добавил дополнительное смещение к центру для увеличения плотности
Crazy1beatch Dec 8, 2023
2dbde8e
Исправил тест на плотность, чтобы окружность полностью включала все п…
Crazy1beatch Dec 8, 2023
29fd4d6
Поменял неймспейсы тестов, рассчёт радиуса окружности и добавил отрис…
Crazy1beatch Dec 10, 2023
af7a46d
Фикс чистокода и области видимости centralPoint
Crazy1beatch Dec 10, 2023
67fa44a
Добавил тест на проверку, что центр сохраняется неизменным
Crazy1beatch Dec 10, 2023
d0b7429
Добавил сдвиг по диагонали
Crazy1beatch Dec 10, 2023
a3e8b1e
Фикс логики рассчёта радиуса окружности
Crazy1beatch Dec 10, 2023
c373da4
Исправил логику рассчёта радиуса окружности, убрал рандом из тестов, …
Crazy1beatch Dec 11, 2023
ae7ed65
Убрал свойство CentralPoint из интерфейса CircularCloudLayouter
Crazy1beatch Dec 11, 2023
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
19 changes: 19 additions & 0 deletions cs/TagsCloudVisualization/ArchimedeanSpiral.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Drawing;

namespace TagsCloudVisualization
{
public class ArchimedeanSpiral(Point centralPoint, double scale = 1)
{
private double angle { get; set; }
private const double DeltaAngle = Math.PI / 180;

public Point GetNextPoint()
{
var newX = (int)(centralPoint.X + scale * angle * Math.Cos(angle));
var newY = (int)(centralPoint.Y + scale * angle * Math.Sin(angle));
angle += DeltaAngle;

return new Point(newX, newY);
}
}
}
78 changes: 78 additions & 0 deletions cs/TagsCloudVisualization/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.Drawing;

namespace TagsCloudVisualization
{
public class CircularCloudLayouter(Point centerPoint)
{
public Point CenterPoint { get; } = centerPoint;
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
public IReadOnlyList<Rectangle> Rectangles => rectangles.AsReadOnly();
private readonly List<Rectangle> rectangles = [];
private readonly ArchimedeanSpiral spiral = new(centerPoint);
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved

public Rectangle PutNextRectangle(Size rectangleSize)
{
if (rectangleSize.Height <= 0 || rectangleSize.Width <= 0)
throw new ArgumentException(
$"rectangleSize with zero or negative height or width is prohibited!",
nameof(rectangleSize)
);
while (true)
{
var nextPoint = spiral.GetNextPoint();
var rectangle = new Rectangle(
new Point(nextPoint.X - rectangleSize.Width / 2, nextPoint.Y - rectangleSize.Height / 2),
rectangleSize
);
if (CheckIfIntersectsWithOthers(rectangle)) continue;
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
rectangle = GetCloserToCenterRectangle(rectangle);
rectangles.Add(rectangle);
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
break;
}

return rectangles[^1];
}

private bool CheckIfIntersectsWithOthers(Rectangle rectangle) =>
rectangles.Any(x => x.IntersectsWith(rectangle));

private Rectangle GetCloserToCenterRectangle(Rectangle rectangle)
{
var direction = GetDirection(rectangle);
for (var i = 0; i < direction.Count; i++)
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
{
var newRectangle = GetMovedRectangle(rectangle, direction[i].X, direction[i].Y);
while (!CheckIfIntersectsWithOthers(newRectangle))
{
if (CenterPoint.X - newRectangle.Size.Width / 2 == newRectangle.X
|| CenterPoint.Y - newRectangle.Size.Height / 2 == newRectangle.Y)
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
break;
rectangle = newRectangle;
newRectangle = GetMovedRectangle(rectangle, direction[i].X, direction[i].Y);
}
}

return rectangle;
}

private List<(int X, int Y)> GetDirection(Rectangle rectangle)
{
var horizontalDiffer = CenterPoint.X - rectangle.Size.Width / 2 - rectangle.X;
var verticalDiffer = CenterPoint.Y - rectangle.Size.Height / 2 - rectangle.Y;
var directions = new List<(int X, int Y)>();
if (horizontalDiffer != 0)
directions.Add((horizontalDiffer > 0 ? 1 : -1, 0));
if (CenterPoint.Y - rectangle.Size.Height / 2 != rectangle.Y)
directions.Add((0, verticalDiffer > 0 ? 1 : -1));
return directions;
}

private static Rectangle GetMovedRectangle(Rectangle rectangle, int xDelta, int yDelta) =>
new(
new Point(
rectangle.X + xDelta,
rectangle.Y + yDelta
),
rectangle.Size
);
}
}
25 changes: 25 additions & 0 deletions cs/TagsCloudVisualization/Drawer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Drawing;

namespace TagsCloudVisualization
{
public class Drawer
{
public static Image GetImage(Size size, IEnumerable<Rectangle> rectangles)
{
if (size.Width <= 0 || size.Height <= 0)
throw new ArgumentException("size width and height should be positive", "size");
var image = new Bitmap(size.Width, size.Height);
using (var gr = Graphics.FromImage(image))
{
gr.Clear(Color.Black);
using (var brush = new SolidBrush(Color.White))
{
foreach (var rectangle in rectangles)
gr.FillRectangle(brush, rectangle);
}
}

return image;
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions cs/TagsCloudVisualization/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Drawing;

namespace TagsCloudVisualization
{
internal class Program
{
private static void Main()
{
var decImage = DrawDecreasingSize();
var rndImage = DrawRandomSize();
var sameImage = DrawSameSize();

decImage.Save("CloudTag_DecreasingSize.png");
rndImage.Save("CloudTag_RandomSize.png");
sameImage.Save("CloudTag_SameSize.png");
}

private static Image DrawDecreasingSize()
{
var layouter = new CircularCloudLayouter(new Point(340, 340));
layouter.PutNextRectangle(new Size(200, 80));

for (var i = 0; i < 20; i++)
layouter.PutNextRectangle(new Size(80, 40));

for (var i = 0; i < 200; i++)
layouter.PutNextRectangle(new Size(40, 20));

return Drawer.GetImage(new Size(340 * 2, 340 * 2), layouter.Rectangles);
}

private static Image DrawSameSize()
{
var layouter = new CircularCloudLayouter(new Point(340, 340));
for (var i = 0; i < 200; i++)
layouter.PutNextRectangle(new Size(50, 25));

return Drawer.GetImage(new Size(340 * 2, 340 * 2), layouter.Rectangles);
}

private static Image DrawRandomSize()
{
var layouter = new CircularCloudLayouter(new Point(340, 340));
var rnd = new Random();
for (var i = 0; i < 200; i++)
{
var width = rnd.Next(30, 60);
layouter.PutNextRectangle(new Size(width, rnd.Next(width / 2 - 10, width / 2)));
}

return Drawer.GetImage(new Size(340 * 2, 340 * 2), layouter.Rectangles);
}
}
}
10 changes: 10 additions & 0 deletions cs/TagsCloudVisualization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Примеры работы программы:

### Облако тегов уменьшающихся размеров тегов
![CloudTag_DecreasingSize.png](Drawings%2FCloudTag_DecreasingSize.png)

### Облакр тегов рандомного размера
![CloudTag_RandomSize.png](Drawings%2FCloudTag_RandomSize.png)

### Облако тегов одинаковых размеров
![CloudTag_SameSize.png](Drawings%2FCloudTag_SameSize.png)
15 changes: 15 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudVisualization.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NUnit.ConsoleRunner" Version="3.16.3" />
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
</ItemGroup>

</Project>
25 changes: 25 additions & 0 deletions cs/TagsCloudVisualizationTests/TagsCloudVisualizationTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\TagsCloudVisualization\TagsCloudVisualization.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Drawing;
using FluentAssertions;
using NUnit.Framework;
using TagsCloudVisualization;

namespace TagsCloudVisualizationTests.UnitTests
{
[TestFixture]
public class ArchimedeanSpiral_Should
{
[TestCaseSource(typeof(TestDataArchimedeanSpiral), nameof(TestDataArchimedeanSpiral.Different_CenterPoints))]
public void ReturnCenterPoint_WhenFirstTime_GetNextPoint(Point point)
{
var spiral = new ArchimedeanSpiral(point);
spiral.GetNextPoint().Should().BeEquivalentTo(point);
}

[TestCaseSource(typeof(TestDataArchimedeanSpiral),
nameof(TestDataArchimedeanSpiral.DifferentIterationsAdded_ExpectedPoints))]
public void ReturnsCorrectPoint_When(int iterations, Point expectedPoint)
{
var spiral = new ArchimedeanSpiral(new Point());
for (var i = 0; i < iterations; i++)
spiral.GetNextPoint();

spiral.GetNextPoint().Should().BeEquivalentTo(expectedPoint);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Drawing;
using FluentAssertions;
using NUnit.Framework;
using TagsCloudVisualization;

namespace TagsCloudVisualizationTests.UnitTests
{
class CircularCloudLayouter_Should
{
[TestCaseSource(typeof(TestDataCircularCloudLayouter),
nameof(TestDataCircularCloudLayouter.ZeroOrLessHeightOrWidth_Size))]
public void Throw_WhenPutNewRectangle_WidthOrHeightLessEqualsZero(Size size)
{
var action = new Action(() => new CircularCloudLayouter(new Point()).PutNextRectangle(size));
action.Should().Throw<ArgumentException>()
.Which.Message.Should().Contain("zero or negative height or width");
}

[Test]
public void RectanglesEmpty_AfterCreation()
{
var layouter = new CircularCloudLayouter(new Point());
layouter.Rectangles.Should().BeEmpty();
}

[TestCaseSource(typeof(TestDataArchimedeanSpiral), nameof(TestDataArchimedeanSpiral.Different_CenterPoints))]
public void Add_FirstRectangle_ToCenter(Point center)
{
var layouter = new CircularCloudLayouter(center);
layouter.PutNextRectangle(new Size(10, 2));
layouter.Rectangles.Should().HaveCount(1)
.And.BeEquivalentTo(new Rectangle(
new Point(center.X - 10 / 2, center.Y - 2 / 2), new Size(10, 2)));
}

[Test]
public void AddSeveralRectangles_Correctly()
{
var amount = 25;
var layouter = CreateValidator_WithSeveralRectangles(amount);
layouter.Rectangles.Should().HaveCount(amount);
}

[TestCaseSource(typeof(TestDataArchimedeanSpiral), nameof(TestDataArchimedeanSpiral.Different_CenterPoints))]
public void AddSeveralRectangles_DoNotIntersect(Point point)
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
{
var layouter = CreateValidator_WithSeveralRectangles(25);

var rectangles = layouter.Rectangles;
for (var i = 1; i < rectangles.Count; i++)
rectangles.Skip(i).All(x => !rectangles[i - 1].IntersectsWith(x)).Should().Be(true);
}

[Test]
public void DensityTest()
{
var layouter = new CircularCloudLayouter(new Point(500, 500));
for (var i = 0; i < 2000; i++)
layouter.PutNextRectangle(new Size(35, 15));
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
var rectanglesSquare = 0;
var radius = 0;
foreach (var rectangle in layouter.Rectangles)
{
rectanglesSquare += rectangle.Width * rectangle.Height;
var x = Math.Abs(rectangle.X) + rectangle.Width / 2 - layouter.CenterPoint.X;
var y = Math.Abs(rectangle.Y) + rectangle.Height / 2 - layouter.CenterPoint.Y;
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
radius = Math.Max(radius, (int)Math.Sqrt(x * x + y * y));
LevShisterov marked this conversation as resolved.
Show resolved Hide resolved
}
var circleSquare = Math.PI * radius * radius;
(rectanglesSquare / circleSquare).Should().BeGreaterOrEqualTo(0.95);
}

private static CircularCloudLayouter CreateValidator_WithSeveralRectangles(int amount)
{
var layouter = new CircularCloudLayouter(new Point());
for (var i = 1; i < amount + 1; i++)
{
layouter.PutNextRectangle(new Size(i * 20, i * 10));
}

return layouter;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Drawing;
using NUnit.Framework;

namespace TagsCloudVisualizationTests.UnitTests
{
public class TestDataArchimedeanSpiral
{
public static IEnumerable<TestCaseData> Different_CenterPoints()
{
yield return new TestCaseData(new Point(0, 0)).SetName("(0, 0) center");
yield return new TestCaseData(new Point(343, 868)).SetName("(343, 868) center");
yield return new TestCaseData(new Point(-343, -868)).SetName("(-343, -868) center");
}

public static IEnumerable<TestCaseData> DifferentIterationsAdded_ExpectedPoints()
{
yield return new TestCaseData(0, new Point(0, 0)).SetName("0 iterations, central point");
yield return new TestCaseData(90, new Point(0, (int)(Math.PI / 2))).SetName("90 iterations, half PI");
yield return new TestCaseData(180, new Point((int)(-Math.PI), 0)).SetName("180 iterations, PI");
yield return new TestCaseData(270, new Point(0, (int)(-Math.PI * 3 / 2))).SetName("270 iterations, 3/2 PI");
yield return new TestCaseData(360, new Point((int)(2 * Math.PI), 0)).SetName("360 iterations, double PI");
yield return new TestCaseData(450, new Point(0, (int)(Math.PI * 5 / 2))).SetName("450 iterations, 5/2 PI");
yield return new TestCaseData(540, new Point((int)(-3 * Math.PI), 0)).SetName("540 iterations, triple PI");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Drawing;
using NUnit.Framework;

namespace TagsCloudVisualizationTests.UnitTests
{
public class TestDataCircularCloudLayouter
{
public static IEnumerable<TestCaseData> ZeroOrLessHeightOrWidth_Size()
{
yield return new TestCaseData(new Size(0, 1)).SetName("Zero_Width_Size");
yield return new TestCaseData(new Size(1, 0)).SetName("Zero_Height_Size");
yield return new TestCaseData(new Size(0, 0)).SetName("Zero_Width_And_Height_Size");
yield return new TestCaseData(new Size(int.MinValue, 1)).SetName("Negative_Width_Size");
yield return new TestCaseData(new Size(1, int.MinValue)).SetName("Negative_Height_Size");
yield return new TestCaseData(new Size(int.MinValue, int.MinValue))
.SetName("Negative_Width_And_Height_Size");
}
}
}
Loading