diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkSharpLayoutInitialPlacement.cs b/src/__Libraries/StellaOps.ElkSharp/ElkSharpLayoutInitialPlacement.cs index e807f2d1d..2796ca5d8 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkSharpLayoutInitialPlacement.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkSharpLayoutInitialPlacement.cs @@ -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) {