#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];
}
}
}