Route long sweeps through top corridor unconditionally

Long horizontal sweeps (>40% graph width) now always route through
the top corridor instead of cutting through the node field. Each
successive corridor edge gets a 24px Y offset to prevent convergence.

Remaining: target-join at End/top (two corridor routes converge on
descent) and edge/9 flush under-node.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-01 23:15:18 +03:00
parent 3a95165221
commit f2dc84a790
2 changed files with 111 additions and 73 deletions

View File

@@ -331,6 +331,65 @@ internal static partial class ElkEdgeRouterIterative
current = RepairRemainingEdgeNodeCrossings(current, nodes);
}
// Unconditional corridor reroute for long sweeps through the node field.
// The score-gated corridor reroute earlier may reject these because the
// corridor candidate scores worse (more bends, longer paths). But visually,
// long horizontal highways through the graph are ugly. Apply unconditionally.
{
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 localMinSweep = graphWidthLocal * 0.4d;
var corridorResult = current.Edges.ToArray();
var corridorFixed = 0;
for (var ei = 0; ei < corridorResult.Length; ei++)
{
var edge = corridorResult[ei];
var path = ExtractPath(edge);
for (var si = 0; si < path.Count - 1; si++)
{
if (Math.Abs(path[si].Y - path[si + 1].Y) > 2d) continue;
var segLen = Math.Abs(path[si + 1].X - path[si].X);
var laneY = path[si].Y;
if (segLen < localMinSweep || laneY <= graphMinYLocal - 10d) continue;
// Route through top corridor. Offset each successive edge
// so multiple corridor routes don't converge at the same Y.
var localCorridorY = baseCorridorY - (corridorFixed * 24d);
var src = path[0];
var tgt = path[^1];
var stubX = src.X + 24d;
var newPath = new List<ElkPoint>
{
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,
SourcePortId = edge.SourcePortId, TargetPortId = edge.TargetPortId,
Kind = edge.Kind, Label = edge.Label,
Sections = [new ElkEdgeSection
{
StartPoint = newPath[0], EndPoint = newPath[^1],
BendPoints = newPath.Skip(1).Take(newPath.Count - 2).ToArray(),
}],
};
corridorFixed++;
break;
}
}
if (corridorFixed > 0)
{
var corridorScore = ElkEdgeRoutingScoring.ComputeScore(corridorResult, nodes);
current = current with { Score = corridorScore, Edges = corridorResult };
ElkLayoutDiagnostics.LogProgress(
$"Unconditional corridor reroute: {corridorFixed} edges to corridor");
}
}
return current;
}