Collapse short doglegs: routing-level (gated) + rendering-level (30px)
Routing: CollapseShortDoglegs processes one dogleg at a time, accepts only if no entry-angle/node-crossing/shared-lane regressions. Rendering: jog filter increased to 30px to catch 19px+24px doglegs that the routing can't collapse without violations. The filter snaps the next point's axis to prevent diagonals. Sharp corners (r=0) for tight doglegs where both segments < 30px. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2052,7 +2052,7 @@ public sealed class WorkflowRenderSvgRenderer
|
||||
var dxIn = Math.Abs(curr.X - prev.X);
|
||||
var dyIn = Math.Abs(curr.Y - prev.Y);
|
||||
var segLen = dxIn + dyIn;
|
||||
if (segLen < 24d && i < mutablePoints.Count - 1)
|
||||
if (segLen < 30d && i < mutablePoints.Count - 1)
|
||||
{
|
||||
var next = mutablePoints[i + 1];
|
||||
if (dxIn < dyIn)
|
||||
|
||||
@@ -629,6 +629,46 @@ internal static partial class ElkEdgePostProcessor
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collapses the FIRST short dogleg found across all edges.
|
||||
/// Returns the modified array or the original if none found.
|
||||
/// Call repeatedly to collapse one dogleg at a time.
|
||||
/// </summary>
|
||||
internal static ElkRoutedEdge[] CollapseShortDoglegs(ElkRoutedEdge[] edges, ElkPositionedNode[] nodes)
|
||||
{
|
||||
if (edges.Length == 0) return edges;
|
||||
for (var i = 0; i < edges.Length; i++)
|
||||
{
|
||||
var edge = edges[i];
|
||||
var path = ExtractFullPath(edge);
|
||||
if (path.Count < 4) continue;
|
||||
var newPath = new List<ElkPoint>(path);
|
||||
for (var j = 1; j < newPath.Count - 2; j++)
|
||||
{
|
||||
var prev = newPath[j - 1];
|
||||
var curr = newPath[j];
|
||||
var next = newPath[j + 1];
|
||||
var seg1 = Math.Abs(curr.X - prev.X) + Math.Abs(curr.Y - prev.Y);
|
||||
var seg2 = Math.Abs(next.X - curr.X) + Math.Abs(next.Y - curr.Y);
|
||||
if (seg1 >= 30d || seg2 >= 30d || seg1 < 1d || seg2 < 1d) continue;
|
||||
var s1V = Math.Abs(curr.X - prev.X) < 2d;
|
||||
var s2H = Math.Abs(next.Y - curr.Y) < 2d;
|
||||
var s1H = Math.Abs(curr.Y - prev.Y) < 2d;
|
||||
var s2V = Math.Abs(next.X - curr.X) < 2d;
|
||||
if ((s1V && s2H) || (s1H && s2V))
|
||||
{
|
||||
newPath[j] = new ElkPoint { X = next.X, Y = prev.Y };
|
||||
newPath.RemoveAt(j + 1);
|
||||
var cleaned = RemoveCollinearPoints(newPath);
|
||||
var result = edges.ToArray();
|
||||
result[i] = BuildSingleSectionEdge(edge, cleaned);
|
||||
return result; // return after first collapse
|
||||
}
|
||||
}
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
|
||||
private static List<ElkPoint> RemoveCollinearPoints(List<ElkPoint> path)
|
||||
{
|
||||
if (path.Count < 3)
|
||||
|
||||
@@ -272,6 +272,32 @@ internal static partial class ElkEdgeRouterIterative
|
||||
}
|
||||
}
|
||||
|
||||
// Collapse short doglegs (two consecutive <30px segments forming an
|
||||
// L-shape) into single bends. These create visible zigzag steps.
|
||||
// Collapse short doglegs PER-EDGE: some collapses create entry-angle
|
||||
// violations. Process one edge at a time and only keep safe ones.
|
||||
for (var dei = 0; dei < current.Edges.Length; dei++)
|
||||
{
|
||||
var singleEdge = ElkEdgePostProcessor.CollapseShortDoglegs(
|
||||
current.Edges, nodes);
|
||||
if (ReferenceEquals(singleEdge, current.Edges))
|
||||
{
|
||||
break; // no more doglegs to collapse
|
||||
}
|
||||
|
||||
var singleScore = ElkEdgeRoutingScoring.ComputeScore(singleEdge, nodes);
|
||||
if (singleScore.EntryAngleViolations <= current.Score.EntryAngleViolations
|
||||
&& singleScore.NodeCrossings <= current.Score.NodeCrossings
|
||||
&& singleScore.SharedLaneViolations <= current.Score.SharedLaneViolations)
|
||||
{
|
||||
current = current with { Score = singleScore, Edges = singleEdge };
|
||||
}
|
||||
else
|
||||
{
|
||||
break; // can't collapse more without violations
|
||||
}
|
||||
}
|
||||
|
||||
// Straighten short diagonal stubs at gateway boundary vertices.
|
||||
var straightened = ElkEdgePostProcessor.StraightenGatewayCornerDiagonals(current.Edges, nodes);
|
||||
if (!ReferenceEquals(straightened, current.Edges))
|
||||
|
||||
Reference in New Issue
Block a user