Improve rendering
This commit is contained in:
235
src/__Libraries/StellaOps.ElkSharp/ElkEdgeChannelGutters.cs
Normal file
235
src/__Libraries/StellaOps.ElkSharp/ElkEdgeChannelGutters.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user