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

Ослина Анастасия #249

Open
wants to merge 1 commit 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
50 changes: 50 additions & 0 deletions cs/TagsCloudVisualization/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Drawing;
using TagsCloudVisualization.Interfaces;

namespace TagsCloudVisualization;

public class CircularCloudLayouter : ICloudLayouter
{
private readonly ICloudDistribution distribution;
private readonly List<Rectangle> rectangles = [];
private readonly Point center = new();

public CircularCloudLayouter(ICloudDistribution distribution)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай также сделаем возможность указывать позицию центра

{
this.distribution = distribution;
}

public Rectangle PutNextRectangle(Size rectangleSize)
{
if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0)
throw new ArgumentException("The rectangle size must be greater than zero.");

var newRectangle = new Rectangle(distribution.GetNextPoint(), rectangleSize);

while (IsIntersecting(newRectangle))
newRectangle.Location = distribution.GetNextPoint();

rectangles.Add(newRectangle);
return newRectangle;
}

public List<Rectangle> GetRectangles() => rectangles;

public Point GetCenter() => center;

public Size GetCloudSize()
{
if (rectangles.Count == 0)
throw new ArgumentException("Cloud cannot be empty.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мы выбрасываем ArgumentException хотя не передаем никаких аргументов. Может стоит немного поменять логику, например возвращать Size(0, 0)


var maxX = rectangles.Select(rec => rec.X + rec.Width).Max();
var minX = rectangles.Select(rec => rec.X).Min();
var maxY = rectangles.Select(rec => rec.Y + rec.Height).Max();
var minY = rectangles.Select(rec => rec.Y).Min();

return new Size(maxX - minX, maxY - minY);
}

private bool IsIntersecting(Rectangle newRectangle)
=> rectangles.Any(r => r.IntersectsWith(newRectangle));
}
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/TagsCloudVisualization/Images/rectangle_32.png
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.
9 changes: 9 additions & 0 deletions cs/TagsCloudVisualization/Interfaces/ICloudDistribution.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Drawing;

namespace TagsCloudVisualization.Interfaces;

public interface ICloudDistribution
{
public Point GetNextPoint();
public Point GetCenter();
}
11 changes: 11 additions & 0 deletions cs/TagsCloudVisualization/Interfaces/ICloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Drawing;

namespace TagsCloudVisualization.Interfaces;

public interface ICloudLayouter
{
public Rectangle PutNextRectangle(Size rectangleSize);
public List<Rectangle> GetRectangles();
public Point GetCenter();
public Size GetCloudSize();
}
12 changes: 12 additions & 0 deletions cs/TagsCloudVisualization/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace TagsCloudVisualization;

public class Program
{
private const int RectangleCount = 32;
private const string ImagesPath = @"..\..\..\Images\";

static void Main(string[] args)
{
TagsCloudRunner.Create(RectangleCount, ImagesPath).Run();
}
}
5 changes: 5 additions & 0 deletions cs/TagsCloudVisualization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
!["Example 1"](./Images/rectangle_32.png)

!["Example 2"](./Images/rectangle_128.png)

!["Example 3"](./Images/rectangle_512.png)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Drawing;
using TagsCloudVisualization.Interfaces;

namespace TagsCloudVisualization.TagsCloudDistributions;

public class SpiralDistribution : ICloudDistribution
{
private const double AngleStep = 0.02;
private const double RadiusStep = 0.01;
private double angle;
private double radius;
private readonly Point cloudCenter = new();

public Point GetNextPoint()
{
var nextPoint = ConvertPolarToCartesian();
angle += AngleStep;
radius += RadiusStep;
return nextPoint;
}

public Point GetCenter() => cloudCenter;

private Point ConvertPolarToCartesian()
{
var cartesian = new Point((int)(radius * Math.Cos(angle)), (int)(radius * Math.Sin(angle)));
cartesian.Offset(cloudCenter);
return cartesian;
}
}
68 changes: 68 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Drawing;
using TagsCloudVisualization.Interfaces;

namespace TagsCloudVisualization;

public class TagsCloudRenderer
{
private const int FrameSize = 50;
private readonly IEnumerable<Rectangle> rectangles;
private readonly Bitmap bitmap;

private TagsCloudRenderer(IEnumerable<Rectangle> rectangles, Bitmap bitmap)
{
this.rectangles = rectangles;
this.bitmap = bitmap;
}

public static TagsCloudRenderer Create(ICloudLayouter cloudLayouter)
{
if (cloudLayouter == null || cloudLayouter.GetRectangles().Count == 0)
throw new ArgumentException("The cloud layout is empty");

var cloudSize = cloudLayouter.GetCloudSize();
var bitmap = new Bitmap(cloudSize.Width + FrameSize, cloudSize.Height + FrameSize);
var rectangles = cloudLayouter.GetRectangles().Select(rec =>
rec with { X = rec.X + bitmap.Width / 2, Y = rec.Y + bitmap.Height / 2 });

return new TagsCloudRenderer(rectangles, bitmap);
}

public void Render()
{
using var graphics = Graphics.FromImage(bitmap);
DrawRectangles(graphics);
}

private void DrawRectangles(Graphics graphics)
{
var rnd = new Random();
foreach (var rect in rectangles)
{
var color = Color.FromArgb(rnd.Next(256), rnd.Next(256), rnd.Next(256));
var brush = new SolidBrush(color);
graphics.FillRectangle(brush, rect);
}
}

public void SaveToPath(string fileName, string imagesPath)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно вынести в отдельный класс, что придерживаться Single Responsibility Principle. Рендерер как будто не про сохранение картинок

{
if (fileName == null)
throw new ArgumentException("The file name cannot be null.");

if (imagesPath == null)
throw new ArgumentException("The images path cannot be null.");

if (!Directory.Exists(imagesPath))
Directory.CreateDirectory(imagesPath);

var safeFileName = GetSafeFileName(fileName);
bitmap.Save(Path.Combine(imagesPath, safeFileName));
}

private static string GetSafeFileName(string fileName)
{
var invalidChars = Path.GetInvalidFileNameChars();
return string.Concat(fileName.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries));
}
}
53 changes: 53 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Drawing;
using TagsCloudVisualization.Interfaces;
using TagsCloudVisualization.TagsCloudDistributions;

