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