Refactor ElkSharp routing sources into partial modules

This commit is contained in:
master
2026-03-28 11:56:35 +02:00
parent 7be4e855d6
commit 7057819f4d
34 changed files with 33377 additions and 21402 deletions

View File

@@ -2,6 +2,23 @@ namespace StellaOps.ElkSharp;
internal static class ElkNodePlacement
{
internal static NodePlacementGrid ResolvePlacementGrid(IReadOnlyCollection<ElkNode> nodes)
{
var actualNodes = nodes
.Where(node => node.Kind is not "Start" and not "End")
.ToArray();
if (actualNodes.Length == 0)
{
return new NodePlacementGrid(160d, 80d);
}
var averageWidth = actualNodes.Average(node => node.Width);
var averageHeight = actualNodes.Average(node => node.Height);
return new NodePlacementGrid(
XStep: Math.Max(64d, Math.Round(averageWidth / 8d) * 8d),
YStep: Math.Max(48d, Math.Round(averageHeight / 8d) * 8d));
}
internal static int ResolveOrderingIterationCount(
ElkLayoutOptions options,
int edgeCount,
@@ -218,6 +235,7 @@ internal static class ElkNodePlacement
sortedNodes,
desiredCoordinates,
nodeSpacing,
0d,
horizontal: direction == ElkLayoutDirection.LeftToRight);
for (var nodeIndex = 0; nodeIndex < sortedNodes.Length; nodeIndex++)
@@ -231,10 +249,130 @@ internal static class ElkNodePlacement
}
}
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++)
@@ -243,6 +381,7 @@ internal static class ElkNodePlacement
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--)
@@ -251,6 +390,7 @@ internal static class ElkNodePlacement
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++)
@@ -259,6 +399,37 @@ internal static class ElkNodePlacement
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;
}
}