1 /*
2  *  "Word Cloud (Tag Cloud)".
3  *  Copyright (C) 2011 by George Mamaladze.
4  *  http://sourcecodecloud.codeplex.com/
5  *  https://www.codeproject.com/Articles/224231/Word-Cloud-Tag-Cloud-Generator-Control-for-NET-Win
6  *
7  *  This licensed under The Code Project Open License (CPOL).
8  *
9  *  Adapted for the GEDKeeper project by Sergey V. Zhdanovskih in September 2017.
10  */
11 
12 using System;
13 using System.Collections.Generic;
14 using System.Drawing;
15 
16 namespace GKWordsCloudPlugin.WordsCloud
17 {
18     public interface ICloudRenderer : IDisposable
19     {
Measure(string text, int weight)20         SizeF Measure(string text, int weight);
Draw(Word word, bool highlight)21         void Draw(Word word, bool highlight);
22     }
23 
24     public class CloudModel
25     {
26         private readonly PointF fCenter;
27         private readonly QuadTree<Word> fQuadTree;
28         private readonly RectangleF fSurface;
29 
CloudModel(SizeF size)30         public CloudModel(SizeF size)
31         {
32             fSurface = new RectangleF(new PointF(0, 0), size);
33             fQuadTree = new QuadTree<Word>(fSurface);
34             fCenter = new PointF(fSurface.X + size.Width / 2, fSurface.Y + size.Height / 2);
35         }
36 
Arrange(List<Word> words, ICloudRenderer renderer)37         public void Arrange(List<Word> words, ICloudRenderer renderer)
38         {
39             if (words == null) {
40                 throw new ArgumentNullException("words");
41             }
42 
43             foreach (Word word in words) {
44                 SizeF size = renderer.Measure(word.Text, word.Occurrences);
45 
46                 RectangleF freeRectangle;
47                 if (TryFindFreeRectangle(size, out freeRectangle)) {
48                     word.Rectangle = freeRectangle;
49                     word.IsExposed = true;
50                     fQuadTree.Insert(word);
51                 }
52             }
53         }
54 
GetWordsInArea(RectangleF area)55         public IEnumerable<Word> GetWordsInArea(RectangleF area)
56         {
57             return fQuadTree.Query(area);
58         }
59 
IsInsideSurface(RectangleF target)60         private bool IsInsideSurface(RectangleF target)
61         {
62             return target.X >= fSurface.X && target.Y >= fSurface.Y &&
63             target.Bottom <= fSurface.Bottom && target.Right <= fSurface.Right;
64         }
65 
TryFindFreeRectangle(SizeF size, out RectangleF foundRectangle)66         public bool TryFindFreeRectangle(SizeF size, out RectangleF foundRectangle)
67         {
68             foundRectangle = RectangleF.Empty;
69             double alpha = GetStartPseudoAngle(size);
70             const double stepAlpha = Math.PI / 60;
71 
72             const double pointsOnSpital = 500;
73 
74             for (int pointIndex = 0; pointIndex < pointsOnSpital; pointIndex++) {
75                 double dX = pointIndex / pointsOnSpital * Math.Sin(alpha) * fCenter.X;
76                 double dY = pointIndex / pointsOnSpital * Math.Cos(alpha) * fCenter.Y;
77                 foundRectangle = new RectangleF((float)(fCenter.X + dX) - size.Width / 2, (float)(fCenter.Y + dY) - size.Height / 2, size.Width, size.Height);
78 
79                 alpha += stepAlpha;
80                 if (!IsInsideSurface(foundRectangle)) {
81                     return false;
82                 }
83 
84                 if (!fQuadTree.HasContent(foundRectangle)) {
85                     return true;
86                 }
87             }
88 
89             return false;
90         }
91 
GetStartPseudoAngle(SizeF size)92         private static float GetStartPseudoAngle(SizeF size)
93         {
94             return size.Height * size.Width;
95         }
96     }
97 }
98