diff --git a/GraphLayout/MSAGL/Core/Routing/EdgeRoutingSettings.cs b/GraphLayout/MSAGL/Core/Routing/EdgeRoutingSettings.cs index 31460e9c..31e48bb3 100644 --- a/GraphLayout/MSAGL/Core/Routing/EdgeRoutingSettings.cs +++ b/GraphLayout/MSAGL/Core/Routing/EdgeRoutingSettings.cs @@ -61,7 +61,10 @@ public double PolylinePadding { /// For rectilinear, the penalty for a bend, as a percentage of the Manhattan distance between the source and target ports. /// public double BendPenalty { get; set; } - + /// + /// minimal distance between two edges in rectilinear case + /// + public double EdgeSeparationRectilinear = 0; /// ///the settings for general edge bundling /// diff --git a/GraphLayout/MSAGL/Layout/Layered/LayeredLayoutEngine.cs b/GraphLayout/MSAGL/Layout/Layered/LayeredLayoutEngine.cs index 335e2e4c..0bb3143e 100644 --- a/GraphLayout/MSAGL/Layout/Layered/LayeredLayoutEngine.cs +++ b/GraphLayout/MSAGL/Layout/Layered/LayeredLayoutEngine.cs @@ -305,7 +305,7 @@ void RunPostLayering() { //todo: are these good values? var rer = new RectilinearEdgeRouter(originalGraph, sugiyamaSettings.NodeSeparation/3, sugiyamaSettings.NodeSeparation/4, - true); + true, sugiyamaSettings.EdgeRoutingSettings.EdgeSeparationRectilinear); rer.RouteToCenterOfObstacles = routingSettings.EdgeRoutingMode == EdgeRoutingMode.RectilinearToCenter; rer.BendPenaltyAsAPercentageOfDistance = routingSettings.BendPenalty; diff --git a/GraphLayout/MSAGL/Miscellaneous/LayoutHelpers.cs b/GraphLayout/MSAGL/Miscellaneous/LayoutHelpers.cs index 68e1fa08..e483fae6 100644 --- a/GraphLayout/MSAGL/Miscellaneous/LayoutHelpers.cs +++ b/GraphLayout/MSAGL/Miscellaneous/LayoutHelpers.cs @@ -208,6 +208,7 @@ public static void RouteAndLabelEdges(GeometryGraph geometryGraph, LayoutAlgorit var mode = (straighLineRoutingThreshold == 0 || geometryGraph.Nodes.Count < straighLineRoutingThreshold) ? ers.EdgeRoutingMode : EdgeRoutingMode.StraightLine; if (mode == EdgeRoutingMode.Rectilinear || mode == EdgeRoutingMode.RectilinearToCenter) { + double edgeSepar = layoutSettings.EdgeRoutingSettings == null ? 0 : layoutSettings.EdgeRoutingSettings.EdgeSeparationRectilinear; RectilinearInteractiveEditor.CreatePortsAndRouteEdges( ers.CornerRadius, layoutSettings.NodeSeparation / 3, @@ -215,7 +216,7 @@ public static void RouteAndLabelEdges(GeometryGraph geometryGraph, LayoutAlgorit edgesToRoute, mode, true, - ers.BendPenalty, cancelToken); + ers.BendPenalty, edgeSepar, cancelToken); } else if (mode == EdgeRoutingMode.Spline || mode == EdgeRoutingMode.SugiyamaSplines) { new SplineRouter(geometryGraph, filteredEdgesToRoute, ers.Padding, ers.PolylinePadding, ers.ConeAngle, null) { diff --git a/GraphLayout/MSAGL/Routing/Rectilinear/Nudging/Nudger.cs b/GraphLayout/MSAGL/Routing/Rectilinear/Nudging/Nudger.cs index b4f59671..92eb539a 100644 --- a/GraphLayout/MSAGL/Routing/Rectilinear/Nudging/Nudger.cs +++ b/GraphLayout/MSAGL/Routing/Rectilinear/Nudging/Nudger.cs @@ -38,17 +38,17 @@ bool HasGroups { /// "nudge" paths to decrease the number of intersections and stores the results inside WidePaths of "paths" /// /// paths through the graph - /// two parallel paths should be separated by this distance if it is feasible + /// two parallel paths should be separated by this distance if it is feasible /// polygonal convex obstacles organized in a tree; the obstacles here are padded original obstacles /// /// - internal Nudger(IEnumerable paths, double cornerFitRad, IEnumerable obstacles, + internal Nudger(IEnumerable paths, double edgeSeparation, IEnumerable obstacles, Dictionary> ancestorsSets) { AncestorsSets = ancestorsSets; HierarchyOfGroups = RectangleNode.CreateRectangleNodeOnEnumeration( ancestorsSets.Keys.Where(shape => shape.IsGroup).Select(group => new RectangleNode(group, group.BoundingBox))); Obstacles = obstacles; - EdgeSeparation = Math.Max(2 * cornerFitRad, MinimalEdgeSeparation); + EdgeSeparation = Math.Max(2 * edgeSeparation, MinimalEdgeSeparation); Paths = new List(paths); HierarchyOfObstacles = RectangleNode.CreateRectangleNodeOnEnumeration( @@ -885,15 +885,15 @@ static IEnumerable RemoveSwitchbacksAndMiddlePoints(IEnumerable po /// this function defines the final path coordinates /// /// the set of paths, point sequences - /// the radius of the arc inscribed into the path corners + /// the radius of the arc inscribed into the path corners /// an enumeration of padded obstacles /// /// /// the mapping of the path to its modified path - internal static void NudgePaths(IEnumerable paths, double cornerFitRadius, IEnumerable paddedObstacles, Dictionary> ancestorsSets, bool removeStaircases) { + internal static void NudgePaths(IEnumerable paths, double edgeSeparation, IEnumerable paddedObstacles, Dictionary> ancestorsSets, bool removeStaircases) { if (!paths.Any()) return; - var nudger = new Nudger(paths, cornerFitRadius, paddedObstacles, ancestorsSets); + var nudger = new Nudger(paths, edgeSeparation, paddedObstacles, ancestorsSets); nudger.Calculate(Direction.North, true); nudger.Calculate(Direction.East, false); nudger.Calculate(Direction.North, false); diff --git a/GraphLayout/MSAGL/Routing/Rectilinear/RectilinearEdgeRouter.cs b/GraphLayout/MSAGL/Routing/Rectilinear/RectilinearEdgeRouter.cs index 70a572f4..ec705079 100644 --- a/GraphLayout/MSAGL/Routing/Rectilinear/RectilinearEdgeRouter.cs +++ b/GraphLayout/MSAGL/Routing/Rectilinear/RectilinearEdgeRouter.cs @@ -317,10 +317,10 @@ public RectilinearEdgeRouter() /// The collection of shapes to route around. Contains all source and target shapes /// as well as any intervening obstacles. public RectilinearEdgeRouter(IEnumerable obstacles) - : this(obstacles, DefaultPadding, DefaultCornerFitRadius, useSparseVisibilityGraph: false) { + : this(obstacles, DefaultPadding, DefaultCornerFitRadius, useSparseVisibilityGraph: false, 0) { } - + /// /// Constructor specifying graph and shape information. /// @@ -330,9 +330,10 @@ public RectilinearEdgeRouter(IEnumerable obstacles) /// The radius of the arc inscribed into path corners /// If true, use a sparse visibility graph, which saves memory for large graphs /// but may select suboptimal paths - /// Use obstacle bounding boxes in visibility graph + /// the minimum distance between parallen nodes public RectilinearEdgeRouter(IEnumerable obstacles, double padding, double cornerFitRadius, - bool useSparseVisibilityGraph) { + bool useSparseVisibilityGraph, double minEdgeSeparation) { + EdgeSeparation = minEdgeSeparation; Padding = padding; CornerFitRadius = cornerFitRadius; BendPenaltyAsAPercentageOfDistance = SsstRectilinearPath.DefaultBendPenaltyAsAPercentageOfDistance; @@ -346,7 +347,7 @@ public RectilinearEdgeRouter(IEnumerable obstacles, double padding, doubl AddShapes(obstacles); } - + /// /// Constructor specifying graph information. /// @@ -354,11 +355,12 @@ public RectilinearEdgeRouter(IEnumerable obstacles, double padding, doubl /// The minimum padding from an obstacle's curve to its enclosing polyline. /// The radius of the arc inscribed into path corners /// If true, use a sparse visibility graph, which saves memory for large graphs + /// If true, use a sparse visibility graph, which saves memory for large graphs /// but may select suboptimal paths /// If true, use obstacle bounding boxes in visibility graph public RectilinearEdgeRouter(GeometryGraph graph, double padding, double cornerFitRadius, - bool useSparseVisibilityGraph) - : this(ShapeCreator.GetShapes(graph), padding, cornerFitRadius, useSparseVisibilityGraph) { + bool useSparseVisibilityGraph, double minEdgeSeparation) + : this(ShapeCreator.GetShapes(graph), padding, cornerFitRadius, useSparseVisibilityGraph, minEdgeSeparation) { ValidateArg.IsNotNull(graph, "graph"); foreach (var edge in graph.Edges) { this.AddEdgeGeometryToRoute(edge.EdgeGeometry); @@ -518,12 +520,14 @@ internal virtual void NudgePaths(IEnumerable edgePaths) { // Using VisibilityPolyline retains any reflection/staircases on the convex hull borders; using // PaddedPolyline removes them. - Nudger.NudgePaths(edgePaths, CornerFitRadius, PaddedObstacles, ancestorSets, RemoveStaircases); + Nudger.NudgePaths(edgePaths, EdgeSeparation, PaddedObstacles, ancestorSets, RemoveStaircases); //Nudger.NudgePaths(edgePaths, CornerFitRadius, this.ObstacleTree.GetAllPrimaryObstacles().Select(obs => obs.VisibilityPolyline), ancestorSets, RemoveStaircases); } + private bool removeStaircases = true; private double bendPenaltyAsAPercentageOfDistance; + private double edgeSeparation; readonly List selfEdges = new List(); /// @@ -561,6 +565,10 @@ private static void CalculateArrowheads(EdgeGeometry edgeGeom) { private ObstacleTree ObsTree { get { return this.GraphGenerator.ObsTree; } } + /// + /// the minimal distance between to parallel edges + /// + public double EdgeSeparation { get => Math.Max(edgeSeparation, CornerFitRadius); set => edgeSeparation = value; } private void GenerateObstacleTree() { if ((Obstacles == null) || !Obstacles.Any()) { diff --git a/GraphLayout/MSAGL/Routing/Rectilinear/RectilinearInteractiveEditor.cs b/GraphLayout/MSAGL/Routing/Rectilinear/RectilinearInteractiveEditor.cs index ecf65902..038fdcb2 100644 --- a/GraphLayout/MSAGL/Routing/Rectilinear/RectilinearInteractiveEditor.cs +++ b/GraphLayout/MSAGL/Routing/Rectilinear/RectilinearInteractiveEditor.cs @@ -28,9 +28,9 @@ public static class RectilinearInteractiveEditor { static public void CreatePortsAndRouteEdges(double cornerFitRadius, double padding , IEnumerable obstacleNodes, IEnumerable geometryEdges , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph - , double bendPenaltyAsAPercentageOfDistance, CancelToken ct = null) { + , double bendPenaltyAsAPercentageOfDistance, double minEdgeSeparation, CancelToken ct = null) { var r = FillRouter(cornerFitRadius, padding, obstacleNodes, geometryEdges, edgeRoutingMode, useSparseVisibilityGraph - , bendPenaltyAsAPercentageOfDistance); + , bendPenaltyAsAPercentageOfDistance, minEdgeSeparation); r.Run(ct); CreateSelfEdges(geometryEdges.Where(e => e.SourcePort.Location == e.TargetPort.Location), cornerFitRadius); } @@ -49,9 +49,9 @@ static public void CreatePortsAndRouteEdges(double cornerFitRadius, double paddi static public void CreatePortsAndRouteEdges(double cornerFitRadius, double padding , IEnumerable obstacleNodes, IEnumerable geometryEdges , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph - , bool useObstacleRectangles) { + , bool useObstacleRectangles, double minEdgeSeparation) { CreatePortsAndRouteEdges(cornerFitRadius, padding, obstacleNodes, geometryEdges, edgeRoutingMode - , useSparseVisibilityGraph, SsstRectilinearPath.DefaultBendPenaltyAsAPercentageOfDistance); + , useSparseVisibilityGraph, SsstRectilinearPath.DefaultBendPenaltyAsAPercentageOfDistance, minEdgeSeparation); } /// @@ -66,10 +66,10 @@ static public void CreatePortsAndRouteEdges(double cornerFitRadius, double paddi /// Use a more memory-efficient but possibly path-suboptimal visibility graph static public void CreatePortsAndRouteEdges(double cornerFitRadius, double padding , IEnumerable obstacleNodes, IEnumerable geometryEdges - , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph) + , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph, double minEdgeSeparation) { CreatePortsAndRouteEdges(cornerFitRadius, padding, obstacleNodes, geometryEdges, edgeRoutingMode - , useSparseVisibilityGraph, SsstRectilinearPath.DefaultBendPenaltyAsAPercentageOfDistance); + , useSparseVisibilityGraph, SsstRectilinearPath.DefaultBendPenaltyAsAPercentageOfDistance, minEdgeSeparation); } /// @@ -77,13 +77,13 @@ static public void CreatePortsAndRouteEdges(double cornerFitRadius, double paddi /// /// The populated RectilinearEdgeRouter static RectilinearEdgeRouter FillRouter(double cornerFitRadius, double padding, IEnumerable obstacleNodes, IEnumerable geomEdges - , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph, double bendPenaltyAsAPercentageOfDistance) { + , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph, double bendPenaltyAsAPercentageOfDistance, double minEdgeSeparation) { Debug.Assert((EdgeRoutingMode.Rectilinear == edgeRoutingMode) || (EdgeRoutingMode.RectilinearToCenter == edgeRoutingMode) , "Non-rectilinear edgeRoutingMode"); var nodeShapesMap = new Dictionary(); FillNodeShapesMap(obstacleNodes, geomEdges, nodeShapesMap); - var router = new RectilinearEdgeRouter(nodeShapesMap.Values, padding, cornerFitRadius, useSparseVisibilityGraph) { + var router = new RectilinearEdgeRouter(nodeShapesMap.Values, padding, cornerFitRadius, useSparseVisibilityGraph, minEdgeSeparation) { RouteToCenterOfObstacles = (edgeRoutingMode == EdgeRoutingMode.RectilinearToCenter), BendPenaltyAsAPercentageOfDistance = bendPenaltyAsAPercentageOfDistance }; diff --git a/GraphLayout/Samples/EdgeRoutingSample/Program.cs b/GraphLayout/Samples/EdgeRoutingSample/Program.cs index 3f5ce6fd..301409d8 100644 --- a/GraphLayout/Samples/EdgeRoutingSample/Program.cs +++ b/GraphLayout/Samples/EdgeRoutingSample/Program.cs @@ -52,7 +52,7 @@ static void DemoEdgeRouterHelper(GeometryGraph graph) { LayoutAlgorithmSettings.ShowGraph(graph); #endif - var rectRouter = new RectilinearEdgeRouter(graph, 3,3, true); + var rectRouter = new RectilinearEdgeRouter(graph, 3,3, true, 5); rectRouter.Run(); #if TEST LayoutAlgorithmSettings.ShowGraph(graph); diff --git a/GraphLayout/Test/MSAGLTests/Infrastructure/Rectilinear/RectilinearEdgeRouterWrapper.cs b/GraphLayout/Test/MSAGLTests/Infrastructure/Rectilinear/RectilinearEdgeRouterWrapper.cs index 1b71f288..09c23c7f 100644 --- a/GraphLayout/Test/MSAGLTests/Infrastructure/Rectilinear/RectilinearEdgeRouterWrapper.cs +++ b/GraphLayout/Test/MSAGLTests/Infrastructure/Rectilinear/RectilinearEdgeRouterWrapper.cs @@ -59,7 +59,7 @@ internal class RectilinearEdgeRouterWrapper : RectilinearEdgeRouter internal RectilinearEdgeRouterWrapper(IEnumerable obstacles, double padding, double cornerFitRadius, bool routeToCenterOfObstacles, bool useSparseVisibilityGraph) - : base(obstacles, padding, cornerFitRadius, useSparseVisibilityGraph) + : base(obstacles, padding, cornerFitRadius, useSparseVisibilityGraph, cornerFitRadius) { this.WantPaths = true; this.WantNudger = true; diff --git a/GraphLayout/Test/MSAGLTests/SplineRouterTests.cs b/GraphLayout/Test/MSAGLTests/SplineRouterTests.cs index f04526dd..d08702e8 100644 --- a/GraphLayout/Test/MSAGLTests/SplineRouterTests.cs +++ b/GraphLayout/Test/MSAGLTests/SplineRouterTests.cs @@ -456,7 +456,7 @@ public void SimpleClusterGraphRectilinear() InitialLayout initialLayout = new InitialLayout(g, new FastIncrementalLayoutSettings() { AvoidOverlaps = true }); initialLayout.Run(); - RectilinearEdgeRouter router = new RectilinearEdgeRouter(g, 1, 1, false); + RectilinearEdgeRouter router = new RectilinearEdgeRouter(g, 1, 1, false, 1); router.Run(); EnableDebugViewer(); ShowGraphInDebugViewer(g); diff --git a/GraphLayout/Test/TestForGDI/test.cs b/GraphLayout/Test/TestForGDI/test.cs index a730b601..11f7460c 100644 --- a/GraphLayout/Test/TestForGDI/test.cs +++ b/GraphLayout/Test/TestForGDI/test.cs @@ -732,7 +732,7 @@ static void GroupRoutingTestRect() { var sugiyamaSettings = (SugiyamaLayoutSettings) settings; var router = new RectilinearEdgeRouter(graph, sugiyamaSettings.NodeSeparation/6, sugiyamaSettings.NodeSeparation/6, - true) + true, 0) { BendPenaltyAsAPercentageOfDistance = sugiyamaSettings.EdgeRoutingSettings.BendPenalty }; @@ -914,7 +914,7 @@ static void RouteRectEdgesOfGeomGraph(EdgeRoutingMode edgeRoutingMode, bool useS } var padding = (settings == null) ? 3 : settings.NodeSeparation / 3; - var router = new RectilinearEdgeRouter(nodeShapeMap.Values, padding, 3, useSparseVisibilityGraph) + var router = new RectilinearEdgeRouter(nodeShapeMap.Values, padding, 3, useSparseVisibilityGraph, 0) { RouteToCenterOfObstacles = edgeRoutingMode == EdgeRoutingMode.RectilinearToCenter, BendPenaltyAsAPercentageOfDistance = bendPenalty @@ -980,7 +980,7 @@ static void RectilinearTestOnGeomGraph(EdgeRoutingMode edgeRoutingMode, bool use } var router = new RectilinearEdgeRouter(nodeShapeMap.Values, RectilinearEdgeRouter.DefaultPadding, - RectilinearEdgeRouter.DefaultCornerFitRadius, useSparseVisibilityGraph) { + RectilinearEdgeRouter.DefaultCornerFitRadius, useSparseVisibilityGraph, 0) { RouteToCenterOfObstacles = edgeRoutingMode == EdgeRoutingMode.RectilinearToCenter, BendPenaltyAsAPercentageOfDistance = bendPenalty };