197 lines
8.1 KiB
C#
197 lines
8.1 KiB
C#
namespace StellaOps.ElkSharp;
|
|
|
|
internal static partial class ElkNodePlacement
|
|
{
|
|
internal static void RefineHorizontalPlacement(
|
|
Dictionary<string, ElkPositionedNode> positionedNodes,
|
|
IReadOnlyList<ElkNode[]> layers,
|
|
IReadOnlyDictionary<string, List<string>> incomingNodeIds,
|
|
IReadOnlyDictionary<string, List<string>> outgoingNodeIds,
|
|
IReadOnlyDictionary<string, ElkNode> nodesById,
|
|
double nodeSpacing,
|
|
int iterationCount,
|
|
ElkLayoutDirection direction)
|
|
{
|
|
if (iterationCount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (var iteration = 0; iteration < iterationCount; iteration++)
|
|
{
|
|
var layerIndices = iteration % 2 == 0
|
|
? Enumerable.Range(0, layers.Count)
|
|
: Enumerable.Range(0, layers.Count).Reverse();
|
|
|
|
foreach (var layerIndex in layerIndices)
|
|
{
|
|
var layer = layers[layerIndex];
|
|
if (layer.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var desiredY = new double[layer.Length];
|
|
for (var nodeIndex = 0; nodeIndex < layer.Length; nodeIndex++)
|
|
{
|
|
var node = layer[nodeIndex];
|
|
var preferredCenter = ElkNodePlacementPreferredCenter.ResolvePreferredCenter(
|
|
node.Id,
|
|
incomingNodeIds,
|
|
outgoingNodeIds,
|
|
positionedNodes,
|
|
horizontal: true);
|
|
desiredY[nodeIndex] = preferredCenter.HasValue
|
|
? preferredCenter.Value - (node.Height / 2d)
|
|
: positionedNodes[node.Id].Y;
|
|
}
|
|
|
|
for (var nodeIndex = 1; nodeIndex < layer.Length; nodeIndex++)
|
|
{
|
|
var minY = desiredY[nodeIndex - 1] + layer[nodeIndex - 1].Height + nodeSpacing;
|
|
if (desiredY[nodeIndex] < minY)
|
|
{
|
|
desiredY[nodeIndex] = minY;
|
|
}
|
|
}
|
|
|
|
for (var nodeIndex = 0; nodeIndex < layer.Length; nodeIndex++)
|
|
{
|
|
var current = positionedNodes[layer[nodeIndex].Id];
|
|
positionedNodes[layer[nodeIndex].Id] = ElkLayoutHelpers.CreatePositionedNode(
|
|
nodesById[layer[nodeIndex].Id],
|
|
current.X,
|
|
desiredY[nodeIndex],
|
|
direction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void RefineVerticalPlacement(
|
|
Dictionary<string, ElkPositionedNode> positionedNodes,
|
|
IReadOnlyList<ElkNode[]> layers,
|
|
IReadOnlyDictionary<string, List<string>> incomingNodeIds,
|
|
IReadOnlyDictionary<string, List<string>> outgoingNodeIds,
|
|
IReadOnlyDictionary<string, ElkNode> nodesById,
|
|
double nodeSpacing,
|
|
int iterationCount,
|
|
ElkLayoutDirection direction)
|
|
{
|
|
if (iterationCount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (var iteration = 0; iteration < iterationCount; iteration++)
|
|
{
|
|
var layerIndices = iteration % 2 == 0
|
|
? Enumerable.Range(0, layers.Count)
|
|
: Enumerable.Range(0, layers.Count).Reverse();
|
|
|
|
foreach (var layerIndex in layerIndices)
|
|
{
|
|
var layer = layers[layerIndex];
|
|
if (layer.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var desiredX = new double[layer.Length];
|
|
for (var nodeIndex = 0; nodeIndex < layer.Length; nodeIndex++)
|
|
{
|
|
var node = layer[nodeIndex];
|
|
var preferredCenter = ElkNodePlacementPreferredCenter.ResolvePreferredCenter(
|
|
node.Id,
|
|
incomingNodeIds,
|
|
outgoingNodeIds,
|
|
positionedNodes,
|
|
horizontal: false);
|
|
desiredX[nodeIndex] = preferredCenter.HasValue
|
|
? preferredCenter.Value - (node.Width / 2d)
|
|
: positionedNodes[node.Id].X;
|
|
}
|
|
|
|
for (var nodeIndex = 1; nodeIndex < layer.Length; nodeIndex++)
|
|
{
|
|
var minX = desiredX[nodeIndex - 1] + layer[nodeIndex - 1].Width + nodeSpacing;
|
|
if (desiredX[nodeIndex] < minX)
|
|
{
|
|
desiredX[nodeIndex] = minX;
|
|
}
|
|
}
|
|
|
|
for (var nodeIndex = 0; nodeIndex < layer.Length; nodeIndex++)
|
|
{
|
|
var current = positionedNodes[layer[nodeIndex].Id];
|
|
positionedNodes[layer[nodeIndex].Id] = ElkLayoutHelpers.CreatePositionedNode(
|
|
nodesById[layer[nodeIndex].Id],
|
|
desiredX[nodeIndex],
|
|
current.Y,
|
|
direction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void SnapOriginalPrimaryAxes(
|
|
Dictionary<string, ElkPositionedNode> positionedNodes,
|
|
IReadOnlyList<ElkNode[]> layers,
|
|
IReadOnlySet<string> dummyNodeIds,
|
|
IReadOnlyDictionary<string, List<string>> incomingNodeIds,
|
|
IReadOnlyDictionary<string, List<string>> outgoingNodeIds,
|
|
IReadOnlyDictionary<string, ElkNode> originalNodesById,
|
|
double nodeSpacing,
|
|
ElkLayoutDirection direction)
|
|
{
|
|
for (var iteration = 0; iteration < 3; iteration++)
|
|
{
|
|
foreach (var layer in layers)
|
|
{
|
|
var actualNodes = layer
|
|
.Where(node => !dummyNodeIds.Contains(node.Id) && originalNodesById.ContainsKey(node.Id))
|
|
.Select(node => originalNodesById[node.Id])
|
|
.ToArray();
|
|
if (actualNodes.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var nodeDesiredPairs = new (ElkNode Node, double Desired)[actualNodes.Length];
|
|
for (var nodeIndex = 0; nodeIndex < actualNodes.Length; nodeIndex++)
|
|
{
|
|
var positioned = positionedNodes[actualNodes[nodeIndex].Id];
|
|
var preferredCenter = ElkNodePlacementPreferredCenter.ResolveOriginalPreferredCenter(
|
|
actualNodes[nodeIndex].Id,
|
|
incomingNodeIds,
|
|
outgoingNodeIds,
|
|
positionedNodes,
|
|
horizontal: direction == ElkLayoutDirection.LeftToRight);
|
|
nodeDesiredPairs[nodeIndex] = (actualNodes[nodeIndex], preferredCenter.HasValue
|
|
? preferredCenter.Value - ((direction == ElkLayoutDirection.LeftToRight ? positioned.Height : positioned.Width) / 2d)
|
|
: (direction == ElkLayoutDirection.LeftToRight ? positioned.Y : positioned.X));
|
|
}
|
|
|
|
Array.Sort(nodeDesiredPairs, (a, b) => a.Desired.CompareTo(b.Desired));
|
|
var sortedNodes = nodeDesiredPairs.Select(p => p.Node).ToArray();
|
|
var desiredCoordinates = nodeDesiredPairs.Select(p => p.Desired).ToArray();
|
|
|
|
EnforceLinearSpacing(
|
|
sortedNodes,
|
|
desiredCoordinates,
|
|
nodeSpacing,
|
|
0d,
|
|
horizontal: direction == ElkLayoutDirection.LeftToRight);
|
|
|
|
for (var nodeIndex = 0; nodeIndex < sortedNodes.Length; nodeIndex++)
|
|
{
|
|
var current = positionedNodes[sortedNodes[nodeIndex].Id];
|
|
positionedNodes[sortedNodes[nodeIndex].Id] = direction == ElkLayoutDirection.LeftToRight
|
|
? ElkLayoutHelpers.CreatePositionedNode(sortedNodes[nodeIndex], current.X, desiredCoordinates[nodeIndex], direction)
|
|
: ElkLayoutHelpers.CreatePositionedNode(sortedNodes[nodeIndex], desiredCoordinates[nodeIndex], current.Y, direction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|