namespace StellaOps.ElkSharp; internal static partial class ElkCompoundLayout { private static ElkNode[][] OptimizeLayerOrderingForHierarchy( ElkNode[][] initialLayers, IReadOnlyDictionary> incomingNodeIds, IReadOnlyDictionary> outgoingNodeIds, IReadOnlyDictionary inputOrder, int iterationCount, ElkCompoundHierarchy hierarchy, IReadOnlySet dummyNodeIds) { var layers = initialLayers .Select(layer => layer.ToList()) .ToArray(); var effectiveIterations = Math.Max(1, iterationCount); for (var iteration = 0; iteration < effectiveIterations; iteration++) { for (var layerIndex = 1; layerIndex < layers.Length; layerIndex++) { OrderLayerWithHierarchy(layers, layerIndex, incomingNodeIds, inputOrder, hierarchy, dummyNodeIds); } for (var layerIndex = layers.Length - 2; layerIndex >= 0; layerIndex--) { OrderLayerWithHierarchy(layers, layerIndex, outgoingNodeIds, inputOrder, hierarchy, dummyNodeIds); } } return layers .Select(layer => layer.ToArray()) .ToArray(); } private static void OrderLayerWithHierarchy( IReadOnlyList> layers, int layerIndex, IReadOnlyDictionary> adjacentNodeIds, IReadOnlyDictionary inputOrder, ElkCompoundHierarchy hierarchy, IReadOnlySet dummyNodeIds) { var currentLayer = layers[layerIndex]; if (currentLayer.Count == 0) { return; } var positions = ElkNodeOrdering.BuildNodeOrderPositions(layers); var layerNodesById = currentLayer.ToDictionary(node => node.Id, StringComparer.Ordinal); var realNodeIdsInLayer = currentLayer .Where(node => !dummyNodeIds.Contains(node.Id)) .Select(node => node.Id) .ToHashSet(StringComparer.Ordinal); var orderedRealIds = OrderNodesForSubtree( null, hierarchy, realNodeIdsInLayer, adjacentNodeIds, positions, inputOrder); var orderedDummies = currentLayer .Where(node => dummyNodeIds.Contains(node.Id)) .OrderBy(node => ElkNodeOrdering.ResolveOrderingRank(node.Id, adjacentNodeIds, positions)) .ThenBy(node => positions[node.Id]) .ThenBy(node => inputOrder[node.Id]) .ToArray(); currentLayer.Clear(); foreach (var nodeId in orderedRealIds) { currentLayer.Add(layerNodesById[nodeId]); } currentLayer.AddRange(orderedDummies); } private static List OrderNodesForSubtree( string? parentNodeId, ElkCompoundHierarchy hierarchy, IReadOnlySet realNodeIdsInLayer, IReadOnlyDictionary> adjacentNodeIds, IReadOnlyDictionary positions, IReadOnlyDictionary inputOrder) { var blocks = new List(); foreach (var childNodeId in hierarchy.GetChildIds(parentNodeId)) { if (realNodeIdsInLayer.Contains(childNodeId)) { blocks.Add(BuildBlock([childNodeId])); continue; } if (!hierarchy.IsCompoundNode(childNodeId)) { continue; } var descendantNodeIds = OrderNodesForSubtree( childNodeId, hierarchy, realNodeIdsInLayer, adjacentNodeIds, positions, inputOrder); if (descendantNodeIds.Count == 0) { continue; } blocks.Add(BuildBlock(descendantNodeIds)); } return blocks .OrderBy(block => block.Rank) .ThenBy(block => block.MinCurrentPosition) .ThenBy(block => block.MinInputOrder) .SelectMany(block => block.NodeIds) .ToList(); HierarchyOrderBlock BuildBlock(IReadOnlyList nodeIds) { var ranks = new List(); foreach (var nodeId in nodeIds) { var nodeRank = ElkNodeOrdering.ResolveOrderingRank(nodeId, adjacentNodeIds, positions); if (!double.IsInfinity(nodeRank)) { ranks.Add(nodeRank); } } ranks.Sort(); var rank = ranks.Count switch { 0 => double.PositiveInfinity, _ when ranks.Count % 2 == 1 => ranks[ranks.Count / 2], _ => (ranks[(ranks.Count / 2) - 1] + ranks[ranks.Count / 2]) / 2d, }; return new HierarchyOrderBlock( nodeIds, rank, nodeIds.Min(nodeId => positions.GetValueOrDefault(nodeId, int.MaxValue)), nodeIds.Min(nodeId => inputOrder.GetValueOrDefault(nodeId, int.MaxValue))); } } private readonly record struct HierarchyOrderBlock( IReadOnlyList NodeIds, double Rank, int MinCurrentPosition, int MinInputOrder); }