#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 { private 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 IdealEdgeLength { get; set; } = 50; public bool PerformEdgeRouting { get; set; } = false; public Routing RoutingMode { get; set; } 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 } 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); } layout = new ConstrainedForceDirectedLayout { DefaultEdgeLength = IdealEdgeLength, PerformEdgeRouting = PerformEdgeRouting, LayoutWidth = (int)Chart.Size.Width, LayoutHeight = (int)Chart.Size.Height, MinHorizontalSpacing = 0, MinVerticalSpacing = 0, EdgeRoutingMode = RoutingMode }; 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 * 2; if (len == 2) { if (double.IsInfinity(penWidth) || double.IsNaN(penWidth)) penWidth = 1; pen = new Pen(Color.Black, (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; 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) { Line line; 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(Color.Black, (float)penWidth) { CustomEndCap = new AdjustableArrowCap(5, 3) }; } else { endPoint = end; pen = new Pen(Color.Black, (float)penWidth); } line = new Line(Chart, new PointD(start.X, start.Y), 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) { layout.Run(); } private 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 brush = (SolidBrush)shape.Brush; if (rectangle != null) { var r = new Rectangle(this.Chart, shape.LowerLeft, shape.UpperRight, new Pen(shape.Pen.Color), new SolidBrush(brush.Color)); return new LabeledPrimitive(r, v.Label, shape.Font); } if (ellipse != null) { var e = new Ellipse(this.Chart, shape.LowerLeft, shape.UpperRight, new Pen(shape.Pen.Color), new SolidBrush(brush.Color)); return new LabeledPrimitive(e, v.Label, shape.Font); } throw new Exception(string.Format("Unknown shape {0}.", shape)); } } }