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

Горбатов Александр #212

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
52 changes: 52 additions & 0 deletions cs/TagsCloudVisualization/CircularCloudBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Drawing;

namespace TagsCloudVisualization;

public class CircularCloudBuilder : ICircularCloudBuilder
{
private readonly int radiusIncrement;
private readonly double angleIncrement;

public CircularCloudBuilder(int radiusIncrement, double angleIncrement)
{
if (radiusIncrement == 0 || Math.Abs(angleIncrement - 0) < 1e-3)
throw new ArgumentException("Parameters for the algorithm must not be null");

this.radiusIncrement = radiusIncrement;
this.angleIncrement = angleIncrement;
}

private static void ValidateArguments(Size rectangleSize, List<Rectangle> placedRectangles)
{
if (rectangleSize.Height < 0 || rectangleSize.Width < 0)
throw new ArgumentException("Height and width must not be negative.");
if (placedRectangles is null)
throw new ArgumentException("The list of placed rectangles cannot be null.");
}

public Rectangle GetNextPosition(Point center, Size rectangleSize, List<Rectangle> placedRectangles)
{
ValidateArguments(rectangleSize, placedRectangles);

var radius = 0d;
var angle = 0d;
var rectangle = new Rectangle(Point.Empty, rectangleSize);
do
{
var x = center.X + radius * Math.Cos(angle);
var y = center.Y + radius * Math.Sin(angle);

angle += angleIncrement;

if (Math.Abs(angle) >= 2 * Math.PI)
{
angle = 0;
radius += radiusIncrement;
}

rectangle.Location = new Point((int) x, (int) y);
} while (placedRectangles.AnyIntersectsWith(rectangle));

return rectangle;
}
}
41 changes: 41 additions & 0 deletions cs/TagsCloudVisualization/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Drawing;

namespace TagsCloudVisualization;

public class CircularCloudLayouter : ICloudLayouter
{
public List<Rectangle> PlacedRectangles { get; } = new();
private readonly Point center;
private readonly ICircularCloudBuilder circularCloudBuilder;

public CircularCloudLayouter(Point center, ICircularCloudBuilder circularCloudBuilder)
{
this.circularCloudBuilder = circularCloudBuilder;
this.center = center;
}

private Rectangle PlaceFirstRectangle(Size rectangleSize)
{
var firstRectangleLocation = new Point(center.X - rectangleSize.Width / 2,
center.Y - rectangleSize.Height / 2);
return new Rectangle(firstRectangleLocation, rectangleSize);
}

public Rectangle PutNextRectangle(Size rectangleSize)
{
if (rectangleSize.Height < 0 || rectangleSize.Width < 0)
throw new ArgumentException("Height and width must be positive");

if (PlacedRectangles.Count == 0)
{
var firstRectangle = PlaceFirstRectangle(rectangleSize);
PlacedRectangles.Add(firstRectangle);
return firstRectangle;
}

var rectangle = circularCloudBuilder.GetNextPosition(center, rectangleSize, PlacedRectangles);

PlacedRectangles.Add(rectangle);
return rectangle;
}
}
28 changes: 28 additions & 0 deletions cs/TagsCloudVisualization/CloudLayouterExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Drawing;

namespace TagsCloudVisualization;

