ElkSharp edge routing: boundary slots, gateway repairs, corridor spacing

Major edge routing improvements including corridor spacing, crossing reduction,
focused gateway boundary repairs, setter families, and advanced restabilization.
Adds workflow renderer tests for document-processing and artifact inspection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-06 08:52:02 +03:00
parent e3e87942c7
commit 5d6435fdb2
41 changed files with 8334 additions and 246 deletions

View File

@@ -14,6 +14,7 @@ internal static partial class ElkEdgeRouterIterative
int maxRounds = 3)
{
var current = solution;
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
for (var round = 0; round < maxRounds; round++)
{
@@ -30,7 +31,10 @@ internal static partial class ElkEdgeRouterIterative
.Take(MaxWinnerPolishBatchedRootEdges)
.Select(pair => pair.Key)
.ToArray();
var batchedFocusEdgeIds = ExpandWinningSolutionFocus(current.Edges, batchedRootEdgeIds).ToArray();
var batchedFocusEdgeIds = ResolveBoundarySlotRepairFocus(
current.Edges,
nodesById,
batchedRootEdgeIds);
if (batchedFocusEdgeIds.Length > 0)
{
var batchedCandidateEdges = BuildFinalBoundarySlotCandidate(
@@ -53,7 +57,10 @@ internal static partial class ElkEdgeRouterIterative
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
.Select(pair => pair.Key))
{
var focusEdgeIds = ExpandWinningSolutionFocus(current.Edges, [edgeId]).ToArray();
var focusEdgeIds = ResolveBoundarySlotRepairFocus(
current.Edges,
nodesById,
[edgeId]);
if (focusEdgeIds.Length == 0)
{
continue;
@@ -86,6 +93,76 @@ internal static partial class ElkEdgeRouterIterative
return current;
}
private static string[] ResolveBoundarySlotRepairFocus(
IReadOnlyCollection<ElkRoutedEdge> edges,
IReadOnlyDictionary<string, ElkPositionedNode> nodesById,
IReadOnlyCollection<string> rootEdgeIds)
{
if (rootEdgeIds.Count == 0)
{
return [];
}
if (TryResolveGatewayBoundarySlotLocalFocus(edges, nodesById, rootEdgeIds, out var localFocus))
{
return localFocus;
}
return ExpandWinningSolutionFocus(edges, rootEdgeIds).ToArray();
}
private static bool TryResolveGatewayBoundarySlotLocalFocus(
IReadOnlyCollection<ElkRoutedEdge> edges,
IReadOnlyDictionary<string, ElkPositionedNode> nodesById,
IReadOnlyCollection<string> rootEdgeIds,
out string[] focusEdgeIds)
{
focusEdgeIds = [];
if (rootEdgeIds.Count == 0)
{
return false;
}
var edgesById = edges.ToDictionary(edge => edge.Id, StringComparer.Ordinal);
var localFocus = new HashSet<string>(StringComparer.Ordinal);
foreach (var edgeId in rootEdgeIds)
{
if (!edgesById.TryGetValue(edgeId, out var edge))
{
return false;
}
var touchesGateway = false;
if (!string.IsNullOrWhiteSpace(edge.SourceNodeId)
&& nodesById.TryGetValue(edge.SourceNodeId, out var sourceNode)
&& ElkShapeBoundaries.IsGatewayShape(sourceNode))
{
touchesGateway = true;
}
if (!touchesGateway
&& !string.IsNullOrWhiteSpace(edge.TargetNodeId)
&& nodesById.TryGetValue(edge.TargetNodeId, out var targetNode)
&& ElkShapeBoundaries.IsGatewayShape(targetNode))
{
touchesGateway = true;
}
if (!touchesGateway)
{
return false;
}
localFocus.Add(edgeId);
}
focusEdgeIds = localFocus
.OrderBy(edgeId => edgeId, StringComparer.Ordinal)
.ToArray();
return focusEdgeIds.Length > 0;
}
private static CandidateSolution ApplyFinalPostSlotHardRulePolish(
CandidateSolution solution,
ElkPositionedNode[] nodes,