1 /* 2 * "GEDKeeper", the personal genealogical database editor. 3 * Copyright (C) 2009-2021 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.Drawing; 23 using System.Drawing.Drawing2D; 24 using System.Windows.Forms; 25 using BSLib.DataViz.ArborGVT; 26 using GKCore; 27 using GKUI.Platform; 28 29 namespace GKUI.Components 30 { 31 public sealed class ArborNodeEx : ArborNode 32 { 33 public Color Color; 34 public RectangleF Box; 35 ArborNodeEx(string sign)36 public ArborNodeEx(string sign) : base(sign) 37 { 38 Color = Color.Gray; 39 } 40 } 41 42 public sealed class ArborSystemEx : ArborSystem 43 { 44 private WinUITimer fTimer; 45 ArborSystemEx(double repulsion, double stiffness, double friction, IArborRenderer renderer)46 public ArborSystemEx(double repulsion, double stiffness, double friction, IArborRenderer renderer) 47 : base(repulsion, stiffness, friction, renderer) 48 { 49 this.fTimer = null; 50 } 51 TimerElapsed(object sender, EventArgs e)52 private void TimerElapsed(object sender, EventArgs e) 53 { 54 TickTimer(); 55 } 56 StartTimer()57 protected override void StartTimer() 58 { 59 fTimer = new WinUITimer(TimerInterval/* / 1000*/, TimerElapsed); 60 fTimer.Start(); 61 } 62 StopTimer()63 protected override void StopTimer() 64 { 65 if (fTimer != null) { 66 fTimer.Stop(); 67 fTimer.Dispose(); 68 fTimer = null; 69 } 70 } 71 CreateNode(string sign)72 protected override ArborNode CreateNode(string sign) 73 { 74 return new ArborNodeEx(sign); 75 } 76 CreateEdge(ArborNode src, ArborNode tgt, double len, double stiffness, bool directed = false)77 protected override ArborEdge CreateEdge(ArborNode src, ArborNode tgt, double len, double stiffness, bool directed = false) 78 { 79 return new ArborEdge(src, tgt, len, stiffness, directed); 80 } 81 } 82 83 public sealed class ArborViewer : UserControl, IArborRenderer 84 { 85 private bool fEnergyDebug; 86 private ArborNode fDragged; 87 private readonly Font fDrawFont; 88 private bool fNodesDragging; 89 private readonly StringFormat fStrFormat; 90 private readonly ArborSystemEx fSys; 91 private readonly SolidBrush fBlackBrush; 92 private readonly SolidBrush fWhiteBrush; 93 94 public bool EnergyDebug 95 { 96 get { return fEnergyDebug; } 97 set { fEnergyDebug = value; } 98 } 99 100 public bool NodesDragging 101 { 102 get { return fNodesDragging; } 103 set { fNodesDragging = value; } 104 } 105 106 public ArborSystemEx Sys 107 { 108 get { return fSys; } 109 } 110 ArborViewer()111 public ArborViewer() 112 { 113 BorderStyle = BorderStyle.Fixed3D; 114 TabStop = true; 115 BackColor = Color.White; 116 117 DoubleBuffered = true; 118 SetStyle(ControlStyles.AllPaintingInWmPaint, true); 119 SetStyle(ControlStyles.OptimizedDoubleBuffer, true); 120 121 // repulsion - отталкивание, stiffness - тугоподвижность, friction - сила трения 122 fSys = new ArborSystemEx(10000, 500/*1000*/, 0.1, this); 123 fSys.SetViewSize(Width, Height); 124 fSys.AutoStop = false; 125 126 fEnergyDebug = false; 127 fDrawFont = new Font("Calibri", 9); 128 129 fStrFormat = new StringFormat(); 130 fStrFormat.Alignment = StringAlignment.Center; 131 fStrFormat.LineAlignment = StringAlignment.Center; 132 133 fBlackBrush = new SolidBrush(Color.Black); 134 fWhiteBrush = new SolidBrush(Color.White); 135 fDragged = null; 136 fNodesDragging = false; 137 } 138 Dispose(bool disposing)139 protected override void Dispose(bool disposing) 140 { 141 if (disposing) 142 { 143 fSys.Dispose(); 144 fDrawFont.Dispose(); 145 fWhiteBrush.Dispose(); 146 fBlackBrush.Dispose(); 147 fStrFormat.Dispose(); 148 } 149 base.Dispose(disposing); 150 } 151 OnSizeChanged(EventArgs e)152 protected override void OnSizeChanged(EventArgs e) 153 { 154 base.OnSizeChanged(e); 155 156 fSys.SetViewSize(Width, Height); 157 Invalidate(); 158 } 159 OnPaint(PaintEventArgs e)160 protected override void OnPaint(PaintEventArgs e) 161 { 162 Graphics gfx = e.Graphics; 163 164 try { 165 gfx.SmoothingMode = SmoothingMode.AntiAlias; 166 167 foreach (ArborNode node in fSys.Nodes) { 168 var xnode = node as ArborNodeEx; 169 170 xnode.Box = getNodeRect(gfx, node); 171 gfx.FillRectangle(new SolidBrush(xnode.Color), xnode.Box); 172 gfx.DrawString(node.Sign, fDrawFont, fWhiteBrush, xnode.Box, fStrFormat); 173 } 174 175 using (Pen grayPen = new Pen(Color.Gray, 1)) { 176 grayPen.StartCap = LineCap.NoAnchor; 177 grayPen.EndCap = LineCap.ArrowAnchor; 178 179 foreach (ArborEdge edge in fSys.Edges) { 180 var srcNode = edge.Source as ArborNodeEx; 181 var tgtNode = edge.Target as ArborNodeEx; 182 183 ArborPoint pt1 = fSys.GetViewCoords(srcNode.Pt); 184 ArborPoint pt2 = fSys.GetViewCoords(tgtNode.Pt); 185 186 ArborPoint tail = intersect_line_box(pt1, pt2, srcNode.Box); 187 ArborPoint head = (tail.IsNull()) ? ArborPoint.Null : intersect_line_box(tail, pt2, tgtNode.Box); 188 189 if (!head.IsNull() && !tail.IsNull()) { 190 gfx.DrawLine(grayPen, (int)tail.X, (int)tail.Y, (int)head.X, (int)head.Y); 191 } 192 } 193 } 194 195 if (fEnergyDebug) { 196 string energy = "max=" + fSys.EnergyMax.ToString("0.00000") + ", mean=" + fSys.EnergyMean.ToString("0.00000"); 197 gfx.DrawString(energy, fDrawFont, fBlackBrush, 10, 10); 198 } 199 } catch (Exception ex) { 200 Logger.WriteError("ArborViewer.OnPaint()", ex); 201 } 202 203 base.OnPaint(e); 204 } 205 intersect_line_line(ArborPoint p1, ArborPoint p2, ArborPoint p3, ArborPoint p4)206 public static ArborPoint intersect_line_line(ArborPoint p1, ArborPoint p2, ArborPoint p3, ArborPoint p4) 207 { 208 double denom = ((p4.Y - p3.Y) * (p2.X - p1.X) - (p4.X - p3.X) * (p2.Y - p1.Y)); 209 if (denom == 0) return ArborPoint.Null; // lines are parallel 210 211 double ua = ((p4.X - p3.X) * (p1.Y - p3.Y) - (p4.Y - p3.Y) * (p1.X - p3.X)) / denom; 212 double ub = ((p2.X - p1.X) * (p1.Y - p3.Y) - (p2.Y - p1.Y) * (p1.X - p3.X)) / denom; 213 214 if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return ArborPoint.Null; 215 216 return new ArborPoint(p1.X + ua * (p2.X - p1.X), p1.Y + ua * (p2.Y - p1.Y)); 217 } 218 intersect_line_box(ArborPoint p1, ArborPoint p2, RectangleF boxTuple)219 public ArborPoint intersect_line_box(ArborPoint p1, ArborPoint p2, RectangleF boxTuple) 220 { 221 double bx = boxTuple.X; 222 double by = boxTuple.Y; 223 double bw = boxTuple.Width; 224 double bh = boxTuple.Height; 225 226 ArborPoint tl = new ArborPoint(bx, by); 227 ArborPoint tr = new ArborPoint(bx + bw, by); 228 ArborPoint bl = new ArborPoint(bx, by + bh); 229 ArborPoint br = new ArborPoint(bx + bw, by + bh); 230 231 ArborPoint pt; 232 233 pt = intersect_line_line(p1, p2, tl, tr); 234 if (!pt.IsNull()) return pt; 235 236 pt = intersect_line_line(p1, p2, tr, br); 237 if (!pt.IsNull()) return pt; 238 239 pt = intersect_line_line(p1, p2, br, bl); 240 if (!pt.IsNull()) return pt; 241 242 pt = intersect_line_line(p1, p2, bl, tl); 243 if (!pt.IsNull()) return pt; 244 245 return ArborPoint.Null; 246 } 247 start()248 public void start() 249 { 250 fSys.Start(); 251 } 252 OnMouseDown(MouseEventArgs e)253 protected override void OnMouseDown(MouseEventArgs e) 254 { 255 base.OnMouseDown(e); 256 if (!Focused) Focus(); 257 258 Point mpt = e.Location; 259 if (fNodesDragging) { 260 fDragged = fSys.GetNearestNode(mpt.X, mpt.Y); 261 262 if (fDragged != null) { 263 fDragged.Fixed = true; 264 } 265 } 266 } 267 OnMouseUp(MouseEventArgs e)268 protected override void OnMouseUp(MouseEventArgs e) 269 { 270 base.OnMouseUp(e); 271 272 if (fNodesDragging && fDragged != null) { 273 fDragged.Fixed = false; 274 //fDragged.Mass = 1000; 275 fDragged = null; 276 } 277 } 278 OnMouseMove(MouseEventArgs e)279 protected override void OnMouseMove(MouseEventArgs e) 280 { 281 base.OnMouseMove(e); 282 283 Point mpt = e.Location; 284 if (fNodesDragging && fDragged != null) { 285 fDragged.Pt = fSys.GetModelCoords(mpt.X, mpt.Y); 286 } 287 } 288 getNodeRect(Graphics gfx, ArborNode node)289 public RectangleF getNodeRect(Graphics gfx, ArborNode node) 290 { 291 SizeF tsz = gfx.MeasureString(node.Sign, fDrawFont); 292 float w = tsz.Width + 10; 293 float h = tsz.Height + 4; 294 ArborPoint pt = fSys.GetViewCoords(node.Pt); 295 pt.X = Math.Floor(pt.X); 296 pt.Y = Math.Floor(pt.Y); 297 298 return new RectangleF((float)pt.X - w / 2, (float)pt.Y - h / 2, w, h); 299 } 300 getNodeByCoord(int x, int y)301 public ArborNode getNodeByCoord(int x, int y) 302 { 303 return fSys.GetNearestNode(x, y); 304 305 /*foreach (ArborNode node in fSys.Nodes) 306 { 307 if (node.Box.Contains(x, y)) { 308 return node; 309 } 310 } 311 return null;*/ 312 } 313 } 314 } 315