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