namespace StellaOps.ElkSharp; internal static partial class ElkCompoundLayout { internal static ElkLayoutResult Layout( ElkGraph graph, ElkLayoutOptions options, ElkCompoundHierarchy hierarchy, CancellationToken cancellationToken) { var nodesById = graph.Nodes.ToDictionary(node => node.Id, StringComparer.Ordinal); var visibleNodes = graph.Nodes .Where(node => hierarchy.IsLayoutVisibleNode(node.Id)) .ToArray(); var visibleNodesById = visibleNodes.ToDictionary(node => node.Id, StringComparer.Ordinal); var (inputOrder, backEdgeIds) = ElkLayerAssignment.BuildTraversalInputOrder(visibleNodes, graph.Edges, visibleNodesById); var outgoing = visibleNodes.ToDictionary(node => node.Id, _ => new List(), StringComparer.Ordinal); var incomingNodeIds = visibleNodes.ToDictionary(node => node.Id, _ => new List(), StringComparer.Ordinal); var outgoingNodeIds = visibleNodes.ToDictionary(node => node.Id, _ => new List(), StringComparer.Ordinal); foreach (var edge in graph.Edges) { outgoing[edge.SourceNodeId].Add(edge); incomingNodeIds[edge.TargetNodeId].Add(edge.SourceNodeId); outgoingNodeIds[edge.SourceNodeId].Add(edge.TargetNodeId); } var layersByNodeId = ElkLayerAssignment.AssignLayersByInputOrder(visibleNodes, outgoing, inputOrder, backEdgeIds); var dummyResult = ElkLayerAssignment.InsertDummyNodes(visibleNodes, graph.Edges, layersByNodeId, inputOrder, backEdgeIds); var allNodes = dummyResult.AllNodes; var allEdges = dummyResult.AllEdges; var augmentedNodesById = allNodes.ToDictionary(node => node.Id, StringComparer.Ordinal); var augmentedInputOrder = dummyResult.AugmentedInputOrder; var augmentedIncoming = allNodes.ToDictionary(node => node.Id, _ => new List(), StringComparer.Ordinal); var augmentedOutgoing = allNodes.ToDictionary(node => node.Id, _ => new List(), StringComparer.Ordinal); foreach (var edge in allEdges) { augmentedIncoming[edge.TargetNodeId].Add(edge.SourceNodeId); augmentedOutgoing[edge.SourceNodeId].Add(edge.TargetNodeId); } var orderingIterations = ElkNodePlacement.ResolveOrderingIterationCount(options, allEdges.Count, layersByNodeId.Count); var layers = allNodes .GroupBy(node => dummyResult.AugmentedLayers[node.Id]) .OrderBy(group => group.Key) .Select(group => group.OrderBy(node => augmentedInputOrder[node.Id]).ToArray()) .ToArray(); layers = OptimizeLayerOrderingForHierarchy( layers, augmentedIncoming, augmentedOutgoing, augmentedInputOrder, orderingIterations, hierarchy, dummyResult.DummyNodeIds); var placementIterations = ElkNodePlacement.ResolvePlacementIterationCount(options, allNodes.Count, layers.Length); var placementGrid = ElkNodePlacement.ResolvePlacementGrid(visibleNodes); var positionedVisibleNodes = new Dictionary(StringComparer.Ordinal); var globalNodeWidth = visibleNodes.Max(node => node.Width); var edgeDensityFactor = Math.Min(1.8d, 1d + (Math.Max(0, allEdges.Count - 15) * 0.02d)); var adaptiveNodeSpacing = options.NodeSpacing * edgeDensityFactor; if (options.Direction == ElkLayoutDirection.LeftToRight) { ElkSharpLayoutInitialPlacement.PlaceNodesLeftToRight( positionedVisibleNodes, layers, dummyResult, augmentedIncoming, augmentedOutgoing, augmentedNodesById, incomingNodeIds, outgoingNodeIds, visibleNodesById, adaptiveNodeSpacing, options, placementIterations, placementGrid); } else { ElkSharpLayoutInitialPlacement.PlaceNodesTopToBottom( positionedVisibleNodes, layers, dummyResult, augmentedIncoming, augmentedOutgoing, augmentedNodesById, incomingNodeIds, outgoingNodeIds, visibleNodesById, globalNodeWidth, adaptiveNodeSpacing, options, placementIterations, placementGrid); } var graphBounds = ElkGraphValidator.ComputeGraphBounds(positionedVisibleNodes.Values .Where(node => !dummyResult.DummyNodeIds.Contains(node.Id)) .ToArray()); var layerBoundariesByNodeId = ElkLayoutHelpers.BuildLayerBoundariesByNodeId(positionedVisibleNodes, dummyResult.AugmentedLayers); var edgeChannels = ElkEdgeChannels.ComputeEdgeChannels(graph.Edges, positionedVisibleNodes, options.Direction, layerBoundariesByNodeId); var reconstructedEdges = ElkEdgeRouter.ReconstructDummyEdges( graph.Edges, dummyResult, positionedVisibleNodes, augmentedNodesById, options.Direction, graphBounds, edgeChannels, layerBoundariesByNodeId); var routedEdges = graph.Edges .Select(edge => { if (reconstructedEdges.TryGetValue(edge.Id, out var routed)) { return routed; } var channel = ElkSharpLayoutHelpers.ResolveSinkOverride( edgeChannels.GetValueOrDefault(edge.Id), edge.Id, dummyResult, edgeChannels, graph.Edges); return ElkEdgeRouter.RouteEdge( edge, visibleNodesById, positionedVisibleNodes, options.Direction, graphBounds, channel, layerBoundariesByNodeId); }) .ToArray(); for (var gutterPass = 0; gutterPass < 3; gutterPass++) { cancellationToken.ThrowIfCancellationRequested(); if (!ElkEdgeChannelGutters.ExpandVerticalCorridorGutters( positionedVisibleNodes, routedEdges, dummyResult.AugmentedLayers, augmentedNodesById, options.LayerSpacing, options.Direction)) { break; } graphBounds = ElkGraphValidator.ComputeGraphBounds(positionedVisibleNodes.Values .Where(node => !dummyResult.DummyNodeIds.Contains(node.Id)) .ToArray()); layerBoundariesByNodeId = ElkLayoutHelpers.BuildLayerBoundariesByNodeId(positionedVisibleNodes, dummyResult.AugmentedLayers); edgeChannels = ElkEdgeChannels.ComputeEdgeChannels(graph.Edges, positionedVisibleNodes, options.Direction, layerBoundariesByNodeId); reconstructedEdges = ElkEdgeRouter.ReconstructDummyEdges( graph.Edges, dummyResult, positionedVisibleNodes, augmentedNodesById, options.Direction, graphBounds, edgeChannels, layerBoundariesByNodeId); routedEdges = graph.Edges .Select(edge => reconstructedEdges.TryGetValue(edge.Id, out var rerouted) ? rerouted : ElkEdgeRouter.RouteEdge( edge, visibleNodesById, positionedVisibleNodes, options.Direction, graphBounds, ElkSharpLayoutHelpers.ResolveSinkOverride( edgeChannels.GetValueOrDefault(edge.Id), edge.Id, dummyResult, edgeChannels, graph.Edges), layerBoundariesByNodeId)) .ToArray(); } for (var compactPass = 0; compactPass < 2; compactPass++) { cancellationToken.ThrowIfCancellationRequested(); if (!ElkEdgeChannelGutters.CompactSparseVerticalCorridorGutters( positionedVisibleNodes, routedEdges, dummyResult.AugmentedLayers, augmentedNodesById, options.LayerSpacing, options.Direction)) { break; } graphBounds = ElkGraphValidator.ComputeGraphBounds(positionedVisibleNodes.Values .Where(node => !dummyResult.DummyNodeIds.Contains(node.Id)) .ToArray()); layerBoundariesByNodeId = ElkLayoutHelpers.BuildLayerBoundariesByNodeId(positionedVisibleNodes, dummyResult.AugmentedLayers); edgeChannels = ElkEdgeChannels.ComputeEdgeChannels(graph.Edges, positionedVisibleNodes, options.Direction, layerBoundariesByNodeId); reconstructedEdges = ElkEdgeRouter.ReconstructDummyEdges( graph.Edges, dummyResult, positionedVisibleNodes, augmentedNodesById, options.Direction, graphBounds, edgeChannels, layerBoundariesByNodeId); routedEdges = graph.Edges .Select(edge => reconstructedEdges.TryGetValue(edge.Id, out var rerouted) ? rerouted : ElkEdgeRouter.RouteEdge( edge, visibleNodesById, positionedVisibleNodes, options.Direction, graphBounds, ElkSharpLayoutHelpers.ResolveSinkOverride( edgeChannels.GetValueOrDefault(edge.Id), edge.Id, dummyResult, edgeChannels, graph.Edges), layerBoundariesByNodeId)) .ToArray(); if (!ElkEdgeChannelGutters.ExpandVerticalCorridorGutters( positionedVisibleNodes, routedEdges, dummyResult.AugmentedLayers, augmentedNodesById, options.LayerSpacing, options.Direction)) { continue; } graphBounds = ElkGraphValidator.ComputeGraphBounds(positionedVisibleNodes.Values .Where(node => !dummyResult.DummyNodeIds.Contains(node.Id)) .ToArray()); layerBoundariesByNodeId = ElkLayoutHelpers.BuildLayerBoundariesByNodeId(positionedVisibleNodes, dummyResult.AugmentedLayers); edgeChannels = ElkEdgeChannels.ComputeEdgeChannels(graph.Edges, positionedVisibleNodes, options.Direction, layerBoundariesByNodeId); reconstructedEdges = ElkEdgeRouter.ReconstructDummyEdges( graph.Edges, dummyResult, positionedVisibleNodes, augmentedNodesById, options.Direction, graphBounds, edgeChannels, layerBoundariesByNodeId); routedEdges = graph.Edges .Select(edge => reconstructedEdges.TryGetValue(edge.Id, out var rerouted) ? rerouted : ElkEdgeRouter.RouteEdge( edge, visibleNodesById, positionedVisibleNodes, options.Direction, graphBounds, ElkSharpLayoutHelpers.ResolveSinkOverride( edgeChannels.GetValueOrDefault(edge.Id), edge.Id, dummyResult, edgeChannels, graph.Edges), layerBoundariesByNodeId)) .ToArray(); } var finalVisibleNodes = positionedVisibleNodes.Values .Where(node => !dummyResult.DummyNodeIds.Contains(node.Id)) .OrderBy(node => inputOrder.GetValueOrDefault(node.Id, int.MaxValue)) .ToArray(); routedEdges = ElkEdgePostProcessor.SnapAnchorsToNodeBoundary(routedEdges, finalVisibleNodes); routedEdges = ElkEdgeRouterIterative.Optimize(routedEdges, finalVisibleNodes, options, cancellationToken); var compoundNodes = BuildCompoundPositionedNodes(graph.Nodes, hierarchy, positionedVisibleNodes, options); if (TryResolveNegativeCoordinateShift(compoundNodes, out var shiftX, out var shiftY)) { compoundNodes = ShiftNodes(graph.Nodes, compoundNodes, shiftX, shiftY, options.Direction); routedEdges = ShiftEdges(routedEdges, shiftX, shiftY); } routedEdges = InsertCompoundBoundaryCrossings(routedEdges, compoundNodes, hierarchy); ElkLayoutDiagnostics.LogProgress("ElkSharp compound layout optimize returned"); return new ElkLayoutResult { GraphId = graph.Id, Nodes = graph.Nodes.Select(node => compoundNodes[node.Id]).ToArray(), Edges = routedEdges, }; } }