-
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
Смышляев Дмитрий #253
base: master
Are you sure you want to change the base?
Смышляев Дмитрий #253
Changes from 18 commits
5640931
e8bbaa4
dea7bad
d3e8a08
17192d9
da86659
15de3a2
2da40b8
db6cda6
f76aa00
701cfc9
aa0fcf4
a63955b
9e58a80
b091b09
5613ceb
e2b8821
0a13f73
9677dde
9b11a11
3d57500
799cb26
c275525
7a1456c
9dd56e2
f04ed3d
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,19 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public static class BitmapGenerator | ||
{ | ||
public static Bitmap GenerateWindowsBitmap(ICloudLayouter cloudLayouter, Size bitmapSize, in Size[] rectSizes) | ||
{ | ||
var bitmap = new Bitmap(bitmapSize.Width, bitmapSize.Height); | ||
using var graphics = Graphics.FromImage(bitmap); | ||
|
||
var rects = rectSizes | ||
.Select(cloudLayouter.PutNextRectangle) | ||
.ToArray(); | ||
graphics.DrawRectangles(Pens.MediumPurple, rects); | ||
|
||
return bitmap; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
public interface ICloudLayouter | ||
{ | ||
public Rectangle PutNextRectangle(Size rectangleSize); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using System.Drawing; | ||
using System.Drawing.Imaging; | ||
using TagsCloudVisualization.SpiralLayouter; | ||
|
||
namespace TagsCloudVisualization; | ||
|
||
internal class Program | ||
{ | ||
private const int IMAGE_WIDTH = 1920; | ||
private const int IMAGE_HEIGHT = 1080; | ||
|
||
private const int RECTANGLE_COUNT = 400; | ||
private const int MAX_RECT_WIDTH = 75; | ||
private const int MAX_RECT_HEIGHT = 75; | ||
|
||
private const string MEDIA_ROOT = "images"; | ||
|
||
public static void Main(string[] args) | ||
{ | ||
var random = new Random(); | ||
|
||
var center = new Point(IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2); | ||
var spiralLayouter = new SpiralCloudLayouter(center); | ||
|
||
var rectangleSizes = new Size[RECTANGLE_COUNT] | ||
.Select(_ => (random.Next(5, MAX_RECT_WIDTH), random.Next(5, MAX_RECT_HEIGHT))) | ||
.Select(rawSize => new Size(rawSize.Item1, rawSize.Item2)) | ||
.ToArray(); | ||
var imageSize = new Size(IMAGE_WIDTH, IMAGE_HEIGHT); | ||
|
||
var bitmap = BitmapGenerator.GenerateWindowsBitmap(spiralLayouter, imageSize, rectangleSizes); | ||
Directory.CreateDirectory(MEDIA_ROOT); | ||
bitmap.Save(Path.Combine(MEDIA_ROOT, $"{RECTANGLE_COUNT}.jpeg"), ImageFormat.Jpeg); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
namespace TagsCloudVisualization.SpiralLayouter; | ||
|
||
public enum Direction | ||
{ | ||
Up, | ||
Right, | ||
Down, | ||
Left, | ||
} | ||
|
||
internal static class DirectionExtensions | ||
{ | ||
public static Direction AntiClockwiseRotate(this Direction direction) | ||
{ | ||
return direction switch | ||
{ | ||
Direction.Up => Direction.Left, | ||
Direction.Left => Direction.Down, | ||
Direction.Down => Direction.Right, | ||
Direction.Right => Direction.Up, | ||
_ => throw new ArgumentOutOfRangeException(nameof(direction), direction, null) | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace TagsCloudVisualization.SpiralLayouter.PointGenerator; | ||
|
||
public interface IPointGenerator<T> : IEnumerable<T>, IEnumerator<T> | ||
{ | ||
/* | ||
* Marking interface for infinite enumerators | ||
* That can be used as point generator | ||
*/ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
using System.Collections; | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.SpiralLayouter.PointGenerator; | ||
|
||
public class PolarArchimedesSpiral : IPointGenerator<Point> | ||
{ | ||
private double currentAngle; | ||
|
||
public Point Center { get; } | ||
public double Radius { get; } | ||
public double AngleOffset { get; } | ||
|
||
object? IEnumerator.Current => Current; | ||
public Point Current { get; private set; } | ||
|
||
public PolarArchimedesSpiral(Point center, double radius, double angleOffset) | ||
{ | ||
if (radius <= 0 || angleOffset <= 0) | ||
{ | ||
var argName = radius <= 0 ? nameof(radius) : nameof(angleOffset); | ||
throw new ArgumentException("Spiral params should be positive.", argName); | ||
} | ||
|
||
Center = center; | ||
Current = center; | ||
|
||
Radius = radius; | ||
AngleOffset = angleOffset * Math.PI / 180; | ||
} | ||
|
||
public IEnumerator<Point> GetEnumerator() => this; | ||
IEnumerator IEnumerable.GetEnumerator() => this; | ||
|
||
public bool MoveNext() | ||
{ | ||
currentAngle += AngleOffset; | ||
var polarCoordinate = Radius / (2 * Math.PI) * currentAngle; | ||
|
||
var xOffset = (int)Math.Round(polarCoordinate * Math.Cos(currentAngle)); | ||
var yOffset = (int)Math.Round(polarCoordinate * Math.Sin(currentAngle)); | ||
|
||
Current = Center + new Size(xOffset, yOffset); | ||
|
||
return true; | ||
} | ||
|
||
public void Reset() | ||
{ | ||
Current = Center; | ||
currentAngle = 0; | ||
} | ||
|
||
|
||
# region Dispose code | ||
public void Dispose() | ||
{ | ||
Dispose(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
protected virtual void Dispose(bool disposing) {} | ||
# endregion | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
using System.Collections; | ||
using System.Drawing; | ||
|
||
namespace TagsCloudVisualization.SpiralLayouter.PointGenerator; | ||
|
||
public class SquareArchimedesSpiral : IPointGenerator<Point> | ||
{ | ||
private int neededPoints = 1; | ||
private int pointsToPlace = 1; | ||
private Direction direction = Direction.Up; | ||
|
||
public int Step { get; } | ||
public Point Center { get; } | ||
|
||
object? IEnumerator.Current => Current; | ||
public Point Current { get; private set; } | ||
|
||
public SquareArchimedesSpiral(Point center, int step) | ||
{ | ||
if (step <= 0) | ||
throw new ArgumentException("Step should be positive number"); | ||
|
||
Step = step; | ||
Center = center; | ||
Current = center; | ||
} | ||
|
||
public IEnumerator<Point> GetEnumerator() => this; | ||
IEnumerator IEnumerable.GetEnumerator() => this; | ||
|
||
public bool MoveNext() | ||
{ | ||
pointsToPlace--; | ||
Current += GetOffsetSize(); | ||
|
||
if (pointsToPlace == 0) | ||
{ | ||
direction = direction.AntiClockwiseRotate(); | ||
if (direction is Direction.Up or Direction.Down) neededPoints++; | ||
pointsToPlace = neededPoints; | ||
} | ||
return true; | ||
} | ||
|
||
public void Reset() | ||
{ | ||
neededPoints = 1; | ||
Current = Center; | ||
pointsToPlace = neededPoints; | ||
} | ||
|
||
private Size GetOffsetSize() => direction switch | ||
{ | ||
Direction.Up => new Size(0, Step), | ||
Direction.Right => new Size(Step, 0), | ||
Direction.Down => new Size(0, -Step), | ||
Direction.Left => new Size(-Step, 0), | ||
_ => throw new ArgumentOutOfRangeException() | ||
}; | ||
|
||
# region Dispose code | ||
public void Dispose() | ||
{ | ||
Dispose(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
protected virtual void Dispose(bool disposing) {} | ||
# endregion | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using System.Drawing; | ||
using TagsCloudVisualization.SpiralLayouter.PointGenerator; | ||
|
||
namespace TagsCloudVisualization.SpiralLayouter; | ||
|
||
public class SpiralCloudLayouter : ICloudLayouter | ||
{ | ||
private List<Rectangle> placedRectangles; | ||
private IPointGenerator<Point> pointGenerator; | ||
|
||
public Point Center { get; private set; } | ||
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. Зачем нужен setter? |
||
|
||
public SpiralCloudLayouter(Point center) | ||
{ | ||
Center = center; | ||
placedRectangles = []; | ||
pointGenerator = new PolarArchimedesSpiral(center, 3, 0.5); | ||
} | ||
|
||
public Rectangle PutNextRectangle(Size rectangleSize) | ||
{ | ||
using var spiralEnumerator = pointGenerator.GetEnumerator(); | ||
do | ||
{ | ||
var rectangleCenter = spiralEnumerator.Current; | ||
var rectangleUpperLeft = rectangleCenter - rectangleSize / 2; | ||
var rect = new Rectangle(rectangleUpperLeft, rectangleSize); | ||
|
||
if (!placedRectangles.Any(r => r.IntersectsWith(rect))) | ||
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. Скорее |
||
{ | ||
placedRectangles.Add(rect); | ||
pointGenerator.Reset(); | ||
return rect; | ||
} | ||
} while(spiralEnumerator.MoveNext()); | ||
|
||
return Rectangle.Empty; // Never happens because squareFibonacciSpiral infinite | ||
} | ||
} |
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="6.0.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using System.Drawing; | ||
using FluentAssertions; | ||
using NUnit.Framework; | ||
using TagsCloudVisualization.SpiralLayouter.PointGenerator; | ||
|
||
namespace TagsCloudVisualizationTests.SpiralLayouter.PointGenerator; | ||
|
||
[TestFixture] | ||
public class PolarArchimedesSpiralTest | ||
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. Кажется, у тебя есть тест на какой-то один случай, когда спираль круглая/квадратная. Я думаю, что благодаря такому разделению ты можешь относительно просто проверить в этих тестах, что точки, которые генерирует спираль стремятся к окружности/квадрату. |
||
{ | ||
private const string NOT_POSITIVE_ARGUMENT_ERROR = "Spiral params should be positive."; | ||
|
||
public static IEnumerable<TestCaseData> InitAtGivenPointAngleAndRadiusTestCases | ||
{ | ||
get | ||
{ | ||
yield return new TestCaseData(new Point(0, 0), 2, 3); | ||
yield return new TestCaseData(new Point(-10, 10), 10.1, 15.6); | ||
yield return new TestCaseData(new Point(2000, 10000), 200.1111111, 23); | ||
} | ||
} | ||
|
||
[TestCaseSource(nameof(InitAtGivenPointAngleAndRadiusTestCases))] | ||
public void PolarArchimedesSpiral_InitAtGivenPointAngleAndRadius(Point center, double radius, double angleOffset) | ||
{ | ||
var polarSpiral = new PolarArchimedesSpiral(center, radius, angleOffset); | ||
|
||
polarSpiral.Radius.Should().Be(radius); | ||
polarSpiral.Center.Should().BeEquivalentTo(center); | ||
polarSpiral.AngleOffset.Should().Be(angleOffset * Math.PI / 180); | ||
} | ||
|
||
public static IEnumerable<TestCaseData> ThrowErrorOnNotPositiveNumberTestCases | ||
{ | ||
get | ||
{ | ||
yield return new TestCaseData(new Point(0, 0), 0.0000, 11); | ||
yield return new TestCaseData(new Point(0, 0), -10, -100); | ||
yield return new TestCaseData(new Point(0, 0), 52, double.MinValue); | ||
} | ||
} | ||
|
||
[TestCaseSource(nameof(ThrowErrorOnNotPositiveNumberTestCases))] | ||
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 PolarArchimedesSpiral_ThrowError_OnNotPositiveNumber(Point center, double radius, double angleOffset) | ||
{ | ||
var negativeParameter = radius <= 0 ? nameof(radius) : nameof(angleOffset); | ||
var polarSpiralCtor = () => new PolarArchimedesSpiral(center, radius, angleOffset); | ||
|
||
polarSpiralCtor.Should() | ||
.Throw<ArgumentException>() | ||
.WithMessage($"{NOT_POSITIVE_ARGUMENT_ERROR} (Parameter '{negativeParameter}')"); | ||
} | ||
|
||
|
||
[Test] | ||
public void PolarArchimedesSpiral_CalculateSpecialPoints() | ||
{ | ||
var polarSpiral = new PolarArchimedesSpiral(new Point(0, 0), 2, 360); | ||
var expected = new[] | ||
{ | ||
new Point(2, 0), new Point(4, 0), new Point(6, 0), new Point(8, 0) | ||
}; | ||
|
||
var expectedAndReceived = expected.Zip(polarSpiral); | ||
|
||
polarSpiral.Current.Should().BeEquivalentTo(new Point(0, 0)); | ||
foreach (var (expectedPoint, receivedPoint) in expectedAndReceived) | ||
{ | ||
expectedPoint.Should().BeEquivalentTo(receivedPoint); | ||
} | ||
} | ||
} |
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.
Смущает, что у тебя спираль на самом деле не просто спираль. Она ещё знает что-то про размещения.
Думаю, что это не так плохо, если ты честно будешь позиционировать этот класс как спираль и что-то для передвижения . И этот метод, тогда, должен делать смещение (а не Reset).