-
Notifications
You must be signed in to change notification settings - Fork 308
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
Горбатов Александр #212
base: master
Are you sure you want to change the base?
Горбатов Александр #212
Changes from 8 commits
b5d3909
9cf07f4
bf7edf4
0daac4f
7f47249
bb80777
8aaf0c0
bd6d361
e382de7
90ec034
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,84 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public class CircularCloudLayouter : ICloudLayouter | ||
{ | ||
public List<Rectangle> PlacedRectangles { get; } = new(); | ||
private readonly Point center; | ||
|
||
public CircularCloudLayouter(Point center) | ||
{ | ||
this.center = center; | ||
} | ||
|
||
private Rectangle CalculateRectanglePosition(Size rectangleSize, int radiusIncrement, double angleIncrement) | ||
{ | ||
var rectangle = new Rectangle(Point.Empty, rectangleSize); | ||
|
||
var radius = 0d; | ||
var angle = 0d; | ||
|
||
do | ||
{ | ||
var x = center.X + radius * Math.Cos(angle); | ||
var y = center.Y + radius * Math.Sin(angle); | ||
rectangle.Location = new Point((int) x, (int) y); | ||
|
||
angle += angleIncrement; | ||
|
||
if (!(angle >= 2 * Math.PI)) | ||
continue; | ||
|
||
angle = 0; | ||
radius += radiusIncrement; | ||
} while (IntersectsWithAny(rectangle)); | ||
|
||
return rectangle; | ||
} | ||
|
||
private Rectangle PlaceRectangle(Size rectangleSize) | ||
{ | ||
var newRectangle = new Rectangle(Point.Empty, rectangleSize); | ||
|
||
if (PlacedRectangles.Count == 0) | ||
{ | ||
newRectangle.Location = new Point(center.X - newRectangle.Width / 2, center.Y - newRectangle.Height / 2); | ||
return newRectangle; | ||
} | ||
|
||
const int radiusIncrement = 1; | ||
const double angleIncrement = 0.1d; | ||
|
||
return CalculateRectanglePosition(rectangleSize, radiusIncrement, angleIncrement); | ||
} | ||
|
||
public Rectangle GetCloudBorders() | ||
{ | ||
var left = PlacedRectangles.Min(r => r.Left); | ||
var right = PlacedRectangles.Max(r => r.Right); | ||
var top = PlacedRectangles.Min(r => r.Top); | ||
var bottom = PlacedRectangles.Max(r => r.Bottom); | ||
|
||
var width = right - left; | ||
var height = bottom - top; | ||
return new Rectangle(left, top, width, height); | ||
} | ||
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. Тоже более общий кажется его можно унести в другой класс |
||
|
||
public Rectangle PutNextRectangle(Size rectangleSize) | ||
{ | ||
if (rectangleSize.Height < 0 || rectangleSize.Width < 0) | ||
throw new ArgumentException("Height and width must be positive"); | ||
|
||
var newRectangle = PlaceRectangle(rectangleSize); | ||
PlacedRectangles.Add(newRectangle); | ||
return newRectangle; | ||
} | ||
|
||
private bool IntersectsWithAny(Rectangle newRectangle) | ||
{ | ||
return PlacedRectangles | ||
.Any(rectangle => rectangle | ||
.IntersectsWith(newRectangle)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public interface ICloudLayouter | ||
{ | ||
List<Rectangle> PlacedRectangles { get; } | ||
Rectangle PutNextRectangle(Size rectangleSize); | ||
Rectangle GetCloudBorders(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public static class Program | ||
{ | ||
public static void Main() | ||
{ | ||
var random = new Random(); | ||
|
||
var layouter = new CircularCloudLayouter(new Point(500, 500)); | ||
for (var i = 1; i <= 100; i++) | ||
layouter.PutNextRectangle(new Size(random.Next(201), random.Next(201))); | ||
|
||
var drawer = new TagsCloudDrawer(layouter); | ||
|
||
var bitmap = drawer.DrawRectangles(new Pen(Color.Red, 3), 3); | ||
TagsCloudDrawer.SaveImage(bitmap, Directory.GetCurrentDirectory(), "image.jpeg"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
![25 rectangles 50x10.jpeg](Images%2F25%20rectangles%2050x10.jpeg) | ||
25 rectangles 50x10 | ||
![100 rectangles 10x10.jpeg](Images%2F100%20rectangles%2010x10.jpeg) | ||
100 rectangles 10x10 | ||
![100 rectangles with random size.jpeg](Images%2F100%20rectangles%20with%20random%20size.jpeg) | ||
100 rectangles with random size |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
using System.Drawing; | ||
using System.Drawing.Imaging; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public class TagsCloudDrawer | ||
{ | ||
private readonly ICloudLayouter layouter; | ||
|
||
public TagsCloudDrawer(ICloudLayouter layouter) | ||
{ | ||
this.layouter = layouter; | ||
} | ||
|
||
public Bitmap DrawRectangles(Pen pen, int scale) | ||
{ | ||
if (scale <= 0) | ||
throw new ArgumentException("Scale must be a positive number."); | ||
if (pen is null) | ||
throw new ArgumentException("Pen must not be null."); | ||
|
||
var borders = layouter.GetCloudBorders(); | ||
var bitmap = new Bitmap(borders.Width * scale, borders.Height * scale); | ||
var graphics = Graphics.FromImage(bitmap); | ||
|
||
var rectanglesWithShift = layouter.PlacedRectangles | ||
.Select(r => | ||
new Rectangle( | ||
(r.X - borders.X) * scale, | ||
(r.Y - borders.Y) * scale, | ||
r.Width * scale, | ||
r.Height * scale)); | ||
|
||
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. Давай тоже вынесем в отдельный класс |
||
foreach (var rectangle in rectanglesWithShift) | ||
graphics.DrawRectangle(pen, rectangle); | ||
|
||
return bitmap; | ||
} | ||
|
||
public static void SaveImage(Bitmap bitmap, string dirPath, string filename) | ||
{ | ||
if (string.IsNullOrWhiteSpace(filename) || filename.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) | ||
throw new ArgumentException("The provided filename is not valid."); | ||
|
||
try | ||
{ | ||
Directory.CreateDirectory(dirPath); | ||
} | ||
catch (Exception) | ||
{ | ||
throw new ArgumentException("The provided directory path is not valid."); | ||
} | ||
|
||
bitmap.Save(Path.Combine(dirPath, filename), ImageFormat.Jpeg); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net7.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<LangVersion>11</LangVersion> | ||
<OutputType>Exe</OutputType> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="System.Drawing.Common" Version="8.0.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
using System.Drawing; | ||
using NUnit.Framework.Interfaces; | ||
using TagsCloudVisualization; | ||
|
||
namespace TagsCloudVisualizationTests; | ||
|
||
public class CircularCloudLayouterTests | ||
{ | ||
private CircularCloudLayouter layouter = null!; | ||
private Random random = null!; | ||
|
||
[OneTimeSetUp] | ||
public void OneTimeSetUp() | ||
{ | ||
random = new Random(); | ||
} | ||
|
||
[SetUp] | ||
public void SetUp() | ||
{ | ||
layouter = new CircularCloudLayouter(new Point(500, 500)); | ||
} | ||
|
||
[TestCase(-200, -300)] | ||
[TestCase(-200, 300)] | ||
[TestCase(200, -300)] | ||
public void PutNextRectangle_ThrowsArgumentException_WhenRectanlgeSizeContainsNegativeParameters(int width, | ||
int height) | ||
{ | ||
Assert.Throws<ArgumentException>(() => layouter.PutNextRectangle(new Size(width, height))); | ||
} | ||
|
||
[TestCase(true)] | ||
[TestCase(false)] | ||
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. Посмотри на [Values], с ним намного проще будут выглядеть 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. Остальные тесты можно на него тоже переделать |
||
public void PutNextRecangle_PlacesRectangleWithoutIntersection_OnMultipleRectangles(bool randomRectangleSize) | ||
{ | ||
var rectangles = new List<Rectangle>(); | ||
var rectSize = new Size(20, 10); | ||
|
||
for (var i = 1; i <= 50; i++) | ||
{ | ||
if (randomRectangleSize) | ||
rectSize = new Size(random.Next(201), random.Next(201)); | ||
|
||
var newRectangle = layouter.PutNextRectangle(rectSize); | ||
|
||
foreach (var rectangle in rectangles) | ||
Assert.That(rectangle.IntersectsWith(newRectangle), Is.False); | ||
|
||
rectangles.Add(newRectangle); | ||
} | ||
} | ||
|
||
[TestCase(10, true)] | ||
[TestCase(10, false)] | ||
[TestCase(50, true)] | ||
[TestCase(50, false)] | ||
[TestCase(100, true)] | ||
[TestCase(100, false)] | ||
public void PutNextRectangle_HasCircularShape_OnDifferentInputData(int numberOfRectangles, bool randomRectangleSize) | ||
{ | ||
PlaceRectangles(numberOfRectangles, randomRectangleSize); | ||
|
||
var borders = layouter.GetCloudBorders(); | ||
|
||
var heightToWidthRatio = (double) Math.Min(borders.Width, borders.Height) / Math.Max(borders.Width, borders.Height); | ||
|
||
Assert.That(heightToWidthRatio, Is.GreaterThan(0.8).Within(0.05)); | ||
} | ||
|
||
|
||
[TestCase(10, true)] | ||
[TestCase(10, false)] | ||
[TestCase(50, true)] | ||
[TestCase(50, false)] | ||
[TestCase(100, true)] | ||
[TestCase(100, false)] | ||
public void PutNextRectangle_IsDenseEnough_OnDifferentInputData(int numberOfRectangles, bool randomRectangleSize) | ||
{ | ||
PlaceRectangles(numberOfRectangles, randomRectangleSize); | ||
|
||
var borders = layouter.GetCloudBorders(); | ||
|
||
var radius = Math.Max(borders.Width, borders.Height) / 2; | ||
var circleSquare = Math.PI * radius * radius; | ||
var rectanglesAccumulatedSquare = layouter.PlacedRectangles.Sum(r => r.Width * r.Height); | ||
|
||
var rectanglesToCircleSquareRatio = rectanglesAccumulatedSquare / circleSquare; | ||
|
||
Assert.That(rectanglesToCircleSquareRatio, Is.GreaterThan(0.7).Within(0.05)); | ||
} | ||
|
||
[Timeout(5000)] | ||
[TestCase(true)] | ||
[TestCase(false)] | ||
public void PutNextRectangle_HasSufficientPerformance_OnLargeAmountOfRectangles(bool randomRectangleSize) | ||
{ | ||
PlaceRectangles(200, randomRectangleSize); | ||
} | ||
|
||
[TearDown] | ||
public void GenerateImage_OnTestFail() | ||
{ | ||
if (TestContext.CurrentContext.Result.Outcome == ResultState.Success) | ||
return; | ||
|
||
var drawer = new TagsCloudDrawer(layouter); | ||
var bitmap = drawer.DrawRectangles(new Pen(Color.Red, 1), 5); | ||
TagsCloudDrawer.SaveImage(bitmap, @"..\..\..\FailedTests",$"{TestContext.CurrentContext.Test.Name}.jpeg"); | ||
} | ||
|
||
private void PlaceRectangles(int numberOfRectangles, bool randomRectangleSize) | ||
{ | ||
var rectSize = new Size(20, 10); | ||
|
||
for (var i = 1; i <= numberOfRectangles; i++) | ||
{ | ||
if (randomRectangleSize) | ||
rectSize = new Size(random.Next(201), random.Next(201)); | ||
layouter.PutNextRectangle(rectSize); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
global using NUnit.Framework; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
using System.Drawing; | ||
using NSubstitute; | ||
using TagsCloudVisualization; | ||
|
||
namespace TagsCloudVisualizationTests; | ||
|
||
public class TagsCloudDrawerTests | ||
{ | ||
private TagsCloudDrawer drawer = null!; | ||
private Pen pen = null!; | ||
|
||
[SetUp] | ||
public void SetUp() | ||
{ | ||
var layouter = Substitute.For<ICloudLayouter>(); | ||
drawer = new TagsCloudDrawer(layouter); | ||
pen = new Pen(Color.Red, 1); | ||
} | ||
|
||
[TestCase(false, -2)] | ||
[TestCase(false, 0)] | ||
[TestCase(true, 2)] | ||
public void DrawRectangles_ThrowsArgumentException_OnInvalidArguments(bool penIsNull, int scale) | ||
{ | ||
Assert.Throws<ArgumentException>(() => drawer.DrawRectangles((penIsNull ? null : pen)!, scale)); | ||
} | ||
|
||
[TestCase("test", "<>\\")] | ||
[TestCase("test", "name|123")] | ||
[TestCase("test", null)] | ||
[TestCase("test", "")] | ||
[TestCase(null, "filename")] | ||
[TestCase("","filename")] | ||
[TestCase(" ","filename")] | ||
[TestCase(@"\:\","filename")] | ||
public void SaveImage_ThrowsArgumentException_OnInvalidParameters(string dirPath, string filename) | ||
{ | ||
Assert.Throws<ArgumentException>(() => TagsCloudDrawer.SaveImage(new Bitmap(1, 1), dirPath, filename)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net7.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
|
||
<IsPackable>false</IsPackable> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="6.12.0" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1"/> | ||
<PackageReference Include="NSubstitute" Version="5.1.0" /> | ||
<PackageReference Include="NUnit" Version="3.13.3"/> | ||
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2"/> | ||
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/> | ||
<PackageReference Include="coverlet.collector" Version="3.2.0"/> | ||
</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.
Давай вынесем алгоритм по построению спирали в отдельный класс
Допустим чтобы можно было его удобно протестировать и посмотреть твой код при разных условиях