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