namespace TagsCloudVisualization;

public class TagsCloudRunner
{
private readonly int rectanglesCount;
private readonly string imagesPath;
private readonly ICloudLayouter cloudLayouter;

private TagsCloudRunner(int rectanglesCount, string imagesPath)
{
this.imagesPath = imagesPath;
this.rectanglesCount = rectanglesCount;
cloudLayouter = new CircularCloudLayouter(new SpiralDistribution());
}

public static TagsCloudRunner Create(int rectanglesCount, string imagesPath)
{
if (rectanglesCount <= 0)
throw new ArgumentException("The rectangles count must be greater than zero.");

if (imagesPath == null)
throw new ArgumentException("The images path cannot be null.");

return new TagsCloudRunner(rectanglesCount, imagesPath);
}

public void Run()
{
FillInCloud();
RenderAndSaveImage();
}

private void FillInCloud()
{
var rnd = new Random();
for (var i = 0; i < rectanglesCount; i++)
{
var rectangleSize = new Size(rnd.Next(5, 30), rnd.Next(5, 30));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не плохо бы добавить, чтобы можно было управлять размерами прямоугольников

cloudLayouter.PutNextRectangle(rectangleSize);
}
}

private void RenderAndSaveImage()
{
var render = TagsCloudRenderer.Create(cloudLayouter);
render.Render();
render.SaveToPath($"rectangle_{rectanglesCount}.png", imagesPath);
}
}
14 changes: 14 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudVisualization.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>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

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

</Project>
103 changes: 103 additions & 0 deletions cs/TagsCloudVisualizationTest/CircularCloudLayouterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System.Drawing;
using FluentAssertions;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using TagsCloudVisualization.TagsCloudDistributions;

namespace TagsCloudVisualization.Tests;


[TestFixture]
public class CircularCloudLayouterTest

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests)

{
private CircularCloudLayouter layouter;
private const string ImagesPath = @"..\..\..\Images\";

[SetUp]
public void Setup()
{
layouter = new CircularCloudLayouter(new SpiralDistribution());
}

[TearDown]
public void TearDown()
{
if (TestContext.CurrentContext.Result.Outcome.Status != TestStatus.Failed)
return;
var fileName = $"{TestContext.CurrentContext.Test.FullName}.png";
var render = TagsCloudRenderer.Create(layouter);
render.Render();
render.SaveToPath(fileName, ImagesPath);
Console.WriteLine($"Tag cloud visualization saved to file /Images/{fileName}");
}

[Test]
public void CircularCloudLayouter_Initialize()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Из названия не понятно, что должно быть после инициализации

{
layouter.GetRectangles().Count.Should().Be(0);
layouter.GetCenter().Should().Be(new Point(0, 0));
}

[TestCase(-1, 2, TestName = "width < 0")]
[TestCase(0, 2, TestName = "width == 0")]
[TestCase(1, -2, TestName = "height < 0")]
[TestCase(1, 0, TestName = "height == 0")]
public void PutNextRectangle_ShouldThrowArgumentException_AfterInvalidRectangleSize(int width, int height)
{
var action = () => layouter.PutNextRectangle(new Size(width, height));

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

[Test]
public void PutNextRectangle_ShouldReturnCorrectRectangleSize()
{
var rectangle = layouter.PutNextRectangle(new Size(16, 18));

rectangle.Size.Should().Be(new Size(16, 18));
}

[Test]
public void PutNextRectangle_ShouldReturnRectangleInCloudCenter()
{
var rectangle = layouter.PutNextRectangle(new Size(16, 18));

rectangle.Location.Should().Be(layouter.GetCenter());
}

[TestCase(16)]
[TestCase(64)]
[TestCase(256)]
public void PutNextRectangle_GeneratesRectanglesWithoutIntersects(int rectanglesCount)
{
GenerateRectangles(rectanglesCount, new Size(15, 20));

HasIntersectedRectangles(layouter.GetRectangles()).Should().Be(false);
}

[Test]
public void GetCloudSize_ShouldThrowArgumentException_EmptyCloud()
{
var action = () => layouter.GetCloudSize();
action.Should().Throw<ArgumentException>();
}

private void GenerateRectangles(int rectanglesCount, Size size)
{
for (var i = 0; i < rectanglesCount; i++)
layouter.PutNextRectangle(size);
}

private bool HasIntersectedRectangles(List<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;
}
}
20 changes: 20 additions & 0 deletions cs/TagsCloudVisualizationTest/TagsCloudVisualizationTest.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>
</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" />
</ItemGroup>

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

</Project>
Loading