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

Ватлин Алексей #211

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
145 changes: 145 additions & 0 deletions TagCloudVisualizationTests/CircularCloudLayouterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using NUnit.Framework.Interfaces;
using FluentAssertions;
using System.Drawing;
using TagsCloudVisualization.CloudLayouter;
using TagsCloudVisualization.Visualizers;
using NUnit.Framework;

namespace TagsCloudVisualization.Tests.CircularCloudLayouterTests;

[TestFixture, NonParallelizable]
public class CircularCloudLayouterTests
{
private CircularCloudLayouter cloudLayouter;
private const int imageWidth = 1500;
private const int imageHeight = 1500;

[SetUp]
public void Init()
{
var center = new Point(imageWidth / 2, imageHeight / 2);
cloudLayouter = new CircularCloudLayouter(center);
cloudLayouter.GenerateCloud(100);
}

[TearDown]
public void TearDown()
{
if (TestContext.CurrentContext.Result.Outcome.Status != TestStatus.Failed)
return;
var directory = "FailedVisualisations";
var path = Path.Combine(directory, $"{TestContext.CurrentContext.Test.Name}_visualisation.png");
var visuliser = new ImageCreator();
visuliser.CreateBitmap(cloudLayouter.GeneratedRectangles, new(imageWidth, imageHeight), path);
Console.WriteLine($"Tag cloud visualization saved to file {path}");
}

[TestCase(0, 1, TestName = "WhenWidthIsZero")]
[TestCase(1, 0, TestName = "WhenHeightIsZero")]
[TestCase(-1, 1, TestName = "WhenWidthIsNegative")]
[TestCase(1, -1, TestName = "WhenHeightIsNegative")]
public void PutNextRectangle_ShouldThrowArgumentException(int width, int height)
{
var size = new Size(width, height);

var action = () => cloudLayouter.PutNextRectangle(size);

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

[Test]
public void PutNextRectangle_FirstRectangle_ShouldBeInCenter()
{
cloudLayouter = new CircularCloudLayouter(cloudLayouter.Center);
var rectangleSize = new Size(10, 10);
var expectedRectangle = new Rectangle(
cloudLayouter.Center.X - rectangleSize.Width / 2,
cloudLayouter.Center.Y - rectangleSize.Height / 2,
rectangleSize.Width,
rectangleSize.Height
);

var actualRectangle = cloudLayouter.PutNextRectangle(rectangleSize);

actualRectangle.Should().BeEquivalentTo(expectedRectangle);
}

[Test, Parallelizable(ParallelScope.Self)]
[Repeat(10)]
public void PutNextRectangle_Rectangles_ShouldNotHaveIntersects() =>
AreRectanglesHaveIntersects(cloudLayouter.GeneratedRectangles).Should().BeFalse();

[Test]
[Repeat(10)]
public void PutNextRectangle_CloudCenterMust_ShouldBeInLayoterCenter()
{
var maxRectangleSize = 10;
var expectedDiscrepancy = maxRectangleSize;
var minRectangleSize = 1;
var center = cloudLayouter.Center;

cloudLayouter.GenerateCloud(100, minRectangleSize, maxRectangleSize);

var actualCenter = GetCenterOfAllRectangles(cloudLayouter.GeneratedRectangles);
actualCenter.X.Should().BeInRange(center.X - expectedDiscrepancy, center.X + expectedDiscrepancy);
actualCenter.Y.Should().BeInRange(center.Y - expectedDiscrepancy, center.Y + expectedDiscrepancy);
}

[Test]
[Repeat(10)]
public void PutNextRectangle_RectanglesDensity_ShouldBeMax()
{
var expectedDensity = 0.45;
var center = cloudLayouter.Center;
var rectangles = cloudLayouter.GeneratedRectangles;

var rectanglesArea = rectangles.Sum(rect => rect.Width * rect.Height);

var radius = GetMaxDistanceBetweenRectangleAndCenter(rectangles);
var circleArea = Math.PI * radius * radius;
var density = rectanglesArea / circleArea;
density.Should().BeGreaterThanOrEqualTo(expectedDensity);
}

private Point GetCenterOfAllRectangles(List<Rectangle> rectangles)
{
var top = rectangles.Max(r => r.Top);
var right = rectangles.Max(r => r.Right);
var bottom = rectangles.Min(r => r.Bottom);
var left = rectangles.Min(r => r.Left);
var x = left + (right - left) / 2;
var y = bottom + (top - bottom) / 2;
return new(x, y);
}

private double GetMaxDistanceBetweenRectangleAndCenter(List<Rectangle> rectangles)
{
var center = GetCenterOfAllRectangles(rectangles);
double maxDistance = -1;
foreach (var rectangle in rectangles)
{
var corners = new Point[4]
{
new(rectangle.Top, rectangle.Left),
new(rectangle.Bottom, rectangle.Left),
new(rectangle.Top, rectangle.Right),
new(rectangle.Bottom, rectangle.Right)
};
var distance = corners.Max(p => GetDistanceBetweenPoints(p, center));
maxDistance = Math.Max(maxDistance, distance);
}
return maxDistance;
}

private static bool AreRectanglesHaveIntersects(List<Rectangle> rectangles)
{
for (var i = 0; i < rectangles.Count; i++)
for (var j = i + 1; j < rectangles.Count; j++)
if (rectangles[i].IntersectsWith(rectangles[j]))
return true;
return false;
}

private static double GetDistanceBetweenPoints(Point point1, Point point2)
=> Math.Sqrt(Math.Pow(point1.X - point2.X, 2) + Math.Pow(point1.Y - point2.Y, 2));
}
43 changes: 43 additions & 0 deletions TagCloudVisualizationTests/SpiralPointsGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using FluentAssertions;
using System.Drawing;
using TagsCloudVisualization.PointsGenerators;
using NUnit.Framework;

namespace TagsCloudVisualization.Tests.SpiralPointsGeneratorTests;

[TestFixture, Parallelizable(ParallelScope.All)]
public class SpiralPointsGeneratorTests
{
[TestCase(0, 1, TestName = "WhenStepIsZero")]
[TestCase(1, 0, TestName = "WhenAngleOffsetIsZero")]
public void Constructor_ShouldThrowArgumentException(double step, double angleOffset)
{
var act = () => new SpiralPointsGenerator(new Point(0, 0), step, angleOffset);

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

[TestCaseSource(nameof(GeneratePointsTestCases))]
public void GetNextPointPosition_ShouldReturnCorrectPoint(double step, double angleOffset, int pointNumber, Point expectedPoint)
{
var pointsGenerator = new SpiralPointsGenerator(new Point(0, 0), step, angleOffset);

var actualPoint = pointsGenerator.GetNextPointPosition();
for (var i = 0; i < pointNumber - 1; i++)
actualPoint = pointsGenerator.GetNextPointPosition();

actualPoint.Should().Be(expectedPoint);
}

public static TestCaseData[] GeneratePointsTestCases =
{
new TestCaseData(0.1, 0.1, 1, new Point(0, 0)),
new TestCaseData(1, 1, 1, new Point(0, 0)),
new TestCaseData(1, 1, 3, new Point(0, 1)),
new TestCaseData(1, 1, 5, new Point(-2, -3)),
new TestCaseData(3, 1, 3, new Point(-2, 5)),
new TestCaseData(3, 1, 5, new Point(-7, -9)),
new TestCaseData(5, 1, 3, new Point(-4, 9)),
new TestCaseData(5, 1, 5, new Point(-13, -15)),
};
}
34 changes: 34 additions & 0 deletions TagCloudVisualizationTests/TagCloudVisualizationTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<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="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.3.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.4.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>

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

<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>

</Project>
31 changes: 31 additions & 0 deletions TagCloudVisualizationTests/TagCloudVisualizationTests.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35327.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagsCloudVisualization", "..\TagsCloudVisualization\TagsCloudVisualization.csproj", "{F131A5F9-48B5-465B-B9CB-CD459DE76A8E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagCloudVisualizationTests", "TagCloudVisualizationTests.csproj", "{312FED20-0051-4DD3-B787-4601BE046411}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F131A5F9-48B5-465B-B9CB-CD459DE76A8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F131A5F9-48B5-465B-B9CB-CD459DE76A8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F131A5F9-48B5-465B-B9CB-CD459DE76A8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F131A5F9-48B5-465B-B9CB-CD459DE76A8E}.Release|Any CPU.Build.0 = Release|Any CPU
{312FED20-0051-4DD3-B787-4601BE046411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{312FED20-0051-4DD3-B787-4601BE046411}.Debug|Any CPU.Build.0 = Debug|Any CPU
{312FED20-0051-4DD3-B787-4601BE046411}.Release|Any CPU.ActiveCfg = Release|Any CPU
{312FED20-0051-4DD3-B787-4601BE046411}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {24AAD118-F9C5-4FA5-B5F6-2A75C584C1D1}
EndGlobalSection
EndGlobal
50 changes: 50 additions & 0 deletions TagsCloudVisualization/CloudLayouter/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Drawing;
using TagsCloudVisualization.CloudLayouter.PointsGenerators;
using TagsCloudVisualization.PointsGenerators;

namespace TagsCloudVisualization.CloudLayouter;

public class CircularCloudLayouter : ICircularCloudLayouter
{
public Point Center { get; }
public List<Rectangle> GeneratedRectangles { get; }
private readonly IPointsGenerator spiral;

public CircularCloudLayouter(Point center)
{
Center = center;
GeneratedRectangles = new List<Rectangle>();
spiral = new SpiralPointsGenerator(center);
}

public CircularCloudLayouter(Point center, int step, int angleOffset) : this(center)
{
spiral = new SpiralPointsGenerator(center, step, angleOffset);
}

public Rectangle PutNextRectangle(Size rectangleSize)
{
if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0)
throw new ArgumentException($"{nameof(rectangleSize)} height and width must be greater than zero");
Rectangle rectangle;
do
{
rectangle = GetNextRectangle(rectangleSize);
} while (GeneratedRectangles.Any(rectangle.IntersectsWith));
GeneratedRectangles.Add(rectangle);
return rectangle;
}

private Rectangle GetNextRectangle(Size rectangleSize)
{
var rectanglePosition = spiral.GetNextPointPosition();
return CreateRectangle(rectanglePosition, rectangleSize);
}

private static Rectangle CreateRectangle(Point center, Size rectangleSize)
{
var x = center.X - rectangleSize.Width / 2;
var y = center.Y - rectangleSize.Height / 2;
return new Rectangle(x, y, rectangleSize.Width, rectangleSize.Height);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagsCloudVisualization.CloudLayouter;

public interface ICircularCloudLayouter
{
Rectangle PutNextRectangle(Size rectangleSize);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Drawing;

namespace TagsCloudVisualization.CloudLayouter;

public static class ICircularCloudLayouterExtensions
{
public static void GenerateCloud(
this ICircularCloudLayouter cloudLayouter,
int rectanglesNumber = 1000,
int minRectangleSize = 10,
int maxRectangleSize = 50)
{
var random = new Random();
new Rectangle[rectanglesNumber]
.Select(x => new Size(
random.Next(minRectangleSize, maxRectangleSize),
random.Next(minRectangleSize, maxRectangleSize)))
.Select(size => cloudLayouter.PutNextRectangle(size))
.ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagsCloudVisualization.CloudLayouter.PointsGenerators;

public interface IPointsGenerator
{
Point GetNextPointPosition();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Drawing;

namespace TagsCloudVisualization.CloudLayouter.PointsGenerators;

public class SpiralPointsGenerator : IPointsGenerator
{
private readonly double step;
private readonly double angleOffset;
private readonly Point center;
private double currentAngle = 0;

public SpiralPointsGenerator(Point center, double step = 0.1, double angleOffset = 0.1)
{
if (step == 0 || angleOffset == 0)
throw new ArgumentException($"Step and angleOffset must not be zero");
this.center = center;
this.step = step;
this.angleOffset = angleOffset;
}

public Point GetNextPointPosition()
{
var radius = step * currentAngle;
var x = (int)(center.X + radius * Math.Cos(currentAngle));
var y = (int)(center.Y + radius * Math.Sin(currentAngle));
currentAngle += angleOffset;
return new(x, y);
}
}
12 changes: 12 additions & 0 deletions TagsCloudVisualization/ConsoleClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TagsCloudVisualization
{
internal class ConsoleClient
{
}
}
Loading