236 lines
8.3 KiB
C#
236 lines
8.3 KiB
C#
namespace StellaOps.ElkSharp;
|
|
|
|
internal static class ElkEdgeChannelGutters
|
|
{
|
|
internal static bool ExpandVerticalCorridorGutters(
|
|
Dictionary<string, ElkPositionedNode> positionedNodes,
|
|
IReadOnlyCollection<ElkRoutedEdge> routedEdges,
|
|
IReadOnlyDictionary<string, int> layersByNodeId,
|
|
IReadOnlyDictionary<string, ElkNode> nodesById,
|
|
double baseLayerSpacing,
|
|
ElkLayoutDirection direction)
|
|
{
|
|
if (direction != ElkLayoutDirection.LeftToRight)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var boundariesByLayer = layersByNodeId
|
|
.Where(entry => positionedNodes.ContainsKey(entry.Key))
|
|
.GroupBy(entry => entry.Value)
|
|
.OrderBy(group => group.Key)
|
|
.Select(group =>
|
|
{
|
|
var nodes = group.Select(entry => positionedNodes[entry.Key]).ToArray();
|
|
return new
|
|
{
|
|
Layer = group.Key,
|
|
Boundary = new LayerBoundary(
|
|
nodes.Min(node => node.X),
|
|
nodes.Max(node => node.X + node.Width),
|
|
nodes.Min(node => node.Y),
|
|
nodes.Max(node => node.Y + node.Height)),
|
|
};
|
|
})
|
|
.ToArray();
|
|
if (boundariesByLayer.Length < 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var requiredBoundaryDeltas = new Dictionary<int, double>();
|
|
for (var boundaryIndex = 0; boundaryIndex < boundariesByLayer.Length - 1; boundaryIndex++)
|
|
{
|
|
var current = boundariesByLayer[boundaryIndex];
|
|
var next = boundariesByLayer[boundaryIndex + 1];
|
|
var gap = next.Boundary.MinX - current.Boundary.MaxX;
|
|
if (gap <= 0d)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var verticalSegments = routedEdges
|
|
.SelectMany(edge => edge.Sections.SelectMany(section =>
|
|
{
|
|
var points = new List<ElkPoint> { section.StartPoint };
|
|
points.AddRange(section.BendPoints);
|
|
points.Add(section.EndPoint);
|
|
return points.Zip(points.Skip(1), (start, end) => new
|
|
{
|
|
Edge = edge,
|
|
Start = start,
|
|
End = end,
|
|
});
|
|
}))
|
|
.Where(segment =>
|
|
Math.Abs(segment.Start.X - segment.End.X) <= 0.01d
|
|
&& Math.Abs(segment.End.Y - segment.Start.Y) >= 36d
|
|
&& segment.Start.X > current.Boundary.MaxX + 8d
|
|
&& segment.Start.X < next.Boundary.MinX - 8d)
|
|
.ToArray();
|
|
var laneCount = verticalSegments
|
|
.Select(segment => Math.Round(segment.Start.X / 12d) * 12d)
|
|
.Distinct()
|
|
.Count();
|
|
if (laneCount == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var familyCount = verticalSegments
|
|
.Select(segment => ElkEdgeChannelBands.ResolveLaneFamilyKey(segment.Edge.Label))
|
|
.Distinct(StringComparer.Ordinal)
|
|
.Count();
|
|
var desiredGap = Math.Max(
|
|
baseLayerSpacing + 88d,
|
|
136d + (laneCount * 28d) + (Math.Max(0, familyCount - 1) * 24d));
|
|
if (gap >= desiredGap)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
requiredBoundaryDeltas[current.Layer] = desiredGap - gap;
|
|
}
|
|
|
|
if (requiredBoundaryDeltas.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var nodeId in positionedNodes.Keys.ToArray())
|
|
{
|
|
if (!layersByNodeId.TryGetValue(nodeId, out var nodeLayer))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var shiftX = requiredBoundaryDeltas
|
|
.Where(entry => nodeLayer > entry.Key)
|
|
.Sum(entry => entry.Value);
|
|
if (shiftX <= 0.01d)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var current = positionedNodes[nodeId];
|
|
positionedNodes[nodeId] = ElkLayoutHelpers.CreatePositionedNode(nodesById[nodeId], current.X + shiftX, current.Y, direction);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool CompactSparseVerticalCorridorGutters(
|
|
Dictionary<string, ElkPositionedNode> positionedNodes,
|
|
IReadOnlyCollection<ElkRoutedEdge> routedEdges,
|
|
IReadOnlyDictionary<string, int> layersByNodeId,
|
|
IReadOnlyDictionary<string, ElkNode> nodesById,
|
|
double baseLayerSpacing,
|
|
ElkLayoutDirection direction)
|
|
{
|
|
if (direction != ElkLayoutDirection.LeftToRight)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var boundariesByLayer = layersByNodeId
|
|
.Where(entry => positionedNodes.ContainsKey(entry.Key))
|
|
.GroupBy(entry => entry.Value)
|
|
.OrderBy(group => group.Key)
|
|
.Select(group =>
|
|
{
|
|
var nodes = group.Select(entry => positionedNodes[entry.Key]).ToArray();
|
|
return new
|
|
{
|
|
Layer = group.Key,
|
|
Boundary = new LayerBoundary(
|
|
nodes.Min(node => node.X),
|
|
nodes.Max(node => node.X + node.Width),
|
|
nodes.Min(node => node.Y),
|
|
nodes.Max(node => node.Y + node.Height)),
|
|
};
|
|
})
|
|
.ToArray();
|
|
if (boundariesByLayer.Length < 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var boundaryShifts = new Dictionary<int, double>();
|
|
for (var boundaryIndex = 0; boundaryIndex < boundariesByLayer.Length - 1; boundaryIndex++)
|
|
{
|
|
var current = boundariesByLayer[boundaryIndex];
|
|
var next = boundariesByLayer[boundaryIndex + 1];
|
|
var gap = next.Boundary.MinX - current.Boundary.MaxX;
|
|
if (gap <= 0d)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var verticalSegments = routedEdges
|
|
.SelectMany(edge => edge.Sections.SelectMany(section =>
|
|
{
|
|
var points = new List<ElkPoint> { section.StartPoint };
|
|
points.AddRange(section.BendPoints);
|
|
points.Add(section.EndPoint);
|
|
return points.Zip(points.Skip(1), (start, end) => new
|
|
{
|
|
Edge = edge,
|
|
Start = start,
|
|
End = end,
|
|
});
|
|
}))
|
|
.Where(segment =>
|
|
Math.Abs(segment.Start.X - segment.End.X) <= 0.01d
|
|
&& Math.Abs(segment.End.Y - segment.Start.Y) >= 36d
|
|
&& segment.Start.X > current.Boundary.MaxX + 8d
|
|
&& segment.Start.X < next.Boundary.MinX - 8d)
|
|
.ToArray();
|
|
var laneCount = verticalSegments
|
|
.Select(segment => Math.Round(segment.Start.X / 12d) * 12d)
|
|
.Distinct()
|
|
.Count();
|
|
var familyCount = verticalSegments
|
|
.Select(segment => ElkEdgeChannelBands.ResolveLaneFamilyKey(segment.Edge.Label))
|
|
.Distinct(StringComparer.Ordinal)
|
|
.Count();
|
|
|
|
var desiredGap = Math.Max(
|
|
baseLayerSpacing * 0.72d,
|
|
120d + (laneCount * 20d) + (Math.Max(0, familyCount - 1) * 16d));
|
|
var maxGap = desiredGap + 28d;
|
|
if (gap <= maxGap)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
boundaryShifts[current.Layer] = desiredGap - gap;
|
|
}
|
|
|
|
if (boundaryShifts.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var nodeId in positionedNodes.Keys.ToArray())
|
|
{
|
|
if (!layersByNodeId.TryGetValue(nodeId, out var nodeLayer))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var shiftX = boundaryShifts
|
|
.Where(entry => nodeLayer > entry.Key)
|
|
.Sum(entry => entry.Value);
|
|
if (Math.Abs(shiftX) <= 0.01d)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var current = positionedNodes[nodeId];
|
|
positionedNodes[nodeId] = ElkLayoutHelpers.CreatePositionedNode(nodesById[nodeId], current.X + shiftX, current.Y, direction);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|