From 324cd7923b4c955dc9d39319644af1779d87e207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kon=C3=AD=C4=8Dek?= Date: Sun, 4 Apr 2010 12:15:21 +0000 Subject: [PATCH] Object graph visualizer - replaced Graphviz's neato.exe by my custom spline routing. Removed all native Graphviz libraries. Routing algorithm (Graphviz's algorithm is similar): search shortest path (Dijkstra - fast enough, A* would be faster) in the following graph: vertices are corners off all boxes, moved outside of the boxes a little. Edges: any pair of vertices that can be connected by straight line not while not intersecting any box. Having the shortest path as poly-line. Just curve the corners (looks surprisingly good). Edges are routed independently, so they can cross. Having our own implementation, we could work on edge non-crossing algorithm in the future. That is, if the whole layout and routing won't be replaced by GraphSharp in the end. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5665 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Debugger.AddIn/Debugger.AddIn.csproj | 36 +-- .../Graph/Layout/GraphEdgeRouter.cs | 46 ++++ .../Visualizers/Graph/Layout/GraphMatcher.cs | 12 +- .../Graph/Layout/PositionedEdge.cs | 18 +- .../Graph/Layout/PositionedGraphNode.cs | 14 +- .../Graph/Layout/SplineRouting/Box.cs | 62 +++++ .../Graph/Layout/SplineRouting/Edge.cs | 27 +++ .../Graph/Layout/SplineRouting/EdgeRouter.cs | 45 ++++ .../Graph/Layout/SplineRouting/GeomUtils.cs | 104 ++++++++ .../Graph/Layout/SplineRouting/IEdge.cs | 23 ++ .../Graph/Layout/SplineRouting/IPoint.cs | 21 ++ .../Graph/Layout/SplineRouting/IRect.cs | 24 ++ .../Graph/Layout/SplineRouting/Point2D.cs | 27 +++ .../SplineRouting/RouteGraph/EdgeStartEnd.cs | 34 +++ .../SplineRouting/RouteGraph/RouteGraph.cs | 222 ++++++++++++++++++ .../RouteGraph/RouteGraphEdge.cs | 30 +++ .../SplineRouting/RouteGraph/RouteVertex.cs | 60 +++++ .../SplineRouting/RouteGraph/RoutedEdge.cs | 87 +++++++ .../ShortestPath/AStarShortestPathFinder.cs | 69 ++++++ .../Graph/Layout/Tree/NeatoEdgeRouter.cs | 7 +- .../Graph/Layout/Tree/TreeLayouter.cs | 5 +- 21 files changed, 937 insertions(+), 36 deletions(-) create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/GraphEdgeRouter.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Box.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Edge.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/EdgeRouter.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/GeomUtils.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IEdge.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IPoint.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IRect.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Point2D.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/EdgeStartEnd.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteGraph.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteGraphEdge.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteVertex.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RoutedEdge.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/ShortestPath/AStarShortestPathFinder.cs diff --git a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj index 7234b9fb95..951611997b 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj +++ b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj @@ -159,6 +159,21 @@ + + + + + + + + + + + + + + + ObjectGraphControl.xaml @@ -272,23 +287,6 @@ - - - - - - - - - - - - - - - - - @@ -357,8 +355,10 @@ + + + - diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/GraphEdgeRouter.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/GraphEdgeRouter.cs new file mode 100644 index 0000000000..52556ee909 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/GraphEdgeRouter.cs @@ -0,0 +1,46 @@ +// +// +// +// +// $Revision$ +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; + +using Debugger.AddIn.Visualizers.Graph.SplineRouting; + +namespace Debugger.AddIn.Visualizers.Graph.Layout +{ + /// + /// Adapts generic to work with . + /// + public class GraphEdgeRouter + { + EdgeRouter router = new EdgeRouter(); + + public GraphEdgeRouter() + { + } + + public PositionedGraph RouteEdges(PositionedGraph posGraph) + { + List routedEdges = router.RouteEdges(posGraph.Nodes, posGraph.Edges); + int i = 0; + // assume routedEdges come in the same order as posGraph.Edges + foreach (var edge in posGraph.Edges) { + SetEdgeSplinePoints(edge, routedEdges[i]); + i++; + } + return posGraph; + } + + void SetEdgeSplinePoints(PositionedEdge edge, RoutedEdge routedEdge) + { + foreach (Point2D point in routedEdge.SplinePoints) { + edge.SplinePoints.Add(new Point(point.X, point.Y)); + } + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/GraphMatcher.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/GraphMatcher.cs index 46214325f4..c2507a46e6 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/GraphMatcher.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/GraphMatcher.cs @@ -22,7 +22,6 @@ namespace Debugger.AddIn.Visualizers.Graph.Layout public GraphDiff MatchGraphs(PositionedGraph oldGraph, PositionedGraph newGraph) { - // handle any of the graphs null if (oldGraph == null) { if (newGraph == null) @@ -33,14 +32,19 @@ namespace Debugger.AddIn.Visualizers.Graph.Layout { GraphDiff addAllDiff = new GraphDiff(); foreach (PositionedGraphNode newNode in newGraph.Nodes) - { addAllDiff.SetAdded(newNode); - } return addAllDiff; } } + else if (newGraph == null) + { + GraphDiff removeAllDiff = new GraphDiff(); + foreach (PositionedGraphNode oldNode in oldGraph.Nodes) + removeAllDiff.SetRemoved(oldNode); + return removeAllDiff; + } - // both graph are not null + // none of the graphs is null GraphDiff diff = new GraphDiff(); Dictionary newNodeForHashCode = buildHashToNodeMap(newGraph); diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/PositionedEdge.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/PositionedEdge.cs index 60d980837c..bd77cf2edb 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/PositionedEdge.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/PositionedEdge.cs @@ -15,15 +15,13 @@ namespace Debugger.AddIn.Visualizers.Graph.Layout /// /// Edge with position information. /// - public class PositionedEdge : NamedEdge + public class PositionedEdge : NamedEdge, SplineRouting.IEdge { private IList splinePoints = new List(); - - // ReadOnlyCollection ? /// /// Control points of edge's spline, in standart format: 1 start point + 3 points per segment /// - public IList SplinePoints + public IList SplinePoints { get { @@ -39,5 +37,17 @@ namespace Debugger.AddIn.Visualizers.Graph.Layout /// Drawn spline representation of this edge. /// public System.Windows.Shapes.Path Spline { get; set; } + + public SplineRouting.IRect From { + get { + return this.Source.ContainingNode; + } + } + + public SplineRouting.IRect To { + get { + return this.Target; + } + } } } diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/PositionedGraphNode.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/PositionedGraphNode.cs index 0a68fd7430..7264593891 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/PositionedGraphNode.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/PositionedGraphNode.cs @@ -7,9 +7,9 @@ using System; using System.Collections.Generic; -using Debugger.AddIn.Visualizers.Graph.Drawing; -using System.Windows; using System.Linq; +using System.Windows; +using Debugger.AddIn.Visualizers.Graph.Drawing; using Debugger.AddIn.Visualizers.Utils; namespace Debugger.AddIn.Visualizers.Graph.Layout @@ -17,7 +17,7 @@ namespace Debugger.AddIn.Visualizers.Graph.Layout /// /// ObjectNode with added position information. /// - public class PositionedGraphNode + public class PositionedGraphNode : SplineRouting.IRect { public static readonly double MaxHeight = 300; @@ -158,5 +158,13 @@ namespace Debugger.AddIn.Visualizers.Graph.Layout this.ContentNodeCollapsed(sender, e); } #endregion + + SplineRouting.Box box; + public SplineRouting.IRect Inflated(double padding) + { + if (box == null) + box = new SplineRouting.Box(this); + return box.Inflated(padding); + } } } diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Box.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Box.cs new file mode 100644 index 0000000000..255e36821a --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Box.cs @@ -0,0 +1,62 @@ +// +// +// +// +// $Revision$ +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Description of Box. + /// + public class Box : IRect, ICloneable + { + public Rectangle rect; + Box inflatedCache; + + public Box(double left, double top, double width, double height) + { + this.rect = new Rectangle(); + this.Left = left; + this.Top = top; + this.Width = width; + this.Height = height; + } + + public Box(IRect original) + { + this.rect = new Rectangle(); + this.Left = original.Left; + this.Top = original.Top; + this.Width = original.Width; + this.Height = original.Height; + } + + public double Left { get { return Canvas.GetLeft(rect); } set {Canvas.SetLeft(rect, value); } } + public double Top { get { return Canvas.GetTop(rect); } set {Canvas.SetTop(rect, value); } } + public double Width { get { return rect.Width; } set { rect.Width = value; } } + public double Height { get { return rect.Height; } set { rect.Height = value; } } + + public IRect Inflated(double padding) + { + //if (inflatedCache.ContainsKey(padding)) + if (inflatedCache == null) + { + inflatedCache = GeomUtils.InflateRect(this, padding); + } + return inflatedCache; + } + + public object Clone() + { + return new Box(this.Left, this.Top, this.Width, this.Height); + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Edge.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Edge.cs new file mode 100644 index 0000000000..e084fdc331 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Edge.cs @@ -0,0 +1,27 @@ +// +// +// +// +// $Revision$ +// +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Description of Edge. + /// + public class Edge : IEdge + { + public Edge() + { + } + + public IRect From { get; set; } + public IRect To { get; set; } + public IPoint StartPoint { get; set; } + public IPoint EndPoint { get; set; } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/EdgeRouter.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/EdgeRouter.cs new file mode 100644 index 0000000000..8ce61d4688 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/EdgeRouter.cs @@ -0,0 +1,45 @@ +// +// +// +// +// $Revision$ +// +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Description of EdgeRouter. + /// + public class EdgeRouter + { + public EdgeRouter() + { + } + + public List RouteEdges(IEnumerable nodes, IEnumerable edges) + { + var routeGraph = RouteGraph.InitializeVertices(nodes, edges); + List routedEdges = new List(); + var occludedEdges = new List(); + foreach (IEdge edge in edges) { + var straightEdge = routeGraph.TryRouteEdgeStraight(edge); + if (straightEdge != null) + routedEdges.Add(straightEdge); + else + occludedEdges.Add(edge); + } + if (occludedEdges.Count > 0) { + // there are some edges that couldn't be routed as straight lines + routeGraph.ComputeVisibilityGraph(); + foreach (IEdge edge in occludedEdges) { + RoutedEdge routedEdge = routeGraph.RouteEdge(edge); + routedEdges.Add(routedEdge); + } + } + return routedEdges; + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/GeomUtils.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/GeomUtils.cs new file mode 100644 index 0000000000..4e67cb7991 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/GeomUtils.cs @@ -0,0 +1,104 @@ +// +// +// +// +// $Revision$ +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Description of GeomUtils. + /// + public class GeomUtils + { + static readonly double eps = 1e-8; + + public GeomUtils() + { + } + + public static IPoint RectCenter(IRect rect) + { + return new Point2D(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2); + } + + public static double LineLenght(IPoint lineStart, IPoint lineEnd) + { + var dx = lineEnd.X - lineStart.X; + var dy = lineEnd.Y - lineStart.Y; + return Math.Sqrt(dx * dx + dy * dy); + } + + public static Point2D? LineRectIntersection(IPoint lineStart, IPoint lineEnd, IRect rect) + { + double vx = lineEnd.X - lineStart.X; + double vy = lineEnd.Y - lineStart.Y; + double right = rect.Left + rect.Width; + double bottom = rect.Top + rect.Height; + + var isectTop = isectHoriz(lineStart, vx, vy, rect.Top, rect.Left, right); + if (isectTop != null) + return isectTop; + var isectBottom = isectHoriz(lineStart, vx, vy, bottom, rect.Left, right); + if (isectBottom != null) + return isectBottom; + var isectLeft = isectVert(lineStart, vx, vy, rect.Left, rect.Top, bottom); + if (isectLeft != null) + return isectLeft; + var isectRight = isectVert(lineStart, vx, vy, right, rect.Top, bottom); + if (isectRight != null) + return isectRight; + + return null; + } + + static Point2D? isectHoriz(IPoint lineStart, double vx, double vy, double yBound, double left, double right) + { + if (Math.Abs(vy) < eps) + return null; + double t = (yBound - lineStart.Y) / vy; + if (t > 0 && t < 1) + { + double iX = (lineStart.X + t * vx); + if (iX >= left && iX <= right) + return new Point2D(iX, yBound); + } + return null; + } + + static Point2D? isectVert(IPoint lineStart, double vx, double vy, double xBound, double top, double bottom) + { + if (Math.Abs(vx) < eps) + return null; + double t = (xBound - lineStart.X) / vx; + if (t > 0 && t < 1) + { + double iY = (lineStart.Y + t * vy); + if (iY >= top && iY <= bottom) + return new Point2D(xBound, iY); + } + return null; + } + + public static Box InflateRect(IRect rect, double padding) + { + return new Box(rect.Left - padding, rect.Top - padding, rect.Width + padding * 2, rect.Height + padding * 2); + } + + public static Point2D Interpolate(Point2D pointA, Point2D pointB, double t) + { + double b = 1 - t; + return new Point2D(pointA.X * t + pointB.X * b, pointA.Y * t + pointB.Y * b); + } + + /*public static double LineLineIntersection(Point line1Start, Point line1End, Point line2Start, Point line2End) + { + + }*/ + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IEdge.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IEdge.cs new file mode 100644 index 0000000000..9240ca46dc --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IEdge.cs @@ -0,0 +1,23 @@ +// +// +// +// +// $Revision$ +// +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Enables passing any type of graph (implementing IRect, IEdge) into EdgeRouter. + /// + public interface IEdge + { + IRect From { get; } + IRect To { get; } + //IPoint StartPoint { get; set; } + //IPoint EndPoint { get; set; } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IPoint.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IPoint.cs new file mode 100644 index 0000000000..af27200a97 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IPoint.cs @@ -0,0 +1,21 @@ +// +// +// +// +// $Revision$ +// +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Enables passing any type of graph (implementing IRect, IEdge) into EdgeRouter. + /// + public interface IPoint + { + double X { get; set; } + double Y { get; set; } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IRect.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IRect.cs new file mode 100644 index 0000000000..89c6f761e5 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/IRect.cs @@ -0,0 +1,24 @@ +// +// +// +// +// $Revision$ +// +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Enables passing any type of graph (implementing IRect, IEdge) into EdgeRouter. + /// + public interface IRect + { + double Left { get; } + double Top { get; } + double Width { get; } + double Height { get; } + IRect Inflated(double padding); + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Point2D.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Point2D.cs new file mode 100644 index 0000000000..d150bcbb6f --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/Point2D.cs @@ -0,0 +1,27 @@ +// +// +// +// +// $Revision$ +// +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Description of Point2D. + /// + public struct Point2D : IPoint + { + public Point2D(double x, double y) : this() + { + this.X = x; + this.Y = y; + } + + public double X { get; set; } + public double Y { get; set; } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/EdgeStartEnd.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/EdgeStartEnd.cs new file mode 100644 index 0000000000..6fe77776be --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/EdgeStartEnd.cs @@ -0,0 +1,34 @@ +// +// +// +// +// $Revision$ +// +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Description of EdgeStartEnd. + /// + public class EdgeStartEnd + { + public IRect From { get; set; } + public IRect To { get; set; } + + public override bool Equals(object obj) + { + var other = obj as EdgeStartEnd; + if (other == null) + return false; + return this.From == other.From && this.To == other.To; + } + + public override int GetHashCode() + { + return this.From.GetHashCode() ^ this.To.GetHashCode(); + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteGraph.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteGraph.cs new file mode 100644 index 0000000000..4721d74cf1 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteGraph.cs @@ -0,0 +1,222 @@ +// +// +// +// +// $Revision$ +// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Description of RouteGraph. + /// + public class RouteGraph + { + static readonly double boxPadding = 15; + //static readonly double boxSafetyMargin = 5; // inflate boxes for collision testing + static readonly double multiEdgeGap = 10; + + List boxes = new List(); + public List Boxes { + get { return boxes; } + } + + List vertices = new List(); + public List Vertices { + get { return vertices; } + } + + AStarShortestPathFinder pathFinder; + + public RouteGraph() + { + pathFinder = new AStarShortestPathFinder(this); + } + + public static RouteGraph InitializeVertices(IEnumerable nodes, IEnumerable edges) + { + var graph = new RouteGraph(); + // add vertices for node corners + foreach (var node in nodes) { + graph.Boxes.Add(node); + foreach (var vertex in GetRectCorners(node, boxPadding)) { + graph.Vertices.Add(vertex); + } + } + // add vertices for egde endpoints + foreach (var multiEdgeGroup in edges.GroupBy(edge => GetStartEnd(edge))) { + int multiEdgeCount = multiEdgeGroup.Count(); + IRect fromRect = multiEdgeGroup.First().From; + IRect toRect = multiEdgeGroup.First().To; + var sourceCenter = GeomUtils.RectCenter(fromRect); + var targetCenter = GeomUtils.RectCenter(toRect); + if (Math.Abs(sourceCenter.X - targetCenter.X) > Math.Abs(sourceCenter.Y - targetCenter.Y) || + (fromRect == toRect)) + { + // the line is horizontal + double multiEdgeSpanSource = GetMultiEdgeSpan(fromRect.Height, multiEdgeCount, multiEdgeGap); + double multiEdgeSpanTarget = GetMultiEdgeSpan(toRect.Height, multiEdgeCount, multiEdgeGap); + double originSourceCurrentY = sourceCenter.Y - multiEdgeSpanSource / 2; + double originTargetCurrentY = targetCenter.Y - multiEdgeSpanTarget / 2; + foreach (var edge in multiEdgeGroup) { + Point2D sourceOrigin = new Point2D(sourceCenter.X, originSourceCurrentY); + Point2D targetOrigin = new Point2D(targetCenter.X, originTargetCurrentY); + // Here user could provide custom edgeStart and edgeEnd + // inflate boxes a little so that edgeStart and edgeEnd are a little outside of the box (to prevent floating point errors) + if (edge.From == edge.To) { + var edgeStart = new Point2D(fromRect.Left + fromRect.Width + 0.01, originSourceCurrentY); + var edgeEnd = new Point2D(fromRect.Left + fromRect.Width / 2, fromRect.Top); + graph.AddEdgeEndpointVertices(edge, edgeStart, edgeEnd); + } else { + var edgeStart = GeomUtils.LineRectIntersection(sourceOrigin, targetOrigin, edge.From.Inflated(1e-3)); + var edgeEnd = GeomUtils.LineRectIntersection(sourceOrigin, targetOrigin, edge.To.Inflated(1e-3)); + graph.AddEdgeEndpointVertices(edge, edgeStart, edgeEnd); + } + originSourceCurrentY += multiEdgeSpanSource / (multiEdgeCount - 1); + originTargetCurrentY += multiEdgeSpanTarget / (multiEdgeCount - 1); + } + } + else + { + // the line is vertical + double multiEdgeSpanSource = GetMultiEdgeSpan(fromRect.Width, multiEdgeCount, multiEdgeGap); + double multiEdgeSpanTarget = GetMultiEdgeSpan(toRect.Width, multiEdgeCount, multiEdgeGap); + double originSourceCurrentX = sourceCenter.X - multiEdgeSpanSource / 2; + double originTargetCurrentX = targetCenter.X - multiEdgeSpanTarget / 2; + foreach (var edge in multiEdgeGroup) { + Point2D sourceOrigin = new Point2D(originSourceCurrentX, sourceCenter.Y); + Point2D targetOrigin = new Point2D(originTargetCurrentX, targetCenter.Y); + // Here user could provide custom edgeStart and edgeEnd + // inflate boxes a little so that edgeStart and edgeEnd are a little outside of the box (to prevent floating point errors) + var edgeStart = GeomUtils.LineRectIntersection(sourceOrigin, targetOrigin, edge.From.Inflated(1e-3)); + var edgeEnd = GeomUtils.LineRectIntersection(sourceOrigin, targetOrigin, edge.To.Inflated(1e-3)); + graph.AddEdgeEndpointVertices(edge, edgeStart, edgeEnd); + originSourceCurrentX += multiEdgeSpanSource / (multiEdgeCount - 1); + originTargetCurrentX += multiEdgeSpanTarget / (multiEdgeCount - 1); + } + } + } + return graph; + } + + void AddEdgeEndpointVertices(IEdge edge, Point2D? edgeStart, Point2D? edgeEnd) + { + if (edgeStart == null || edgeEnd == null) { + // should not happen + return; + } + var startPoint = new RouteVertex(edgeStart.Value.X, edgeStart.Value.Y); + startPoint.IsEdgeEndpoint = true; + var endPoint = new RouteVertex(edgeEnd.Value.X, edgeEnd.Value.Y); + endPoint.IsEdgeEndpoint = true; + this.vertices.Add(startPoint); + this.vertices.Add(endPoint); + // remember what RouteVertices we created for this user edge + this.setStartVertex(edge, startPoint); + this.setEndVertex(edge, endPoint); + } + + static IEnumerable GetRectCorners(IRect rect, double padding) + { + double left = rect.Left - padding; + double top = rect.Top - padding; + double right = left + rect.Width + 2 * padding; + double bottom = top + rect.Height + 2 * padding; + yield return new RouteVertex(left, top); + yield return new RouteVertex(right, top); + yield return new RouteVertex(right, bottom); + yield return new RouteVertex(left, bottom); + } + + public void ComputeVisibilityGraph() + { + for (int i = 0; i < this.Vertices.Count; i++) { + for (int j = i + 1; j < this.Vertices.Count; j++) { + var vertex = this.Vertices[i]; + var vertex2 = this.Vertices[j]; + if (Visible(vertex, vertex2)) + { + // bidirectional edge + vertex.AddNeighbor(vertex2); + vertex2.AddNeighbor(vertex); + } + } + } + } + + public bool Visible(IPoint vertex, IPoint vertex2) + { + // test for intersection with every box + foreach (var rect in this.Boxes) { + if (GeomUtils.LineRectIntersection(vertex, vertex2, rect) != null) + return false; + } + return true; + } + + public RoutedEdge RouteEdge(IEdge edge) + { + var pathVertices = pathFinder.FindShortestPath(getStartVertex(edge), getEndVertex(edge)); + return BuildRoutedEdge(pathVertices); + } + + public RoutedEdge TryRouteEdgeStraight(IEdge edge) + { + var startVertex = getStartVertex(edge); + var endVertex = getEndVertex(edge); + if (Visible(startVertex, endVertex)) { + // route the edge straight + return BuildRoutedEdge(new [] {startVertex, endVertex }); + } else + return null; + } + + public RoutedEdge BuildRoutedEdge(IEnumerable points) + { + var routedEdge = new RoutedEdge(); + foreach (var point in points) { + routedEdge.Points.Add(new Point2D(point.X, point.Y)); + } + return routedEdge; + } + + Dictionary edgeStarts = new Dictionary(); + Dictionary edgeEnds = new Dictionary(); + + RouteVertex getStartVertex(IEdge edge) + { + return edgeStarts[edge]; + } + RouteVertex getEndVertex(IEdge edge) + { + return edgeEnds[edge]; + } + void setStartVertex(IEdge edge, RouteVertex value) + { + edgeStarts[edge] = value; + } + void setEndVertex(IEdge edge, RouteVertex value) + { + edgeEnds[edge] = value; + } + + static EdgeStartEnd GetStartEnd(IEdge edge) + { + return new EdgeStartEnd { From = edge.From, To = edge.To }; + } + + static double GetMultiEdgeSpan(double space, int multiEdgeCount, double multiEdgeGap) + { + if ((multiEdgeCount + 1) * multiEdgeGap < space) + // the edges fit, maintain the gap + return (multiEdgeCount - 1) * multiEdgeGap; + else + // there are too many edges, we have to make smaller gaps to fit edges into given space + return space - multiEdgeGap; + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteGraphEdge.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteGraphEdge.cs new file mode 100644 index 0000000000..ecb9a0e321 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteGraphEdge.cs @@ -0,0 +1,30 @@ +// +// +// +// +// $Revision$ +// +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Edge in the RouteGraph. + /// + public class RouteGraphEdge + { + public RouteVertex StartVertex { get; private set; } + public RouteVertex EndVertex { get; private set; } + + public double Length { get; private set; } + + public RouteGraphEdge(RouteVertex startVertex, RouteVertex endVertex) + { + this.StartVertex = startVertex; + this.EndVertex = endVertex; + this.Length = GeomUtils.LineLenght(startVertex, endVertex); + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteVertex.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteVertex.cs new file mode 100644 index 0000000000..c8d2324f18 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RouteVertex.cs @@ -0,0 +1,60 @@ +// +// +// +// +// $Revision$ +// +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Description of RouteVertex. + /// + public class RouteVertex : IPoint + { + public double X { get; set; } + public double Y { get; set; } + + public RouteVertex(double x, double y) + { + this.X = x; + this.Y = y; + } + + List neighbors = new List(); + public List Neighbors { + get { return neighbors; } + } + + public RouteVertex() + { + Reset(); + IsUsed = false; + IsEdgeEndpoint = false; + } + + public void AddNeighbor(RouteVertex target) + { + this.Neighbors.Add(new RouteGraphEdge(this, target)); + } + + public double Distance { get; set; } + public bool IsPermanent { get; set; } + public RouteVertex Predecessor { get; set; } + public bool IsEdgeEndpoint { get; set; } + public bool IsUsed { get; set; } + public bool IsAvailable { get { return !IsUsed && !IsEdgeEndpoint; } } + + public void Reset() + { + Distance = 10e+6; + IsPermanent = false; + Predecessor = null; + //IsUsed = false; + //IsEdgeEndpoint = false; + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RoutedEdge.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RoutedEdge.cs new file mode 100644 index 0000000000..f87da0ecec --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/RouteGraph/RoutedEdge.cs @@ -0,0 +1,87 @@ +// +// +// +// +// $Revision$ +// +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// The result of the main method EdgeRouter.RouteEdges(), passed back to the user. + /// + public class RoutedEdge + { + public double Lenght { + get { + double len = 0; + for (int i = 1; i < this.Points.Count; i++) { + len += GeomUtils.LineLenght(this.Points[i - 1], this.Points[i]); + } + return len; + } + } + + List points; + public List Points + { + get { return this.points; } + } + + public RoutedEdge() + { + this.points = new List(); + } + + ReadOnlyCollection splinePoints; + public ReadOnlyCollection SplinePoints + { + get + { + if (this.splinePoints == null) + { + List sPoints = RouteSpline(this.Points); + splinePoints = sPoints.AsReadOnly(); + } + return this.splinePoints; + } + } + + private static double tBend = 0.4; + + private List RouteSpline(List anchorPoints) + { + var result = new List(); + if (anchorPoints.Count == 0) + return new List(); + Point2D point1 = anchorPoints[0]; + result.Add(point1); + for (int i = 2; i < anchorPoints.Count; i++) { + var point2 = anchorPoints[i - 1]; + var point3 = anchorPoints[i]; + var anchor1 = GeomUtils.Interpolate(point1, point2, 1 - tBend); + var anchor2 = GeomUtils.Interpolate(point2, point3, tBend); + // straight segment + result.Add(anchor1); // guide point1 -> anchor1 + result.Add(point1); + result.Add(anchor1); // guide anchor1 -> point2 + // bend + result.Add(point2); // guide anchor1 -> point2 (more carved ?) + result.Add(point2); + result.Add(anchor2); // guide point2 -> anchor2 + point1 = anchor2; + } + // last straight segment + var lastPoint = anchorPoints[anchorPoints.Count - 1]; + result.Add(lastPoint); + result.Add(point1); + result.Add(lastPoint); + return result; + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/ShortestPath/AStarShortestPathFinder.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/ShortestPath/AStarShortestPathFinder.cs new file mode 100644 index 0000000000..cf4de51406 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/SplineRouting/ShortestPath/AStarShortestPathFinder.cs @@ -0,0 +1,69 @@ +// +// +// +// +// $Revision$ +// +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Debugger.AddIn.Visualizers.Graph.SplineRouting +{ + /// + /// Description of AStarShortestPathFinder. + /// + public class AStarShortestPathFinder + { + RouteGraph graph; + + public AStarShortestPathFinder(RouteGraph routeGraph) + { + this.graph = routeGraph; + } + + public List FindShortestPath(RouteVertex start, RouteVertex end) + { + start.IsEdgeEndpoint = false; + end.IsEdgeEndpoint = false; + foreach (var vertex in this.graph.Vertices) { + vertex.Reset(); + } + start.Distance = 0; + bool reached = false; + while (!reached) + { + RouteVertex minVertex = null; + foreach (var minCandidate in graph.Vertices.Where(v => !v.IsPermanent && v.IsAvailable)) { + if (minVertex == null || minCandidate.Distance < minVertex.Distance) { + minVertex = minCandidate; + } + } + minVertex.IsPermanent = true; + if (minVertex == end) { + reached = true; + } + foreach (var edge in minVertex.Neighbors) { + if (minVertex.Distance + edge.Length < edge.EndVertex.Distance) { + edge.EndVertex.Distance = minVertex.Distance + edge.Length; + edge.EndVertex.Predecessor = minVertex; + } + } + } + List path = new List(); + RouteVertex pathVertex = end; + while (pathVertex != start) { + if (pathVertex == null) + break; + path.Add(pathVertex); + //pathVertex.IsUsed = true; + pathVertex = pathVertex.Predecessor; + } + path.Add(start); + start.IsEdgeEndpoint = true; + end.IsEdgeEndpoint = true; + path.Reverse(); + return path; + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/Tree/NeatoEdgeRouter.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/Tree/NeatoEdgeRouter.cs index af03e023da..7482282a08 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/Tree/NeatoEdgeRouter.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/Tree/NeatoEdgeRouter.cs @@ -33,14 +33,11 @@ namespace Debugger.AddIn.Visualizers.Graph.Layout { DotFormatter dotFormatter = new BoxDotFormatter(graphWithNodesPositioned); - // start Neato.exe - NeatoProcess neatoProcess = NeatoProcess.Start(); - // convert PosGraph to .dot string string dotGraphString = dotFormatter.OutputGraphInDotFormat(); - // pass to neato.exe - string dotGraphStringWithPositions = neatoProcess.CalculatePositions(dotGraphString); + // pass to neato.exe and wait for output + string dotGraphStringWithPositions = NeatoProcess.Start().CalculatePositions(dotGraphString); // parse edge positions from neato's plain output format PositionedGraph result = dotFormatter.ParseEdgePositions(dotGraphStringWithPositions); diff --git a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/Tree/TreeLayouter.cs b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/Tree/TreeLayouter.cs index 79d705b6e7..838fee2528 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/Tree/TreeLayouter.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Visualizers/Graph/Layout/Tree/TreeLayouter.cs @@ -51,8 +51,9 @@ namespace Debugger.AddIn.Visualizers.Graph.Layout // second layout pass calculateNodePosRecursive((TreeGraphNode)resultGraph.Root, 0, 0); - var neatoRouter = new NeatoEdgeRouter(); - resultGraph = neatoRouter.CalculateEdges(resultGraph); + //var neatoRouter = new NeatoEdgeRouter(); + //resultGraph = neatoRouter.CalculateEdges(resultGraph); + resultGraph = new GraphEdgeRouter().RouteEdges(resultGraph); return resultGraph; }