Unify minLineClearance across pipeline via ElkLayoutClearance
Add ElkLayoutClearance (thread-static scoped holder) so all 15+ ResolveMinLineClearance call sites in scoring/post-processing use the same NodeSpacing-aware clearance as the iterative optimizer. Formula: max(avgNodeSize/2, nodeSpacing * 1.2) At NodeSpacing=40: max(52.7, 48) = 52.7 (unchanged) At NodeSpacing=60: max(52.7, 72) = 72 (wider corridors) The infrastructure is in place. Wider spacing (50+) still needs routing-level tuning for the different edge convergence patterns that arise from different node arrangements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,12 @@ internal static class ElkEdgeHorizontalRoutingGutters
|
||||
var minClearance = serviceNodes.Length > 0
|
||||
? Math.Min(serviceNodes.Average(n => n.Width), serviceNodes.Average(n => n.Height)) / 2d
|
||||
: 50d;
|
||||
// Use layout-wide clearance if available (scales with NodeSpacing).
|
||||
var overrideClearance = ElkLayoutClearance.Current;
|
||||
if (overrideClearance > 0d)
|
||||
{
|
||||
minClearance = overrideClearance;
|
||||
}
|
||||
|
||||
// Scan routed edges for horizontal segments with under-node or alongside violations.
|
||||
var gutterRequirements = new Dictionary<double, double>(); // gutterY → requiredShift
|
||||
|
||||
@@ -318,6 +318,13 @@ internal static partial class ElkEdgePostProcessor
|
||||
|
||||
private static double ResolveMinLineClearance(IReadOnlyCollection<ElkPositionedNode> nodes)
|
||||
{
|
||||
// Use the layout-wide clearance if set (scales with NodeSpacing).
|
||||
var overrideClearance = ElkLayoutClearance.Current;
|
||||
if (overrideClearance > 0d)
|
||||
{
|
||||
return overrideClearance;
|
||||
}
|
||||
|
||||
var serviceNodes = nodes.Where(node => node.Kind is not "Start" and not "End").ToArray();
|
||||
return serviceNodes.Length > 0
|
||||
? Math.Min(serviceNodes.Average(node => node.Width), serviceNodes.Average(node => node.Height)) / 2d
|
||||
|
||||
@@ -158,6 +158,12 @@ internal static partial class ElkEdgeRouterIterative
|
||||
|
||||
private static double ResolveMinLineClearance(IReadOnlyCollection<ElkPositionedNode> nodes)
|
||||
{
|
||||
var overrideClearance = ElkLayoutClearance.Current;
|
||||
if (overrideClearance > 0d)
|
||||
{
|
||||
return overrideClearance;
|
||||
}
|
||||
|
||||
var serviceNodes = nodes.Where(node => node.Kind is not "Start" and not "End").ToArray();
|
||||
return serviceNodes.Length > 0
|
||||
? Math.Min(serviceNodes.Average(node => node.Width), serviceNodes.Average(node => node.Height)) / 2d
|
||||
|
||||
@@ -37,9 +37,9 @@ internal static partial class ElkEdgeRouterIterative
|
||||
? Math.Min(serviceNodes.Average(n => n.Width), serviceNodes.Average(n => n.Height)) / 2d
|
||||
: 50d;
|
||||
// Scale clearance with NodeSpacing: wider spacing should produce wider
|
||||
// routing corridors. Use the larger of node-size-based clearance and
|
||||
// spacing-proportional clearance so the pipeline adapts to any spacing.
|
||||
// routing corridors so edges don't visually hug node boundaries.
|
||||
var minLineClearance = Math.Max(nodeSizeClearance, layoutOptions.NodeSpacing * 1.2d);
|
||||
using var clearanceScope = ElkLayoutClearance.Set(minLineClearance);
|
||||
var diagnostics = ElkLayoutDiagnostics.Current;
|
||||
|
||||
var validSolutions = new List<CandidateSolution>();
|
||||
|
||||
@@ -233,6 +233,12 @@ internal static partial class ElkEdgeRoutingScoring
|
||||
|
||||
private static double ResolveMinLineClearance(IReadOnlyCollection<ElkPositionedNode> nodes)
|
||||
{
|
||||
var overrideClearance = ElkLayoutClearance.Current;
|
||||
if (overrideClearance > 0d)
|
||||
{
|
||||
return overrideClearance;
|
||||
}
|
||||
|
||||
var serviceNodes = nodes.Where(n => n.Kind is not "Start" and not "End").ToArray();
|
||||
return serviceNodes.Length > 0
|
||||
? Math.Min(serviceNodes.Average(n => n.Width), serviceNodes.Average(n => n.Height)) / 2d
|
||||
|
||||
35
src/__Libraries/StellaOps.ElkSharp/ElkLayoutClearance.cs
Normal file
35
src/__Libraries/StellaOps.ElkSharp/ElkLayoutClearance.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace StellaOps.ElkSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Layout-wide minimum line clearance, computed once from NodeSpacing at
|
||||
/// the optimization entry point and available to all scoring/post-processing
|
||||
/// helpers via <see cref="Current"/>. This avoids threading a parameter
|
||||
/// through 15+ static call sites.
|
||||
/// </summary>
|
||||
internal static class ElkLayoutClearance
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static double _current;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layout-wide minimum line clearance. Returns 0 if not set
|
||||
/// (callers fall back to node-size-based clearance).
|
||||
/// </summary>
|
||||
internal static double Current => _current;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the layout-wide clearance for the duration of a layout operation.
|
||||
/// Call at the start of optimization with the NodeSpacing-aware value.
|
||||
/// </summary>
|
||||
internal static IDisposable Set(double minLineClearance)
|
||||
{
|
||||
var previous = _current;
|
||||
_current = minLineClearance;
|
||||
return new ClearanceScope(previous);
|
||||
}
|
||||
|
||||
private sealed class ClearanceScope(double previous) : IDisposable
|
||||
{
|
||||
public void Dispose() => _current = previous;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user