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

Смышляев Дмитрий #253

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5640931
Обновил структуру проекта
vafle228 Nov 12, 2024
e8bbaa4
Тест на инициализацию конструктора
vafle228 Nov 12, 2024
dea7bad
Вынес CloudLayouter как интерфейс
vafle228 Nov 12, 2024
d3e8a08
Дискретная спираль фибоначи
vafle228 Nov 12, 2024
17192d9
Генерация точек спирали
vafle228 Nov 12, 2024
da86659
Забыл про Direction ...
vafle228 Nov 12, 2024
15de3a2
Размещение прямоугольников
vafle228 Nov 13, 2024
2da40b8
Забыл про тест на пересечение
vafle228 Nov 13, 2024
db6cda6
Добавил генерацию изображений
vafle228 Nov 13, 2024
f76aa00
Это был не Фибоначи ...
vafle228 Nov 13, 2024
701cfc9
Новый генератор точек
vafle228 Nov 13, 2024
aa0fcf4
Еще игра в рефакторинг
vafle228 Nov 13, 2024
a63955b
Спираль в полярных
vafle228 Nov 13, 2024
9e58a80
Негативные аргументы конструктора
vafle228 Nov 13, 2024
b091b09
Генерация точек
vafle228 Nov 13, 2024
5613ceb
Сгенерированные картинки
vafle228 Nov 13, 2024
e2b8821
Убрал мусор
vafle228 Nov 13, 2024
0a13f73
Не тот мусор -_-
vafle228 Nov 13, 2024
9677dde
Переопределил интерфейс генераторов точек
vafle228 Nov 17, 2024
9b11a11
Переписал логику на новый интерфейс генератора
vafle228 Nov 17, 2024
3d57500
Починил тесты
vafle228 Nov 17, 2024
799cb26
Рефакторинг тестов
vafle228 Nov 17, 2024
c275525
Тесты на форму
vafle228 Nov 17, 2024
7a1456c
Тесты на плотность укладки
vafle228 Nov 17, 2024
9dd56e2
Слегка поменял генерацию картинок
vafle228 Nov 18, 2024
f04ed3d
Добавил генерацию картинок для упавших случаев
vafle228 Nov 18, 2024
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
19 changes: 19 additions & 0 deletions cs/TagsCloudVisualization/BitmapGenerator.cs
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;
}
}
8 changes: 8 additions & 0 deletions cs/TagsCloudVisualization/ICloudLayouter.cs
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);
}
35 changes: 35 additions & 0 deletions cs/TagsCloudVisualization/Program.cs
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()
Copy link

Choose a reason for hiding this comment

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

Смущает, что у тебя спираль на самом деле не просто спираль. Она ещё знает что-то про размещения.

Думаю, что это не так плохо, если ты честно будешь позиционировать этот класс как спираль и что-то для передвижения . И этот метод, тогда, должен делать смещение (а не 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
}
39 changes: 39 additions & 0 deletions cs/TagsCloudVisualization/SpiralLayouter/SpiralCloudLayouter.cs
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; }
Copy link

Choose a reason for hiding this comment

The 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)))
Copy link

Choose a reason for hiding this comment

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

Скорее .All(!Intersects)

{
placedRectangles.Add(rect);
pointGenerator.Reset();
return rect;
}
} while(spiralEnumerator.MoveNext());

return Rectangle.Empty; // Never happens because squareFibonacciSpiral infinite
}
}
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="6.0.0" />
</ItemGroup>

</Project>
Binary file added cs/TagsCloudVisualization/images/Spiral/100.jpeg
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/Spiral/200.jpeg
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/Spiral/300.jpeg
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/Spiral/400.jpeg
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/Square/100.jpeg
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/Square/200.jpeg
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/Square/300.jpeg
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/Square/400.jpeg
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/Square/500.jpeg
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/Square/600.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
Copy link

Choose a reason for hiding this comment

The 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))]
Copy link

Choose a reason for hiding this comment

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

Тут такое ощущение, что проще будет использовать TestCase обычный) Точка у тебя всегда (0, 0), а остальные параметры вполне себе можно положить в атрибут :)

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);
}
}
}
Loading