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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user