Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkSharpLayoutInitialPlacement.cs

253 lines
11 KiB
C#

namespace StellaOps.ElkSharp;
internal static class ElkSharpLayoutInitialPlacement
{
internal static void PlaceNodesLeftToRight(
Dictionary<string, ElkPositionedNode> positionedNodes, ElkNode[][] layers,
DummyNodeResult dummyResult, Dictionary<string, List<string>> augmentedIncoming,
Dictionary<string, List<string>> augmentedOutgoing, Dictionary<string, ElkNode> augmentedNodesById,
Dictionary<string, List<string>> incomingNodeIds, Dictionary<string, List<string>> outgoingNodeIds,
Dictionary<string, ElkNode> 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<double>();
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<string, ElkPositionedNode> positionedNodes, ElkNode[][] layers,
DummyNodeResult dummyResult, Dictionary<string, List<string>> augmentedIncoming,
Dictionary<string, List<string>> augmentedOutgoing, Dictionary<string, ElkNode> augmentedNodesById,
Dictionary<string, List<string>> incomingNodeIds, Dictionary<string, List<string>> outgoingNodeIds,
Dictionary<string, ElkNode> 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<double>();
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);
}
}
}
}