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

Рушкова Ольга #29

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
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
25 changes: 25 additions & 0 deletions TagCloudDI/CloudCreator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using TagCloudDI.CloudVisualize;
using TagCloudDI.Data;

namespace TagCloudDI
{
public class CloudCreator
{
private DataProvider dataProvider;
private CloudVisualizer cloudVisualizer;
private readonly DefaultImageSaver imageSaver;
public CloudCreator(DataProvider dataProvider, CloudVisualizer visualizer)
{
this.dataProvider = dataProvider;
cloudVisualizer = visualizer;
imageSaver = new DefaultImageSaver();
}

public string CreateTagCloud(string pathToFileWithWords)
{
var words = dataProvider.GetPreprocessedWords(pathToFileWithWords);
var image = cloudVisualizer.CreateImage(words);
return imageSaver.SaveImage(image);
}
}
}
36 changes: 36 additions & 0 deletions TagCloudDI/CloudLayouter/BruteForceNearestFinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace TagsCloudVisualization.CloudLayouter
{
public static class BruteForceNearestFinder
{
public static Rectangle? FindNearestByDirection(Rectangle r, Direction direction, List<Rectangle> rectangles)
{
if (rectangles.FirstOrDefault() == default)
return null;
var nearestByDirection = rectangles
.Select(possibleNearest =>
(Distance: CalculateMinDistanceBy(direction, possibleNearest, r), Nearest: possibleNearest))
.Where(el => el.Distance >= 0)
.ToList();

return nearestByDirection.Count > 0 ? nearestByDirection.MinBy(el => el.Distance).Nearest : null;
}


public static int CalculateMinDistanceBy(Direction direction,
Rectangle possibleNearest, Rectangle rectangleForFind)
{
return direction switch
{
Direction.Left => rectangleForFind.Left - possibleNearest.Right,
Direction.Right => possibleNearest.Left - rectangleForFind.Right,
Direction.Top => rectangleForFind.Top - possibleNearest.Bottom,
Direction.Bottom => possibleNearest.Top - rectangleForFind.Bottom,
};
}
}
}
34 changes: 34 additions & 0 deletions TagCloudDI/CloudLayouter/CirclePositionDistributor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Drawing;

namespace TagsCloudVisualization;

public class CirclePositionDistributor
{
public Point Center { get; }
public int Radius { get; private set; }

private const int DeltaRadius = 5;
private int currentPositionDegrees;


public CirclePositionDistributor(Point center)
{
Center = center;
Radius = 5;
}

public Point GetNextPosition()
{
currentPositionDegrees += 1;
if (currentPositionDegrees > 360)
{
currentPositionDegrees = 0;
Radius += DeltaRadius;
}
var positionAngleInRadians = currentPositionDegrees * Math.PI / 180.0;
return new Point(
Center.X + (int)Math.Ceiling(Radius * Math.Cos(positionAngleInRadians)),
Center.Y + (int)Math.Ceiling(Radius * Math.Sin(positionAngleInRadians)));
}
}
68 changes: 68 additions & 0 deletions TagCloudDI/CloudLayouter/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using TagCloudDI;

namespace TagsCloudVisualization.CloudLayouter
{
public class CircularCloudLayouter : ICloudLayouter
{
private readonly List<Rectangle> storage;
private readonly CloudCompressor compressor;
private readonly CirclePositionDistributor distributor;

public CircularCloudLayouter(): this(new Point(0,0), [])
{ }

private CircularCloudLayouter(Point center, List<Rectangle> storage)
{
this.storage = storage;
distributor = new(center);
compressor = new(center, storage);
}

public static CircularCloudLayouter CreateLayouterWithStartRectangles(Point center, List<Rectangle> storage)
{
return new CircularCloudLayouter(center, storage);
}

public Rectangle PutNextRectangle(Size nextRectangle)
{
ValidateRectangleSize(nextRectangle);

var inserted = PutRectangleWithoutIntersection(nextRectangle);
var rectangleWithOptimalPosition = compressor.CompressCloudAfterInsertion(inserted);

storage.Add(rectangleWithOptimalPosition);

return rectangleWithOptimalPosition;
}

public Rectangle PutRectangleWithoutIntersection(Size forInsertionSize)
{
bool isIntersected;
Rectangle forInsertion;
do
{
var possiblePosition = distributor.GetNextPosition();
forInsertion = new Rectangle(possiblePosition, forInsertionSize);
isIntersected = forInsertion.IntersectedWithAnyFrom(storage);
}
while (isIntersected);

return forInsertion;
}

private static void ValidateRectangleSize(Size forInsertion)
{
if (forInsertion.Width <= 0 || forInsertion.Height <= 0)
throw new ArgumentException($"Rectangle has incorrect size: width = {forInsertion.Width}, height = {forInsertion.Height}");
}

public void Clear()
{
storage.Clear();
}
}
}
98 changes: 98 additions & 0 deletions TagCloudDI/CloudLayouter/CloudCompressor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System.Collections.Generic;
using System.Drawing;
using TagsCloudVisualization.CloudLayouter;

