#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)) { }
}
}
}