#region License Information
/* HeuristicLab
* Copyright (C) 2002-2015 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.Core;
using HeuristicLab.Random;
using HeuristicLab.Visualization;
using Pen = System.Drawing.Pen;
using Rectangle = HeuristicLab.Visualization.Rectangle;
using Routing = HeuristicLab.VariableInteractionNetworks.Views.ConstrainedForceDirectedLayout.EdgeRouting;
namespace HeuristicLab.VariableInteractionNetworks.Views {
public partial class DirectedGraphChart : ChartControl {
public new Bitmap Picture { get { return base.Picture; } }
private readonly ConstrainedForceDirectedLayout layout;
// provide direct and reverse mapping between graph objects and layout shapes
private Dictionary vertexMap;
private Dictionary arcMap;
private Dictionary arcShapes;
private Dictionary vertexShapes;
// graph layout options
public double DefaultEdgeLength {
get { return layout.DefaultEdgeLength; }
set { layout.DefaultEdgeLength = value; }
}
public bool PerformEdgeRouting {
get { return layout.PerformEdgeRouting; }
set { layout.PerformEdgeRouting = value; }
}
public Routing RoutingMode {
get { return layout.EdgeRoutingMode; }
set { layout.EdgeRoutingMode = value; }
}
public IVertex GetVertex(IPrimitive primitive) {
IVertex v;
vertexMap.TryGetValue(primitive, out v);
return v;
}
public IArc GetArc(IPrimitive primitive) {
IArc a;
arcMap.TryGetValue(primitive, out a);
return a;
}
public RectangularPrimitiveBase GetVertexShape(IVertex v) {
RectangularPrimitiveBase p;
vertexShapes.TryGetValue(v, out p);
return p;
}
public LinearPrimitiveBase GetArcShape(IArc arc) {
LinearPrimitiveBase l;
arcShapes.TryGetValue(arc, out l);
return l;
}
public ToolTip ToolTip {
get { return toolTip; }
}
public DirectedGraphChart() {
InitializeComponent();
var directedGraphChartMode = new DirectedGraphChartMode(this);
this.AddChartModes(directedGraphChartMode, new PanChartMode(this), new ZoomInChartMode(this), new ZoomOutChartMode(this));
shapes = new Dictionary();
SmoothingMode = SmoothingMode.HighQuality; // set antialiasing for nicer rendering
layout = new ConstrainedForceDirectedLayout {
LayoutWidth = (int)Chart.Size.Width,
LayoutHeight = (int)Chart.Size.Height,
MinHorizontalSpacing = 0,
MinVerticalSpacing = 0,
};
}
private IDirectedGraph graph;
public IDirectedGraph Graph {
get { return graph; }
set {
if (value == null || graph == value || !value.Vertices.Any()) return;
graph = value;
Draw();
}
}
private void RegisterEvents() { }
private void DeregisterEvents() { }
public void Draw() {
SuspendRendering();
Chart.Group.Clear();
vertexMap = new Dictionary();
arcMap = new Dictionary();
var rng = new MersenneTwister();
vertexShapes = new Dictionary();
arcShapes = new Dictionary();
var vertexRectangles = new Dictionary();
foreach (var v in graph.Vertices) {
var shape = CreateShape(v);
shape.ToolTipText = v.Label;
vertexMap[shape] = v;
vertexShapes[v] = shape;
var width = (float)shape.Size.Width;
var height = (float)shape.Size.Height;
var x = (float)(rng.NextDouble() * (Chart.Size.Width - width));
var y = (float)(rng.NextDouble() * (Chart.Size.Height - height));
vertexRectangles[v] = new RectangleF(x, y, width, height);
}
var arcs = graph.Vertices.SelectMany(x => x.InArcs).ToList();
if (arcs.Count != graph.Arcs.Count())
throw new Exception("Arcs count does not match!");
layout.Run(vertexRectangles);
var vertexPositions = layout.VertexPositions;
var arcRoutes = layout.ArcRoutes;
var max = arcRoutes.Keys.Max(x => x.Weight);
// move shapes to the positions computed by the layout
foreach (var pair in vertexPositions) {
var shape = vertexShapes[pair.Key];
var pos = vertexPositions[pair.Key];
shape.Move(new Offset(pos.X, pos.Y));
}
foreach (var pair in arcRoutes) {
var arc = pair.Key;
var weight = arc.Weight;
var points = pair.Value;
var target = (LabeledPrimitive)vertexShapes[pair.Key.Target];
var len = points.Length; // len = 2 when no edge routingMode is performed
Pen pen;
var penWidth = weight / max * 3;
var penColor = Color.Black;
if (len == 2) {
if (double.IsInfinity(penWidth) || double.IsNaN(penWidth))
penWidth = 1;
pen = new Pen(penColor, (float)penWidth) { CustomEndCap = new AdjustableArrowCap(5, 3) };
var start = points[0];
var end = points[1];
var line = new Line(Chart, new PointD(start.X, start.Y), new PointD(end.X, end.Y));
var rect = target.Primitive as Rectangle;
var ell = target.Primitive as Ellipse;
// calculate intersection point
PointD intersectionPoint;
if (rect != null)
intersectionPoint = rect.ComputeIntersect(line).FirstOrDefault();
else if (ell != null)
intersectionPoint = ell.ComputeIntersect(line).FirstOrDefault();
else
throw new InvalidOperationException("Unknown vertex shape.");
if (intersectionPoint == default(PointD))
intersectionPoint = end;
line = new Line(Chart, new PointD(start.X, start.Y), intersectionPoint, pen) { ToolTipText = arc.Label };
Chart.Group.Add(line);
arcShapes[arc] = line;
arcMap[line] = arc;
} else {
for (int i = 0; i < points.Length - 1; ++i) {
var start = points[i];
var end = points[i + 1];
var segment = new Line(Chart, new PointD(start.X, start.Y), new PointD(end.X, end.Y));
var rect = target.Primitive as Rectangle;
var ell = target.Primitive as Ellipse;
PointD endPoint;
List intersectionPoints;
if (rect != null) {
intersectionPoints = rect.ComputeIntersect(segment);
} else if (ell != null) {
intersectionPoints = ell.ComputeIntersect(segment);
} else {
throw new InvalidOperationException("Unknown vertex shape.");
}
if (intersectionPoints.Any()) {
endPoint = intersectionPoints.First();
pen = new Pen(penColor, (float)penWidth) { CustomEndCap = new AdjustableArrowCap(5, 3) };
} else {
endPoint = end;
pen = new Pen(penColor, (float)penWidth);
}
var startPoint = new PointD(start.X, start.Y);
var line = new Line(Chart, startPoint, endPoint, pen) { ToolTipText = arc.Label };
Chart.Group.Add(line);
if (intersectionPoints.Any()) {
arcShapes[arc] = line;
arcMap[line] = arc;
break;
}
}
}
}
// draw vertex shapes after the arcs so they appear on top
foreach (var pair in vertexPositions) {
var shape = vertexShapes[pair.Key];
shape.RedrawRequired += ShapeRedrawRequired;
Chart.Group.Add(shape);
}
ResumeRendering();
}
private void ShapeRedrawRequired(object sender, EventArgs args) {
}
private readonly Dictionary shapes;
public void AddShape(Type t, LabeledPrimitive shape) {
shapes[t] = shape;
}
public void ClearShapes() {
shapes.Clear();
}
private LabeledPrimitive CreateShape(IVertex v) {
var type = v.GetType();
if (!shapes.ContainsKey(type))
throw new ArgumentException(string.Format("No shape was associated with vertex type {0}", type));
var shape = shapes[type];
var p = shape.Primitive;
var rectangle = p as Rectangle;
var ellipse = p as Ellipse;
var pen = new Pen(shape.Pen.Color);
var brush = new SolidBrush(((SolidBrush)shape.Brush).Color);
if (rectangle != null) {
var r = new Rectangle(this.Chart, shape.LowerLeft, shape.UpperRight, pen, brush);
return new LabeledPrimitive(r, v.Label, shape.Font);
}
if (ellipse != null) {
var e = new Ellipse(this.Chart, shape.LowerLeft, shape.UpperRight, pen, brush);
return new LabeledPrimitive(e, v.Label, shape.Font);
}
throw new Exception(string.Format("Unknown shape {0}.", shape));
}
}
}