public static class CloudLayouterExtensions
{
public static Rectangle GetBorders(this List<Rectangle> placedRectangles)
{
if (placedRectangles is null || placedRectangles.Count == 0)
throw new InvalidOperationException("The list of placed rectangles cannot be null or empty");

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

public static bool AnyIntersectsWith(this IEnumerable<Rectangle> rectangles, Rectangle rectangle)
{
return rectangles
.Any(r => r
.IntersectsWith(rectangle));
}
}
8 changes: 8 additions & 0 deletions cs/TagsCloudVisualization/ICircularCloudBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagsCloudVisualization;

public interface ICircularCloudBuilder
{
public Rectangle GetNextPosition(Point center, Size rectangleSize, List<Rectangle> placedRectangles);
}
9 changes: 9 additions & 0 deletions cs/TagsCloudVisualization/ICloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Drawing;

namespace TagsCloudVisualization;

public interface ICloudLayouter
{
List<Rectangle> PlacedRectangles { get; }
Rectangle PutNextRectangle(Size rectangleSize);
}
17 changes: 17 additions & 0 deletions cs/TagsCloudVisualization/ImageParameters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace TagsCloudVisualization;

public class ImageParameters
{
public int OffsetX { get; set; }
public int OffsetY { get; set; }
public int Height { get; set; }
public int Width { get; set; }

public ImageParameters(int offsetX, int offsetY, int height, int width)
{
OffsetX = offsetX;
OffsetY = offsetY;
Height = height;
Width = width;
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions cs/TagsCloudVisualization/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Drawing;

namespace TagsCloudVisualization;

public static class Program
{
public static void Main()
{
var layouter = new CircularCloudLayouter(new Point(0, 0), new CircularCloudBuilder(1, 0.1d));
for (var i = 1; i <= 100; i++)
layouter.PutNextRectangle(new Size(20, 10));

var drawer = new TagsCloudDrawer(layouter, new Pen(Color.Red, 3), 3);

var bitmap = drawer.DrawTagCloud();
TagsCloudDrawer.SaveImage(bitmap, Directory.GetCurrentDirectory(), "image.jpeg");
}
}
6 changes: 6 additions & 0 deletions cs/TagsCloudVisualization/README.md
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
84 changes: 84 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudDrawer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Drawing;
using System.Drawing.Imaging;

namespace TagsCloudVisualization;

public class TagsCloudDrawer
{
private readonly ICloudLayouter layouter;
private readonly Pen pen;
private readonly int scale;


public TagsCloudDrawer(ICloudLayouter layouter, Pen pen, int scale)
{
this.layouter = layouter;
if (scale <= 0)
throw new ArgumentException("Scale must be a positive number.");
this.pen = pen ?? throw new ArgumentException("Pen must not be null.");
this.scale = scale;
}

private static ImageParameters AdjustImageParameters(Rectangle borders)
{
var width = borders.Width;
var height = borders.Height;
var offsetX = 0;
var offsetY = 0;
if (borders.Left < 0)
{
width += Math.Abs(borders.Left);
offsetX = borders.Left;
}

if (borders.Top < 0)
{
height += Math.Abs(borders.Top);
offsetY = borders.Top;
}

if (borders.Right > width)
width += borders.Right - width;

if (borders.Bottom > height)
height += borders.Bottom - height;

return new ImageParameters(offsetX, offsetY, height, width);
}

public Bitmap DrawTagCloud()
{
if (layouter.PlacedRectangles is null)
throw new ArgumentException("The list of rectangles cannot be null");

var borders = layouter.PlacedRectangles.GetBorders();
var adjustedSize = AdjustImageParameters(borders);

var bitmap = new Bitmap(adjustedSize.Width * scale, adjustedSize.Height * scale);
var graphics = Graphics.FromImage(bitmap);
graphics.TranslateTransform(-adjustedSize.OffsetX * scale, -adjustedSize.OffsetY * scale);

foreach (var rectangle in layouter.PlacedRectangles.Select(r =>
new Rectangle(r.X * scale, r.Y * scale, r.Width * scale, r.Height * scale)))
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);
}
}
15 changes: 15 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudVisualization.csproj
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>
35 changes: 35 additions & 0 deletions cs/TagsCloudVisualizationTests/CircularCloudBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Drawing;
using TagsCloudVisualization;

namespace TagsCloudVisualizationTests;

[TestFixture]
public class CircularCloudBuilderTests
{
private CircularCloudBuilder builder = null!;
private List<Rectangle> placedRectangles = null!;

[SetUp]
public void SetUp()
{
builder = new CircularCloudBuilder(1, 0.1d);
placedRectangles = new List<Rectangle>();
}

[TestCase(0, 0.1)]
[TestCase(1, 0)]
public void Constructor_ThrowsArgumentException_OnInvalidArguments(int radiusIncrement, double angleIncrement)
{
Assert.Throws<ArgumentException>(() => new CircularCloudBuilder(radiusIncrement, angleIncrement));
}

[TestCase(-200, 300, false)]
[TestCase(200, -300, false)]
[TestCase(200, 300, true)]
public void GetNextPosition_ThrowsArgumentException_OnInvalidArguments(int width, int height, bool isListNull)
{
Assert.Throws<ArgumentException>(() => builder.GetNextPosition(new Point(500, 500),
new Size(width, height),
(isListNull ? null : placedRectangles)!));
}
}
Loading