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 all 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
28 changes: 28 additions & 0 deletions cs/TagsCloudVisualization/BitmapGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Drawing;

namespace TagsCloudVisualization;

public static class BitmapGenerator
{
public static Bitmap GenerateWindowsBitmap(ICloudLayouter cloudLayouter, Size bitmapSize, in Size[] rectSizes)
{
var rects = rectSizes.Select(cloudLayouter.PutNextRectangle);
return GenerateWindowsBitmap(rects.ToList(), bitmapSize);
}

public static Bitmap GenerateWindowsBitmap(List<Rectangle> rects, Size bitmapSize)
{
var bitmap = new Bitmap(bitmapSize.Width, bitmapSize.Height);
using var graphics = Graphics.FromImage(bitmap);

var xOffset = bitmap.Width / 2 - rects.First().X;
var yOffset = bitmap.Height / 2 - rects.First().Y;

foreach (var rect in rects)
{
var offsetPos = new Point(rect.X + xOffset, rect.Y + yOffset);
graphics.DrawRectangle(Pens.MediumPurple, new Rectangle(offsetPos, rect.Size));
}
return bitmap;
}
}
18 changes: 18 additions & 0 deletions cs/TagsCloudVisualization/BitmapSaver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Drawing;
using System.Drawing.Imaging;

namespace TagsCloudVisualization;

public class BitmapSaver(string saveDirectory)
{
private string saveDirectory = saveDirectory;

public string SaveBitmap(Bitmap bitmap, string fileName)
{
Directory.CreateDirectory(saveDirectory);
var filepath = Path.Combine(saveDirectory, fileName);
bitmap.Save(filepath, ImageFormat.Jpeg);

return Path.Combine(Directory.GetCurrentDirectory(), filepath);
}
}
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);
}
36 changes: 36 additions & 0 deletions cs/TagsCloudVisualization/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Drawing;
using System.Drawing.Imaging;
using TagsCloudVisualization.SpiralLayouter;
using TagsCloudVisualization.SpiralLayouter.PointGenerator;

namespace TagsCloudVisualization;

internal class Program
{
private const int IMAGE_WIDTH = 1920;
private const int IMAGE_HEIGHT = 1080;

private const int RECTANGLE_COUNT = 100;
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, new PolarArchimedesSpiral(1, 1));

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);
var saver = new BitmapSaver(MEDIA_ROOT);
saver.SaveBitmap(bitmap, $"{RECTANGLE_COUNT}.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,11 @@
using System.Drawing;

namespace TagsCloudVisualization.SpiralLayouter.PointGenerator;

public interface IPointGenerator
{
/*
* Infinite point generator
*/
public IEnumerable<Point> StartFrom(Point startPoint);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Collections;
using System.Drawing;

namespace TagsCloudVisualization.SpiralLayouter.PointGenerator;

public class PolarArchimedesSpiral : IPointGenerator
{
public double Radius { get; }
public double AngleOffset { get; }


public PolarArchimedesSpiral(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);
}

Radius = radius;
AngleOffset = angleOffset * Math.PI / 180;
}

public IEnumerable<Point> StartFrom(Point startPoint)
{
var currentAngle = 0.0;
while (true)
{
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));

yield return startPoint + new Size(xOffset, yOffset);

currentAngle += AngleOffset;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Collections;
using System.Drawing;

namespace TagsCloudVisualization.SpiralLayouter.PointGenerator;

public class SquareArchimedesSpiral : IPointGenerator
{
public int Step { get; }

public SquareArchimedesSpiral(int step)
{
if (step <= 0)
throw new ArgumentException("Step should be positive number");

Step = step;
}

public IEnumerable<Point> StartFrom(Point startPoint)
{
var neededPoints = 1;
var pointsToPlace = 1;
var direction = Direction.Up;
var currentPoint = Point.Empty;

while (true)
{
yield return currentPoint;

pointsToPlace--;
currentPoint += GetOffsetSize(direction);

if (pointsToPlace == 0)
{
direction = direction.AntiClockwiseRotate();
if (direction is Direction.Up or Direction.Down) neededPoints++;
pointsToPlace = neededPoints;
}
}
}

private Size GetOffsetSize(Direction direction) => 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(nameof(direction))
};
}
48 changes: 48 additions & 0 deletions cs/TagsCloudVisualization/SpiralLayouter/SpiralCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Drawing;
using TagsCloudVisualization.SpiralLayouter.PointGenerator;

