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

Зубков Андрей #210

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
38 changes: 38 additions & 0 deletions cs/TagCloud/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Drawing;

namespace TagCloud;

public class CircularCloudLayouter : ICircularCloudLayouter
{
private readonly IPointGenerator pointGenerator;
public IList<Rectangle> Rectangles { get; }

public CircularCloudLayouter(IPointGenerator pointGenerator)
{
Rectangles = new List<Rectangle>();
this.pointGenerator = pointGenerator;
}

public Rectangle PutNextRectangle(Size rectangleSize)
{
if (rectangleSize.Height <= 0 || rectangleSize.Width <= 0)
{
throw new ArgumentException("Rectangle size parameters should be positive");
}

var rectangle = new Rectangle(pointGenerator.GetNextPoint(), rectangleSize);
while (!CanPlaceRectangle(rectangle))
{
rectangle = new Rectangle(pointGenerator.GetNextPoint() - rectangleSize / 2, rectangleSize);
}

Rectangles.Add(rectangle);

return rectangle;
}

private bool CanPlaceRectangle(Rectangle newRectangle)
{
return !Rectangles.Any(rectangle => rectangle.IntersectsWith(newRectangle));
}
}
46 changes: 46 additions & 0 deletions cs/TagCloud/CloudDrawer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Drawing;

namespace TagCloud;

public static class CloudDrawer
{
public static Bitmap DrawTagCloud(IList<Rectangle> rectangles, int border = 10)
{
var imageSize = GetImageSize(rectangles, border);
var shift = GetImageShift(rectangles, border);
var image = new Bitmap(imageSize.Width, imageSize.Height);
using var graphics = Graphics.FromImage(image);
foreach (var rectangle in rectangles)
{
var shiftedCoordinates = new Point(rectangle.X - shift.Width, rectangle.Y - shift.Height);
using var brush = new SolidBrush(GetRandomColor());
graphics.FillRectangle(brush, new Rectangle(shiftedCoordinates, rectangle.Size));
}

return image;
}

private static Size GetImageShift(IList<Rectangle> rectangles, int border)
{
var minX = rectangles.Min(rectangle => rectangle.Left);
var minY = rectangles.Min(rectangle => rectangle.Top);

return new Size(minX - border, minY - border);
}

private static Size GetImageSize(IList<Rectangle> rectangles, int border)
{
var minX = rectangles.Min(rectangle => rectangle.Left);
var maxX = rectangles.Max(rectangle => rectangle.Right);
var minY = rectangles.Min(rectangle => rectangle.Top);
var maxY = rectangles.Max(rectangle => rectangle.Bottom);

return new Size(maxX - minX + 2 * border, maxY - minY + 2 * border);
}

private static Color GetRandomColor()
{
var random = new Random();
return Color.FromArgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255));
}
}
8 changes: 8 additions & 0 deletions cs/TagCloud/ICircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagCloud;

public interface ICircularCloudLayouter
{
Rectangle PutNextRectangle(Size rectangleSize);
}
8 changes: 8 additions & 0 deletions cs/TagCloud/IPointGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagCloud;

public interface IPointGenerator
{
Point GetNextPoint();
}
36 changes: 36 additions & 0 deletions cs/TagCloud/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Drawing;

namespace TagCloud
{
public class Program
{
private const int Width = 1920;
private const int Height = 1080;

static void Main(string[] args)
{
var layouter = new CircularCloudLayouter(new SpiralGenerator(new Point(Width / 2, Height / 2), 1, 0.01));

var random = new Random();

for (var i = 0; i < 150; i++)
{
layouter.PutNextRectangle(new Size(50 + random.Next(0, 100), 50 + random.Next(0, 100)));
}

var filename = "Sample";
var path = @$"{Environment.CurrentDirectory}\..\..\..\Samples";
var absPath = Path.GetFullPath(path);

if (!Directory.Exists(absPath))
{
Directory.CreateDirectory(absPath);
}

absPath += @$"\{filename}.png";

using var bitmap = CloudDrawer.DrawTagCloud(layouter.Rectangles);
bitmap.Save(absPath);
}
}
}
Binary file added cs/TagCloud/Samples/150 Equal rectangles.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/TagCloud/Samples/150 Random rectangles.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/TagCloud/Samples/150 Squares.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/TagCloud/Samples/50 Random rectangles.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/TagCloud/Samples/500 Random rectangles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions cs/TagCloud/Samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Layout examples

