#region License Information /* HeuristicLab * Copyright (C) 2002-2016 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 = 25; private const double YIncrement = 25; private const double Diameter = 20; private readonly Brush defaultBrush; private readonly Pen defaultPen; private Dictionary nodeMap; private Dictionary, VisualGenealogyGraphArc> arcMap; #region chart options public bool SimpleLineages { get; set; } public bool LockGenealogy { get; set; } public Func ColorSelector { get; set; } #endregion private bool DrawInProgress { get; set; } // do not try to update the chart while the drawing is not finished 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 { if (value == null || value == SelectedGraphNode) return; SelectedVisualNode = GetMappedNode(value); RedrawLineages(); } } private void Clear() { nodeMap = new Dictionary(); arcMap = new Dictionary, VisualGenealogyGraphArc>(); Chart.Group.Clear(); } private Visualization.Rectangle TargetRectangle { get; set; } protected VisualGenealogyGraphNode SelectedVisualNode { get; set; } public VisualGenealogyGraphNode GetMappedNode(IGenealogyGraphNode node) { VisualGenealogyGraphNode v; nodeMap.TryGetValue(node, out v); return v; } public 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(new GridlessChart(0, 0, 800, 600)) { InitializeComponent(); AddChartModes(new PanChartMode(this), new ZoomInChartMode(this), new ZoomOutChartMode(this), new SelectChartMode(this)); defaultBrush = new SolidBrush(Color.Transparent); defaultPen = new Pen(Color.DarkGray); ColorSelector = node => ColorGradient.Colors[(int)Math.Round(node.Quality * 255)]; } protected virtual void DrawGraph(double xIncrement, double yIncrement, double diameter) { this.SuspendRendering(); 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 rect = new Visualization.Rectangle(Chart, new PointD(x, y), new PointD(x + diameter, y + diameter)); var font = new Font(FontFamily.GenericSansSerif, 12); var genLabel = new LabeledPrimitive(rect, rank.Rank.ToString(), font); Chart.Group.Add(genLabel); x += xIncrement; 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(ColorSelector(node)); var visualNode = new VisualGenealogyGraphNode(Chart, x, y, x + diameter, y + diameter, defaultPen, brush) { Data = node, ToolTipText = "Rank: " + node.Rank + nl + "Quality: " + string.Format("{0:N4}", 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); Chart.OneLayerDown(visualArc); // send it behind the visual nodes if (!arcMap.ContainsKey(Tuple.Create(visualParent, visualNode))) { arcMap.Add(Tuple.Create(visualParent, visualNode), visualArc); } } } ResumeRendering(); 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 PictureBoxOnMouseUp(object sender, MouseEventArgs e) { if (Mode is SelectChartMode) { var primitive = Chart.GetAllPrimitives(e.Location).FirstOrDefault(p => p is VisualGenealogyGraphNode); if (primitive != null && primitive != SelectedVisualNode) { SelectedVisualNode = (VisualGenealogyGraphNode)primitive; RedrawLineages(); // redraw ancestries, mark node etc. if (SelectedVisualNode != null) OnGenealogyGraphNodeClicked(SelectedVisualNode, null); // emit clicked event } } base.PictureBoxOnMouseUp(sender, e); } private void RedrawLineages() { if (!LockGenealogy) { // new node has been selected, clean up SuspendRendering(); 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, ColorSelector); ((SolidBrush)visualNode.Brush).Color = Color.Transparent; DrawLineage(visualNode, n => n.OutgoingArcs, a => a.Target, ColorSelector); } MarkSelectedNode(); // update ResumeRendering(); } #endregion #region drawing routines private static void DrawLineage(VisualGenealogyGraphNode node, Func> arcSelector, Func nodeSelector, Func getNodeColor) { var brush = (SolidBrush)node.Brush; if (brush.Color != Color.Transparent) return; // this lineage was already drawn (avoid redrawing common ancestors) brush.Color = getNodeColor(node.Data); 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, getNodeColor(source), getNodeColor(target)); DrawLineage(nodeSelector(arc), arcSelector, nodeSelector, getNodeColor); } } 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) { var lowerLeft = new PointD(x1, y1); var upperRight = new PointD(x2, y2); TargetRectangle = new Visualization.Rectangle(Chart, lowerLeft, upperRight, new Pen(Color.Black), null); Chart.Group.Add(TargetRectangle); } else { var lowerLeft = new PointD(x1, y1); var upperRight = new PointD(x2, y2); TargetRectangle.SetPosition(lowerLeft, upperRight); } } 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); } } } public void HighlightNodes(IEnumerable nodes, bool clearPrimitives = true) { if (clearPrimitives) ClearPrimitives(); foreach (var node in nodes) { var graphNode = GetMappedNode(node); graphNode.Brush = new SolidBrush(ColorSelector(node)); } } 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(ColorSelector(visualNode.Data)); Chart.IntoForeground(visualNode); } // draw arcs first and then nodes 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 = new Pen(new LinearGradientBrush(start, end, ColorSelector(source), ColorSelector(target))); Chart.IntoBackground(arc); } } 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 = new Pen(new LinearGradientBrush(start, end, ColorSelector(source), ColorSelector(target))); Chart.IntoBackground(arc); } #endregion // workaround to disable the chart grid private class GridlessChart : Chart { public GridlessChart(PointD lowerLeft, PointD upperRight) : base(lowerLeft, upperRight) { Grid = null; } public GridlessChart(double x1, double y1, double x2, double y2) : this(new PointD(x1, y1), new PointD(x2, y2)) { } } } }