namespace StellaOps.ElkSharp; internal static class ElkSharpLayoutInitialPlacement { internal static void PlaceNodesLeftToRight( Dictionary positionedNodes, ElkNode[][] layers, DummyNodeResult dummyResult, Dictionary> augmentedIncoming, Dictionary> augmentedOutgoing, Dictionary augmentedNodesById, Dictionary> incomingNodeIds, Dictionary> outgoingNodeIds, Dictionary nodesById, double adaptiveNodeSpacing, ElkLayoutOptions options, int placementIterations, NodePlacementGrid placementGrid) { var globalNodeHeight = augmentedNodesById.Values .Where(n => !dummyResult.DummyNodeIds.Contains(n.Id)) .Max(x => x.Height); var gridNodeSpacing = Math.Max(adaptiveNodeSpacing, placementGrid.YStep * 0.4d); var edgeDensityFactor = adaptiveNodeSpacing / options.NodeSpacing; var adaptiveLayerSpacing = Math.Max( options.LayerSpacing * Math.Min(1.15d, 0.92d + (Math.Max(0d, edgeDensityFactor - 1d) * 0.35d)), placementGrid.XStep * 0.45d); var layerXPositions = new double[layers.Length]; var currentX = 0d; for (var layerIndex = 0; layerIndex < layers.Length; layerIndex++) { layerXPositions[layerIndex] = currentX; currentX += layers[layerIndex].Max(x => x.Width) + adaptiveLayerSpacing; } var slotHeight = globalNodeHeight; for (var layerIndex = 0; layerIndex < layers.Length; layerIndex++) { var layer = layers[layerIndex]; var desiredY = new double[layer.Length]; for (var nodeIndex = 0; nodeIndex < layer.Length; nodeIndex++) { var node = layer[nodeIndex]; var centers = new List(); foreach (var srcId in augmentedIncoming[node.Id]) { if (positionedNodes.TryGetValue(srcId, out var srcPos)) { centers.Add(srcPos.Y + (srcPos.Height / 2d)); } } if (centers.Count > 0) { centers.Sort(); var mid = centers.Count / 2; var median = centers.Count % 2 == 1 ? centers[mid] : (centers[mid - 1] + centers[mid]) / 2d; desiredY[nodeIndex] = median - (node.Height / 2d); } else { desiredY[nodeIndex] = nodeIndex * (slotHeight + gridNodeSpacing); } } for (var nodeIndex = 1; nodeIndex < layer.Length; nodeIndex++) { var prevIsDummy = dummyResult.DummyNodeIds.Contains(layer[nodeIndex - 1].Id); var currIsDummy = dummyResult.DummyNodeIds.Contains(layer[nodeIndex].Id); var pairSpacing = (prevIsDummy && currIsDummy) ? 2d : (prevIsDummy || currIsDummy) ? Math.Min(gridNodeSpacing, options.NodeSpacing * 0.5d) : gridNodeSpacing; var minY = desiredY[nodeIndex - 1] + layer[nodeIndex - 1].Height + pairSpacing; if (desiredY[nodeIndex] < minY) { desiredY[nodeIndex] = minY; } } for (var nodeIndex = 0; nodeIndex < layer.Length; nodeIndex++) { positionedNodes[layer[nodeIndex].Id] = ElkLayoutHelpers.CreatePositionedNode( layer[nodeIndex], layerXPositions[layerIndex], desiredY[nodeIndex], options.Direction); } } var minNodeY = positionedNodes.Values.Min(n => n.Y); if (minNodeY < -0.01d) { foreach (var nodeId in positionedNodes.Keys.ToArray()) { var pos = positionedNodes[nodeId]; positionedNodes[nodeId] = ElkLayoutHelpers.CreatePositionedNode( augmentedNodesById[nodeId], pos.X, pos.Y - minNodeY, options.Direction); } } ElkNodePlacement.RefineHorizontalPlacement(positionedNodes, layers, incomingNodeIds, outgoingNodeIds, augmentedNodesById, gridNodeSpacing, placementIterations, options.Direction); ElkNodePlacement.SnapOriginalPrimaryAxes(positionedNodes, layers, dummyResult.DummyNodeIds, incomingNodeIds, outgoingNodeIds, nodesById, gridNodeSpacing, options.Direction); ElkNodePlacementAlignment.CompactTowardIncomingFlow(positionedNodes, layers, dummyResult.DummyNodeIds, incomingNodeIds, nodesById, gridNodeSpacing, options.Direction); ElkNodePlacement.SnapOriginalPrimaryAxes(positionedNodes, layers, dummyResult.DummyNodeIds, incomingNodeIds, outgoingNodeIds, nodesById, gridNodeSpacing, options.Direction); ElkNodePlacementPreferredCenter.AlignDummyNodesToFlow(positionedNodes, layers, dummyResult.DummyNodeIds, augmentedIncoming, augmentedOutgoing, augmentedNodesById, options.Direction); ElkNodePlacementAlignment.CenterMultiIncomingNodes( positionedNodes, incomingNodeIds, nodesById, options.Direction); ElkNodePlacementAlignment.PropagateSuccessorPositionBackward( positionedNodes, outgoingNodeIds, nodesById, options.Direction); minNodeY = positionedNodes.Values.Min(n => n.Y); if (minNodeY < -0.01d) { foreach (var nodeId in positionedNodes.Keys.ToArray()) { var pos = positionedNodes[nodeId]; positionedNodes[nodeId] = ElkLayoutHelpers.CreatePositionedNode( augmentedNodesById[nodeId], pos.X, pos.Y - minNodeY, options.Direction); } } } internal static void PlaceNodesTopToBottom( Dictionary positionedNodes, ElkNode[][] layers, DummyNodeResult dummyResult, Dictionary> augmentedIncoming, Dictionary> augmentedOutgoing, Dictionary augmentedNodesById, Dictionary> incomingNodeIds, Dictionary> outgoingNodeIds, Dictionary nodesById, double globalNodeWidth, double adaptiveNodeSpacing, ElkLayoutOptions options, int placementIterations, NodePlacementGrid placementGrid) { var gridNodeSpacing = Math.Max(adaptiveNodeSpacing, placementGrid.XStep * 0.4d); var layerYPositions = new double[layers.Length]; var currentY = 0d; for (var layerIndex = 0; layerIndex < layers.Length; layerIndex++) { layerYPositions[layerIndex] = currentY; currentY += layers[layerIndex].Max(x => x.Height) + Math.Max(options.LayerSpacing, placementGrid.YStep * 0.45d); } var slotWidth = globalNodeWidth; for (var layerIndex = 0; layerIndex < layers.Length; layerIndex++) { var layer = layers[layerIndex]; var desiredX = new double[layer.Length]; for (var nodeIndex = 0; nodeIndex < layer.Length; nodeIndex++) { var node = layer[nodeIndex]; var centers = new List(); foreach (var srcId in augmentedIncoming[node.Id]) { if (positionedNodes.TryGetValue(srcId, out var srcPos)) { centers.Add(srcPos.X + (srcPos.Width / 2d)); } } if (centers.Count > 0) { centers.Sort(); var mid = centers.Count / 2; var median = centers.Count % 2 == 1 ? centers[mid] : (centers[mid - 1] + centers[mid]) / 2d; desiredX[nodeIndex] = median - (node.Width / 2d); } else { desiredX[nodeIndex] = nodeIndex * (slotWidth + gridNodeSpacing); } } for (var nodeIndex = 1; nodeIndex < layer.Length; nodeIndex++) { var prevIsDummyX = dummyResult.DummyNodeIds.Contains(layer[nodeIndex - 1].Id); var currIsDummyX = dummyResult.DummyNodeIds.Contains(layer[nodeIndex].Id); var pairSpacingX = (prevIsDummyX && currIsDummyX) ? 2d : (prevIsDummyX || currIsDummyX) ? Math.Min(gridNodeSpacing, options.NodeSpacing * 0.5d) : gridNodeSpacing; var minX = desiredX[nodeIndex - 1] + layer[nodeIndex - 1].Width + pairSpacingX; if (desiredX[nodeIndex] < minX) { desiredX[nodeIndex] = minX; } } for (var nodeIndex = 0; nodeIndex < layer.Length; nodeIndex++) { positionedNodes[layer[nodeIndex].Id] = ElkLayoutHelpers.CreatePositionedNode( layer[nodeIndex], desiredX[nodeIndex], layerYPositions[layerIndex], options.Direction); } } var minNodeX = positionedNodes.Values.Min(n => n.X); if (minNodeX < -0.01d) { foreach (var nodeId in positionedNodes.Keys.ToArray()) { var pos = positionedNodes[nodeId]; positionedNodes[nodeId] = ElkLayoutHelpers.CreatePositionedNode( augmentedNodesById[nodeId], pos.X - minNodeX, pos.Y, options.Direction); } } ElkNodePlacement.RefineVerticalPlacement(positionedNodes, layers, incomingNodeIds, outgoingNodeIds, augmentedNodesById, gridNodeSpacing, placementIterations, options.Direction); ElkNodePlacement.SnapOriginalPrimaryAxes(positionedNodes, layers, dummyResult.DummyNodeIds, incomingNodeIds, outgoingNodeIds, nodesById, gridNodeSpacing, options.Direction); ElkNodePlacementAlignment.CompactTowardIncomingFlow(positionedNodes, layers, dummyResult.DummyNodeIds, incomingNodeIds, nodesById, gridNodeSpacing, options.Direction); ElkNodePlacement.SnapOriginalPrimaryAxes(positionedNodes, layers, dummyResult.DummyNodeIds, incomingNodeIds, outgoingNodeIds, nodesById, gridNodeSpacing, options.Direction); ElkNodePlacementPreferredCenter.AlignDummyNodesToFlow(positionedNodes, layers, dummyResult.DummyNodeIds, augmentedIncoming, augmentedOutgoing, augmentedNodesById, options.Direction); ElkNodePlacementAlignment.CenterMultiIncomingNodes( positionedNodes, incomingNodeIds, nodesById, options.Direction); ElkNodePlacementAlignment.PropagateSuccessorPositionBackward( positionedNodes, outgoingNodeIds, nodesById, options.Direction); minNodeX = positionedNodes.Values.Min(n => n.X); if (minNodeX < -0.01d) { foreach (var nodeId in positionedNodes.Keys.ToArray()) { var pos = positionedNodes[nodeId]; positionedNodes[nodeId] = ElkLayoutHelpers.CreatePositionedNode( augmentedNodesById[nodeId], pos.X - minNodeX, pos.Y, options.Direction); } } } }