#### 150 Equal reactangles:
![150_equal_rectangles](https://github.com/HardreaM/tdd/blob/master/cs/TagCloud/Samples/150%20Equal%20rectangles.png)

#### 50 Random reactangles:
![50_random_rectangles](https://github.com/HardreaM/tdd/blob/master/cs/TagCloud/Samples/50%20Random%20rectangles.png)

#### 150 Random reactangles:
![150_random_rectangles](https://github.com/HardreaM/tdd/blob/master/cs/TagCloud/Samples/150%20Random%20rectangles.png)

#### 500 Random reactangles:
![500_random_rectangles](https://github.com/HardreaM/tdd/blob/master/cs/TagCloud/Samples/500%20Random%20rectangles.png)

#### 150 Squares:
![150_squares](https://github.com/HardreaM/tdd/blob/master/cs/TagCloud/Samples/150%20Squares.png)
30 changes: 30 additions & 0 deletions cs/TagCloud/SpiralGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Drawing;

namespace TagCloud;

public class SpiralGenerator : IPointGenerator
{
private readonly Point startPoint;
private readonly int spiralDensity;
private readonly double angleShift;
private double currentAngle;

public SpiralGenerator(Point startPoint, int spiralDensity = 1, double angleShift = 0.1)
{
if (startPoint.X < 0 || startPoint.Y < 0)
throw new ArgumentException("Spiral center point coordinates should be non-negative");
this.startPoint = startPoint;
this.spiralDensity = spiralDensity;
this.angleShift = angleShift;
}

public Point GetNextPoint()
{
var radius = spiralDensity * currentAngle;
var x = (int)(Math.Cos(currentAngle) * radius);
var y = (int)(Math.Sin(currentAngle) * radius);
currentAngle += angleShift;

return new Point(startPoint.X + x, startPoint.Y + y);
}
}
14 changes: 14 additions & 0 deletions cs/TagCloud/TagCloud.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

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

</Project>
96 changes: 96 additions & 0 deletions cs/TagCloudTests/CircularCloudLayouter_Should.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using NUnit.Framework.Interfaces;

[TestFixture]
public class CircularCloudLayouter_Should
{
private const int Width = 1920;
private const int Height = 1080;
private CircularCloudLayouter sut;


[SetUp]
public void Setup()
{
sut = new CircularCloudLayouter(new SpiralGenerator(new Point(Width / 2, Height / 2)));
}

[TearDown]
public void TearDown()
{
if (TestContext.CurrentContext.Result.Outcome == ResultState.Failure)
{
var bitmap = CloudDrawer.DrawTagCloud(sut.Rectangles);

var path = @$"{Environment.CurrentDirectory}\..\..\..\FailedTests\{this.GetType()}";
var absPath = Path.GetFullPath(path);

if (!Directory.Exists(absPath))
{
Directory.CreateDirectory(absPath);
}

var fileName = TestContext.CurrentContext.Test.Name;
absPath += @$"\{fileName}.png";

bitmap.Save(absPath);

TestContext.Out.WriteLine($"Tag cloud visualization saved to file <{absPath}>");
}
}

[Test]
public void HaveEmptyRectanglesList_WhenCreated()
{
sut.Rectangles.Count.Should().Be(0);
}

[TestCase(1, TestName = "One rectangle")]
[TestCase(10, TestName = "10 rectangles")]
public void HaveAllRectangles_WhichWerePut(int rectanglesCount)
{
for (var i = 0; i < rectanglesCount; i++)
{
sut.PutNextRectangle(new Size(2, 3));
}

sut.Rectangles.Count.Should().Be(rectanglesCount);
}

[TestCase(0, 5, TestName = "Width is zero")]
[TestCase(5, 0, TestName = "Height is zero")]
[TestCase(-5, 5, TestName = "Width is negative")]
[TestCase(5, -5, TestName = "Height is negative")]
public void ThrowException_OnInvalidSizeArguments(int width, int height)
{
Action action = () => { sut.PutNextRectangle(new Size(0, 0)); };

action.Should().Throw<ArgumentException>();
}

[Test]
public void ContainNonIntersectingRectangles()
{
var random = new Random();

for (var i = 0; i < 50; i++)
{
sut.PutNextRectangle(new Size(50 + random.Next(0, 100), 50 + random.Next(0, 100)));
}

HasIntersectedRectangles(sut.Rectangles).Should().Be(false);
}

private bool HasIntersectedRectangles(IList<Rectangle> rectangles)
{
for (var i = 0; i < rectangles.Count - 1; i++)
{
for (var j = i + 1; j < rectangles.Count; j++)
{
if (rectangles[i].IntersectsWith(rectangles[j]))
return true;
}
}

return false;
}
}
44 changes: 44 additions & 0 deletions cs/TagCloudTests/SpiralGenerator_Should.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[TestFixture]
public class SpiralGenerator_Should
{
private const int Width = 1920;
private const int Height = 1080;
private SpiralGenerator sut;
private Point startPoint;

[SetUp]
public void Setup()
{
startPoint = new Point(Width / 2, Height / 2);
sut = new SpiralGenerator(startPoint);
}

[Test]
public void ReturnCenterPoint_OnFirstCall()
{
sut.GetNextPoint().Should().BeEquivalentTo(startPoint);
}

[Test]
public void ReturnDifferentPoints_AfterMultipleCalls()
{
var spiralPoints = new HashSet<Point>();

for (var i = 0; i < 50; i++)
{
spiralPoints.Add(sut.GetNextPoint());
}

spiralPoints.Count.Should().BeGreaterThan(1);
}

[TestCase(-1, 1, TestName = "X is negative")]
[TestCase(1, -1, TestName = "Y is negative")]
[TestCase(-1, -1, TestName = "X and Y are negative")]
public void ThrowException_OnInvalidCenterPoint(int x, int y)
{
Action action = () => { new SpiralGenerator(new Point(x, y)); };

action.Should().Throw<ArgumentException>();
}
}
21 changes: 21 additions & 0 deletions cs/TagCloudTests/TagCloudTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</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" />
</ItemGroup>

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

</Project>
4 changes: 4 additions & 0 deletions cs/TagCloudTests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
global using NUnit.Framework;
global using FluentAssertions;
global using TagCloud;
global using System.Drawing;
Loading