Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.EvolutionTracking/HeuristicLab.EvolutionTracking.Views/3.4/GenealogyGraphChart.cs @ 10732

Last change on this file since 10732 was 10732, checked in by bburlacu, 10 years ago

#1772: Some improvements on the way the genealogy graph and the lineages are drawn.

File size: 11.5 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2014 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab 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 * HeuristicLab 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 HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Drawing;
25using System.Drawing.Drawing2D;
26using System.Linq;
27using System.Windows.Forms;
28using HeuristicLab.Common;
29using HeuristicLab.Visualization;
30
31namespace HeuristicLab.EvolutionTracking.Views {
32  public partial class GenealogyGraphChart : ChartControl {
33    private IGenealogyGraph genealogyGraph;
34
35    private const double XIncrement = 30;
36    private const double YIncrement = 30;
37    private const double Diameter = 20;
38
39    public IGenealogyGraph GenealogyGraph {
40      get { return genealogyGraph; }
41      set {
42        if (value == null) return;
43        genealogyGraph = value;
44        Clear();
45        DrawGraph(XIncrement, YIncrement, Diameter);
46      }
47    }
48
49    public IGenealogyGraphNode SelectedGraphNode { get; private set; }
50
51    private void Clear() {
52      if (nodeMap == null)
53        nodeMap = new Dictionary<IGenealogyGraphNode, VisualGenealogyGraphNode>();
54      else nodeMap.Clear();
55
56      if (arcMap == null)
57        arcMap = new Dictionary<Tuple<VisualGenealogyGraphNode, VisualGenealogyGraphNode>, VisualGenealogyGraphArc>();
58      else arcMap.Clear();
59
60      Chart.Group.Clear();
61    }
62
63    private Dictionary<IGenealogyGraphNode, VisualGenealogyGraphNode> nodeMap;
64    private Dictionary<Tuple<VisualGenealogyGraphNode, VisualGenealogyGraphNode>, VisualGenealogyGraphArc> arcMap;
65
66    #region chart modes
67    public bool SimpleLineages { get; set; }
68    public bool LockGenealogy { get; set; }
69    public bool TraceFragments { get; set; }
70    #endregion
71
72    private Visualization.Rectangle TargetRectangle { get; set; }
73    private bool DrawInProgress { get; set; } // do not try to update the chart while the drawing is not finished
74    private VisualGenealogyGraphNode SelectedVisualNode { get; set; }
75
76    private VisualGenealogyGraphNode GetMappedNode(IGenealogyGraphNode node) {
77      VisualGenealogyGraphNode v;
78      nodeMap.TryGetValue(node, out v);
79      return v;
80    }
81
82    private VisualGenealogyGraphArc GetMappedArc(IGenealogyGraphNode source, IGenealogyGraphNode target) {
83      VisualGenealogyGraphNode visualSource, visualTarget;
84      nodeMap.TryGetValue(source, out visualSource);
85      nodeMap.TryGetValue(target, out visualTarget);
86
87      if (visualSource == null || visualTarget == null) return null;
88
89      VisualGenealogyGraphArc arc;
90      arcMap.TryGetValue(new Tuple<VisualGenealogyGraphNode, VisualGenealogyGraphNode>(visualSource, visualTarget), out arc);
91      return arc;
92    }
93
94    public GenealogyGraphChart()
95      : base() {
96      InitializeComponent();
97    }
98
99    protected virtual void DrawGraph(double xIncrement, double yIncrement, double diameter) {
100      Chart.UpdateEnabled = false;
101      DrawInProgress = true;
102
103      var ranks = GenealogyGraph.Ranks.Select(t => new { Rank = t.Key, Nodes = t.Value }).OrderBy(p => p.Rank).ToList();
104      double x = 0;
105      double y = PreferredSize.Height + yIncrement + 2 * diameter;
106
107      foreach (var rank in ranks) {
108        var nodes = rank.Nodes.ToList();
109        nodes.Sort((a, b) => b.CompareTo(a)); // sort descending by quality
110        var nl = Environment.NewLine;
111
112        foreach (var node in nodes) {
113          var pen = new Pen(Color.DarkGray);
114          var brush = new SolidBrush(node.GetColor());
115
116          var visualNode = new VisualGenealogyGraphNode(Chart, x, y, x + diameter, y + diameter, pen, brush) {
117            Data = node,
118            ToolTipText = "Rank: " + node.Rank + nl +
119                          "Quality: " + String.Format("{0:0.0000}", node.Quality) + nl +
120                          "IsElite: " + node.IsElite
121          };
122          Chart.Group.Add(visualNode);
123          nodeMap.Add(node, visualNode);
124
125          x += xIncrement;
126        }
127        y -= yIncrement;
128        x = 0;
129      }
130
131      // add arcs
132      foreach (var node in GenealogyGraph.Nodes) {
133        if (!node.InArcs.Any()) continue;
134        var visualNode = GetMappedNode(node);
135        if (visualNode == null) continue;
136        foreach (var arc in node.InArcs) {
137          var parent = arc.Source;
138          var visualParent = GetMappedNode(parent);
139          if (visualParent == null) continue;
140          var pen = new Pen(Color.Transparent);
141          var visualArc = AddArc(Chart, visualParent, visualNode, pen);
142          if (!arcMap.ContainsKey(Tuple.Create(visualParent, visualNode)))
143            arcMap.Add(Tuple.Create(visualParent, visualNode), visualArc);
144        }
145      }
146      // TODO: connect elites
147
148      Chart.UpdateEnabled = true;
149      Chart.EnforceUpdate();
150
151      DrawInProgress = false;
152    }
153
154    public event MouseEventHandler GenealogyGraphNodeClicked;
155    private void OnGenealogyGraphNodeClicked(object sender, MouseEventArgs e) {
156      var clicked = GenealogyGraphNodeClicked;
157      if (clicked != null) clicked(sender, e);
158    }
159
160    #region event handlers
161    protected override void pictureBox_MouseMove(object sender, MouseEventArgs e) {
162      if (!DrawInProgress) {
163        switch (e.Button) {
164          case MouseButtons.Left:
165            Chart.Mode = ChartMode.Select;
166            Cursor = Cursors.Default;
167            break;
168          case MouseButtons.Middle:
169            Chart.Mode = ChartMode.Move;
170            Cursor = Cursors.Hand;
171            break;
172        }
173      }
174      base.pictureBox_MouseMove(sender, e);
175    }
176    protected override void pictureBox_MouseUp(object sender, MouseEventArgs e) {
177      Cursor = Cursors.Default;
178      if (Chart.Mode == ChartMode.Move) {
179        Chart.Mode = ChartMode.Select;
180        return;
181      }
182      if (Chart.Mode != ChartMode.Select) {
183        base.pictureBox_MouseUp(sender, e);
184        return;
185      }
186      var visualNodes = Chart.GetAllPrimitives(e.Location).Where(p => p is VisualGenealogyGraphNode).ToList();
187      if (!visualNodes.Any()) {
188        SelectedVisualNode = null;
189        return;
190      }
191      if (SelectedVisualNode == visualNodes[0]) return;
192      SelectedVisualNode = visualNodes[0] as VisualGenealogyGraphNode;
193      if (SelectedVisualNode == null) return;
194      SelectedGraphNode = SelectedVisualNode.Data;
195
196      if (!LockGenealogy) {
197        // new node has been selected, clean up
198        Chart.UpdateEnabled = false;
199        if (ModifierKeys != Keys.Shift) {
200          // clear colors
201          ClearPrimitives();
202        }
203        // use a rectangle to highlight the currently selected genealogy graph node
204        var graphNode = SelectedVisualNode.Data;
205        var visualNode = GetMappedNode(graphNode);
206
207        DrawLineage(visualNode, n => SimpleLineages ? n.IncomingArcs.Take(1) : n.IncomingArcs, a => a.Source);
208        DrawLineage(visualNode, n => n.OutgoingArcs, a => a.Target);
209      }
210
211      MarkSelectedNode();
212
213      // update
214      Chart.UpdateEnabled = true;
215      Chart.EnforceUpdate();
216
217      if (SelectedVisualNode != null)
218        /* emit clicked event */
219        OnGenealogyGraphNodeClicked(SelectedVisualNode, e);
220
221      base.pictureBox_MouseUp(sender, e);
222    }
223    #endregion
224
225    private static void DrawLineage(VisualGenealogyGraphNode node, Func<VisualGenealogyGraphNode, IEnumerable<VisualGenealogyGraphArc>> arcSelector, Func<VisualGenealogyGraphArc, VisualGenealogyGraphNode> nodeSelector) {
226      var brush = (SolidBrush)node.Brush;
227      brush.Color = node.Data.GetColor();
228      var arcs = arcSelector(node).ToList();
229      foreach (var arc in arcs) {
230        var source = arc.Source.Data;
231        var target = arc.Target.Data;
232        var start = new Point((int)arc.Start.X, (int)arc.Start.Y);
233        var end = new Point((int)arc.End.X, (int)arc.End.Y);
234        arc.Pen.Brush = new LinearGradientBrush(start, end, source.GetColor(), target.GetColor());
235        arc.Pen.Color = Color.Transparent;
236        //        if (arc == arcs[0]) { arc.Pen.Width = 2; } // mark connection to the root parent
237        DrawLineage(nodeSelector(arc), arcSelector, nodeSelector);
238      }
239    }
240
241    void MarkSelectedNode() {
242      var center = SelectedVisualNode.Center;
243      var size = SelectedVisualNode.Size;
244      double x1 = center.X - size.Width / 2;
245      double x2 = x1 + size.Width;
246      double y1 = center.Y - size.Height / 2;
247      double y2 = y1 + size.Height;
248      if (TargetRectangle == null) {
249        TargetRectangle = new Visualization.Rectangle(Chart, x1, y1, x2, y2, new Pen(Color.Black), null);
250        Chart.Group.Add(TargetRectangle);
251      } else {
252        TargetRectangle.SetPosition(x1, y1, x2, y2);
253      }
254    }
255
256    private static VisualGenealogyGraphArc AddArc(IChart chart, VisualGenealogyGraphNode source, VisualGenealogyGraphNode target, Pen pen, Brush brush = null) {
257      var arc = new VisualGenealogyGraphArc(chart, source, target, pen) { Brush = brush };
258      arc.UpdatePosition();
259      source.OutgoingArcs.Add(arc);
260      target.IncomingArcs.Add(arc);
261      chart.Group.Add(arc);
262      return arc;
263    }
264
265    public virtual void ClearPrimitives() {
266      foreach (var primitive in Chart.Group.Primitives) {
267        if (primitive is VisualGenealogyGraphArc) {
268          primitive.Pen.Brush = new SolidBrush(Color.Transparent);
269        } else if (primitive is VisualGenealogyGraphNode) {
270          var brush = (SolidBrush)primitive.Brush;
271          brush.Color = Color.Transparent;
272          primitive.Pen.Color = Color.DarkGray;
273        }
274      }
275    }
276
277    public void HighlightNodes(IEnumerable<IGenealogyGraphNode> nodes) {
278      Chart.UpdateEnabled = false;
279      ClearPrimitives();
280      foreach (var node in nodes) {
281        var graphNode = GetMappedNode(node);
282        graphNode.Brush = new SolidBrush(node.GetColor());
283      }
284      Chart.UpdateEnabled = true;
285      Chart.EnforceUpdate();
286    }
287
288    public void HighlightAll() {
289      Chart.UpdateEnabled = false;
290      foreach (var visualNode in nodeMap.Values) {
291        visualNode.Brush = new SolidBrush(visualNode.Data.GetColor());
292      }
293      foreach (var arc in arcMap.Values) {
294        var source = arc.Source.Data;
295        var target = arc.Target.Data;
296        var start = new Point((int)arc.Start.X, (int)arc.Start.Y);
297        var end = new Point((int)arc.End.X, (int)arc.End.Y);
298        arc.Pen.Brush = new LinearGradientBrush(start, end, source.GetColor(), target.GetColor());
299      }
300      Chart.UpdateEnabled = true;
301      Chart.EnforceUpdate();
302    }
303  }
304
305  internal static class Util {
306    public static Color GetColor(this IGenealogyGraphNode node) {
307      var colorIndex = (int)Math.Round(node.Quality * ColorGradient.Colors.Count);
308      if (colorIndex >= ColorGradient.Colors.Count) return ColorGradient.Colors.Last();
309      return ColorGradient.Colors[colorIndex];
310    }
311  }
312}
Note: See TracBrowser for help on using the repository browser.