namespace StellaOps.ElkSharp; internal static partial class ElkNodePlacement { internal static void AlignToPlacementGrid( Dictionary positionedNodes, IReadOnlyList layers, IReadOnlySet dummyNodeIds, double nodeSpacing, NodePlacementGrid placementGrid, ElkLayoutDirection direction) { if (layers.Count == 0) { return; } if (direction == ElkLayoutDirection.LeftToRight) { foreach (var layer in layers) { var actualNodes = layer.Where(node => !dummyNodeIds.Contains(node.Id)).ToArray(); if (actualNodes.Length == 0) { continue; } var currentX = positionedNodes[actualNodes[0].Id].X; var snappedX = SnapToPlacementGrid(currentX, placementGrid.XStep); var deltaX = snappedX - currentX; if (Math.Abs(deltaX) > 0.01d) { foreach (var node in layer) { var pos = positionedNodes[node.Id]; positionedNodes[node.Id] = ElkLayoutHelpers.CreatePositionedNode( node, pos.X + deltaX, pos.Y, direction); } } var desiredY = actualNodes .Select(node => SnapToPlacementGrid(positionedNodes[node.Id].Y, placementGrid.YStep)) .ToArray(); EnforceLinearSpacing(actualNodes, desiredY, nodeSpacing, placementGrid.YStep, horizontal: true); for (var i = 0; i < actualNodes.Length; i++) { var current = positionedNodes[actualNodes[i].Id]; positionedNodes[actualNodes[i].Id] = ElkLayoutHelpers.CreatePositionedNode( actualNodes[i], current.X, desiredY[i], direction); } } var minY = positionedNodes.Values.Min(node => node.Y); if (minY < -0.01d) { var shift = SnapForwardToPlacementGrid(-minY, placementGrid.YStep); foreach (var nodeId in positionedNodes.Keys.ToArray()) { var pos = positionedNodes[nodeId]; positionedNodes[nodeId] = pos with { Y = pos.Y + shift }; } } return; } foreach (var layer in layers) { var actualNodes = layer.Where(node => !dummyNodeIds.Contains(node.Id)).ToArray(); if (actualNodes.Length == 0) { continue; } var currentY = positionedNodes[actualNodes[0].Id].Y; var snappedY = SnapToPlacementGrid(currentY, placementGrid.YStep); var deltaY = snappedY - currentY; if (Math.Abs(deltaY) > 0.01d) { foreach (var node in layer) { var pos = positionedNodes[node.Id]; positionedNodes[node.Id] = ElkLayoutHelpers.CreatePositionedNode( node, pos.X, pos.Y + deltaY, direction); } } var desiredX = actualNodes .Select(node => SnapToPlacementGrid(positionedNodes[node.Id].X, placementGrid.XStep)) .ToArray(); EnforceLinearSpacing(actualNodes, desiredX, nodeSpacing, placementGrid.XStep, horizontal: false); for (var i = 0; i < actualNodes.Length; i++) { var current = positionedNodes[actualNodes[i].Id]; positionedNodes[actualNodes[i].Id] = ElkLayoutHelpers.CreatePositionedNode( actualNodes[i], desiredX[i], current.Y, direction); } } var minX = positionedNodes.Values.Min(node => node.X); if (minX < -0.01d) { var shift = SnapForwardToPlacementGrid(-minX, placementGrid.XStep); foreach (var nodeId in positionedNodes.Keys.ToArray()) { var pos = positionedNodes[nodeId]; positionedNodes[nodeId] = pos with { X = pos.X + shift }; } } } internal static void EnforceLinearSpacing( IReadOnlyList layer, double[] desiredCoordinates, double spacing, double gridStep, bool horizontal) { for (var index = 1; index < layer.Count; index++) { var extent = horizontal ? layer[index - 1].Height : layer[index - 1].Width; desiredCoordinates[index] = Math.Max( desiredCoordinates[index], desiredCoordinates[index - 1] + extent + spacing); desiredCoordinates[index] = SnapForwardToPlacementGrid(desiredCoordinates[index], gridStep); } for (var index = layer.Count - 2; index >= 0; index--) { var extent = horizontal ? layer[index].Height : layer[index].Width; desiredCoordinates[index] = Math.Min( desiredCoordinates[index], desiredCoordinates[index + 1] - extent - spacing); desiredCoordinates[index] = SnapBackwardToPlacementGrid(desiredCoordinates[index], gridStep); } for (var index = 1; index < layer.Count; index++) { var extent = horizontal ? layer[index - 1].Height : layer[index - 1].Width; desiredCoordinates[index] = Math.Max( desiredCoordinates[index], desiredCoordinates[index - 1] + extent + spacing); desiredCoordinates[index] = SnapForwardToPlacementGrid(desiredCoordinates[index], gridStep); } } internal static double SnapToPlacementGrid(double value, double gridStep) { if (gridStep <= 1d) { return value; } return Math.Round(value / gridStep) * gridStep; } internal static double SnapForwardToPlacementGrid(double value, double gridStep) { if (gridStep <= 1d) { return value; } return Math.Ceiling(value / gridStep) * gridStep; } internal static double SnapBackwardToPlacementGrid(double value, double gridStep) { if (gridStep <= 1d) { return value; } return Math.Floor(value / gridStep) * gridStep; } }