diff --git a/TagsCloudContainer/Actions/AlgorithmSettingsAction.cs b/TagsCloudContainer/Actions/AlgorithmSettingsAction.cs new file mode 100644 index 000000000..f26a7fc74 --- /dev/null +++ b/TagsCloudContainer/Actions/AlgorithmSettingsAction.cs @@ -0,0 +1,26 @@ +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture.UiActions; + +namespace TagsCloudContainer.Actions +{ + public class AlgorithmSettingsAction : IUiAction + { + private AlgorithmSettings algorithmSettings; + + public AlgorithmSettingsAction(AlgorithmSettings settings) + { + this.algorithmSettings = settings; + } + + public string Category => "Настроить"; + + public string Name => "Алгоритм"; + + public string Description => "Изменить настройки алгоритма"; + + public void Perform() + { + SettingsForm.For(algorithmSettings).ShowDialog(); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Actions/DrawTagCloudAction.cs b/TagsCloudContainer/Actions/DrawTagCloudAction.cs new file mode 100644 index 000000000..9f6b430bb --- /dev/null +++ b/TagsCloudContainer/Actions/DrawTagCloudAction.cs @@ -0,0 +1,30 @@ +using TagsCloudContainer.Client; +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture.UiActions; + +namespace TagsCloudContainer.Actions +{ + public class DrawTagCloudAction : IUiAction + { + private FileSettings fileSettings; + private ITagCloudClient tagCloudClient; + + public DrawTagCloudAction(FileSettings fileSettings, ITagCloudClient tagCloudClient) + { + this.tagCloudClient = tagCloudClient; + this.fileSettings = fileSettings; + } + + public string Category => "Изображение"; + + public string Name => "Отрисовать изображение"; + + public string Description => "Отрисовать изображение облака тегов"; + + public void Perform() + { + tagCloudClient.DrawImage(fileSettings.SourceFilePath, + fileSettings.BoringFilePath); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Actions/ImageSettingsAction.cs b/TagsCloudContainer/Actions/ImageSettingsAction.cs new file mode 100644 index 000000000..6839f9050 --- /dev/null +++ b/TagsCloudContainer/Actions/ImageSettingsAction.cs @@ -0,0 +1,30 @@ +using TagsCloudContainer.Infrastucture.Extensions; +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture.UiActions; + +namespace TagsCloudContainer.Actions +{ + public class ImageSettingsAction : IUiAction + { + private ImageSettings imageSettings; + private PictureBox pictureBox; + + public ImageSettingsAction(ImageSettings settings, PictureBox pictureBox) + { + this.imageSettings = settings; + this.pictureBox = pictureBox; + } + + public string Category => "Настроить"; + + public string Name => "Изображение"; + + public string Description => "Изменить настройки изображения"; + + public void Perform() + { + SettingsForm.For(imageSettings).ShowDialog(); + pictureBox.RecreateImage(imageSettings); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Actions/SaveImageAction.cs b/TagsCloudContainer/Actions/SaveImageAction.cs new file mode 100644 index 000000000..e3bf756d0 --- /dev/null +++ b/TagsCloudContainer/Actions/SaveImageAction.cs @@ -0,0 +1,40 @@ +using TagsCloudContainer.Client; +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture.UiActions; + +namespace TagsCloudContainer.Actions +{ + public class SaveImageAction : IUiAction + { + private FileSettings fileSettings; + private ITagCloudClient tagCloudClient; + + public SaveImageAction(FileSettings settings, ITagCloudClient tagCloudClient) + { + this.fileSettings = settings; + this.tagCloudClient = tagCloudClient; + } + + public string Category => "Изображение"; + + public string Name => "Сохранить"; + + public string Description => "Сохранить изображение"; + + public void Perform() + { + var dialog = new SaveFileDialog + { + CheckFileExists = false, + InitialDirectory = Path.GetFullPath(fileSettings.ImagePath), + DefaultExt = "png", + FileName = "image.png", + Filter = "Изображения (*.png)|*.png|Изображения (*.jpg)|*.jpg|Изображения (*.bmp)|*.bmp" + }; + var res = dialog.ShowDialog(); + + if (res == DialogResult.OK) + tagCloudClient.SaveImage(dialog.FileName); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Actions/SelectBoringWordsFileAction.cs b/TagsCloudContainer/Actions/SelectBoringWordsFileAction.cs new file mode 100644 index 000000000..081d3f892 --- /dev/null +++ b/TagsCloudContainer/Actions/SelectBoringWordsFileAction.cs @@ -0,0 +1,40 @@ +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture.UiActions; + +namespace TagsCloudContainer.Actions +{ + public class SelectBoringWordsFileAction : IUiAction + { + private FileSettings fileSetting; + + public SelectBoringWordsFileAction(FileSettings settings) + { + this.fileSetting = settings; + } + + public string Category => "Файлы"; + + public string Name => "Файл со скучными словами"; + + public string Description => "Выбрать файл со списком скучных слов"; + + public void Perform() + { + var filePath = fileSetting.BoringFilePath; + var dialog = new OpenFileDialog() + { + CheckFileExists = true, + InitialDirectory = Path.GetFullPath(filePath), + DefaultExt = "txt", + FileName = "boring.txt", + Filter = "Текстовые файлы (*.txt)|*.txt" + }; + var res = dialog.ShowDialog(); + + if (res == DialogResult.OK) + filePath = dialog.FileName; + + fileSetting.BoringFilePath = filePath; + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Actions/SelectSourceFileAction.cs b/TagsCloudContainer/Actions/SelectSourceFileAction.cs new file mode 100644 index 000000000..47a101217 --- /dev/null +++ b/TagsCloudContainer/Actions/SelectSourceFileAction.cs @@ -0,0 +1,40 @@ +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture.UiActions; + +namespace TagsCloudContainer.Actions +{ + public class SelectSourceFileAction : IUiAction + { + private FileSettings fileSettings; + + public SelectSourceFileAction(FileSettings settings) + { + this.fileSettings = settings; + } + + public string Category => "Файлы"; + + public string Name => "Файл cо словами"; + + public string Description => "Выбрать файл со словами для облака тегов"; + + public void Perform() + { + var filePath = fileSettings.SourceFilePath; + var dialog = new OpenFileDialog() + { + CheckFileExists = true, + InitialDirectory = Path.GetFullPath(filePath), + DefaultExt = "txt", + FileName = "source.txt", + Filter = "Текстовые файлы (*.txt)|*.txt" + }; + var res = dialog.ShowDialog(); + + if (res == DialogResult.OK) + filePath = dialog.FileName; + + fileSettings.SourceFilePath = filePath; + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Algorithm/CircularCloudLayouter.cs b/TagsCloudContainer/Algorithm/CircularCloudLayouter.cs new file mode 100644 index 000000000..aec035f88 --- /dev/null +++ b/TagsCloudContainer/Algorithm/CircularCloudLayouter.cs @@ -0,0 +1,41 @@ +using TagsCloudContainer.Infrastucture; +using TagsCloudContainer.Infrastucture.Settings; + +namespace TagsCloudContainer.Algorithm +{ + public class CircularCloudLayouter : ICloudLayouter + { + private readonly ImageSettings imageSettings; + private readonly IRectanglePlacer rectanglePlacer; + + + public CircularCloudLayouter(ImageSettings imageSettings, IRectanglePlacer rectanglePlacer) + { + this.imageSettings = imageSettings; + this.rectanglePlacer = rectanglePlacer; + } + + public List GetRectangles(Dictionary wordFrequencies) + { + var rectangles = new List(); + var bitmap = new Bitmap(imageSettings.Width, imageSettings.Height); + var graphics = Graphics.FromImage(bitmap); + + foreach (var word in wordFrequencies.Keys) + { + var fontSize = CalculateFontSize(wordFrequencies, word, imageSettings.Font.Size); + var font = new Font(imageSettings.Font.FontFamily, fontSize, imageSettings.Font.Style, imageSettings.Font.Unit); + var textSize = graphics.MeasureString(word, font); + var rectangle = rectanglePlacer.GetPossibleNextRectangle(rectangles, textSize); + rectangles.Add(new TextRectangle(rectangle, word, font)); + } + + return rectangles; + } + + private float CalculateFontSize(Dictionary wordFrequencies, string word, float fontSize) + { + return fontSize + (wordFrequencies[word] - wordFrequencies.Values.Min()) * 20 / (wordFrequencies.Values.Max()); + } + } +} diff --git a/TagsCloudContainer/Algorithm/FileParser.cs b/TagsCloudContainer/Algorithm/FileParser.cs new file mode 100644 index 000000000..c846dc887 --- /dev/null +++ b/TagsCloudContainer/Algorithm/FileParser.cs @@ -0,0 +1,24 @@ +namespace TagsCloudContainer.Algorithm +{ + public class FileParser : IFileParser + { + public List ReadWordsInFile(string filePath) + { + var words = new List(); + var lines = File.ReadAllLines(filePath); + + foreach (var line in lines) + { + var lineWords = line.ToLower().Trim().Split(' '); + if (lineWords.Length > 1) + { + throw new Exception("There is more than one word in a line"); + } + words.Add(lineWords[0]); + } + + return words; + } + + } +} diff --git a/TagsCloudContainer/Algorithm/ICloudLayouter.cs b/TagsCloudContainer/Algorithm/ICloudLayouter.cs new file mode 100644 index 000000000..98b935fd3 --- /dev/null +++ b/TagsCloudContainer/Algorithm/ICloudLayouter.cs @@ -0,0 +1,9 @@ +using TagsCloudContainer.Infrastucture; + +namespace TagsCloudContainer.Algorithm +{ + public interface ICloudLayouter + { + List GetRectangles(Dictionary wordFrequencies); + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Algorithm/IFileParser.cs b/TagsCloudContainer/Algorithm/IFileParser.cs new file mode 100644 index 000000000..b0186f885 --- /dev/null +++ b/TagsCloudContainer/Algorithm/IFileParser.cs @@ -0,0 +1,7 @@ +namespace TagsCloudContainer.Algorithm +{ + public interface IFileParser + { + List ReadWordsInFile(string filePath); + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Algorithm/IRectanglePlacer.cs b/TagsCloudContainer/Algorithm/IRectanglePlacer.cs new file mode 100644 index 000000000..870733c32 --- /dev/null +++ b/TagsCloudContainer/Algorithm/IRectanglePlacer.cs @@ -0,0 +1,9 @@ +using TagsCloudContainer.Infrastucture; + +namespace TagsCloudContainer.Algorithm +{ + public interface IRectanglePlacer + { + RectangleF GetPossibleNextRectangle(List cloudRectangles, SizeF rectangleSize); + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Algorithm/IWordProcessor.cs b/TagsCloudContainer/Algorithm/IWordProcessor.cs new file mode 100644 index 000000000..f671e4c97 --- /dev/null +++ b/TagsCloudContainer/Algorithm/IWordProcessor.cs @@ -0,0 +1,7 @@ +namespace TagsCloudContainer.Algorithm +{ + public interface IWordProcessor + { + Dictionary CalculateFrequencyInterestingWords(string sourceFilePath, string boringFilePath); + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Algorithm/RectanglePlacer.cs b/TagsCloudContainer/Algorithm/RectanglePlacer.cs new file mode 100644 index 000000000..c92f24566 --- /dev/null +++ b/TagsCloudContainer/Algorithm/RectanglePlacer.cs @@ -0,0 +1,54 @@ +using System.Diagnostics; +using TagsCloudContainer.Infrastucture; +using TagsCloudContainer.Infrastucture.Settings; + +namespace TagsCloudContainer.Algorithm +{ + public sealed class RectanglePlacer : IRectanglePlacer + { + private readonly AlgorithmSettings algorithmSettings; + private readonly ImageSettings imageSettings; + + public RectanglePlacer(AlgorithmSettings algorithmSettings, ImageSettings imageSettings) + { + if (imageSettings.Width / 2 < 0 || imageSettings.Height / 2 < 0) + throw new ArgumentException("the coordinates of the center must be positive numbers"); + + this.algorithmSettings = algorithmSettings; + this.imageSettings = imageSettings; + } + + public RectangleF GetPossibleNextRectangle(List cloudRectangles, SizeF rectangleSize) + { + if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) + throw new ArgumentException("the width and height of the rectangle must be positive numbers"); + + return FindPossibleNextRectangle(cloudRectangles, rectangleSize); + } + + private RectangleF FindPossibleNextRectangle(List cloudRectangles, SizeF rectangleSize) + { + var radius = algorithmSettings.Radius; + var angle = algorithmSettings.Angle; + var center = new Point(imageSettings.Width / 2, imageSettings.Height / 2); + + while (true) + { + var point = new Point( + (int)(center.X + radius * Math.Cos(angle)), + (int)(center.Y + radius * Math.Sin(angle)) + ); + var possibleRectangle = new RectangleF(point, rectangleSize); + + if (!cloudRectangles.Any(textRectangle => textRectangle.Rectangle.IntersectsWith(possibleRectangle))) + { + return possibleRectangle; + } + + angle += algorithmSettings.DeltaAngle; + radius += algorithmSettings.DeltaRadius; + } + } + + } +} diff --git a/TagsCloudContainer/Algorithm/WordProcessor.cs b/TagsCloudContainer/Algorithm/WordProcessor.cs new file mode 100644 index 000000000..fbb599d25 --- /dev/null +++ b/TagsCloudContainer/Algorithm/WordProcessor.cs @@ -0,0 +1,41 @@ +using StopWord; + +namespace TagsCloudContainer.Algorithm +{ + public class WordProcessor : IWordProcessor + { + + private readonly IFileParser parser; + + public WordProcessor(IFileParser parser) + { + this.parser = parser; + } + + public Dictionary CalculateFrequencyInterestingWords(string sourceFilePath, string boringFilePath) + { + var wordFrequencies = new Dictionary(); + var interestingWords = GetInterestingWords(sourceFilePath, boringFilePath); + + foreach (var word in interestingWords) + { + if (!wordFrequencies.ContainsKey(word)) + wordFrequencies.Add(word, 0); + wordFrequencies[word]++; + } + + return wordFrequencies.OrderByDescending(x => x.Value).ToDictionary(); + } + + public List GetInterestingWords(string sourceFilePath, string boringFilePath) + { + var boringWords = parser.ReadWordsInFile(boringFilePath); + var sourceWords = parser.ReadWordsInFile(sourceFilePath); + var stopWords = StopWords.GetStopWords("ru"); + + var interestingWords = sourceWords.Where(word => !boringWords.Contains(word) && !stopWords.Contains(word)).ToList(); + + return interestingWords; + } + } +} diff --git a/TagsCloudContainer/Client/GUITagCloudClient.cs b/TagsCloudContainer/Client/GUITagCloudClient.cs new file mode 100644 index 000000000..a93ce8020 --- /dev/null +++ b/TagsCloudContainer/Client/GUITagCloudClient.cs @@ -0,0 +1,38 @@ +using TagsCloudContainer.Algorithm; +using TagsCloudContainer.Infrastucture.Extensions; +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture.Visualization; + +namespace TagsCloudContainer.Client +{ + public class GUITagCloudClient : ITagCloudClient + { + private readonly PictureBox pictureBox; + private readonly ImageSettings imageSettings; + private readonly IDrawer drawer; + private readonly ICloudLayouter cloudLayouter; + private IWordProcessor wordProcessor; + + public GUITagCloudClient(PictureBox pictureBox, ImageSettings imageSettings, + IDrawer drawer, ICloudLayouter cloudLayouter, IWordProcessor wordProcessor) + { + this.pictureBox = pictureBox; + this.imageSettings = imageSettings; + this.drawer = drawer; + this.cloudLayouter = cloudLayouter; + this.wordProcessor = wordProcessor; + } + + public void DrawImage(string sourceFilePath, string boringFilePath) + { + var wordsCount = wordProcessor.CalculateFrequencyInterestingWords(sourceFilePath, boringFilePath); + var rectangles = cloudLayouter.GetRectangles(wordsCount); + drawer.Draw(rectangles, pictureBox, imageSettings); + } + + public void SaveImage(string filePath) + { + pictureBox.SaveImage(filePath); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Client/ITagCloudClient.cs b/TagsCloudContainer/Client/ITagCloudClient.cs new file mode 100644 index 000000000..284b264c9 --- /dev/null +++ b/TagsCloudContainer/Client/ITagCloudClient.cs @@ -0,0 +1,9 @@ +namespace TagsCloudContainer.Client +{ + public interface ITagCloudClient + { + public void DrawImage(string sourceFilePath, string boringFilePath); + + public void SaveImage(string filePath); + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Infrastucture/Extensions/PictureBoxExtensions.cs b/TagsCloudContainer/Infrastucture/Extensions/PictureBoxExtensions.cs new file mode 100644 index 000000000..93701ca6f --- /dev/null +++ b/TagsCloudContainer/Infrastucture/Extensions/PictureBoxExtensions.cs @@ -0,0 +1,29 @@ +using System.Drawing.Imaging; +using TagsCloudContainer.Infrastucture.Settings; + +namespace TagsCloudContainer.Infrastucture.Extensions +{ + public static class PictureBoxExtensions + { + public static Graphics StartDrawing(this PictureBox source) + { + return Graphics.FromImage(source.Image); + } + + public static void UpdateUi(this PictureBox source) + { + source.Refresh(); + Application.DoEvents(); + } + + public static void RecreateImage(this PictureBox source, ImageSettings imageSettings) + { + source.Image = new Bitmap(imageSettings.Width, imageSettings.Height, PixelFormat.Format24bppRgb); + } + + public static void SaveImage(this PictureBox source, string fileName) + { + source.Image.Save(fileName); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Infrastucture/Settings/AlgorithmSettings.cs b/TagsCloudContainer/Infrastucture/Settings/AlgorithmSettings.cs new file mode 100644 index 000000000..f67c73cab --- /dev/null +++ b/TagsCloudContainer/Infrastucture/Settings/AlgorithmSettings.cs @@ -0,0 +1,40 @@ +using System.ComponentModel; + +namespace TagsCloudContainer.Infrastucture.Settings +{ + public class AlgorithmSettings + { + private double radius = 0; + private double angle = 0; + private double deltaRadius = 0.1; + private double deltaAngle = 0.1; + + [DisplayName(" ")] + public double Radius + { + get => radius; + set => radius = value; + } + + [DisplayName(" ")] + public double Angle + { + get => angle; + set => angle = value; + } + + [DisplayName(" ")] + public double DeltaRadius + { + get => deltaRadius; + set => deltaRadius = value; + } + + [DisplayName(" ")] + public double DeltaAngle + { + get => deltaAngle; + set => deltaAngle = value; + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Infrastucture/Settings/FileSettings.cs b/TagsCloudContainer/Infrastucture/Settings/FileSettings.cs new file mode 100644 index 000000000..51a1bc2ad --- /dev/null +++ b/TagsCloudContainer/Infrastucture/Settings/FileSettings.cs @@ -0,0 +1,35 @@ +namespace TagsCloudContainer.Infrastucture.Settings +{ + public class FileSettings + { + private string imagePath = GetProjectDirectory() + @"\"; + private string sourceFilePath = GetProjectDirectory() + @"\src\sourceWords.txt"; + private string boringFilePath = GetProjectDirectory() + @"\src\boringWords.txt"; + + public string ImagePath + { + get => imagePath; + set => imagePath = File.Exists(value) ? value : imagePath; + } + + public string SourceFilePath + { + get => sourceFilePath; + set => sourceFilePath = File.Exists(value) ? value : sourceFilePath; + } + + public string BoringFilePath + { + get => boringFilePath; + set => boringFilePath = File.Exists(value) ? value : boringFilePath; + } + + private static string GetProjectDirectory() + { + var binDirectory = AppContext.BaseDirectory; + var projectDirectory = Directory.GetParent(binDirectory).Parent.Parent.Parent.FullName; + + return projectDirectory; + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Infrastucture/Settings/ImageSettings.cs b/TagsCloudContainer/Infrastucture/Settings/ImageSettings.cs new file mode 100644 index 000000000..5f0907d6d --- /dev/null +++ b/TagsCloudContainer/Infrastucture/Settings/ImageSettings.cs @@ -0,0 +1,19 @@ +namespace TagsCloudContainer.Infrastucture.Settings +{ + public class ImageSettings + { + public int Width { get; set; } = 600; + + public int Height { get; set; } = 600; + + public Color RectangleBackgroundColor { get; set; } = Color.BlueViolet; + + public Color RectangleBordersColor { get; set; } = Color.Indigo; + + public Color BackgroundColor { get; set; } = Color.Black; + + public Color TextColor { get; set; } = Color.White; + + public Font Font { get; set; } = new Font("Arial", 20, FontStyle.Bold, GraphicsUnit.Point); + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Infrastucture/Settings/SettingsForm.cs b/TagsCloudContainer/Infrastucture/Settings/SettingsForm.cs new file mode 100644 index 000000000..ee8393e1b --- /dev/null +++ b/TagsCloudContainer/Infrastucture/Settings/SettingsForm.cs @@ -0,0 +1,36 @@ +namespace TagsCloudContainer.Infrastucture.Settings +{ + public static class SettingsForm + { + public static SettingsForm For(TSettings settings) + { + return new SettingsForm(settings); + } + } + + public class SettingsForm : Form + { + public SettingsForm(TSettings settings) + { + var okButton = new Button + { + Text = "OK", + DialogResult = DialogResult.OK, + Dock = DockStyle.Bottom, + }; + Controls.Add(okButton); + Controls.Add(new PropertyGrid + { + SelectedObject = settings, + Dock = DockStyle.Fill + }); + AcceptButton = okButton; + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + Text = ""; + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Infrastucture/TextRectangle.cs b/TagsCloudContainer/Infrastucture/TextRectangle.cs new file mode 100644 index 000000000..81ce28b2d --- /dev/null +++ b/TagsCloudContainer/Infrastucture/TextRectangle.cs @@ -0,0 +1,21 @@ +namespace TagsCloudContainer.Infrastucture +{ + public class TextRectangle + { + public RectangleF Rectangle { get; } + + public string Text { get; } + + public Font Font { get; } + + public TextRectangle(RectangleF rectangle, string text, Font font) + { + Rectangle = rectangle; + Text = text; + Font = font; + } + + public float Area => Rectangle.Width * Rectangle.Height; + + } +} diff --git a/TagsCloudContainer/Infrastucture/UiActions/IUiAction.cs b/TagsCloudContainer/Infrastucture/UiActions/IUiAction.cs new file mode 100644 index 000000000..4e8d32f35 --- /dev/null +++ b/TagsCloudContainer/Infrastucture/UiActions/IUiAction.cs @@ -0,0 +1,10 @@ +namespace TagsCloudContainer.Infrastucture.UiActions +{ + public interface IUiAction + { + string Category { get; } + string Name { get; } + string Description { get; } + void Perform(); + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Infrastucture/UiActions/UiActionExtensions.cs b/TagsCloudContainer/Infrastucture/UiActions/UiActionExtensions.cs new file mode 100644 index 000000000..6647332c5 --- /dev/null +++ b/TagsCloudContainer/Infrastucture/UiActions/UiActionExtensions.cs @@ -0,0 +1,30 @@ +namespace TagsCloudContainer.Infrastucture.UiActions +{ + public static class UiActionExtensions + { + public static ToolStripItem[] ToMenuItems(this IUiAction[] actions) + { + var items = actions.GroupBy(a => a.Category) + .Select(g => CreateTopLevelMenuItem(g.Key, g.ToList())) + .Cast() + .ToArray(); + return items; + } + + private static ToolStripMenuItem CreateTopLevelMenuItem(string name, IList items) + { + var menuItems = items.Select(a => a.ToMenuItem()).ToArray(); + return new ToolStripMenuItem(name, null, menuItems); + } + + private static ToolStripItem ToMenuItem(this IUiAction action) + { + return + new ToolStripMenuItem(action.Name, null, (sender, args) => action.Perform()) + { + ToolTipText = action.Description, + Tag = action + }; + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Infrastucture/Visualization/IDrawer.cs b/TagsCloudContainer/Infrastucture/Visualization/IDrawer.cs new file mode 100644 index 000000000..bd1b582cb --- /dev/null +++ b/TagsCloudContainer/Infrastucture/Visualization/IDrawer.cs @@ -0,0 +1,9 @@ +using TagsCloudContainer.Infrastucture.Settings; + +namespace TagsCloudContainer.Infrastucture.Visualization +{ + public interface IDrawer + { + public void Draw(List rectangles, PictureBox pictureBox, ImageSettings imageSettings); + } +} \ No newline at end of file diff --git a/TagsCloudContainer/Infrastucture/Visualization/TagCloudDrawer.cs b/TagsCloudContainer/Infrastucture/Visualization/TagCloudDrawer.cs new file mode 100644 index 000000000..e07954ccd --- /dev/null +++ b/TagsCloudContainer/Infrastucture/Visualization/TagCloudDrawer.cs @@ -0,0 +1,29 @@ +using TagsCloudContainer.Infrastucture.Extensions; +using TagsCloudContainer.Infrastucture.Settings; + +namespace TagsCloudContainer.Infrastucture.Visualization +{ + public class TagCloudDrawer : IDrawer + { + public void Draw(List rectangles, PictureBox pictureBox, ImageSettings imageSettings) + { + using (var graphics = pictureBox.StartDrawing()) + { + graphics.Clear(imageSettings.BackgroundColor); + + using var textBrush = new SolidBrush(imageSettings.TextColor); + using var rectBorderPen = new Pen(imageSettings.RectangleBordersColor); + using var rectBackgroundBrush = new SolidBrush(imageSettings.RectangleBackgroundColor); + + foreach (var rect in rectangles) + { + graphics.FillRectangle(rectBackgroundBrush, rect.Rectangle); + graphics.DrawRectangle(rectBorderPen, rect.Rectangle); + graphics.DrawString(rect.Text, rect.Font, textBrush, rect.Rectangle); + } + } + + pictureBox.UpdateUi(); + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/MainForm.Designer.cs b/TagsCloudContainer/MainForm.Designer.cs new file mode 100644 index 000000000..46ba0c51f --- /dev/null +++ b/TagsCloudContainer/MainForm.Designer.cs @@ -0,0 +1,39 @@ +namespace TagsCloudContainer +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form1"; + } + + #endregion + } +} diff --git a/TagsCloudContainer/MainForm.cs b/TagsCloudContainer/MainForm.cs new file mode 100644 index 000000000..fe65d7261 --- /dev/null +++ b/TagsCloudContainer/MainForm.cs @@ -0,0 +1,23 @@ +using TagsCloudContainer.Infrastucture.Extensions; +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture.UiActions; + +namespace TagsCloudContainer +{ + public partial class MainForm : Form + { + public MainForm(IUiAction[] actions, PictureBox pictureBox, ImageSettings imageSetting) + { + ClientSize = new Size(imageSetting.Width, imageSetting.Height); + var mainMenu = new MenuStrip(); + mainMenu.Items.AddRange(actions.ToMenuItems()); + Controls.Add(mainMenu); + + pictureBox.RecreateImage(imageSetting); + pictureBox.Dock = DockStyle.Fill; + Controls.Add(pictureBox); + + InitializeComponent(); + } + } +} diff --git a/TagsCloudContainer/MainForm.resx b/TagsCloudContainer/MainForm.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/TagsCloudContainer/MainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TagsCloudContainer/Program.cs b/TagsCloudContainer/Program.cs new file mode 100644 index 000000000..7565a9fb8 --- /dev/null +++ b/TagsCloudContainer/Program.cs @@ -0,0 +1,55 @@ +using Autofac; +using TagsCloudContainer.Actions; +using TagsCloudContainer.Algorithm; +using TagsCloudContainer.Client; +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture.UiActions; +using TagsCloudContainer.Infrastucture.Visualization; + +namespace TagsCloudContainer +{ + internal static class Program + { + [STAThread] + static void Main() + { + try + { + var builder = CreateBuilder(); + var container = builder.Build(); + + ApplicationConfiguration.Initialize(); + Application.Run(container.Resolve
()); + } + catch (Exception e) + { + MessageBox.Show(e.Message); + } + } + + public static ContainerBuilder CreateBuilder() + { + var builder = new ContainerBuilder(); + + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().AsSelf().InstancePerLifetimeScope(); + builder.RegisterType().AsSelf().InstancePerLifetimeScope(); + builder.RegisterType().AsSelf().InstancePerLifetimeScope(); + builder.RegisterType().AsSelf().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + + return builder; + } + } +} \ No newline at end of file diff --git a/TagsCloudContainer/TagsCloudContainer.csproj b/TagsCloudContainer/TagsCloudContainer.csproj new file mode 100644 index 000000000..c031c6bc0 --- /dev/null +++ b/TagsCloudContainer/TagsCloudContainer.csproj @@ -0,0 +1,16 @@ + + + + WinExe + net8.0-windows + enable + true + enable + + + + + + + + \ No newline at end of file diff --git a/TagsCloudContainer/src/boringWords.txt b/TagsCloudContainer/src/boringWords.txt new file mode 100644 index 000000000..e69de29bb diff --git a/TagsCloudContainer/src/sourceWords.txt b/TagsCloudContainer/src/sourceWords.txt new file mode 100644 index 000000000..f6d6fb4cb --- /dev/null +++ b/TagsCloudContainer/src/sourceWords.txt @@ -0,0 +1,24 @@ +я +ты +мы +Js +Js +Js +Python +Python +Python +Python +C# +C# +c# +c# +C# +C++ +C++ +Rust +Rust +Go +Go +Go +Go +1C \ No newline at end of file diff --git a/TagsCloudContainerTests/CircularCloudLayouter_Should.cs b/TagsCloudContainerTests/CircularCloudLayouter_Should.cs new file mode 100644 index 000000000..e39958ee5 --- /dev/null +++ b/TagsCloudContainerTests/CircularCloudLayouter_Should.cs @@ -0,0 +1,94 @@ +using TagsCloudContainer.Algorithm; +using FluentAssertions; +using NUnit.Framework; +using TagsCloudContainer.Infrastucture.Settings; +using TagsCloudContainer.Infrastucture; + +namespace TagsCloudContainerTests +{ + + public class CircularCloudLayouter_Should + { + private CircularCloudLayouter layouter; + + [SetUp] + public void SetUp() + { + var algSettings = new AlgorithmSettings(); + var imgSettings = new ImageSettings(); + var rectanglePlacer = new RectanglePlacer(algSettings, imgSettings); + layouter = new CircularCloudLayouter(imgSettings, rectanglePlacer); + } + + [Test] + public void CreateAnEmptyLayout_WhenFrequencyDictionaryIsEmpty() + { + var textRectangles = layouter.GetRectangles(new Dictionary()); + + textRectangles.Should().BeEmpty(); + } + + [Test] + public void CreateLayoutWithoutIntersections_WhenNoIdenticalFrequencies() + { + var wordsFrequencies = new Dictionary() + { + {"яблоко" , 1}, + {"банан", 2}, + {"груша", 6}, + {"мандарин", 17}, + }; + + var textRectangles = layouter.GetRectangles(wordsFrequencies); + + IsIntersections(textRectangles).Should().BeFalse(); + } + + [Test] + public void CreateLayoutWithoutIntersections_WhenIdenticalFrequencies() + { + var wordsFrequencies = new Dictionary() + { + {"яблоко" , 1}, + {"банан", 6}, + {"груша", 6}, + {"мандарин", 17}, + {"апельсин", 17}, + + }; + + var textRectangles = layouter.GetRectangles(wordsFrequencies); + + IsIntersections(textRectangles).Should().BeFalse(); + } + + [Test] + public void CreateLayout_WhenFrequencyWordIncreasesTheSizeRectangleIncreases() + { + var wordsFrequencies = new Dictionary() + { + {"купец" , 1}, + {"упецк", 2}, + {"пецку", 6}, + {"ецкуп", 17}, + }; + + var textRectangles = layouter.GetRectangles(wordsFrequencies.OrderBy(word => word.Value).ToDictionary()); + + textRectangles.Should().BeInAscendingOrder(textRectangle => textRectangle.Area); + } + + public bool IsIntersections(List textRectangles) + { + var rectList = textRectangles.Select(x => x.Rectangle).ToList(); + + for (int i = 0; i < rectList.Count; i++) + for (int j = i + 1; j < rectList.Count; j++) + if (rectList[i].IntersectsWith(rectList[j])) + return true; + + return false; + } + + } +} diff --git a/TagsCloudContainerTests/FileParser_Should.cs b/TagsCloudContainerTests/FileParser_Should.cs new file mode 100644 index 000000000..195c890a9 --- /dev/null +++ b/TagsCloudContainerTests/FileParser_Should.cs @@ -0,0 +1,65 @@ +using FluentAssertions; +using NUnit.Framework; +using TagsCloudContainer.Algorithm; + +namespace TagsCloudContainerTests +{ + public class FileParser_Should + { + private FileParser parser; + + [SetUp] + public void SetUp() + { + parser = new FileParser(); + } + + [Test] + public void ThrowException_IfMoreThanOneWordInLine() + { + var incorrectDataFilePath = GetProjectDirectory() + @"\src\incorrectData.txt"; + + Action action = () => parser.ReadWordsInFile(incorrectDataFilePath); + + action.Should().Throw(); + } + + [Test] + public void ReduceWordsToLowercase() + { + var expectedList = new List { "привет", "занятие", "яблоко", "домашка" }; + var uppercaseDataFilePath = GetProjectDirectory() + @"\src\upperCase.txt"; + var words = parser.ReadWordsInFile(uppercaseDataFilePath); + + words.Should().BeEquivalentTo(expectedList); + } + + [Test] + public void RemoveSpacesFromTheBeginningAndEndOfline() + { + var expectedList = new List { "привет", "занятие", "яблоко", "домашка" }; + var uppercaseDataFilePath = GetProjectDirectory() + @"\src\trimData.txt"; + var words = parser.ReadWordsInFile(uppercaseDataFilePath); + + words.Should().BeEquivalentTo(expectedList); + } + + [Test] + public void ParseEmptyList_WhenFileIsEmpty() + { + var emptyDataFilePath = GetProjectDirectory() + @"\src\emptyData.txt"; + var words = parser.ReadWordsInFile(emptyDataFilePath); + + words.Should().BeEmpty(); + } + + + public string GetProjectDirectory() + { + var binDirectory = AppContext.BaseDirectory; + var projectDirectory = Directory.GetParent(binDirectory).Parent.Parent.Parent.FullName; + + return projectDirectory; + } + } +} diff --git a/TagsCloudContainerTests/TagsCloudContainerTests.csproj b/TagsCloudContainerTests/TagsCloudContainerTests.csproj new file mode 100644 index 000000000..19b247b8f --- /dev/null +++ b/TagsCloudContainerTests/TagsCloudContainerTests.csproj @@ -0,0 +1,20 @@ + + + + net8.0-windows + enable + enable + + + + + + + + + + + + + + diff --git a/TagsCloudContainerTests/WordProcessor_Should.cs b/TagsCloudContainerTests/WordProcessor_Should.cs new file mode 100644 index 000000000..f85dad0d6 --- /dev/null +++ b/TagsCloudContainerTests/WordProcessor_Should.cs @@ -0,0 +1,105 @@ +using FluentAssertions; +using NUnit.Framework; +using StopWord; +using TagsCloudContainer.Algorithm; + +namespace TagsCloudContainerTests +{ + public class WordProcessor_Should + { + private WordProcessor wordProcessor; + + [SetUp] + public void SetUp() + { + wordProcessor = new WordProcessor(new FileParser()); + } + + [Test] + public void GetInterestingWords_WhenFileWithBoringWordsIsEmpty() + { + var sourceFilePath = GetProjectDirectory() + @"\src\sourceData.txt"; + var boringFilePath = GetProjectDirectory() + @"\src\emptyData.txt"; + var stopWords = StopWords.GetStopWords("ru"); + + var interestingWords = wordProcessor.GetInterestingWords(sourceFilePath, boringFilePath); + + interestingWords.Should().NotContain(stopWords); + } + + [Test] + public void GetInterestingWords_WhenFileWithBoringWordsIsNotEmpty() + { + var sourceFilePath = GetProjectDirectory() + @"\src\sourceData.txt"; + var boringFilePath = GetProjectDirectory() + @"\src\boringData.txt"; + var stopWords = StopWords.GetStopWords("ru"); + + var interestingWords = wordProcessor.GetInterestingWords(sourceFilePath, boringFilePath); + var boringWords = new FileParser().ReadWordsInFile(boringFilePath); + + interestingWords.Should().NotContain(stopWords); + interestingWords.Should().NotContain(boringWords); + } + + [Test] + public void CorrectlyCalculatesFrequencyWords() + { + var expectedDictonary = new Dictionary + { + { "js", 3 }, + { "python", 4}, + { "c#", 5 }, + { "c++", 2 }, + { "rust", 2 }, + { "go", 4 }, + { "1c", 1 } + }; + var sourceFilePath = GetProjectDirectory() + @"\src\sourceData.txt"; + var boringFilePath = GetProjectDirectory() + @"\src\emptyData.txt"; + + var wordsFrequency = wordProcessor.CalculateFrequencyInterestingWords(sourceFilePath, boringFilePath); + + wordsFrequency.Should().Contain(expectedDictonary); + } + + [Test] + public void GiveFrequencyWordsDescendingOrder() + { + var expectedDictonary = new Dictionary + { + { "js", 3 }, + { "python", 4}, + { "c#", 5 }, + { "c++", 2 }, + { "rust", 2 }, + { "go", 4 }, + { "1c", 1 } + }; + var sourceFilePath = GetProjectDirectory() + @"\src\sourceData.txt"; + var boringFilePath = GetProjectDirectory() + @"\src\emptyData.txt"; + + var wordsFrequency = wordProcessor.CalculateFrequencyInterestingWords(sourceFilePath, boringFilePath); + + wordsFrequency.Should().BeInDescendingOrder(word => word.Value); + } + + [Test] + public void ReturnEmptyDictionary_WhenSourceWordFileIsEmpty() + { + var sourceFilePath = GetProjectDirectory() + @"\src\emptyData.txt"; + var boringFilePath = GetProjectDirectory() + @"\src\emptyData.txt"; + + var wordsFrequency = wordProcessor.CalculateFrequencyInterestingWords(sourceFilePath, boringFilePath); + + wordsFrequency.Should().BeEmpty(); + } + + public string GetProjectDirectory() + { + var binDirectory = AppContext.BaseDirectory; + var projectDirectory = Directory.GetParent(binDirectory).Parent.Parent.Parent.FullName; + + return projectDirectory; + } + } +} diff --git a/TagsCloudContainerTests/src/boringData.txt b/TagsCloudContainerTests/src/boringData.txt new file mode 100644 index 000000000..70c87ede1 --- /dev/null +++ b/TagsCloudContainerTests/src/boringData.txt @@ -0,0 +1 @@ +Go \ No newline at end of file diff --git a/TagsCloudContainerTests/src/emptyData.txt b/TagsCloudContainerTests/src/emptyData.txt new file mode 100644 index 000000000..e69de29bb diff --git a/TagsCloudContainerTests/src/incorrectData.txt b/TagsCloudContainerTests/src/incorrectData.txt new file mode 100644 index 000000000..d8fbbf2fe --- /dev/null +++ b/TagsCloudContainerTests/src/incorrectData.txt @@ -0,0 +1,3 @@ +привет +курсы +яблоко банан \ No newline at end of file diff --git a/TagsCloudContainerTests/src/sourceData.txt b/TagsCloudContainerTests/src/sourceData.txt new file mode 100644 index 000000000..f6d6fb4cb --- /dev/null +++ b/TagsCloudContainerTests/src/sourceData.txt @@ -0,0 +1,24 @@ +я +ты +мы +Js +Js +Js +Python +Python +Python +Python +C# +C# +c# +c# +C# +C++ +C++ +Rust +Rust +Go +Go +Go +Go +1C \ No newline at end of file diff --git a/TagsCloudContainerTests/src/trimData.txt b/TagsCloudContainerTests/src/trimData.txt new file mode 100644 index 000000000..1816d1a74 --- /dev/null +++ b/TagsCloudContainerTests/src/trimData.txt @@ -0,0 +1,4 @@ +Привет + заНЯтие + яблоко + доМашКА \ No newline at end of file diff --git a/TagsCloudContainerTests/src/upperCase.txt b/TagsCloudContainerTests/src/upperCase.txt new file mode 100644 index 000000000..42749b5a4 --- /dev/null +++ b/TagsCloudContainerTests/src/upperCase.txt @@ -0,0 +1,4 @@ +Привет +заНЯтие +яблоко +доМашКА \ No newline at end of file diff --git a/di.sln b/di.sln index b27b7c05d..99a13659d 100644 --- a/di.sln +++ b/di.sln @@ -1,6 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FractalPainter", "FractalPainter\FractalPainter.csproj", "{4D70883B-6F8B-4166-802F-8EDC9BE93199}" +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34316.72 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FractalPainter", "FractalPainter\FractalPainter.csproj", "{4D70883B-6F8B-4166-802F-8EDC9BE93199}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudContainer", "TagsCloudContainer\TagsCloudContainer.csproj", "{05820AAB-152F-4496-AE33-9CEDF7DCEE89}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudContainerTests", "TagsCloudContainerTests\TagsCloudContainerTests.csproj", "{A1414BCF-8FFE-4177-ADC0-C1D105543F3F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -12,5 +19,19 @@ Global {4D70883B-6F8B-4166-802F-8EDC9BE93199}.Debug|Any CPU.Build.0 = Debug|Any CPU {4D70883B-6F8B-4166-802F-8EDC9BE93199}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D70883B-6F8B-4166-802F-8EDC9BE93199}.Release|Any CPU.Build.0 = Release|Any CPU + {05820AAB-152F-4496-AE33-9CEDF7DCEE89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05820AAB-152F-4496-AE33-9CEDF7DCEE89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05820AAB-152F-4496-AE33-9CEDF7DCEE89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05820AAB-152F-4496-AE33-9CEDF7DCEE89}.Release|Any CPU.Build.0 = Release|Any CPU + {A1414BCF-8FFE-4177-ADC0-C1D105543F3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1414BCF-8FFE-4177-ADC0-C1D105543F3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1414BCF-8FFE-4177-ADC0-C1D105543F3F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1414BCF-8FFE-4177-ADC0-C1D105543F3F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B3E104F5-E57C-4BBE-B260-6EEBC180B20A} EndGlobalSection EndGlobal