diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs index e8083bdbd..72463d688 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs @@ -298,7 +298,10 @@ internal static partial class ElkEdgeRouterIterative { var graphMinYLocal = nodes.Min(n => n.Y); var graphWidthLocal = nodes.Max(n => n.X + n.Width) - nodes.Min(n => n.X); - var baseCorridorY = graphMinYLocal - 56d; + var nodesById = nodes.ToDictionary(n => n.Id, StringComparer.Ordinal); + // Keep corridor close to graph for visual readability. 24px is + // enough clearance for the perpendicular exit stub. + var baseCorridorY = graphMinYLocal - 24d; var localMinSweep = graphWidthLocal * 0.4d; var corridorResult = current.Edges.ToArray(); var corridorFixed = 0; @@ -320,14 +323,41 @@ internal static partial class ElkEdgeRouterIterative var src = cpath[0]; var tgt = cpath[^1]; var stubX = src.X + 24d; - var newPath = new List + + // Find the target node to determine approach geometry. + var tgtNode = nodesById.TryGetValue(edge.TargetNodeId ?? string.Empty, out var tn) ? tn : null; + List newPath; + if (tgtNode is not null && tgtNode.Kind is "End") { - src, - new() { X = stubX, Y = src.Y }, - new() { X = stubX, Y = localCorridorY }, - new() { X = tgt.X, Y = localCorridorY }, - tgt, - }; + // Enter End from the right side: corridor goes past End, + // descends to End's center Y, approaches from right. + // This avoids the ugly long vertical drop from corridor. + // Offset both X and Y for each corridor edge so their + // vertical descent legs don't overlap (parallel vertical + // segments at the same X trigger target-join detection). + var rightApproachX = tgtNode.X + tgtNode.Width + 24d + (corridorFixed * (nodeSizeClearance + 4d)); + var centerY = tgtNode.Y + (tgtNode.Height / 2d); + newPath = + [ + src, + new() { X = stubX, Y = src.Y }, + new() { X = stubX, Y = localCorridorY }, + new() { X = rightApproachX, Y = localCorridorY }, + new() { X = rightApproachX, Y = centerY }, + new() { X = tgtNode.X + tgtNode.Width, Y = centerY }, + ]; + } + else + { + newPath = + [ + src, + new() { X = stubX, Y = src.Y }, + new() { X = stubX, Y = localCorridorY }, + new() { X = tgt.X, Y = localCorridorY }, + tgt, + ]; + } corridorResult[ei] = new ElkRoutedEdge { Id = edge.Id, SourceNodeId = edge.SourceNodeId, TargetNodeId = edge.TargetNodeId,