-
Notifications
You must be signed in to change notification settings - Fork 32
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
Шестопалов Андрей #33
base: master
Are you sure you want to change the base?
Changes from 2 commits
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,116 @@ | ||
using System.Drawing; | ||
using System.Windows.Forms; | ||
using FluentAssertions; | ||
using NUnit.Framework; | ||
|
||
namespace TagsCloudContainer.Tests; | ||
|
||
[TestFixture] | ||
public class CircularCloudContainerTests | ||
{ | ||
private CircularCloudLayouter circularCloudLayouter; | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
circularCloudLayouter = new CircularCloudLayouter(Point.Empty); | ||
var spiral = new ArchimedeanSpiral(Point.Empty); | ||
circularCloudLayouter.Spiral = spiral; | ||
} | ||
|
||
[Test] | ||
public void Constructor_SetCenterCorrectly_WhenInitialized() | ||
{ | ||
var center = Point.Empty; | ||
var cloudCenter = circularCloudLayouter.Center; | ||
|
||
cloudCenter.Should().Be(center); | ||
} | ||
|
||
[Test] | ||
public void CloudSizeIsZero_WhenInitialized() | ||
{ | ||
var actualSize = circularCloudLayouter.Size(); | ||
|
||
actualSize.Should().Be(Size.Empty); | ||
} | ||
|
||
[Test] | ||
public void CloudSizeEqualsFirstTagSize_WhenPuttingFirstTag() | ||
{ | ||
var font = new Font("Arial", 25); | ||
const string text = "text"; | ||
var expectsdRectangleSize = TextRenderer.MeasureText(text, font); | ||
|
||
circularCloudLayouter.PutNextTag(text, 3); | ||
var actualRectangleSize = circularCloudLayouter.Size(); | ||
|
||
actualRectangleSize.Should().Be(expectsdRectangleSize); | ||
} | ||
|
||
[Test] | ||
public void CloudSizeIsCloseToCircleShape_WhenPuttingManyTags() | ||
{ | ||
for (var i = 0; i < 100; i++) | ||
{ | ||
circularCloudLayouter.PutNextTag("test", 4); | ||
} | ||
|
||
var actualSize = circularCloudLayouter.Size(); | ||
var aspectRatio = (double)actualSize.Width / actualSize.Height; | ||
|
||
aspectRatio.Should().BeInRange(0.5, 2.0); | ||
} | ||
|
||
[TestCase(0, TestName = "NoTags_WhenInitialized")] | ||
[TestCase(1, TestName = "SingleTag_WhenPuttingFirstTag")] | ||
[TestCase(10, TestName = "MultipleTags_WhenPuttingALotOfTags")] | ||
public void CloudContains(int rectangleCount) | ||
{ | ||
for (var i = 0; i < rectangleCount; i++) | ||
{ | ||
circularCloudLayouter.PutNextTag("test", 2); | ||
} | ||
|
||
circularCloudLayouter.Tags.Count.Should().Be(rectangleCount); | ||
} | ||
|
||
[Test] | ||
public void PutNextRectangle_ThrowException_WhenCountIsNotPositive() | ||
{ | ||
var putIncorrectRectangle = () => circularCloudLayouter.PutNextTag("test", -5); | ||
|
||
putIncorrectRectangle.Should().Throw<ArgumentException>(); | ||
} | ||
|
||
[Test] | ||
public void PutNextRectangle_PlacesFirstRectangleInCenter() | ||
{ | ||
var center = Point.Empty; | ||
var firstRectangle = circularCloudLayouter.PutNextTag("text", 3); | ||
|
||
var rectangleCenter = new Point( | ||
firstRectangle.Left + firstRectangle.Width / 2, | ||
firstRectangle.Top - firstRectangle.Height / 2); | ||
|
||
rectangleCenter.Should().Be(center); | ||
} | ||
|
||
[Test] | ||
public void PutNextRectangle_CloudTagsIsNotIntersect_WhenPuttingALotOfTags() | ||
{ | ||
circularCloudLayouter.PutNextTag("test", 3); | ||
circularCloudLayouter.PutNextTag("test", 2); | ||
circularCloudLayouter.PutNextTag("test", 1); | ||
|
||
var tags = circularCloudLayouter.Tags; | ||
for (var i = 0; i < tags.Count; i++) | ||
{ | ||
var currentRectangle = tags[i].Rectangle; | ||
tags | ||
.Where((_, j) => j != i) | ||
.All(otherTag => !currentRectangle.IntersectsWith(otherTag.Rectangle)) | ||
.Should().BeTrue(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net48</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<LangVersion>10</LangVersion> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="7.0.0" /> | ||
<PackageReference Include="NUnit" Version="4.3.1" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\TagsCloudContainer\TagsCloudContainer.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Reference Include="System.Windows.Forms" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudContainer; | ||
|
||
public class App | ||
{ | ||
public static string GetFileNameFromUser() | ||
{ | ||
while (true) | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
{ | ||
Console.Write("Введите название файла с текстом: "); | ||
var fileName = Console.ReadLine(); | ||
|
||
if (!string.IsNullOrEmpty(fileName) && File.Exists(fileName)) | ||
{ | ||
return fileName; | ||
} | ||
|
||
Console.WriteLine("Файл не найден. Попробуйте снова."); | ||
} | ||
} | ||
|
||
public static (int width, int height) GetImageDimensionsFromUser() | ||
{ | ||
while (true) | ||
{ | ||
Console.Write("Введите размер изображения (ширина высота): "); | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
var input = Console.ReadLine()?.Split(' '); | ||
|
||
if (input?.Length == 2 && | ||
int.TryParse(input[0], out int width) && | ||
int.TryParse(input[1], out int height) && | ||
width > 0 && height > 0) | ||
{ | ||
return (width, height); | ||
} | ||
|
||
Console.WriteLine("Некорректный ввод. Убедитесь, что вы ввели два положительных целых числа."); | ||
} | ||
} | ||
|
||
public static string GetFontNameFromUser() | ||
{ | ||
Console.Write("Введите название шрифта: "); | ||
return Console.ReadLine() ?? "Arial"; | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
} | ||
|
||
public static Color GetColorFromUser(string prompt) | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
{ | ||
while (true) | ||
{ | ||
Console.Write(prompt); | ||
var colorInput = Console.ReadLine(); | ||
|
||
if (Enum.TryParse(colorInput, true, out KnownColor knownColor)) | ||
{ | ||
return Color.FromKnownColor(knownColor); | ||
} | ||
|
||
Console.WriteLine("Некорректное название цвета. Попробуйте снова."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
using System.Drawing; | ||
using System.Windows.Forms; | ||
using TagsCloudContainer.Extensions; | ||
|
||
namespace TagsCloudContainer; | ||
|
||
public class CircularCloudLayouter | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
{ | ||
public readonly Point Center; | ||
public List<Tag> Tags { get; } | ||
public Color? BackgroundColor { get; private set; } | ||
public Color? TextColor { get; private set; } | ||
public string? FontName { get; private set; } | ||
public ISpiral Spiral { get; set; } = null!; | ||
|
||
public CircularCloudLayouter(Point center) | ||
{ | ||
Center = center; | ||
Tags = []; | ||
} | ||
|
||
public CircularCloudLayouterVisualizer CreateView(int width, int height) | ||
{ | ||
return new CircularCloudLayouterVisualizer(this, new Size(width, height)); | ||
} | ||
|
||
public CircularCloudLayouter SetFontName(string font) | ||
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. Предлагаю эти методы по инициализации вынести в какой-нибудь отдельный класс типа CircularCloudLayouterFactory, а тут оставить только расставление тэгов, чтобы ответственность класса слишком сильно не раздувать. |
||
{ | ||
if (font.FontExists()) | ||
{ | ||
FontName = font; | ||
} | ||
|
||
return this; | ||
} | ||
|
||
public CircularCloudLayouter SetBackgroundColor(Color color) | ||
{ | ||
BackgroundColor = color; | ||
return this; | ||
} | ||
|
||
public CircularCloudLayouter SetTextColor(Color color) | ||
{ | ||
TextColor = color; | ||
return this; | ||
} | ||
|
||
public CircularCloudLayouter PutTags(Dictionary<string,int> words) | ||
{ | ||
var sortedWords = words.OrderByDescending(pair => pair.Value); | ||
|
||
foreach (var word in sortedWords) | ||
{ | ||
PutNextTag(word.Key, word.Value); | ||
} | ||
|
||
return this; | ||
} | ||
|
||
public Rectangle PutNextTag(string text, int count) | ||
{ | ||
if (count < 1) | ||
{ | ||
throw new ArgumentException(nameof(count)); | ||
} | ||
|
||
var font = new Font(FontName ?? "Arial", count * 5 + 10); | ||
var rectangleSize = TextRenderer.MeasureText(text, font); | ||
|
||
Rectangle newRectangle; | ||
|
||
do | ||
{ | ||
var location = Spiral.GetNextPoint(); | ||
location.Offset(-rectangleSize.Width / 2, rectangleSize.Height / 2); | ||
newRectangle = new Rectangle(location, rectangleSize); | ||
} | ||
while (Tags.Select(tag => tag.Rectangle).IsIntersectsWithAny(newRectangle)); | ||
|
||
newRectangle = ShiftRectangleToCenter(newRectangle); | ||
|
||
var tag = new Tag(text, font, newRectangle); | ||
Tags.Add(tag); | ||
return newRectangle; | ||
} | ||
|
||
public Size Size() | ||
{ | ||
if (Tags.Count == 0) | ||
return System.Drawing.Size.Empty; | ||
|
||
var left = Tags.Select(tag => tag.Rectangle).Min(rectangle => rectangle.Left); | ||
var right = Tags.Select(tag => tag.Rectangle).Max(rectangle => rectangle.Right); | ||
var top = Tags.Select(tag => tag.Rectangle).Min(rectangle => rectangle.Top); | ||
var bottom = Tags.Select(tag => tag.Rectangle).Max(rectangle => rectangle.Bottom); | ||
|
||
return new Size(right - left, bottom - top); | ||
} | ||
|
||
private Rectangle ShiftRectangleToCenter(Rectangle rectangle) | ||
{ | ||
var directionToCenter = GetDirectionToCenter(rectangle); | ||
while (directionToCenter != Point.Empty) | ||
{ | ||
var nextRectangle = MoveRectangle(rectangle, directionToCenter); | ||
if (Tags.Select(tag => tag.Rectangle).IsIntersectsWithAny(nextRectangle)) | ||
break; | ||
|
||
rectangle = nextRectangle; | ||
directionToCenter = GetDirectionToCenter(rectangle); | ||
} | ||
|
||
return rectangle; | ||
} | ||
|
||
private Point GetDirectionToCenter(Rectangle rectangle) | ||
{ | ||
var rectangleCenter = new Point( | ||
rectangle.Left + rectangle.Width / 2, | ||
rectangle.Top - rectangle.Height / 2); | ||
|
||
return new Point( | ||
Math.Sign(Center.X - rectangleCenter.X), | ||
Math.Sign(Center.Y - rectangleCenter.Y) | ||
); | ||
} | ||
|
||
private static Rectangle MoveRectangle(Rectangle rectangle, Point direction) | ||
{ | ||
return new Rectangle( | ||
new Point(rectangle.X + direction.X, rectangle.Y + direction.Y), | ||
rectangle.Size); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudContainer; | ||
|
||
public class CircularCloudLayouterVisualizer | ||
{ | ||
private CircularCloudLayouter layouter; | ||
private Size size; | ||
|
||
public CircularCloudLayouterVisualizer(CircularCloudLayouter layouter, Size bitmapSize) | ||
{ | ||
this.layouter = layouter; | ||
size = bitmapSize; | ||
} | ||
|
||
public CircularCloudLayouter SaveImage(string filePath) | ||
{ | ||
using var bitmap = new Bitmap(size.Width, size.Height); | ||
using var graphics = Graphics.FromImage(bitmap); | ||
|
||
graphics.Clear(layouter.BackgroundColor ?? Color.White); | ||
|
||
var centerBitmap = new Point(size.Width / 2, size.Height / 2); | ||
var offsetBitmap = new Point(centerBitmap.X - layouter.Center.X, centerBitmap.Y - layouter.Center.Y); | ||
|
||
var brush = new SolidBrush(layouter.TextColor ?? Color.Black); | ||
|
||
foreach (var rectangle in layouter.Tags) | ||
{ | ||
rectangle.Rectangle.Offset(offsetBitmap); | ||
graphics.DrawString(rectangle.Text, rectangle.Font, brush, rectangle.Rectangle); | ||
} | ||
|
||
bitmap.Save(filePath); | ||
|
||
return layouter; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using System.Drawing; | ||
|
||
namespace TagsCloudContainer.Extensions; | ||
|
||
public static class FontExtensions | ||
{ | ||
public static bool FontExists(this string fontName) | ||
{ | ||
return FontFamily.Families.Any(f => f.Name.Equals(fontName, StringComparison.OrdinalIgnoreCase)); | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
} | ||
} |
This comment was marked as resolved.
Sorry, something went wrong.