-
Notifications
You must be signed in to change notification settings - Fork 277
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
Шмаков Данил #207
base: master
Are you sure you want to change the base?
Шмаков Данил #207
Changes from all commits
eab9d69
39f073b
780f1bf
d693b70
6dbc999
0d4d766
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,38 @@ | ||
using System.Drawing; | ||
|
||
namespace TagCloud; | ||
|
||
public class CircularCloudLayouter | ||
{ | ||
private List<Rectangle> rectangles; | ||
private Point center; | ||
private ICloudShaper shaper; | ||
|
||
public IEnumerable<Rectangle> Rectangles => rectangles; | ||
|
||
public CircularCloudLayouter(Point center) | ||
{ | ||
this.center = center; | ||
rectangles = new List<Rectangle>(); | ||
shaper = SpiralCloudShaper.Create(this.center); | ||
} | ||
|
||
public Rectangle PutNextRectangle(Size size) | ||
{ | ||
if (size.Width <= 0) | ||
throw new ArgumentException("Size width must be positive number"); | ||
if (size.Height <= 0) | ||
throw new ArgumentException("Size height must be positive number"); | ||
|
||
Rectangle rectangle = Rectangle.Empty; | ||
foreach (var point in shaper.GetPossiblePoints()) | ||
{ | ||
rectangle = new Rectangle(point, size); | ||
if (!Rectangles.Any(rect => rect.IntersectsWith(rectangle))) | ||
break; | ||
} | ||
|
||
rectangles.Add(rectangle); | ||
return rectangle; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Drawing; | ||
|
||
namespace TagCloudTests; | ||
|
||
public interface ICloudDrawer | ||
{ | ||
void Draw(IEnumerable<Rectangle> rectangle, string name); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using System.Drawing; | ||
using System.Dynamic; | ||
using TagCloudTests; | ||
|
||
namespace TagCloud; | ||
|
||
public class TagCloudDrawer : ICloudDrawer | ||
{ | ||
private readonly string path; | ||
private readonly IColorSelector selector; | ||
|
||
private TagCloudDrawer(string path, IColorSelector selector) | ||
{ | ||
this.path = path; | ||
this.selector = selector; | ||
} | ||
|
||
public void Draw(IEnumerable<Rectangle> rectangles, string name) | ||
{ | ||
if (rectangles.Count() == 0) | ||
throw new ArgumentException("Empty rectangles list"); | ||
|
||
var minX = rectangles.Min(rect => rect.X); | ||
var maxX = rectangles.Max(rect => rect.Right); | ||
var minY = rectangles.Min(rect => rect.Top); | ||
var maxY = rectangles.Max(rect => rect.Bottom); | ||
|
||
using var bitmap = new Bitmap(maxX - minX + 2, maxY - minY + 2); | ||
using var graphics = Graphics.FromImage(bitmap); | ||
graphics.DrawRectangles( | ||
selector, | ||
rectangles | ||
.Select(rect => rect with { X = -minX + rect.X, Y = -minY + rect.Y }) | ||
.ToArray() | ||
); | ||
SaveToFile(bitmap, name); | ||
} | ||
|
||
private void SaveToFile(Bitmap bitmap, string name) | ||
{ | ||
var pathToFile = @$"{path}\{name}.jpg"; | ||
bitmap.Save(pathToFile); | ||
Console.WriteLine($"Tag cloud visualization saved to file {path}"); | ||
} | ||
|
||
public static TagCloudDrawer Create(string path, IColorSelector selector) | ||
{ | ||
if (!Directory.Exists(path)) | ||
throw new ArgumentException("Directory does not exist"); | ||
return new TagCloudDrawer(path, selector); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using System.Drawing; | ||
using TagCloudTests; | ||
|
||
namespace TagCloud; | ||
|
||
public class ConstantColorSelector : IColorSelector | ||
{ | ||
private Color color; | ||
public ConstantColorSelector(Color color) | ||
{ | ||
this.color = color; | ||
} | ||
|
||
public Color PickColor() => color; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Drawing; | ||
|
||
namespace TagCloudTests; | ||
|
||
public interface IColorSelector | ||
{ | ||
Color PickColor(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using System.Drawing; | ||
|
||
namespace TagCloudTests; | ||
|
||
public class RandomColorSelector : IColorSelector | ||
{ | ||
private readonly Random random; | ||
|
||
public RandomColorSelector() | ||
{ | ||
random = new Random(DateTime.Now.Microsecond); | ||
} | ||
|
||
public Color PickColor() | ||
{ | ||
return Color.FromArgb(255, | ||
random.Next(0, 255), | ||
random.Next(0, 255), | ||
random.Next(0, 255) | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using System.Drawing; | ||
using TagCloudTests; | ||
|
||
namespace TagCloud; | ||
|
||
public static class Extensions | ||
{ | ||
public static void DrawRectangles(this Graphics graphics, IColorSelector selector, Rectangle[] rectangles, int lineWidth = 1) | ||
{ | ||
var pen = new Pen(selector.PickColor(), lineWidth); | ||
foreach (var rectangle in rectangles) | ||
{ | ||
graphics.DrawRectangle(pen, rectangle); | ||
pen.Color = selector.PickColor(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Drawing; | ||
|
||
namespace TagCloud; | ||
|
||
public interface ICloudShaper | ||
This comment was marked as resolved.
Sorry, something went wrong. 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. Сделал |
||
{ | ||
IEnumerable<Point> GetPossiblePoints(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
using System.Drawing; | ||
|
||
namespace TagCloud; | ||
|
||
public class SpiralCloudShaper : ICloudShaper | ||
{ | ||
private Point center; | ||
private double coefficient; | ||
private double deltaAngle; | ||
|
||
private SpiralCloudShaper(Point center, double coefficient, double deltaAngle) | ||
{ | ||
this.center = center; | ||
This comment was marked as resolved.
Sorry, something went wrong. 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. Сделано 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. Считаю, что конструктор должен быть максимально простым, никаких проверок внутри не должно быть, его задача - инициализировать объект, если хочешь сделать проверку на входные параметры, то лучше сделать статичный метод который это будет проверять, и потом сам вызовет тебе приватный конструктор, Как вариант валидацию выносить в отдельный приватный метод 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. Сделал через Create 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. Так же это работает и для других классов |
||
this.deltaAngle = deltaAngle; | ||
this.coefficient = coefficient; | ||
} | ||
|
||
public IEnumerable<Point> GetPossiblePoints() | ||
{ | ||
var currentAngle = 0D; | ||
var position = center; | ||
var previousPoint = position; | ||
while(true) | ||
{ | ||
while (position == previousPoint) | ||
{ | ||
currentAngle += deltaAngle; | ||
previousPoint = position; | ||
position = CalculatePointByCurrentAngle(currentAngle); | ||
} | ||
yield return position; | ||
previousPoint = position; | ||
position = CalculatePointByCurrentAngle(currentAngle); | ||
} | ||
} | ||
|
||
private Point CalculatePointByCurrentAngle(double angle) | ||
{ | ||
return new Point( | ||
center.X + (int)(coefficient * angle * Math.Cos(angle)), | ||
center.Y + (int)(coefficient * angle * Math.Sin(angle)) | ||
); | ||
} | ||
|
||
public static SpiralCloudShaper Create(Point center, double coefficient = 0.1, double deltaAngle = 0.1) | ||
{ | ||
if (coefficient <= 0) | ||
throw new ArgumentException("Spiral coefficient must be positive number"); | ||
if (deltaAngle <= 0) | ||
throw new ArgumentException("Spiral delta angle must be positive number"); | ||
return new SpiralCloudShaper(center, coefficient, deltaAngle); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net7.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</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,119 @@ | ||
using System.Drawing; | ||
using FluentAssertions; | ||
using NUnit.Framework.Interfaces; | ||
using TagCloud; | ||
|
||
namespace TagCloudTests; | ||
|
||
public class CircularCloudLayouterTests | ||
{ | ||
private const string RelativePathToFailDirectory = @"..\..\..\Fails"; | ||
|
||
private CircularCloudLayouter layouter; | ||
private Point center; | ||
private ICloudDrawer drawer; | ||
|
||
[OneTimeSetUp] | ||
public void OneTimeSetUp() | ||
{ | ||
drawer = TagCloudDrawer.Create(Path.GetFullPath(RelativePathToFailDirectory), new RandomColorSelector()); | ||
} | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
center = new Point(0, 0); | ||
layouter = new CircularCloudLayouter(center); | ||
} | ||
|
||
[TearDown] | ||
public void Tear_Down() | ||
{ | ||
if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) | ||
drawer.Draw(layouter.Rectangles, TestContext.CurrentContext.Test.FullName); | ||
} | ||
|
||
[Test] | ||
public void ReturnEmptyList_WhenCreated() | ||
{ | ||
layouter.Rectangles.Should().BeEmpty(); | ||
} | ||
|
||
[Test] | ||
public void ReturnOneElementList_WhenAddOne() | ||
{ | ||
layouter.PutNextRectangle(new Size(1, 1)); | ||
layouter.Rectangles.Count().Should().Be(1); | ||
} | ||
|
||
[Test] | ||
public void ReturnTwoElementList_WhenAddTwo() | ||
{ | ||
layouter.PutNextRectangle(new Size(1, 1)); | ||
layouter.PutNextRectangle(new Size(1, 1)); | ||
layouter.Rectangles.Count().Should().Be(2); | ||
NotIntersectedAssert(layouter.Rectangles); | ||
} | ||
|
||
[TestCase(1, 1, 500, 0.77D, TestName = "WithSquareShape")] | ||
[TestCase(20, 10, 500, 0.67D, TestName = "WithRectangleShape")] | ||
public void AddManyNotIntersectedRectangles_WithConstantSize_ByCloudShape(int width, int height, int count, double accuracy) | ||
{ | ||
var size = new Size(width, height); | ||
for (int i = 0; i < count; i++) | ||
layouter.PutNextRectangle(size); | ||
|
||
layouter.Rectangles.Count().Should().Be(count); | ||
NotIntersectedAssert(layouter.Rectangles); | ||
CircleShapeAssertion(layouter.Rectangles, accuracy); | ||
} | ||
|
||
[Test] | ||
public void AddManyNotIntersectedRectangles_WithVariableSize_ByCloudShape() | ||
{ | ||
var rnd = new Random(DateTime.Now.Microsecond); | ||
|
||
for (int i = 0; i < 200; i++) | ||
{ | ||
var size = new Size(rnd.Next(20, 40), rnd.Next(20, 40)); | ||
layouter.PutNextRectangle(size); | ||
} | ||
|
||
layouter.Rectangles.Count().Should().Be(200); | ||
NotIntersectedAssert(layouter.Rectangles); | ||
CircleShapeAssertion(layouter.Rectangles, 0.6D); | ||
} | ||
|
||
[TestCase(-1, 1, TestName = "WithNegativeWidth")] | ||
[TestCase(1, -1, TestName = "WithNegativeHeight")] | ||
[TestCase(-1, -1, TestName = "WithNegativeWidthAndHeight")] | ||
public void Throw_ThenTryPutRectangle(int width, int height) | ||
{ | ||
Assert.Throws<ArgumentException>(() => layouter.PutNextRectangle(new Size(width, height))); | ||
} | ||
|
||
private void CircleShapeAssertion(IEnumerable<Rectangle> rectangles, double accuracy) | ||
{ | ||
var circleRadius = rectangles | ||
.SelectMany(rect => new[] | ||
{ | ||
rect.Location, | ||
rect.Location with { X = rect.Right }, | ||
rect.Location with { Y = rect.Bottom }, | ||
rect.Location with { X = rect.Right, Y = rect.Bottom } | ||
}) | ||
.Max(point => point.GetDistanceTo(center)); | ||
Console.WriteLine(circleRadius); | ||
var containingCircleRadius = Math.PI * circleRadius * circleRadius; | ||
var rectanglesTotalArea = rectangles.Sum(rect => rect.Width * rect.Height); | ||
Assert.GreaterOrEqual(rectanglesTotalArea / containingCircleRadius, accuracy); | ||
} | ||
|
||
public static void NotIntersectedAssert(IEnumerable<Rectangle> rectangles) | ||
{ | ||
rectangles | ||
.HasIntersectedRectangles() | ||
.Should() | ||
.BeFalse(); | ||
} | ||
} |
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. Чет шакалит, но лан |
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.
Зачем делать интерфейс и жестко привязываться к реализации в конструкторе? Через конструктор можно принимать ICloudShaper