-
Notifications
You must be signed in to change notification settings - Fork 307
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
base: master
Are you sure you want to change the base?
Ослина Анастасия #249
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
{ | ||
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."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||
} |
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(); | ||
} |
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(); | ||
} |
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(); | ||
} | ||
} |
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; | ||
} | ||
} |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||
} | ||
} |
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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} |
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> |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} |
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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Давай также сделаем возможность указывать позицию центра