253 lines
11 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|