namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterHighway { private static Dictionary> BuildTargetSideGroups( IReadOnlyList edges, IReadOnlyDictionary nodesById, double graphMinY, double graphMaxY) { var edgesByTargetSide = new Dictionary>(StringComparer.Ordinal); for (var i = 0; i < edges.Count; i++) { var edge = edges[i]; if (!ShouldProcessEdge(edge, graphMinY, graphMaxY)) { continue; } if (!nodesById.TryGetValue(edge.TargetNodeId ?? string.Empty, out var targetNode)) { continue; } if (ElkShapeBoundaries.IsGatewayShape(targetNode)) { continue; } var path = ExtractFullPath(edge); if (path.Count < 2) { continue; } var side = ElkEdgeRoutingGeometry.ResolveBoundarySide(path[^1], targetNode); var key = $"{targetNode.Id}|{side}"; if (!edgesByTargetSide.TryGetValue(key, out var list)) { list = []; edgesByTargetSide[key] = list; } list.Add(i); } return edgesByTargetSide; } private static GroupEvaluation? EvaluateTargetSideGroup( IReadOnlyList edges, IReadOnlyList edgeIndices, ElkPositionedNode targetNode, string side, double minLineClearance) { var members = edgeIndices .Select(index => CreateMember(edges[index], index, side)) .Where(member => member.Path.Count >= 2) .OrderBy(member => member.EdgeId, StringComparer.Ordinal) .ToList(); if (members.Count < 2) { return null; } var pairMetrics = ComputePairMetrics(members); var actualGap = ComputeMinEndpointGap(members); var requiredGap = ElkBoundarySlots.ResolveRequiredBoundarySlotGap( targetNode, side, members.Count, minLineClearance); var requiresSpread = (actualGap + CoordinateTolerance) < requiredGap && !pairMetrics.AllPairsApplicable; if (!requiresSpread && pairMetrics.ShortestSharedRatio < MinHighwayRatio) { requiresSpread = pairMetrics.HasSharedSegment; } var diagnostic = new ElkHighwayDiagnostics { TargetNodeId = targetNode.Id, SharedAxis = side, SharedCoord = Math.Round(members.Average(member => member.EndpointCoord), 1), EdgeIds = members.Select(member => member.EdgeId).ToArray(), MinRatio = pairMetrics.HasSharedSegment ? Math.Round(pairMetrics.ShortestSharedRatio, 3) : 0d, WasBroken = requiresSpread, Reason = requiresSpread ? pairMetrics.HasSharedSegment && pairMetrics.ShortestSharedRatio < MinHighwayRatio ? $"shared ratio {pairMetrics.ShortestSharedRatio:F2} < {MinHighwayRatio:F2}" : $"gap {actualGap:F0}px < required gap {requiredGap:F0}px" : pairMetrics.AllPairsApplicable ? $"shared ratio {pairMetrics.ShortestSharedRatio:F2} >= {MinHighwayRatio:F2}" : $"gap {actualGap:F0}px >= required gap {requiredGap:F0}px", }; return new GroupEvaluation(members, diagnostic); } private static HighwayPairMetrics ComputePairMetrics(IReadOnlyList members) { var shortestSharedRatio = double.MaxValue; var hasSharedSegment = false; var allPairsApplicable = true; for (var i = 0; i < members.Count; i++) { for (var j = i + 1; j < members.Count; j++) { var sharedLength = ElkEdgeRoutingGeometry.ComputeLongestSharedApproachSegmentLength( members[i].Path, members[j].Path); if (sharedLength <= 1d) { allPairsApplicable = false; continue; } hasSharedSegment = true; var shortestPath = Math.Min(members[i].PathLength, members[j].PathLength); if (shortestPath <= 1d) { allPairsApplicable = false; continue; } var ratio = sharedLength / shortestPath; shortestSharedRatio = Math.Min(shortestSharedRatio, ratio); if (ratio < MinHighwayRatio) { allPairsApplicable = false; } } } return new HighwayPairMetrics( HasSharedSegment: hasSharedSegment, AllPairsApplicable: allPairsApplicable && hasSharedSegment, ShortestSharedRatio: hasSharedSegment ? shortestSharedRatio : 0d); } private static double ComputeMinEndpointGap(IReadOnlyList members) { var coords = members .Select(member => member.EndpointCoord) .OrderBy(value => value) .ToArray(); if (coords.Length < 2) { return double.MaxValue; } var minGap = double.MaxValue; for (var i = 1; i < coords.Length; i++) { minGap = Math.Min(minGap, coords[i] - coords[i - 1]); } return minGap; } private static HighwayMember CreateMember(ElkRoutedEdge edge, int index, string side) { var path = ExtractFullPath(edge); var endpointCoord = side is "left" or "right" ? path[^1].Y : path[^1].X; return new HighwayMember( Index: index, EdgeId: edge.Id, Path: path, PathLength: ElkEdgeRoutingGeometry.ComputePathLength(edge), EndpointCoord: endpointCoord); } private readonly record struct HighwayMember( int Index, string EdgeId, List Path, double PathLength, double EndpointCoord); private readonly record struct HighwayPairMetrics( bool HasSharedSegment, bool AllPairsApplicable, double ShortestSharedRatio); private readonly record struct GroupEvaluation( IReadOnlyList Members, ElkHighwayDiagnostics Diagnostic); }