namespace TagsCloudVisualization
{
internal class CloudCompressor
{
private readonly Point compressionPoint;
private readonly List<Rectangle> cloud;
private int minDistanceForMoving = 1;

public CloudCompressor(Point compressTo, List<Rectangle> cloud)
{
compressionPoint = compressTo;
this.cloud = cloud;
}


public Rectangle CompressCloudAfterInsertion(Rectangle forInsertion)
{
var toCompressionPoint = GetDirectionsForMovingForCompress(forInsertion);
var beforeIntersection = forInsertion;
var prevDirectionHasIntersection = false;
for (var i = 0; i < toCompressionPoint.Count; i++)
{
var direction = toCompressionPoint[i];

while (!forInsertion.IntersectedWithAnyFrom(cloud)
&& !IsIntersectCompressionPointAxis(direction, forInsertion))
{
beforeIntersection = forInsertion;
var distance = GetDistanceForMoving(forInsertion, direction);
forInsertion.Location = MoveByDirection(forInsertion.Location,
distance == 0 ? minDistanceForMoving : distance, direction);
}

var wasIntersection = !IsIntersectCompressionPointAxis(direction, forInsertion);
if (!prevDirectionHasIntersection && wasIntersection)
{
forInsertion = beforeIntersection;
prevDirectionHasIntersection = true;
}
}
return beforeIntersection;
}


private int GetDistanceForMoving(Rectangle forMoving, Direction toCompressionPoint)
{
var nearest = BruteForceNearestFinder.FindNearestByDirection(forMoving, toCompressionPoint, cloud);
if (nearest == null) return minDistanceForMoving;
return BruteForceNearestFinder.CalculateMinDistanceBy(toCompressionPoint, nearest.Value, forMoving);
}

private bool IsIntersectCompressionPointAxis(Direction toCompressionPoint, Rectangle current)
{
return toCompressionPoint switch
{
Direction.Left => current.Left < compressionPoint.X,
Direction.Right => current.Right > compressionPoint.X,
Direction.Top => current.Top < compressionPoint.Y,
Direction.Bottom => current.Bottom > compressionPoint.Y
};
}

private static Point MoveByDirection(Point forMoving, int distance, Direction whereMoving)
{
var factorForDistanceByX = whereMoving switch
{
Direction.Left => -1,
Direction.Right => 1,
_ => 0
};
var factorForDistanceByY = whereMoving switch
{
Direction.Top => -1,
Direction.Bottom => 1,
_ => 0
};
forMoving.X += distance * factorForDistanceByX;
forMoving.Y += distance * factorForDistanceByY;

return forMoving;
}

private List<Direction> GetDirectionsForMovingForCompress(Rectangle forMoving)
{
var directions = new List<Direction>();
if (forMoving.Bottom <= compressionPoint.Y) directions.Add(Direction.Bottom);
if (forMoving.Top >= compressionPoint.Y) directions.Add(Direction.Top);
if (forMoving.Left >= compressionPoint.X) directions.Add(Direction.Left);
if (forMoving.Right <= compressionPoint.X) directions.Add(Direction.Right);

return directions;
}
}
}
10 changes: 10 additions & 0 deletions TagCloudDI/CloudLayouter/Direction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace TagsCloudVisualization.CloudLayouter
{
public enum Direction
{
Left,
Right,
Top,
Bottom
}
}
90 changes: 90 additions & 0 deletions TagCloudDI/CloudVisualize/CloudVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Drawing;

