1 /*
2  *  "GEDKeeper", the personal genealogical database editor.
3  *  Copyright (C) 2009-2017 by Sergey V. Zhdanovskih.
4  *
5  *  This file is part of "GEDKeeper".
6  *
7  *  This program is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 using System;
22 using System.Collections.Generic;
23 using Eto.Drawing;
24 using Eto.Forms;
25 
26 namespace GKUI.Components
27 {
28     public class HintRequestEventArgs : EventArgs
29     {
30         public int FragmentNumber { get; private set; }
31         public int Size { get; private set; }
32         public string Hint { get; set; }
33 
HintRequestEventArgs(int fragmentNumber, int size)34         public HintRequestEventArgs(int fragmentNumber, int size)
35         {
36             FragmentNumber = fragmentNumber;
37             Size = size;
38         }
39     }
40 
HintRequestEventHandler(object sender, HintRequestEventArgs args)41     public delegate void HintRequestEventHandler(object sender, HintRequestEventArgs args);
42 
43     /// <summary>
44     /// Logarithmic graph of fragmented data.
45     /// </summary>
46     public sealed class LogChart : Drawable
47     {
48         private class Fragment
49         {
50             public int SrcVal;
51 
52             public double Val;
53             public double Log;
54             public double Percent;
55 
56             public int X;
57             public int Width;
58 
59             public Rectangle Rect;
60         }
61 
62         private readonly Brush fEmptyBrush;
63         private readonly Brush fFragBrush;
64         private readonly List<Fragment> fList;
65         private string fHint;
66 
67         public event HintRequestEventHandler OnHintRequest;
68 
LogChart()69         public LogChart()
70         {
71             fList = new List<Fragment>();
72 
73             fEmptyBrush = new SolidBrush(Colors.Gray);
74             fFragBrush = new SolidBrush(Colors.Green);
75         }
76 
Dispose(bool disposing)77         protected override void Dispose(bool disposing)
78         {
79             if (disposing) {
80                 fFragBrush.Dispose();
81                 fEmptyBrush.Dispose();
82             }
83             base.Dispose(disposing);
84         }
85 
Clear()86         public void Clear()
87         {
88             fList.Clear();
89             UpdateContents();
90         }
91 
AddFragment(int val)92         public void AddFragment(int val)
93         {
94             if (val < 1) return;
95 
96             Fragment frag = new Fragment();
97 
98             frag.SrcVal = val;
99             if (val == 1) val++;
100             frag.Val = val;
101 
102             fList.Add(frag);
103 
104             UpdateContents();
105         }
106 
UpdateContents()107         private void UpdateContents()
108         {
109             int count = fList.Count;
110             if (count == 0) return;
111 
112             int wid = Width - (count - 1);
113             if (wid <= 0) return;
114 
115             Fragment frag;
116 
117             // this is a calculation of the simple sum of fragments
118             double sum = 0.0;
119             for (int i = 0; i < count; i++) {
120                 frag = fList[i];
121                 sum = sum + frag.Val;
122             }
123 
124             // the calculation of the logarithm of the fragment and the sum of the logarithms
125             double logSum = 0.0;
126             for (int i = 0; i < count; i++) {
127                 frag = fList[i];
128                 frag.Log = Math.Log(frag.Val, sum);
129 
130                 logSum = logSum + frag.Log;
131             }
132 
133             // calculate visual width of the fragments and their sum
134             int resWidth = 0;
135             for (int i = 0; i < count; i++) {
136                 frag = fList[i];
137                 frag.Percent = frag.Log / logSum;
138                 frag.Width = (int)(wid * frag.Percent);
139 
140                 resWidth = resWidth + frag.Width;
141             }
142 
143             // to distribute the difference between the actual width of the component and the sum of the width of the fragments
144             int d = wid - resWidth;
145             if (d > 0) {
146                 // the difference distribute between the highest allocated fragments
147                 List<Fragment> ordList = new List<Fragment>(fList);
148                 ordList.Sort(new FragmentComparer());
149 
150                 int idx = 0;
151                 while (d > 0) {
152                     frag = ordList[idx];
153                     frag.Width = frag.Width + 1;
154 
155                     if (idx == count - 1) {
156                         idx = 0;
157                     } else idx++;
158 
159                     d--;
160                 }
161             }
162 
163             // prepare the regions of rendering fragments
164             int x = 0;
165             for (int i = 0; i < count; i++) {
166                 frag = fList[i];
167 
168                 frag.X = x;
169                 frag.Rect = new Rectangle(x, 0, frag.Width, Height);
170                 x = x + (frag.Width + 1);
171             }
172 
173             Invalidate();
174         }
175 
176         private class FragmentComparer: IComparer<Fragment>
177         {
Compare(Fragment x, Fragment y)178             public int Compare(Fragment x, Fragment y)
179             {
180                 return -x.Width.CompareTo(y.Width);
181             }
182         }
183 
OnSizeChanged(EventArgs e)184         protected override void OnSizeChanged(EventArgs e)
185         {
186             base.OnSizeChanged(e);
187             UpdateContents();
188         }
189 
OnPaint(PaintEventArgs e)190         protected override void OnPaint(PaintEventArgs e)
191         {
192             base.OnPaint(e);
193 
194             if (Width <= 0 || Height <= 0) return;
195 
196             Graphics gfx = e.Graphics;
197 
198             int count = fList.Count;
199             if (count > 0) {
200                 for (int i = 0; i < count; i++) {
201                     Fragment frag = fList[i];
202                     DrawRect(gfx, frag.X, frag.Width, fFragBrush);
203                 }
204             } else {
205                 DrawRect(gfx, 0, Width, fEmptyBrush);
206             }
207         }
208 
DrawRect(Graphics gfx, int x, int width, Brush lb)209         private void DrawRect(Graphics gfx, int x, int width, Brush lb)
210         {
211             if (width > 0) {
212                 gfx.FillRectangle(lb, x, 0, width, Height);
213             }
214         }
215 
HintRequest(int fragmentNumber, int size)216         private string HintRequest(int fragmentNumber, int size)
217         {
218             var onHintRequest = OnHintRequest;
219             if (onHintRequest == null) return string.Empty;
220 
221             HintRequestEventArgs args = new HintRequestEventArgs(fragmentNumber, size);
222             onHintRequest(this, args);
223             return args.Hint;
224         }
225 
OnMouseMove(MouseEventArgs e)226         protected override void OnMouseMove(MouseEventArgs e)
227         {
228             Point mpt = new Point(e.Location);
229 
230             string hint = "";
231             int count = fList.Count;
232             for (int i = 0; i < count; i++) {
233                 Fragment frag = fList[i];
234 
235                 if (frag.Rect.Contains(mpt.X, mpt.Y)) {
236                     hint = HintRequest(i + 1, frag.SrcVal);
237                     break;
238                 }
239             }
240 
241             if (fHint != hint) {
242                 fHint = hint;
243                 //fToolTip.Show(hint, this, mpt.X, mpt.Y, 3000);
244                 ToolTip = hint;
245             }
246 
247             e.Handled = true;
248             base.OnMouseMove(e);
249         }
250     }
251 }
252