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

Муканов Арман #223

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
90 changes: 90 additions & 0 deletions cs/TagsCloudVisualization/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Drawing;

namespace TagsCloudVisualization;

public class CircularCloudLayouter : ILayouter
{
private readonly List<Rectangle> rectangles;
private Point center;
private double spiralStep;
private double angle;

public CircularCloudLayouter(Point center)
{
this.center = center;
rectangles = new List<Rectangle>();
spiralStep = 1;
}

public IReadOnlyList<Rectangle> AddedRectangles => rectangles;

public Rectangle PutNextRectangle(Size rectangleSize)
{
if (rectangleSize.Width == 0 || rectangleSize.Height == 0)
throw new ArgumentException($"{nameof(rectangleSize)} should be with positive width and height");
var location = GetPosition(rectangleSize);
var rectangle = new Rectangle(location, rectangleSize);
rectangles.Add(rectangle);
return rectangle;
}

private Point GetPosition(Size rectangleSize)
{
if (rectangles.Count == 0)
{
center.Offset(new Point(rectangleSize / -2));
return center;
}

return FindApproximatePosition(rectangleSize);
}

private Point FindApproximatePosition(Size rectangleSize)
{
var currentAngle = angle;
while (true)
{
var candidateLocation = new Point(center.X + (int)(spiralStep * Math.Cos(currentAngle)),
center.Y + (int)(spiralStep * Math.Sin(currentAngle)));
var candidateRectangle = new Rectangle(candidateLocation, rectangleSize);

if (!IntersectsWithAny(candidateRectangle))
{
rectangles.Add(candidateRectangle);
angle = currentAngle;
return candidateRectangle.Location;
}

currentAngle += GetAngleStep();
if (currentAngle > Math.PI * 2)

Choose a reason for hiding this comment

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

Лучше в отдельные функции унести содержимое условий
А для чисел завести константы

Copy link
Author

Choose a reason for hiding this comment

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

ок

{
currentAngle %= Math.PI * 2;
UpdateSpiral();
}
}
}

private bool IntersectsWithAny(Rectangle candidateRectangle)
{
return rectangles
.Any(candidateRectangle.IntersectsWith);
}

private void UpdateSpiral()
{
spiralStep += 1;
}

private double GetAngleStep()
{
var defaultStep = Math.PI / 10;
var count = (int)spiralStep / 10;
if (count > 0)
{
defaultStep /= count;
}

return defaultStep;
}
}
14 changes: 14 additions & 0 deletions cs/TagsCloudVisualization/ILayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TagsCloudVisualization
{
public interface ILayouter
{
public Rectangle PutNextRectangle(Size rectangleSize);
}
}
39 changes: 39 additions & 0 deletions cs/TagsCloudVisualization/LayouterVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.VisualBasic;
using NUnit.Framework;
using System.Drawing;
using System.Drawing.Imaging;

namespace TagsCloudVisualization;

public class LayouterVisualizer
{
private readonly Bitmap bitmap;

Choose a reason for hiding this comment

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

Что думаешь насчёт применения свойств здесь?

Copy link
Author

Choose a reason for hiding this comment

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

Думаю стоит переделать под свойство. Это даст больше гибкости будущем. Можно выполнять дополнительные действия при обращении или изменении объекта.


public LayouterVisualizer(Size imageSize)
{
bitmap = new Bitmap(imageSize.Width, imageSize.Height);
}

public void VisualizeRectangle(Rectangle rectangle)
{
using var g = Graphics.FromImage(bitmap);
using var brush = new SolidBrush(Color.White);

var pen = new Pen(brush, 3); ;

Choose a reason for hiding this comment

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

Есть ли решение по поводу магического числа 3?

Copy link
Author

Choose a reason for hiding this comment

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

Инкапсулировать в свойство.

g.DrawRectangle(pen, rectangle);
}

public void VisualizeRectangles(IReadOnlyCollection<Rectangle> rectangles)
{
foreach (var rect in rectangles)
{
VisualizeRectangle(rect);
}
}

public void SaveImage(string file, ImageFormat format)

Choose a reason for hiding this comment

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

Как считаешь нужен ли тут String?

Copy link
Author

Choose a reason for hiding this comment

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

Думаю, что нужен, потому что будет неочевидно, куда сохраняется изображение

Choose a reason for hiding this comment

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

Речь про string vs String

Copy link
Author

Choose a reason for hiding this comment

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

string - это же просто alias для String. Поэтому я не вижу преимуществ в использовании String

{
bitmap.Save(file, format);

Choose a reason for hiding this comment

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

Какие есть идеи в случае появления ошибок в Save?

Copy link
Author

Choose a reason for hiding this comment

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

try catch...

Console.WriteLine($"Tag cloud visualization saved to {file}");
}
}
7 changes: 7 additions & 0 deletions cs/TagsCloudVisualization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Примеры визуализации облака тегов

![plot](./TagCloud2.jpeg)

![plot](./TagCloud3.jpeg)

![plot](./TagCloud4.jpeg)

Choose a reason for hiding this comment

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

Как считаешь нужен ли уникальный alt у изображений?

Стоит ли подумать о caption или он не нужен?

Copy link
Author

Choose a reason for hiding this comment

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

Alt отображается в случае, если изображение не может быть показано. Он поможет понять, что было на изображении, поэтому его стоит использовать.
Caption тоже будет полезным, потому что caption даёт дополнительную информацию об изображении и контексте.

Binary file added cs/TagsCloudVisualization/TagCloud2.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/TagCloud3.jpeg

Choose a reason for hiding this comment

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

