#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; private readonly Brush defaultBrush; private readonly Pen defaultPen; private Dictionary nodeMap; private Dictionary, VisualGenealogyGraphArc> arcMap; public IGenealogyGraph GenealogyGraph { get { return genealogyGraph; } set { if (value == null) return; genealogyGraph = value; Clear(); DrawGraph(XIncrement, YIncrement, Diameter); } } public IGenealogyGraphNode SelectedGraphNode { get { return SelectedVisualNode == null ? null : SelectedVisualNode.Data; } set { SelectedVisualNode = GetMappedNode(value); UpdateSelectedVisualNode(); } } private void Clear() { nodeMap = new Dictionary(); arcMap = new Dictionary, VisualGenealogyGraphArc>(); Chart.Group.Clear(); } public bool UpdateEnabled { get { return Chart.UpdateEnabled; } set { Chart.UpdateEnabled = value; } } public void EnforceUpdate() { Chart.EnforceUpdate(); } #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 protected 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() { InitializeComponent(); defaultBrush = new SolidBrush(Color.Transparent); defaultPen = new Pen(Color.DarkGray); } 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 brush = new SolidBrush(node.GetColor()); var visualNode = new VisualGenealogyGraphNode(Chart, x, y, x + diameter, y + diameter, defaultPen, brush) { Data = node, ToolTipText = "Rank: " + node.Rank + nl + "Quality: " + String.Format("{0:0.0000}", node.Quality) + nl + "IsElite: " + node.IsElite + nl + "In/Out Degree: " + node.InDegree + " " + node.OutDegree }; Chart.Group.Add(visualNode); nodeMap.Add(node, visualNode); x += xIncrement; } y -= yIncrement; x = 0; } // add arcs foreach (var node in GenealogyGraph.Vertices) { 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 = Pens.Transparent; var visualArc = AddArc(Chart, visualParent, visualNode, pen); visualArc.OneLayerDown(); // send it behind the visual nodes 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; protected void OnGenealogyGraphNodeClicked(object sender, MouseEventArgs e) { var clicked = GenealogyGraphNodeClicked; if (clicked != null) clicked(sender, e); } public event MouseEventHandler GenealogyGraphNodeDoubleClicked; protected void OnGenealogyGraphNodeDoubleClicked(object sender, MouseEventArgs e) { var doubleClicked = GenealogyGraphNodeDoubleClicked; if (doubleClicked != null) doubleClicked(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 primitive = Chart.GetAllPrimitives(e.Location).FirstOrDefault(p => p is VisualGenealogyGraphNode); if (primitive == null) { SelectedVisualNode = null; return; } if (SelectedVisualNode == primitive) return; SelectedVisualNode = primitive as VisualGenealogyGraphNode; if (SelectedVisualNode == null) return; UpdateSelectedVisualNode(); // redraw ancestries, mark node etc. base.pictureBox_MouseUp(sender, e); } private void UpdateSelectedVisualNode() { 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); ((SolidBrush)visualNode.Brush).Color = Color.Transparent; DrawLineage(visualNode, n => n.OutgoingArcs, a => a.Target); } MarkSelectedNode(); // update UpdateEnabled = true; EnforceUpdate(); if (SelectedVisualNode != null) OnGenealogyGraphNodeClicked(SelectedVisualNode, null); // emit clicked event } #endregion private static void DrawLineage(VisualGenealogyGraphNode node, Func> arcSelector, Func nodeSelector) { var brush = (SolidBrush)node.Brush; if (brush.Color != Color.Transparent) return; // this lineage was already drawn (avoid redrawing common ancestors) brush.Color = node.Data.GetColor(); var arcs = arcSelector(node); var pen = new Pen(Color.Transparent); 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 = pen; arc.Pen.Brush = new LinearGradientBrush(start, end, source.GetColor(), target.GetColor()); DrawLineage(nodeSelector(arc), arcSelector, nodeSelector); } } public void HighlightHotPaths() { Chart.UpdateEnabled = false; ClearPrimitives(); var arcs = GenealogyGraph.Vertices.SelectMany(n => n.InArcs).ToList(); foreach (var arc in arcs) { arc.Weight = 1.0; } // reset weights var rank = GenealogyGraph.Ranks.Max(x => x.Key); foreach (var graphNode in GenealogyGraph.GetByRank(rank)) { foreach (var ancestor in graphNode.Ancestors) { foreach (var arc in ancestor.InArcs) { arc.Weight++; } } } double max = arcs.Max(a => a.Weight); double min = arcs.Min(a => a.Weight); if (min.IsAlmost(max)) return; //translate interval (min,max) to interval (0,255) foreach (var arc in arcs) { var vArc = GetMappedArc(arc.Source, arc.Target); int colorIndex = (int)Math.Round((arc.Weight - min) * 255 / (max - min)); if (colorIndex > 254) colorIndex = 254; vArc.Pen = new Pen(ColorGradient.Colors[colorIndex]); // vArc.Pen.Brush = new SolidBrush(ColorGradient.Colors[colorIndex]); } Chart.UpdateEnabled = true; Chart.EnforceUpdate(); } 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 void ClearArcs() { foreach (var primitive in Chart.Group.Primitives.OfType()) { primitive.Pen = Pens.Transparent; } } public void ClearNodes() { foreach (var primitive in Chart.Group.Primitives.OfType()) { primitive.Brush = new SolidBrush(Color.Transparent); primitive.Pen = new Pen(Color.LightGray); } } public virtual void ClearPrimitives() { foreach (var primitive in Chart.Group.Primitives) { if (primitive is VisualGenealogyGraphArc) { primitive.Pen = Pens.Transparent; } else if (primitive is VisualGenealogyGraphNode) { primitive.Brush = new SolidBrush(Color.Transparent); primitive.Pen = new Pen(Color.DarkGray); // primitive.Pen = Pens.Transparent; // remove the node contour } } } public void HighlightNodes(IEnumerable nodes, bool clearPrimitives = true) { if (clearPrimitives) ClearPrimitives(); foreach (var node in nodes) { var graphNode = GetMappedNode(node); graphNode.Brush = new SolidBrush(node.GetColor()); } } public void HighlightNodes(IEnumerable nodes, Color color, bool clearPrimitives = true) { if (clearPrimitives) ClearPrimitives(); foreach (var node in nodes.Select(GetMappedNode)) { node.Brush = new SolidBrush(color); } } public void HighlightAll() { 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()); } } public void HighlightArc(IGenealogyGraphNode source, IGenealogyGraphNode target) { var arc = GetMappedArc(source, target) ?? AddArc(Chart, GetMappedNode(source), GetMappedNode(target), new Pen(Color.Transparent)); 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 = new Pen(Color.Transparent); arc.Pen.Brush = new LinearGradientBrush(start, end, source.GetColor(), target.GetColor()); } } internal static class Util { public static Color GetColor(this IGenealogyGraphNode node) { if (double.IsNaN(node.Quality)) return ColorGradient.Colors[0]; var colorIndex = (int)Math.Round(node.Quality * ColorGradient.Colors.Count); if (colorIndex >= ColorGradient.Colors.Count) return ColorGradient.Colors.Last(); return ColorGradient.Colors[colorIndex]; } } }