#region License Information /* HeuristicLab * Copyright (C) 2002-2014 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.Visualization; namespace HeuristicLab.EvolutionTracking.Views { public partial class GenealogyGraphChart : ChartControl { private IGenealogyGraph genealogyGraph; private const double XIncrement = 30; private const double YIncrement = 30; private const double Diameter = 20; public IGenealogyGraph GenealogyGraph { get { return genealogyGraph; } set { if (value == null) return; genealogyGraph = value; Clear(); DrawGraph(XIncrement, YIncrement, Diameter); } } public IGenealogyGraphNode SelectedGraphNode { get; private set; } private void Clear() { if (nodeMap == null) nodeMap = new Dictionary(); else nodeMap.Clear(); if (arcMap == null) arcMap = new Dictionary, VisualGenealogyGraphArc>(); else arcMap.Clear(); Chart.Group.Clear(); } private Dictionary nodeMap; private Dictionary, VisualGenealogyGraphArc> arcMap; #region chart modes public bool SimpleLineages { get; set; } public bool LockGenealogy { get; set; } public bool TraceFragments { get; set; } #endregion private Visualization.Rectangle TargetRectangle { get; set; } private bool DrawInProgress { get; set; } // do not try to update the chart while the drawing is not finished private VisualGenealogyGraphNode SelectedVisualNode { get; set; } private VisualGenealogyGraphNode GetMappedNode(IGenealogyGraphNode node) { VisualGenealogyGraphNode v; nodeMap.TryGetValue(node, out v); return v; } private VisualGenealogyGraphArc GetMappedArc(IGenealogyGraphNode source, IGenealogyGraphNode target) { VisualGenealogyGraphNode visualSource, visualTarget; nodeMap.TryGetValue(source, out visualSource); nodeMap.TryGetValue(target, out visualTarget); if (visualSource == null || visualTarget == null) return null; VisualGenealogyGraphArc arc; arcMap.TryGetValue(new Tuple(visualSource, visualTarget), out arc); return arc; } public GenealogyGraphChart() : base() { InitializeComponent(); } protected virtual void DrawGraph(double xIncrement, double yIncrement, double diameter) { Chart.UpdateEnabled = false; DrawInProgress = true; var ranks = GenealogyGraph.Ranks.Select(t => new { Rank = t.Key, Nodes = t.Value }).OrderBy(p => p.Rank).ToList(); double x = 0; double y = PreferredSize.Height + yIncrement + 2 * diameter; foreach (var rank in ranks) { var nodes = rank.Nodes.ToList(); nodes.Sort((a, b) => b.CompareTo(a)); // sort descending by quality var nl = Environment.NewLine; foreach (var node in nodes) { var pen = new Pen(Color.DarkGray); var brush = new SolidBrush(node.GetColor()); var visualNode = new VisualGenealogyGraphNode(Chart, x, y, x + diameter, y + diameter, pen, brush) { Data = node, ToolTipText = "Rank: " + node.Rank + nl + "Quality: " + String.Format("{0:0.0000}", node.Quality) + nl + "IsElite: " + node.IsElite }; Chart.Group.Add(visualNode); nodeMap.Add(node, visualNode); x += xIncrement; } y -= yIncrement; x = 0; } // add arcs foreach (var node in GenealogyGraph.Nodes) { if (!node.InArcs.Any()) continue; var visualNode = GetMappedNode(node); if (visualNode == null) continue; foreach (var arc in node.InArcs) { var parent = arc.Source; var visualParent = GetMappedNode(parent); if (visualParent == null) continue; var pen = new Pen(Color.Transparent); var visualArc = AddArc(Chart, visualParent, visualNode, pen); if (!arcMap.ContainsKey(Tuple.Create(visualParent, visualNode))) arcMap.Add(Tuple.Create(visualParent, visualNode), visualArc); } } // TODO: connect elites Chart.UpdateEnabled = true; Chart.EnforceUpdate(); DrawInProgress = false; } public event MouseEventHandler GenealogyGraphNodeClicked; private void OnGenealogyGraphNodeClicked(object sender, MouseEventArgs e) { var clicked = GenealogyGraphNodeClicked; if (clicked != null) clicked(sender, e); } #region event handlers protected override void pictureBox_MouseMove(object sender, MouseEventArgs e) { if (!DrawInProgress) { 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.Any()) { SelectedVisualNode = null; return; } if (SelectedVisualNode == visualNodes[0]) return; SelectedVisualNode = visualNodes[0] as VisualGenealogyGraphNode; if (SelectedVisualNode == null) return; SelectedGraphNode = SelectedVisualNode.Data; if (!LockGenealogy) { // new node has been selected, clean up Chart.UpdateEnabled = false; if (ModifierKeys != Keys.Shift) { // clear colors ClearPrimitives(); } // use a rectangle to highlight the currently selected genealogy graph node var graphNode = SelectedVisualNode.Data; var visualNode = GetMappedNode(graphNode); DrawLineage(visualNode, n => SimpleLineages ? n.IncomingArcs.Take(1) : n.IncomingArcs, a => a.Source); DrawLineage(visualNode, n => n.OutgoingArcs, a => a.Target); } MarkSelectedNode(); // update Chart.UpdateEnabled = true; Chart.EnforceUpdate(); if (SelectedVisualNode != null) /* emit clicked event */ OnGenealogyGraphNodeClicked(SelectedVisualNode, e); base.pictureBox_MouseUp(sender, e); } #endregion private static void DrawLineage(VisualGenealogyGraphNode node, Func> arcSelector, Func nodeSelector) { var brush = (SolidBrush)node.Brush; brush.Color = node.Data.GetColor(); var arcs = arcSelector(node).ToList(); 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; // if (arc == arcs[0]) { arc.Pen.Width = 2; } // mark connection to the root parent DrawLineage(nodeSelector(arc), arcSelector, nodeSelector); } } void MarkSelectedNode() { var center = SelectedVisualNode.Center; var size = SelectedVisualNode.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); } } 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; } public virtual void ClearPrimitives() { foreach (var primitive in Chart.Group.Primitives) { if (primitive is VisualGenealogyGraphArc) { primitive.Pen.Brush = new SolidBrush(Color.Transparent); } else if (primitive is VisualGenealogyGraphNode) { var brush = (SolidBrush)primitive.Brush; brush.Color = Color.Transparent; primitive.Pen.Color = Color.DarkGray; } } } public void HighlightNodes(IEnumerable nodes) { Chart.UpdateEnabled = false; ClearPrimitives(); foreach (var node in nodes) { var graphNode = GetMappedNode(node); graphNode.Brush = new SolidBrush(node.GetColor()); } Chart.UpdateEnabled = true; Chart.EnforceUpdate(); } public void HighlightAll() { Chart.UpdateEnabled = false; foreach (var visualNode in nodeMap.Values) { visualNode.Brush = new SolidBrush(visualNode.Data.GetColor()); } foreach (var arc in arcMap.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(); } } internal static class Util { public static Color GetColor(this IGenealogyGraphNode node) { var colorIndex = (int)Math.Round(node.Quality * ColorGradient.Colors.Count); if (colorIndex >= ColorGradient.Colors.Count) return ColorGradient.Colors.Last(); return ColorGradient.Colors[colorIndex]; } } }