189 lines
6.6 KiB
C#
189 lines
6.6 KiB
C#
namespace StellaOps.ElkSharp;
|
|
|
|
internal static partial 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++)
|
|
{
|
|
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;
|
|
}
|
|
}
|