Enforce routing-clearance Y-gaps in Sugiyama placement
After all placement refinement passes converge, pushes connected nodes apart where the Y-gap between source bottom and target top is under 12px. This prevents the Sugiyama median-based optimization from creating routing corridors too narrow for clean orthogonal edge routing. The fix runs as a final one-shot pass in PlaceNodesLeftToRight — no cascade propagation, just individual node nudges. This eliminates the edge/15 under-node violation (source-target gap was 5.4px, now 12px) and improves the overall routing score from -785401 to -684447. Remaining violations (7): target-joins=1, backtracking=3, shared-lanes=1, under-node=2. These involve cross-graph routing patterns (long horizontal sweeps, identical-Y source convergence) that require either layout-level changes to the Sugiyama ordering or multi-wave A* re-routing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,7 @@ internal static class ElkSharpLayoutInitialPlacement
|
||||
{
|
||||
desiredY[nodeIndex] = nodeIndex * (slotHeight + gridNodeSpacing);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (var nodeIndex = 1; nodeIndex < layer.Length; nodeIndex++)
|
||||
@@ -118,6 +119,38 @@ internal static class ElkSharpLayoutInitialPlacement
|
||||
ElkNodePlacementAlignment.PropagateSuccessorPositionBackward(
|
||||
positionedNodes, outgoingNodeIds, nodesById, options.Direction);
|
||||
|
||||
// Final routing-clearance enforcement: after all refinement converged,
|
||||
// push connected nodes apart where the Y-gap is too tight for clean
|
||||
// edge routing (< 12px). This is a one-shot nudge — no cascade.
|
||||
if (options.Direction == ElkLayoutDirection.LeftToRight)
|
||||
{
|
||||
const double minRoutingClearance = 12d;
|
||||
foreach (var nodeId in positionedNodes.Keys.ToArray())
|
||||
{
|
||||
if (!positionedNodes.TryGetValue(nodeId, out var srcNode)
|
||||
|| !outgoingNodeIds.TryGetValue(nodeId, out var outgoing))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var tgtId in outgoing)
|
||||
{
|
||||
if (!positionedNodes.TryGetValue(tgtId, out var tgtNode)
|
||||
|| !augmentedNodesById.TryGetValue(tgtId, out var tgtSpec))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var srcBottom = srcNode.Y + srcNode.Height;
|
||||
if (tgtNode.Y >= srcBottom && tgtNode.Y - srcBottom < minRoutingClearance)
|
||||
{
|
||||
positionedNodes[tgtId] = ElkLayoutHelpers.CreatePositionedNode(
|
||||
tgtSpec, tgtNode.X, srcBottom + minRoutingClearance, options.Direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
minNodeY = positionedNodes.Values.Min(n => n.Y);
|
||||
if (minNodeY < -0.01d)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user