namespace TagCloudDI.CloudVisualize
{
public class CloudVisualizer
{
private VisualizeSettings settings;
private readonly ICloudLayouter layouter;
private readonly IWordColorDistributor distributor;


public CloudVisualizer(VisualizeSettings settings, ICloudLayouter cloudLayouter, IWordColorDistributor distributor)
{
this.settings = settings;
layouter = cloudLayouter;
this.distributor = distributor;
}

public Bitmap CreateImage((string Word, double Frequency)[] source)
{
var words = LayoutWords(source).ToArray();
var tmpImageSize = CalculateImageSize(words);
words = PlaceCloudInImage(words, tmpImageSize);
using var image = new Bitmap(tmpImageSize.Width, tmpImageSize.Height);
using var graphics = Graphics.FromImage(image);

graphics.Clear(settings.BackgroundColor);

for (var i = 0; i < words.Length; i++)
{
var currentWord = words[i];
var font = new Font(settings.FontFamily, currentWord.FontSize);
var color = distributor.GetColor(settings.WordColors);
graphics.DrawRectangle(new Pen(color), currentWord.WordBorder);
graphics.DrawString(currentWord.Word, font, new SolidBrush(color), currentWord.WordBorder);
}
var resizedImage = new Bitmap(image, settings.ImageSize);
return resizedImage;
}

private IEnumerable<WordParameters> LayoutWords((string Word, double Frequency)[] words)
{
var g = Graphics.FromImage(new Bitmap(1, 1));
foreach (var word in words)
{
var fontSize = Math.Max((float)(settings.MaxFontSize * word.Frequency), settings.MinFontSize);
var wordSize = g.MeasureString(word.Word, new Font(settings.FontFamily, fontSize));
var wordSizeInt = new Size((int)Math.Ceiling(wordSize.Width), (int)Math.Ceiling(wordSize.Height));
var border = layouter.PutNextRectangle(wordSizeInt);
yield return new WordParameters(word.Word, border, fontSize);
}
layouter.Clear();
}

private WordParameters[] PlaceCloudInImage(WordParameters[] words, Size tmpImageSize)
{
var deltaForX = CalculateDeltaForMoveByAxis(words, r => r.Left, r => r.Right, tmpImageSize.Width);
var deltaForY = CalculateDeltaForMoveByAxis(words, r => r.Top, r => r.Bottom, tmpImageSize.Height);
foreach (var word in words)
{
word.MoveBorderToNewLocation(new Point(word.WordBorder.Left + deltaForX, word.WordBorder.Y + deltaForY));
}
return words;
}

private int CalculateDeltaForMoveByAxis(
WordParameters[] words,
Func<Rectangle, int> selectorForMin,
Func<Rectangle, int> selectorForMax,
int sizeByAxis)
{
if (words.Length == 0) return 0;
var minByAxis = words.Min(w => selectorForMin(w.WordBorder));
var maxByAxis = words.Max(w => selectorForMax(w.WordBorder));
return minByAxis < 0
? -1 * minByAxis
: maxByAxis > sizeByAxis
? sizeByAxis - maxByAxis
: 0;
}

private Size CalculateImageSize(WordParameters[] words)
{
var width = words.Max(w => w.WordBorder.Right) - words.Min(w => w.WordBorder.Left);
var height = words.Max(w => w.WordBorder.Bottom) - words.Min(w => w.WordBorder.Top);
var sizeForRectangles = Math.Max(width, height);
return new Size(sizeForRectangles, sizeForRectangles);
}
}
}
23 changes: 23 additions & 0 deletions TagCloudDI/CloudVisualize/DefaultImageSaver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Drawing.Imaging;
using System.Drawing;

namespace TagCloudDI.CloudVisualize
{
internal class DefaultImageSaver: IImageSaver
{
private ImageFormat format = ImageFormat.Png;

public string SaveImage(Bitmap image, string? filePath = null)
{
var rnd = new Random();
filePath ??= Path.Combine(Path.GetTempPath(), $"tagCloud{rnd.Next()}.{FormatToString()}");
image.Save(filePath, format);
return filePath;
}

private string? FormatToString()
{
return new ImageFormatConverter().ConvertToString(format)?.ToLower();
}
}
}
Loading