#region License Information /* HeuristicLab * Copyright (C) 2002-2010 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Windows.Forms; using HeuristicLab.Common; using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding; using HeuristicLab.Visualization; namespace HeuristicLab.EvolutionaryTracking.Views { public partial class GenealogyGraphChart : ChartControl { private Dictionary visualNodeMap; // map the uid of the genealogy graph vertex with a visual node (in this way each node is uniquely determined) private Dictionary, VisualGenealogyGraphArc> visualArcMap; private const int Diameter = 20; // node diameter private int x, y; // coordinates for positioning each node private const int Cx = 30, Cy = 30; // position increments private VisualGenealogyGraphNode selectedGenealogyGraphNode; public VisualGenealogyGraphNode SelectedGenealogyGraphNode { get { return selectedGenealogyGraphNode; } } private Visualization.Rectangle targetRectangle; // provides a rectangle mark of the currently selected genealogy graph node public bool SimpleLineages { get; set; } public bool LockGenealogy { get; set; } private bool drawing; private SymbolicExpressionTreeGenealogyGraph graph; public SymbolicExpressionTreeGenealogyGraph Graph { get { return graph; } internal set { graph = value; if (graph == null) return; visualNodeMap = new Dictionary(); visualArcMap = new Dictionary, VisualGenealogyGraphArc>(); Chart.Group.Clear(); DrawGraph(); } } private VisualGenealogyGraphNode GetVisualGenealogyGraphNode(IVertex node) { VisualGenealogyGraphNode visualNode; visualNodeMap.TryGetValue(node.Id, out visualNode); return visualNode; } private VisualGenealogyGraphArc GetVisualGenealogyGraphArc(IVertex source, IVertex target) { VisualGenealogyGraphNode visualSource; VisualGenealogyGraphNode visualTarget; visualNodeMap.TryGetValue(source.Id, out visualSource); visualNodeMap.TryGetValue(target.Id, out visualTarget); if (visualSource != null && visualTarget != null) { VisualGenealogyGraphArc visualArc; visualArcMap.TryGetValue( new Tuple(visualSource, visualTarget), out visualArc); if (visualArc != null) return visualArc; } return null; } public event MouseEventHandler GenealogyGraphNodeClicked; private void OnGenealogyGraphNodeClicked(object sender, MouseEventArgs e) { var clicked = GenealogyGraphNodeClicked; if (clicked != null) clicked(sender, e); } public GenealogyGraphChart() { // InitializeComponent(); Chart = new Chart(0, 0, PreferredSize.Width, PreferredSize.Height); x = 0; y = PreferredSize.Height + Cx + 2 * Diameter; Chart.Mode = ChartMode.Select; } public void DrawGraph() { if (graph == null) return; Chart.UpdateEnabled = false; drawing = true; var layers = Graph.Nodes.GroupBy(n => n.Rank).OrderBy(g => g.Key).Select(g => new { Rank = g.Key, Nodes = g.ToList() }).ToList(); y = PreferredSize.Height + Cx + 2 * Diameter; for (int i = 0; i != layers.Count; ++i) { x = 0; layers[i].Nodes.Sort((b, a) => Graph.Compare(a, b)); double rank = Math.Floor(layers[i].Rank); if (layers[i].Rank.IsAlmost(rank)) { var visualTextNode = new VisualGenealogyGraphTextLabel(Chart, x, y + 2, x + Diameter, y + Diameter) { Brush = new SolidBrush(Color.Black), Font = new Font("Arial", Diameter - 4, FontStyle.Regular, GraphicsUnit.Pixel), Text = String.Format("{0:0}", rank) }; Chart.Group.Add(visualTextNode); } // sort descending by quality (using comparison defined in GenealogyGraphNode class) x += (int)Math.Floor(1.5 * Cx); // reset horizontal coordinate foreach (var node in layers[i].Nodes) { var pen = new Pen(Color.LightGray); var nl = Environment.NewLine; var visualNode = new VisualGenealogyGraphNode(Chart, x, y, x + Diameter, y + Diameter, pen, null) { Data = node, ToolTipText = "Rank: " + node.Rank + nl + "Quality: " + String.Format("{0:0.0000}", node.Quality) + nl + "IsElite: " + node.IsElite }; Chart.Group.Add(visualNode); visualNodeMap.Add(node.Id, visualNode); x += Cx; // increment horizontal coordinate } y -= Cy; // decrement vertical coordinate (because the origin is upside down) // connect elites from successive layers with visual arcs if (i == 0) continue; var currLayer = layers[i].Nodes; var prevLayer = layers[i - 1].Nodes; foreach (var n in currLayer) { foreach (var m in prevLayer) { if (n.SymbolicExpressionTree != m.SymbolicExpressionTree) continue; var v1 = GetVisualGenealogyGraphNode(n); var v2 = GetVisualGenealogyGraphNode(m); var pen = new Pen(Color.LightGray); visualArcMap[Tuple.Create(v2, v1)] = AddArc(Chart, v2, v1, pen); } } } // add arcs separately (to avoid some ordering problems) foreach (SymbolicExpressionGenealogyGraphNode node in Graph.Nodes) { VisualGenealogyGraphNode visualNode = GetVisualGenealogyGraphNode(node); if (node.InEdges == null) continue; foreach (Arc arc in node.InEdges) { var visualParent = GetVisualGenealogyGraphNode(arc.Source); var pen = new Pen(Color.Transparent); visualArcMap[Tuple.Create(visualParent, visualNode)] = AddArc(Chart, visualParent, visualNode, pen); } } // highlight the most recent common ancestor ("eve" individual) // var eve = graph.MostRecentCommonAncestor(); // if (eve != null) { // var visualGraphNode = GetVisualGenealogyGraphNode(eve); // var brush = new SolidBrush(Color.BlueViolet); // visualGraphNode.Brush = brush; // } Chart.UpdateEnabled = true; Chart.EnforceUpdate(); drawing = false; } // add an arc between the source and the target nodes, using the specified pen color // adds the arc to the arc lists of both nodes and to the primitive group of the chart private static VisualGenealogyGraphArc AddArc(IChart chart, VisualGenealogyGraphNode source, VisualGenealogyGraphNode target, Pen pen, Brush brush = null) { var arc = new VisualGenealogyGraphArc(chart, source, target, pen) { Brush = brush }; arc.UpdatePosition(); source.OutgoingArcs.Add(arc); target.IncomingArcs.Add(arc); chart.Group.Add(arc); return arc; } protected override void pictureBox_MouseMove(object sender, MouseEventArgs e) { if (!drawing) { switch (e.Button) { case MouseButtons.Left: Chart.Mode = ChartMode.Select; Cursor = Cursors.Default; break; case MouseButtons.Middle: Chart.Mode = ChartMode.Move; Cursor = Cursors.Hand; break; } base.pictureBox_MouseMove(sender, e); } } protected override void pictureBox_MouseUp(object sender, MouseEventArgs e) { Cursor = Cursors.Default; if (Chart.Mode == ChartMode.Move) { Chart.Mode = ChartMode.Select; return; } if (Chart.Mode != ChartMode.Select) { base.pictureBox_MouseUp(sender, e); return; } var visualNodes = Chart.GetAllPrimitives(e.Location).Where(p => p is VisualGenealogyGraphNode).ToList(); if (visualNodes.Count <= 0) { selectedGenealogyGraphNode = null; return; } if (selectedGenealogyGraphNode == visualNodes[0]) return; selectedGenealogyGraphNode = visualNodes[0] as VisualGenealogyGraphNode; if (selectedGenealogyGraphNode == null) return; if (!LockGenealogy) { // new node has been selected, clean up Chart.UpdateEnabled = false; if (ModifierKeys != Keys.Shift) // clear colors ClearAllNodes(); // use a rectangle to highlight the currently selected genealogy graph node var gNode = selectedGenealogyGraphNode.Data; var visualNode = GetVisualGenealogyGraphNode(gNode); DrawLineage(visualNode, n => SimpleLineages ? n.IncomingArcs.Take(1) : n.IncomingArcs, a => a.Source); visualNode.Brush = null; DrawLineage(visualNode, n => n.OutgoingArcs, a => a.Target); } MarkSelectedNode(); // update Chart.UpdateEnabled = true; Chart.EnforceUpdate(); if (selectedGenealogyGraphNode != null) /* emit clicked event */ OnGenealogyGraphNodeClicked(selectedGenealogyGraphNode, e); } private void DrawLineage(VisualGenealogyGraphNode node, Func> arcSelector, Func nodeSelector) { if (node.Brush != null) return; node.Brush = new SolidBrush(node.Data.GetColor()); var arcs = arcSelector(node); if (arcs == null) return; foreach (var arc in arcs) { var source = arc.Source.Data; var target = arc.Target.Data; var start = new Point((int)arc.Start.X, (int)arc.Start.Y); var end = new Point((int)arc.End.X, (int)arc.End.Y); arc.Pen.Brush = new LinearGradientBrush(start, end, source.GetColor(), target.GetColor()); arc.Pen.Color = Color.Transparent; // arc.Pen.Brush = new SolidBrush(Color.DarkGray); DrawLineage(nodeSelector(arc), arcSelector, nodeSelector); } } void MarkSelectedNode() { var center = selectedGenealogyGraphNode.Center; var size = selectedGenealogyGraphNode.Size; double x1 = center.X - size.Width / 2; double x2 = x1 + size.Width; double y1 = center.Y - size.Height / 2; double y2 = y1 + size.Height; if (targetRectangle == null) { targetRectangle = new Visualization.Rectangle(Chart, x1, y1, x2, y2, new Pen(Color.Black), null); Chart.Group.Add(targetRectangle); } else { targetRectangle.SetPosition(x1, y1, x2, y2); } } public void ClearAllNodes() { foreach (var primitive in Chart.Group.Primitives) { if (primitive is VisualGenealogyGraphArc) { var arc = primitive as VisualGenealogyGraphArc; var sourceData = arc.Source.Data.SymbolicExpressionTree; var targetData = arc.Target.Data.SymbolicExpressionTree; primitive.Pen.Brush = sourceData != targetData ? new SolidBrush(Color.Transparent) : new SolidBrush(Color.LightGray); } else { primitive.Brush = null; } } } public void HighlightNodes(IEnumerable nodes) { Chart.UpdateEnabled = false; ClearAllNodes(); foreach (var node in nodes) { GetVisualGenealogyGraphNode(node).Brush = new SolidBrush(node.GetColor()); } Chart.UpdateEnabled = true; Chart.EnforceUpdate(); } // TODO: optimize and reduce complexity of this method public void HighlightNodes(IEnumerable trees) { foreach (var tree in trees) { var graphNodes = graph.GetGraphNodes(tree); foreach (var graphNode in graphNodes) { GetVisualGenealogyGraphNode(graphNode).Brush = new SolidBrush(graphNode.GetColor()); } } } public void HighlightArcs(IEnumerable arcs, Color color) { foreach (var a in arcs) { var arc = GetVisualGenealogyGraphArc(a.Source, a.Target); if (arc != null) { var source = arc.Source.Data; var target = arc.Target.Data; var start = new Point((int)arc.Start.X, (int)arc.Start.Y); var end = new Point((int)arc.End.X, (int)arc.End.Y); arc.Pen.Brush = new LinearGradientBrush(start, end, source.GetColor(), target.GetColor()); } } } public void HighlightNode(SymbolicExpressionGenealogyGraphNode graphNode, Color color) { GetVisualGenealogyGraphNode(graphNode).Brush = new SolidBrush(color); } public void HighlightAll() { Chart.UpdateEnabled = false; foreach (var visualNode in visualNodeMap.Values) { visualNode.Brush = new SolidBrush(visualNode.Data.GetColor()); } foreach (var arc in visualArcMap.Values) { var source = arc.Source.Data; var target = arc.Target.Data; var start = new Point((int)arc.Start.X, (int)arc.Start.Y); var end = new Point((int)arc.End.X, (int)arc.End.Y); arc.Pen.Brush = new LinearGradientBrush(start, end, source.GetColor(), target.GetColor()); } Chart.UpdateEnabled = true; Chart.EnforceUpdate(); } public void HighlightInDegree() { Chart.UpdateEnabled = false; ClearAllNodes(); double max = Graph.Nodes.Max(x => x.InEdges == null ? 0 : x.InEdges.Count); foreach (var graphNode in Graph.Nodes) { var visualNode = GetVisualGenealogyGraphNode(graphNode); double deg = graphNode.InEdges == null ? 0 : graphNode.InEdges.Count; int index = (int)(deg / max * ColorGradient.Colors.Count); if (index == ColorGradient.Colors.Count) --index; var color = ColorGradient.Colors[index]; visualNode.Brush = new SolidBrush(color); } Chart.UpdateEnabled = true; Chart.EnforceUpdate(); } public void HighlightOutDegree() { Chart.UpdateEnabled = false; ClearAllNodes(); var graphNodes = Graph.Nodes; double max = graphNodes.Max(x => x.OutEdges == null ? 0 : x.OutEdges.Count); foreach (var graphNode in graphNodes) { var visualNode = GetVisualGenealogyGraphNode(graphNode); double deg = graphNode.OutEdges == null ? 0 : graphNode.OutEdges.Count; int index = (int)(deg / max * ColorGradient.Colors.Count); if (index == ColorGradient.Colors.Count) --index; var color = ColorGradient.Colors[index]; visualNode.Brush = new SolidBrush(color); } Chart.UpdateEnabled = true; Chart.EnforceUpdate(); } } internal static class Util { public static Color GetColor(this SymbolicExpressionGenealogyGraphNode node) { var colorIndex = (int)(node.Quality * ColorGradient.Colors.Count); if (colorIndex >= ColorGradient.Colors.Count) --colorIndex; return ColorGradient.Colors[colorIndex]; } } }