Move corridor reroute before final target-join spread
Long sweeps are corridored before the final target-join check so the spread can handle corridor approach convergences. The edge/20+edge/23 convergence at End/top still needs investigation — the spread doesn't detect it (likely End node face slot gap vs approach gap mismatch). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -292,9 +292,64 @@ internal static partial class ElkEdgeRouterIterative
|
|||||||
current = RepairRemainingEdgeNodeCrossings(current, nodes);
|
current = RepairRemainingEdgeNodeCrossings(current, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final target-join repair: the per-edge gateway fixes may create
|
// Unconditional corridor reroute: move long sweeps to top corridor
|
||||||
// new target-join convergences that didn't exist before. Run the
|
// BEFORE the final target-join check so the join detection sees the
|
||||||
// spread one more time to catch them.
|
// corridored paths and can spread the corridor approach stubs.
|
||||||
|
{
|
||||||
|
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 cpath = ExtractPath(edge);
|
||||||
|
for (var si = 0; si < cpath.Count - 1; si++)
|
||||||
|
{
|
||||||
|
if (Math.Abs(cpath[si].Y - cpath[si + 1].Y) > 2d) continue;
|
||||||
|
var segLen = Math.Abs(cpath[si + 1].X - cpath[si].X);
|
||||||
|
var laneY = cpath[si].Y;
|
||||||
|
if (segLen < localMinSweep || laneY <= graphMinYLocal - 10d) continue;
|
||||||
|
var localCorridorY = baseCorridorY - (corridorFixed * 24d);
|
||||||
|
var src = cpath[0];
|
||||||
|
var tgt = cpath[^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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final target-join repair: the per-edge gateway fixes and corridor
|
||||||
|
// reroute may create new target-join convergences.
|
||||||
var finalJoinSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
var finalJoinSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||||
var finalJoinCount = ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(current.Edges, nodes, finalJoinSeverity, 1);
|
var finalJoinCount = ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(current.Edges, nodes, finalJoinSeverity, 1);
|
||||||
ElkLayoutDiagnostics.LogProgress(
|
ElkLayoutDiagnostics.LogProgress(
|
||||||
@@ -331,65 +386,6 @@ internal static partial class ElkEdgeRouterIterative
|
|||||||
current = RepairRemainingEdgeNodeCrossings(current, nodes);
|
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;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user