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

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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Binary file added cs/Images/1000_0,1_0,1_TagCloud.jpg
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/Images/1000_0,1_20_TagCloud.jpg
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/Images/1000_0,1_5_TagCloud.jpg
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/Images/1000_1_0,1_TagCloud.jpg
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/Images/1000_1_1_TagCloud.jpg
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/Images/1000_20_0,001_TagCloud.jpg
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/Images/1000_20_0,1_TagCloud.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions cs/TagsCloudVisualization/CloudLayouter/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Drawing;
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();
}
}
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagsCloudVisualization.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.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);
}
}
22 changes: 22 additions & 0 deletions cs/TagsCloudVisualization/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Drawing;
using TagsCloudVisualization.Visualizers;
using TagsCloudVisualization.CloudLayouter;

namespace TagsCloudVisualization;

public static class Program
{
private const int imageWidth = 1500;
private const int imageHeight = 1500;

public static void Main()
{
var imageSize = new Size(imageWidth, imageHeight);
var center = new Point(imageSize.Width / 2, imageSize.Height / 2);
var layouter = new CircularCloudLayouter(center);
layouter.GenerateCloud();
var rectangles = layouter.GeneratedRectangles;
var visualizer = new SimpleCloudVisualizer();
visualizer.CreateBitmap(rectangles, imageSize);
}
}
4 changes: 4 additions & 0 deletions cs/TagsCloudVisualization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
1000 прямоугольников шаг 0.1 угловое смещение 0.1 <img src="Images\1000_0.1_0.1_TagCloud.jpg">
1000 прямоугольников шаг 0.01 угловое смещение 0.01 <img src="Images\1000_0.01_0.01_TagCloud.jpg">
1000 прямоугольников шаг 1 угловое смещение 1 <img src="Images\1000_1_1_TagCloud.jpg">
1000 прямоугольников шаг 10 угловое смещение 0.01 <img src="Images\1000_10_0.01_TagCloud.jpg">
20 changes: 20 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudVisualization.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BaseOutputPath></BaseOutputPath>
<StartupObject>TagsCloudVisualization.Program</StartupObject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.0" />
</ItemGroup>

</Project>
145 changes: 145 additions & 0 deletions cs/TagsCloudVisualization/Tests/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 SimpleCloudVisualizer();
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 cs/TagsCloudVisualization/Tests/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)),
};
}
Loading