elksharp: separate overlapping End corridors and widen lead-lane jog
Two fixes for the End approach area: 1. SpreadOuterCorridors now splits shared-Y lanes when edges have overlapping X ranges (>40px overlap). edge/20 and edge/23 were both at Y=-235 with 2257px of shared horizontal — now split to Y=-235 and Y=-267 (31.6px gap). Uses the entry's actual corridor Y for shift point matching, not the lane's synthetic CurrentY. 2. Widen the lead-lane pre-terminal jog offset from minLineClearance*0.35 to minLineClearance*0.9. The jog now lands 15px above the End node top instead of 6px above the neighboring edge's arrival slot. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,11 +25,13 @@ internal static partial class ElkEdgePostProcessor
|
||||
|
||||
// Collect all above-graph corridor lanes (distinct rounded Y values)
|
||||
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
|
||||
var corridorEntries = new List<(int EdgeIndex, double CorridorY, bool IsEndBound)>();
|
||||
var corridorEntries = new List<(int EdgeIndex, double CorridorY, bool IsEndBound, double MinX, double MaxX)>();
|
||||
for (var i = 0; i < edges.Length; i++)
|
||||
{
|
||||
var bestAboveY = double.NaN;
|
||||
var bestLength = 0d;
|
||||
var bestMinX = 0d;
|
||||
var bestMaxX = 0d;
|
||||
foreach (var section in edges[i].Sections)
|
||||
{
|
||||
var points = new List<ElkPoint> { section.StartPoint };
|
||||
@@ -48,6 +50,8 @@ internal static partial class ElkEdgePostProcessor
|
||||
{
|
||||
bestLength = length;
|
||||
bestAboveY = points[j].Y;
|
||||
bestMinX = Math.Min(points[j].X, points[j + 1].X);
|
||||
bestMaxX = Math.Max(points[j].X, points[j + 1].X);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +61,7 @@ internal static partial class ElkEdgePostProcessor
|
||||
var isEndBound = !string.IsNullOrWhiteSpace(edges[i].TargetNodeId)
|
||||
&& nodesById.TryGetValue(edges[i].TargetNodeId!, out var targetNode)
|
||||
&& string.Equals(targetNode.Kind, "End", StringComparison.Ordinal);
|
||||
corridorEntries.Add((i, bestAboveY, isEndBound));
|
||||
corridorEntries.Add((i, bestAboveY, isEndBound, bestMinX, bestMaxX));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,15 +70,57 @@ internal static partial class ElkEdgePostProcessor
|
||||
return edges;
|
||||
}
|
||||
|
||||
// Group by rounded corridor Y (edges sharing a corridor lane)
|
||||
var lanes = corridorEntries
|
||||
// Group by rounded corridor Y, then split groups where edges have
|
||||
// overlapping X ranges (visually stacked on top of each other).
|
||||
var rawLanes = corridorEntries
|
||||
.GroupBy(entry => Math.Round(entry.CorridorY, 0))
|
||||
.OrderByDescending(group => group.Key) // closest to graph first (least negative)
|
||||
.Select(group => new
|
||||
.OrderByDescending(group => group.Key)
|
||||
.ToArray();
|
||||
|
||||
var splitLanes = new List<(double CurrentY, (int EdgeIndex, double CorridorY, bool IsEndBound, double MinX, double MaxX)[] Entries)>();
|
||||
foreach (var group in rawLanes)
|
||||
{
|
||||
var entries = group.ToArray();
|
||||
if (entries.Length <= 1)
|
||||
{
|
||||
CurrentY = group.Key,
|
||||
Entries = group.ToArray(),
|
||||
})
|
||||
splitLanes.Add((group.Key, entries));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for X-range overlaps within this lane
|
||||
var hasOverlap = false;
|
||||
for (var a = 0; a < entries.Length && !hasOverlap; a++)
|
||||
{
|
||||
for (var b = a + 1; b < entries.Length; b++)
|
||||
{
|
||||
var overlap = Math.Min(entries[a].MaxX, entries[b].MaxX)
|
||||
- Math.Max(entries[a].MinX, entries[b].MinX);
|
||||
if (overlap > 40d)
|
||||
{
|
||||
hasOverlap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasOverlap)
|
||||
{
|
||||
splitLanes.Add((group.Key, entries));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split: each edge gets its own sub-lane
|
||||
for (var k = 0; k < entries.Length; k++)
|
||||
{
|
||||
splitLanes.Add((group.Key - (k * minGap), new[] { entries[k] }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-sort after splitting
|
||||
var lanes = splitLanes
|
||||
.OrderByDescending(lane => lane.CurrentY)
|
||||
.Select(lane => new { lane.CurrentY, lane.Entries })
|
||||
.ToArray();
|
||||
|
||||
if (lanes.Length < 2)
|
||||
@@ -140,28 +186,27 @@ internal static partial class ElkEdgePostProcessor
|
||||
return edges;
|
||||
}
|
||||
|
||||
for (var i = 0; i < lanes.Length; i++)
|
||||
{
|
||||
var shift = targetYValues[i] - lanes[i].CurrentY;
|
||||
}
|
||||
|
||||
// Apply shifts
|
||||
var result = edges.ToArray();
|
||||
for (var i = 0; i < lanes.Length; i++)
|
||||
{
|
||||
var shift = targetYValues[i] - lanes[i].CurrentY;
|
||||
if (Math.Abs(shift) < 1d)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var entry in lanes[i].Entries)
|
||||
{
|
||||
var edge = result[entry.EdgeIndex];
|
||||
// Use the entry's actual corridor Y for point matching,
|
||||
// not the lane's synthetic CurrentY (which may differ after
|
||||
// lane splitting for overlapping X ranges).
|
||||
var entryShift = targetYValues[i] - Math.Round(entry.CorridorY, 0);
|
||||
if (Math.Abs(entryShift) < 1d)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var shifted = ShiftEdgeCorridorY(
|
||||
edge,
|
||||
lanes[i].CurrentY,
|
||||
shift,
|
||||
Math.Round(entry.CorridorY, 0),
|
||||
entryShift,
|
||||
graphMinY);
|
||||
result[entry.EdgeIndex] = shifted;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user