Как считаешь можно ли хранить файлы в директории с кодом?
Если нет, то какое решение можно придумать?
Если да, то почему?

Copy link
Author

Choose a reason for hiding this comment

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

Думаю, это не очень хорошая практика. Возможные решения:

  1. Выделить директорию в проекте
  2. Хранить файлы в отдельном репозитории/пространстве

Copy link

@the-homeless-god the-homeless-god Dec 12, 2023

Choose a reason for hiding this comment

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

Давай назовём папку а-ля assets/images resources/images и также присвоим названия картинкам более осмысленные?

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/TagCloud4.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudVisualization.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="7.0.0-alpha.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0-preview-23531-01" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit.Console" Version="3.16.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
</ItemGroup>

</Project>
149 changes: 149 additions & 0 deletions cs/TagsCloudVisualization/Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.Text.Encodings.Web;
using FluentAssertions;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;

namespace TagsCloudVisualization;

[TestFixture]
public class Tests

Choose a reason for hiding this comment

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

Стоит иметь на каждый класс свой отдельный файл тестов

Copy link
Author

Choose a reason for hiding this comment

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

ок

{
private readonly string path = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsResults");
private CircularCloudLayouter cloudLayouter;
private Point center;
private Size canvasSize;

private Randomizer Random => TestContext.CurrentContext.Random;

[OneTimeSetUp]
public void OneTimeSetUp()
{
canvasSize = new Size(900, 900);
center = new Point(450, 450);

DeleteTestResults();
}

[SetUp]
public void SetUp()
{
cloudLayouter = new CircularCloudLayouter(center);
}


[TearDown]
public void TearDown()
{
var status = TestContext.CurrentContext.Result.Outcome.Status;
var test = TestContext.CurrentContext.Test;
if (status != TestStatus.Failed) return;

var visualizer = new LayouterVisualizer(canvasSize);
visualizer.VisualizeRectangles(cloudLayouter.AddedRectangles);

Directory.CreateDirectory(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsResults"));
var pathToImage = Path.Combine(path, test.Name + ".jpeg");
visualizer.SaveImage(pathToImage, ImageFormat.Jpeg);
}

[Test]
public void PutNextRectangle_ShouldThrow_WhenSizeNotPositive()
{
var action = () => cloudLayouter.PutNextRectangle(new Size(0, 0));

action.Should().Throw<ArgumentException>();
}

[Test]
public void PutNextRectangle_ShouldReturnRectangleWithGivenSize()
{
var size = new Size(10, 10);

var rectangle = cloudLayouter.PutNextRectangle(size);

rectangle.Size.Should().Be(size);
}

[Test]
public void PutNextRectangle_ShouldPlaceFirstRectangleInCenter()
{
var size = new Size(50, 50);
var offsettedCenter = center - size / 2;

var rectangle = cloudLayouter.PutNextRectangle(size);

rectangle.Location.Should().Be(offsettedCenter);
}

[Test]
public void PutNextRectangle_ShouldReturnNotIntersectingRectangles()
{
var sizes = GetRandomSizes(70);

var actualRectangles = sizes.Select(x => cloudLayouter.PutNextRectangle(x)).ToList();

for (var i = 0; i < actualRectangles.Count - 1; i++)
{
for (var j = i + 1; j < actualRectangles.Count; j++)
{
actualRectangles[i].IntersectsWith(actualRectangles[j]).Should().BeFalse();
}
}
}

[Test]
public void PutNextRectangle_ShouldCreateLayoutCloseToCircle()
{
var sizes = GetRandomSizes(50);

var rectangles = sizes.Select(size => cloudLayouter.PutNextRectangle(size)).ToList();
var rectanglesSquare = rectangles
.Select(x => x.Width * x.Height)
.Sum();
var circleSquare = CalculateBoundingCircleSquare(rectangles);

rectanglesSquare.Should().BeInRange((int)(circleSquare * 0.8), (int)(circleSquare * 1.2));
}

private void DeleteTestResults()
{
var di = new DirectoryInfo(path);
foreach (var file in di.GetFiles())
{
file.Delete();
}
}

private Size GetRandomSize()
{
//return new Size(Random.Next(30, 40), Random.Next(30, 40));
return new Size(Random.Next(100, 120), Random.Next(30, 90));
}

private List<Size> GetRandomSizes(int count)
{
var sizes = new List<Size>(count);
for (var i = 0; i < count; i++)
sizes.Add(GetRandomSize());
return sizes;
}

private double CalculateBoundingCircleSquare(List<Rectangle> rectangles)
{
var rect = rectangles
.Where(x => x.Contains(x.X, center.Y))
.MaxBy(x => Math.Abs(x.X - center.X));
var width = Math.Abs(rect.X - center.X);

rect = rectangles
.Where(x => x.Contains(center.X, x.Y))
.MaxBy(x => Math.Abs(x.Y - center.Y));
var height = Math.Abs(rect.Y - center.Y);

return Math.Max(width, height) * Math.Max(width, height) * Math.PI;
}
}
6 changes: 6 additions & 0 deletions cs/tdd.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BowlingGame", "BowlingGame\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualization", "TagsCloudVisualization\TagsCloudVisualization.csproj", "{07E38DAF-6410-49F7-A689-B1CC76A0FBB2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.Build.0 = Release|Any CPU
{07E38DAF-6410-49F7-A689-B1CC76A0FBB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07E38DAF-6410-49F7-A689-B1CC76A0FBB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07E38DAF-6410-49F7-A689-B1CC76A0FBB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07E38DAF-6410-49F7-A689-B1CC76A0FBB2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down