Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
32 changes: 32 additions & 0 deletions cs/TagsCloudVisualization/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Drawing;

namespace TagsCloudVisualization;

public class CircularCloudLayouter(Point center) : ICircularCloudLayouter
{
private readonly List<Rectangle> rectangles = [];
private readonly SpiralPointGenerator pointGenerator = new(center);


public Rectangle PutNextRectangle(Size rectangleSize)
{
while (true)
{
var point = pointGenerator.GenerateNextPoint();
var rect = CreateRectangleWithCenter(point, rectangleSize);

if (rectangles.Any(r => r.IntersectsWith(rect)))
continue;

rectangles.Add(rect);
return rect;
}
}

private Rectangle CreateRectangleWithCenter(Point center, Size size)
{
var x = center.X - size.Width / 2;
var y = center.Y - size.Height / 2;
return new Rectangle(new Point(x, y), size);
}
}
8 changes: 8 additions & 0 deletions cs/TagsCloudVisualization/ICircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Drawing;

namespace TagsCloudVisualization;

public interface ICircularCloudLayouter
{
Rectangle PutNextRectangle(Size rectangleSize);
}
3 changes: 3 additions & 0 deletions cs/TagsCloudVisualization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
![Природа](wordCloud.png)
![Медицина](wordCloudMedicine.png)
![Спорт](wordCloudSports.png)
22 changes: 22 additions & 0 deletions cs/TagsCloudVisualization/SpiralPointGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Drawing;

namespace TagsCloudVisualization;

public class SpiralPointGenerator(Point center)
{
private const double AngleStepRadians = 0.05;
private const double RadiusGrowthRate = 0.1;
private double angle;
private double radius;

public Point GenerateNextPoint()
{
var x = center.X + (int)(radius * Math.Cos(angle));
var y = center.Y + (int)(radius * Math.Sin(angle));

angle += AngleStepRadians;
radius = RadiusGrowthRate * angle;

return new Point(x, y);
}
}
22 changes: 22 additions & 0 deletions cs/TagsCloudVisualization/TagsCloudVisualization.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net48" Version="1.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="6.0.0-beta.1" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
</ItemGroup>

</Project>
97 changes: 97 additions & 0 deletions cs/TagsCloudVisualization/Tests/CircularCloudLayouterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System.Drawing;
using FluentAssertions;
using NUnit.Framework;


namespace TagsCloudVisualization;

