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 System.Drawing;
24 using System.Windows.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 : Panel
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 readonly ToolTip fToolTip;
66         private string fHint;
67 
68         public event HintRequestEventHandler OnHintRequest;
69 
LogChart()70         public LogChart()
71         {
72             fList = new List<Fragment>();
73 
74             fToolTip = new ToolTip();
75             fToolTip.AutoPopDelay = 5000;
76             fToolTip.InitialDelay = 250;
77             fToolTip.ReshowDelay = 50;
78             fToolTip.ShowAlways = true;
79 
80             fEmptyBrush = new SolidBrush(Color.Gray);
81             fFragBrush = new SolidBrush(Color.Green);
82         }
83 
Dispose(bool disposing)84         protected override void Dispose(bool disposing)
85         {
86             if (disposing) {
87                 fFragBrush.Dispose();
88                 fEmptyBrush.Dispose();
89 
90                 fToolTip.Dispose();
91             }
92             base.Dispose(disposing);
93         }
94 
Clear()95         public void Clear()
96         {
97             fList.Clear();
98             UpdateContents();
99         }
100 
AddFragment(int val)101         public void AddFragment(int val)
102         {
103             if (val < 1) return;
104 
105             Fragment frag = new Fragment();
106 
107             frag.SrcVal = val;
108             if (val == 1) val++;
109             frag.Val = val;
110 
111             fList.Add(frag);
112 
113             UpdateContents();
114         }
115 
UpdateContents()116         private void UpdateContents()
117         {
118             int count = fList.Count;
119             if (count == 0) return;
120 
121             int wid = Width - (count - 1);
122             if (wid <= 0) return;
123 
124             Fragment frag;
125 
126             // this is a calculation of the simple sum of fragments
127             double sum = 0.0;
128             for (int i = 0; i < count; i++) {
129                 frag = fList[i];
130                 sum = sum + frag.Val;
131             }
132 
133             // the calculation of the logarithm of the fragment and the sum of the logarithms
134             double logSum = 0.0;
135             for (int i = 0; i < count; i++) {
136                 frag = fList[i];
137                 frag.Log = Math.Log(frag.Val, sum);
138 
139                 logSum = logSum + frag.Log;
140             }
141 
142             // calculate visual width of the fragments and their sum
143             int resWidth = 0;
144             for (int i = 0; i < count; i++) {
145                 frag = fList[i];
146                 frag.Percent = frag.Log / logSum;
147                 frag.Width = (int)(wid * frag.Percent);
148 
149                 resWidth = resWidth + frag.Width;
150             }
151 
152             // to distribute the difference between the actual width of the component and the sum of the width of the fragments
153             int d = wid - resWidth;
154             if (d > 0) {
155                 // the difference distribute between the highest allocated fragments
156                 List<Fragment> ordList = new List<Fragment>(fList);
157                 ordList.Sort(new FragmentComparer());
158 
159                 int idx = 0;
160                 while (d > 0) {
161                     frag = ordList[idx];
162                     frag.Width = frag.Width + 1;
163 
164                     if (idx == count - 1) {
165                         idx = 0;
166                     } else idx++;
167 
168                     d--;
169                 }
170             }
171 
172             // prepare the regions of rendering fragments
173             int x = 0;
174             for (int i = 0; i < count; i++) {
175                 frag = fList[i];
176 
177                 frag.X = x;
178                 frag.Rect = new Rectangle(x, 0, frag.Width, Height);
179                 x = x + (frag.Width + 1);
180             }
181 
182             Invalidate();
183         }
184 
185         private class FragmentComparer: IComparer<Fragment>
186         {
Compare(Fragment x, Fragment y)187             public int Compare(Fragment x, Fragment y)
188             {
189                 return -x.Width.CompareTo(y.Width);
190             }
191         }
192 
OnPaint(PaintEventArgs e)193         protected override void OnPaint(PaintEventArgs e)
194         {
195             base.OnPaint(e);
196 
197             if (Width <= 0 || Height <= 0) return;
198 
199             Graphics gfx = e.Graphics;
200 
201             int count = fList.Count;
202             if (count > 0) {
203                 for (int i = 0; i < count; i++) {
204                     Fragment frag = fList[i];
205                     DrawRect(gfx, frag.X, frag.Width, fFragBrush);
206                 }
207             } else {
208                 DrawRect(gfx, 0, Width, fEmptyBrush);
209             }
210         }
211 
DrawRect(Graphics gfx, int x, int width, Brush lb)212         private void DrawRect(Graphics gfx, int x, int width, Brush lb)
213         {
214             if (width > 0) {
215                 gfx.FillRectangle(lb, x, 0, width, Height);
216             }
217         }
218 
HintRequest(int fragmentNumber, int size)219         private string HintRequest(int fragmentNumber, int size)
220         {
221             var onHintRequest = OnHintRequest;
222             if (onHintRequest == null) return string.Empty;
223 
224             HintRequestEventArgs args = new HintRequestEventArgs(fragmentNumber, size);
225             onHintRequest(this, args);
226             return args.Hint;
227         }
228 
OnMouseMove(MouseEventArgs e)229         protected override void OnMouseMove(MouseEventArgs e)
230         {
231             base.OnMouseMove(e);
232             Point mpt = e.Location;
233 
234             string hint = "";
235             int count = fList.Count;
236             for (int i = 0; i < count; i++) {
237                 Fragment frag = fList[i];
238 
239                 if (frag.Rect.Contains(mpt.X, mpt.Y)) {
240                     hint = HintRequest(i + 1, frag.SrcVal);
241                     break;
242                 }
243             }
244 
245             if (fHint != hint) {
246                 fHint = hint;
247                 fToolTip.Show(hint, this, e.X, e.Y, 3000);
248             }
249         }
250     }
251 }
252