Refactor ElkSharp hybrid routing and document speed path
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
namespace StellaOps.ElkSharp;
|
||||
|
||||
internal static class ElkNodePlacement
|
||||
internal static partial class ElkNodePlacement
|
||||
{
|
||||
internal static NodePlacementGrid ResolvePlacementGrid(IReadOnlyCollection<ElkNode> nodes)
|
||||
{
|
||||
@@ -57,379 +57,4 @@ internal static 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void AlignToPlacementGrid(
|
||||
Dictionary<string, ElkPositionedNode> positionedNodes,
|
||||
IReadOnlyList<ElkNode[]> layers,
|
||||
IReadOnlySet<string> 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<ElkNode> 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user