namespace TagsCloudVisualization.SpiralLayouter;

public class SpiralCloudLayouter : ICloudLayouter
{
private List<Point> placedPoints;
private IPointGenerator pointGenerator;
private List<Rectangle> placedRectangles;

public Point Center { get; }

public SpiralCloudLayouter(Point center, IPointGenerator pointGenerator)
{
Center = center;
placedPoints = [];
placedRectangles = [];
this.pointGenerator = pointGenerator;
}

public Rectangle PutNextRectangle(Size rectangleSize)
{
Rectangle placedRect;
try
{
placedRect = pointGenerator.StartFrom(Center)
.Except(placedPoints)
.Select(p => CreateRectangle(p, rectangleSize))
.First(r => !placedRectangles.Any(r.IntersectsWith));
}
catch (InvalidOperationException)
{
throw new ArgumentException("There are no more points in generator");
}

placedRectangles.Add(placedRect);
placedPoints.Add(placedRect.Location - placedRect.Size / 2);

return placedRect;
}

private Rectangle CreateRectangle(Point center, Size rectangleSize)
{
var rectangleUpperLeft = center - rectangleSize / 2;
return new Rectangle(rectangleUpperLeft, rectangleSize);
}
}
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.
12 changes: 12 additions & 0 deletions cs/TagsCloudVisualizationTests/Extensions/RectangleExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Drawing;

namespace TagsCloudVisualizationTests.Extensions;

public static class RectangleExtensions
{
public static double Area(this Rectangle rectangle)
{
return rectangle.Width * rectangle.Height;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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.";

[TestCase(2, 3)]
[TestCase(10.1, 15.6)]
[TestCase(200.1111111, 23)]
public void PolarArchimedesSpiral_InitAtGivenPointAngleAndRadius(double radius, double angleOffset)
{
var polarSpiral = new PolarArchimedesSpiral(radius, angleOffset);

polarSpiral.Radius.Should().Be(radius);
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);
}
}

[TestCase(0.0000, 11, Description = "Zero radius error")]
[TestCase(-10, -100, Description = "Negative error with respect to radius")]
[TestCase(52, double.MinValue, Description = "Negative angle offset error")]
public void PolarArchimedesSpiral_ThrowError_OnNotPositiveNumber(double radius, double angleOffset)
{
var negativeParameter = radius <= 0 ? nameof(radius) : nameof(angleOffset);
var polarSpiralCtor = () => new PolarArchimedesSpiral(radius, angleOffset);

polarSpiralCtor.Should()
.Throw<ArgumentException>()
.WithMessage($"{NOT_POSITIVE_ARGUMENT_ERROR} (Parameter '{negativeParameter}')");
}


[Test]
public void PolarArchimedesSpiral_CalculateSpecialPoints()
{
var polarSpiral = new PolarArchimedesSpiral(2, 360);
var expected = new[]
{
new Point(0, 0), new Point(2, 0), new Point(4, 0),
new Point(6, 0), new Point(8, 0), new Point(10, 0)
};

var pointGenerator = polarSpiral.StartFrom(new Point(0, 0));
var expectedAndReceived = expected.Zip(pointGenerator);

foreach (var (expectedPoint, receivedPoint) in expectedAndReceived)
{
expectedPoint.Should().BeEquivalentTo(receivedPoint);
}
}

[TestCase(1)]
[TestCase(10)]
[TestCase(100)]
public void PolarArchimedesSpiral_GenerateCircleLikeShape(int radius)
{
var polarSpiral = new PolarArchimedesSpiral(radius, 5);
var pointGenerator = polarSpiral.StartFrom(new Point(0, 0));

for (var k = 1; k <= 10; k++)
{
var radiusSquare = k * radius * k * radius;
pointGenerator
.Skip((k - 1) * 10)
.Take(10)
.All(p => p.X * p.X + p.Y * p.Y < radiusSquare)
.Should().BeTrue("Circle like shape should be generated");
}
}
}
Loading