[TestFixture]
public class CircularCloudLayouterTest
{
private CircularCloudLayouter layouter;
private Point center;

[SetUp]
public void SetUp()
{
center = new Point(100, 100);
layouter = new CircularCloudLayouter(center);
}

[Test]
public void PutNextRectangle_PutsRectangleAroundCenter()
{
var size = new Size(20, 20);

var rect = layouter.PutNextRectangle(size);

var rectCenter = new Point(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2);
rectCenter.Should().Be(center);
}

[Test]
public void PutNextRectangle_ReturnsDifferentRectangles()
{
var size = new Size(20, 20);

var r1 = layouter.PutNextRectangle(size);
var r2 = layouter.PutNextRectangle(size);

r1.Should().NotBe(r2);
}

[Test]
public void PutNextRectangle_ReturnsNotIntersectRectangles()
{
var size = new Size(20, 20);
var placed = new List<Rectangle>();

for (var i = 0; i < 100; i++)
{
var next = layouter.PutNextRectangle(size);
foreach (var prev in placed)
next.IntersectsWith(prev).Should().BeFalse();
placed.Add(next);
}
}

[Test]
public void PutNextRectangle_CreateDenseRectangles()
{
var size = new Size(20, 20);
var rects = Enumerable.Range(0, 100)
.Select(_ => layouter.PutNextRectangle(size))
.ToArray();

var centers = rects.Select(GetCenter).ToArray();
var avgX = centers.Average(p => p.X);
var avgY = centers.Average(p => p.Y);

Math.Abs(avgX - center.X).Should().BeLessThan(size.Width);
Math.Abs(avgY - center.Y).Should().BeLessThan(size.Height);
}

[Test]
public void PutNextRectangle_CreateSameRectangles_WhenSameSizes()
{
var layouter1 = new CircularCloudLayouter(center);
var layouter2 = new CircularCloudLayouter(center);
var sizes = new[]
{
new Size(10, 10),
new Size(20, 10),
new Size(15, 15),
new Size(5, 30)
};

var rects1 = sizes.Select(s => layouter1.PutNextRectangle(s)).ToArray();
var rects2 = sizes.Select(s => layouter2.PutNextRectangle(s)).ToArray();

rects1.Should().BeEquivalentTo(rects2);
}

private static Point GetCenter(Rectangle rectangle)
{
return new Point(rectangle.Left + rectangle.Width / 2, rectangle.Top + rectangle.Height / 2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System.Drawing;
using FluentAssertions;
using NUnit.Framework;

namespace TagsCloudVisualization;

[TestFixture]
public class VisualizerApprovalTests : VisualizerTestsBase
{
private int imageWidth { get; set; }
private int imageHeight { get; set; }
private Color TextColor { get; set; }
private Color BackgroundColor { get; set; }
private Font Font { get; set; }

[SetUp]
public void SetUp()
{
imageWidth = 1100;
imageHeight = 900;
TextColor = Color.FromArgb(255, 102, 0);
BackgroundColor = Color.FromArgb(0, 28, 39);
Font = new Font("Times New Roman", 16);
}

[Test]
public void GenerateWordCloudImage()
{
var words = new[]
{
"Forest", "River", "Mountain", "Ocean", "Sea",
"Lake", "Valley", "Desert", "Island", "Cliff",
"Meadow", "Field", "Prairie", "Steppe", "Canyon",
"Waterfall", "Glacier", "Volcano", "Geyser", "Lagoon",
"Tree", "Bush", "Grass", "Flower", "Moss",
"Leaf", "Roots", "Branch", "Seed", "Bloom",
"Sunrise", "Sunset", "Rain", "Snow", "Storm",
"Thunder", "Lightning", "Clouds", "Wind", "Fog",
"Wildlife", "Birds", "Animals", "Insects", "Fish",
"Ecosystem", "Nature", "Landscape", "Horizon", "Sky"
};
SetVisualizationData(imageWidth, imageHeight, words, "wordCloudNature_failed.png", Font, TextColor, BackgroundColor);

var visualizer = new Visualizer(new CircularCloudLayouter(new Point(imageWidth / 2, imageHeight / 2)));

visualizer.DrawCloud(
words,
"wordCloud.png",
imageWidth,
imageHeight,
TextColor,
BackgroundColor,
Font);
}

[Test]
public void GenerateSportsWordCloudImage_Failed()
{
var words = new[]
{
"Football", "Soccer", "Basketball", "Baseball", "Hockey",
"Tennis", "Volleyball", "Rugby", "Cricket", "Golf",
"Boxing", "MMA", "Wrestling", "Cycling", "Running",
"Marathon", "Sprint", "Swimming", "Diving", "Rowing",
"Skating", "Skiing", "Snowboarding", "Surfing", "Skateboard",
"Climbing", "Bouldering", "Gymnastics", "Karate", "Judo",
"Taekwondo", "Badminton", "TableTennis", "Handball", "WaterPolo",
"Lacrosse", "Softball", "AmericanFootball", "Fencing", "Archery",
"Triathlon", "Biathlon", "Decathlon", "Heptathlon", "Crossfit",
"Powerlifting", "Weightlifting", "Bodybuilding", "Yoga", "Pilates",
"Aerobics", "Zumba", "Parkour", "Freerun", "Motorsport",
"FormulaOne", "Rally", "Karting", "Esports", "Chess",
"Darts", "Bowling", "Snooker", "Billiards", "Polo",
"Kayaking", "Canoeing", "Windsurfing", "Kitesurfing", "Paragliding",
"Mountaineering", "TrailRunning", "Ultramarathon", "NordicWalking", "Orienteering",
"Stadium", "Arena", "Coach", "Referee", "Captain",
"Team", "League", "Tournament", "Championship", "Playoffs",
"Fitness", "Training", "Warmup", "Cooldown", "Stretching",
"Goal", "Assist", "Penalty", "Offense", "Defense",
"SprintFinish", "Record", "Medal", "Victory", "FairPlay"
};
SetVisualizationData(imageWidth, imageHeight, words, "wordCloudSports_failed.png", Font, TextColor, BackgroundColor);

var visualizer = new Visualizer(new CircularCloudLayouter(new Point(imageWidth / 2, imageHeight / 2)));

visualizer.DrawCloud(
words,
"wordCloudSports.png",
imageWidth,
imageHeight,
TextColor,
BackgroundColor,
Font);
1.Should().Be(2);
}

[Test]
public void GenerateMedicalWordCloudImage()
{
var words = new[]
{
"Medicine", "Health", "Doctor", "Nurse", "Surgeon",
"Therapist", "Cardiologist", "Neurologist", "Pediatrician", "Dentist",
"Oncologist", "Dermatologist", "Psychiatrist", "Radiologist", "Anesthesiologist",
"Pharmacist", "Paramedic", "Clinician", "Internist", "Endocrinologist",
"Hospital", "Clinic", "Ward", "ICU", "Emergency",
"Ambulance", "OperatingRoom", "Laboratory", "Pharmacy", "Reception",
"WaitingRoom", "RecoveryRoom", "TraumaCenter", "Outpatient", "Inpatient",
"Diagnosis", "Treatment", "Therapy", "Surgery", "Operation",
"Rehabilitation", "Consultation", "Checkup", "Vaccination", "Screening",
"Prevention", "Monitoring", "FollowUp", "Referral", "Telemedicine",
"Heart", "Lungs", "Brain", "Liver", "Kidneys",
"Stomach", "Intestines", "Pancreas", "Skin", "Bones",
"Muscles", "Joints", "Spine", "Blood", "ImmuneSystem",
"Infection", "Virus", "Bacteria", "Inflammation", "Tumor",
"Cancer", "Diabetes", "Hypertension", "Stroke", "Allergy",
"Asthma", "Depression", "Anxiety", "Insomnia", "Obesity",
"Symptom", "Fever", "Pain", "Cough", "Fatigue",
"Nausea", "Headache", "Dizziness", "Bleeding", "Swelling",
"Rash", "ShortnessOfBreath", "Palpitations", "Vomiting", "Diarrhea",
"Vaccine", "Antibiotic", "Analgesic", "Antiseptic", "Anesthetic",
"Hormone", "Insulin", "Sedative", "Antiviral", "Steroid",
"Vitamin", "Supplement", "Infusion", "Injection", "Tablet",
"Stethoscope", "Syringe", "Scalpel", "Thermometer", "Glucometer",
"ECG", "XRay", "MRI", "CTScan", "Ultrasound",
"Defibrillator", "Ventilator", "Wheelchair", "Bandage", "Mask",
"Gloves", "Disinfectant", "Microscope", "TestTube", "Monitor",
"Wellness", "Nutrition", "Hydration", "Exercise", "Hygiene",
"Immunity", "Recovery", "FirstAid", "EmergencyCare", "PalliativeCare",
"PublicHealth", "Epidemiology", "ClinicalTrial", "Research", "EvidenceBased"

};
SetVisualizationData(imageWidth, imageHeight, words, "wordCloudMedicine_failed.png", Font, TextColor, BackgroundColor);

var visualizer = new Visualizer(new CircularCloudLayouter(new Point(imageWidth / 2, imageHeight / 2)));

visualizer.DrawCloud(
words,
"wordCloudMedicine.png",
imageWidth,
imageHeight,
TextColor,
BackgroundColor,
Font);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Drawing;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using TagsCloudVisualization;

public class VisualizerTestsBase
{
private int imageWidth ;
private int imageHeight;
private string[] words;
private string fileName;
private Font font;
private Color textColor;
private Color backgroundColor;

public void SetVisualizationData(
int imageWidth,
int imageHeight,
string[] words,
string fileName,
Font font,
Color textColor,
Color backgroundColor)
{
this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
this.words = words;
this.fileName = fileName;
if (font != null) this.font = font;
if (textColor != null) this.textColor = textColor;
if (backgroundColor != null) this.backgroundColor = backgroundColor;
}

[TearDown]
public void TearDown()
{
var result = TestContext.CurrentContext.Result;
if (result.Outcome.Status != TestStatus.Failed)
return;
var directory = TestContext.CurrentContext.TestDirectory;
var path = Path.Combine(directory, fileName);
var visualizer = new Visualizer(new CircularCloudLayouter(new Point(imageWidth / 2, imageHeight / 2)));

visualizer.DrawCloud(
words,
path,
imageWidth,
imageHeight,
textColor,
backgroundColor,
font);

TestContext.Out.WriteLine($"Tag cloud visualization saved to file {path}");
}
}
Loading