Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkCompoundLayout.cs

301 lines
13 KiB
C#

namespace StellaOps.ElkSharp;
internal static partial class ElkCompoundLayout
{
internal static ElkLayoutResult Layout(
ElkGraph graph,
ElkLayoutOptions options,
ElkCompoundHierarchy hierarchy,
CancellationToken cancellationToken)
{
var nodesById = graph.Nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
var visibleNodes = graph.Nodes
.Where(node => hierarchy.IsLayoutVisibleNode(node.Id))
.ToArray();
var visibleNodesById = visibleNodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
var (inputOrder, backEdgeIds) = ElkLayerAssignment.BuildTraversalInputOrder(visibleNodes, graph.Edges, visibleNodesById);
var outgoing = visibleNodes.ToDictionary(node => node.Id, _ => new List<ElkEdge>(), StringComparer.Ordinal);
var incomingNodeIds = visibleNodes.ToDictionary(node => node.Id, _ => new List<string>(), StringComparer.Ordinal);
var outgoingNodeIds = visibleNodes.ToDictionary(node => node.Id, _ => new List<string>(), StringComparer.Ordinal);
foreach (var edge in graph.Edges)
{
outgoing[edge.SourceNodeId].Add(edge);
incomingNodeIds[edge.TargetNodeId].Add(edge.SourceNodeId);
outgoingNodeIds[edge.SourceNodeId].Add(edge.TargetNodeId);
}
var layersByNodeId = ElkLayerAssignment.AssignLayersByInputOrder(visibleNodes, outgoing, inputOrder, backEdgeIds);
var dummyResult = ElkLayerAssignment.InsertDummyNodes(visibleNodes, graph.Edges, layersByNodeId, inputOrder, backEdgeIds);
var allNodes = dummyResult.AllNodes;
var allEdges = dummyResult.AllEdges;
var augmentedNodesById = allNodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
var augmentedInputOrder = dummyResult.AugmentedInputOrder;
var augmentedIncoming = allNodes.ToDictionary(node => node.Id, _ => new List<string>(), StringComparer.Ordinal);
var augmentedOutgoing = allNodes.ToDictionary(node => node.Id, _ => new List<string>(), StringComparer.Ordinal);
foreach (var edge in allEdges)
{
augmentedIncoming[edge.TargetNodeId].Add(edge.SourceNodeId);
augmentedOutgoing[edge.SourceNodeId].Add(edge.TargetNodeId);
}
var orderingIterations = ElkNodePlacement.ResolveOrderingIterationCount(options, allEdges.Count, layersByNodeId.Count);
var layers = allNodes
.GroupBy(node => dummyResult.AugmentedLayers[node.Id])
.OrderBy(group => group.Key)
.Select(group => group.OrderBy(node => augmentedInputOrder[node.Id]).ToArray())
.ToArray();
layers = OptimizeLayerOrderingForHierarchy(
layers,
augmentedIncoming,
augmentedOutgoing,
augmentedInputOrder,
orderingIterations,
hierarchy,
dummyResult.DummyNodeIds);
var placementIterations = ElkNodePlacement.ResolvePlacementIterationCount(options, allNodes.Count, layers.Length);
var placementGrid = ElkNodePlacement.ResolvePlacementGrid(visibleNodes);
var positionedVisibleNodes = new Dictionary<string, ElkPositionedNode>(StringComparer.Ordinal);
var globalNodeWidth = visibleNodes.Max(node => node.Width);
var edgeDensityFactor = Math.Min(1.8d, 1d + (Math.Max(0, allEdges.Count - 15) * 0.02d));
var adaptiveNodeSpacing = options.NodeSpacing * edgeDensityFactor;
if (options.Direction == ElkLayoutDirection.LeftToRight)
{
ElkSharpLayoutInitialPlacement.PlaceNodesLeftToRight(
positionedVisibleNodes,
layers,
dummyResult,
augmentedIncoming,
augmentedOutgoing,
augmentedNodesById,
incomingNodeIds,
outgoingNodeIds,
visibleNodesById,
adaptiveNodeSpacing,
options,
placementIterations,
placementGrid);
}
else
{
ElkSharpLayoutInitialPlacement.PlaceNodesTopToBottom(
positionedVisibleNodes,
layers,
dummyResult,
augmentedIncoming,
augmentedOutgoing,
augmentedNodesById,
incomingNodeIds,
outgoingNodeIds,
visibleNodesById,
globalNodeWidth,
adaptiveNodeSpacing,
options,
placementIterations,
placementGrid);
}
var graphBounds = ElkGraphValidator.ComputeGraphBounds(positionedVisibleNodes.Values
.Where(node => !dummyResult.DummyNodeIds.Contains(node.Id))
.ToArray());
var layerBoundariesByNodeId = ElkLayoutHelpers.BuildLayerBoundariesByNodeId(positionedVisibleNodes, dummyResult.AugmentedLayers);
var edgeChannels = ElkEdgeChannels.ComputeEdgeChannels(graph.Edges, positionedVisibleNodes, options.Direction, layerBoundariesByNodeId);
var reconstructedEdges = ElkEdgeRouter.ReconstructDummyEdges(
graph.Edges,
dummyResult,
positionedVisibleNodes,
augmentedNodesById,
options.Direction,
graphBounds,
edgeChannels,
layerBoundariesByNodeId);
var routedEdges = graph.Edges
.Select(edge =>
{
if (reconstructedEdges.TryGetValue(edge.Id, out var routed))
{
return routed;
}
var channel = ElkSharpLayoutHelpers.ResolveSinkOverride(
edgeChannels.GetValueOrDefault(edge.Id),
edge.Id,
dummyResult,
edgeChannels,
graph.Edges);
return ElkEdgeRouter.RouteEdge(
edge,
visibleNodesById,
positionedVisibleNodes,
options.Direction,
graphBounds,
channel,
layerBoundariesByNodeId);
})
.ToArray();
for (var gutterPass = 0; gutterPass < 3; gutterPass++)
{
cancellationToken.ThrowIfCancellationRequested();
if (!ElkEdgeChannelGutters.ExpandVerticalCorridorGutters(
positionedVisibleNodes,
routedEdges,
dummyResult.AugmentedLayers,
augmentedNodesById,
options.LayerSpacing,
options.Direction))
{
break;
}
graphBounds = ElkGraphValidator.ComputeGraphBounds(positionedVisibleNodes.Values
.Where(node => !dummyResult.DummyNodeIds.Contains(node.Id))
.ToArray());
layerBoundariesByNodeId = ElkLayoutHelpers.BuildLayerBoundariesByNodeId(positionedVisibleNodes, dummyResult.AugmentedLayers);
edgeChannels = ElkEdgeChannels.ComputeEdgeChannels(graph.Edges, positionedVisibleNodes, options.Direction, layerBoundariesByNodeId);
reconstructedEdges = ElkEdgeRouter.ReconstructDummyEdges(
graph.Edges,
dummyResult,
positionedVisibleNodes,
augmentedNodesById,
options.Direction,
graphBounds,
edgeChannels,
layerBoundariesByNodeId);
routedEdges = graph.Edges
.Select(edge => reconstructedEdges.TryGetValue(edge.Id, out var rerouted)
? rerouted
: ElkEdgeRouter.RouteEdge(
edge,
visibleNodesById,
positionedVisibleNodes,
options.Direction,
graphBounds,
ElkSharpLayoutHelpers.ResolveSinkOverride(
edgeChannels.GetValueOrDefault(edge.Id),
edge.Id,
dummyResult,
edgeChannels,
graph.Edges),
layerBoundariesByNodeId))
.ToArray();
}
for (var compactPass = 0; compactPass < 2; compactPass++)
{
cancellationToken.ThrowIfCancellationRequested();
if (!ElkEdgeChannelGutters.CompactSparseVerticalCorridorGutters(
positionedVisibleNodes,
routedEdges,
dummyResult.AugmentedLayers,
augmentedNodesById,
options.LayerSpacing,
options.Direction))
{
break;
}
graphBounds = ElkGraphValidator.ComputeGraphBounds(positionedVisibleNodes.Values
.Where(node => !dummyResult.DummyNodeIds.Contains(node.Id))
.ToArray());
layerBoundariesByNodeId = ElkLayoutHelpers.BuildLayerBoundariesByNodeId(positionedVisibleNodes, dummyResult.AugmentedLayers);
edgeChannels = ElkEdgeChannels.ComputeEdgeChannels(graph.Edges, positionedVisibleNodes, options.Direction, layerBoundariesByNodeId);
reconstructedEdges = ElkEdgeRouter.ReconstructDummyEdges(
graph.Edges,
dummyResult,
positionedVisibleNodes,
augmentedNodesById,
options.Direction,
graphBounds,
edgeChannels,
layerBoundariesByNodeId);
routedEdges = graph.Edges
.Select(edge => reconstructedEdges.TryGetValue(edge.Id, out var rerouted)
? rerouted
: ElkEdgeRouter.RouteEdge(
edge,
visibleNodesById,
positionedVisibleNodes,
options.Direction,
graphBounds,
ElkSharpLayoutHelpers.ResolveSinkOverride(
edgeChannels.GetValueOrDefault(edge.Id),
edge.Id,
dummyResult,
edgeChannels,
graph.Edges),
layerBoundariesByNodeId))
.ToArray();
if (!ElkEdgeChannelGutters.ExpandVerticalCorridorGutters(
positionedVisibleNodes,
routedEdges,
dummyResult.AugmentedLayers,
augmentedNodesById,
options.LayerSpacing,
options.Direction))
{
continue;
}
graphBounds = ElkGraphValidator.ComputeGraphBounds(positionedVisibleNodes.Values
.Where(node => !dummyResult.DummyNodeIds.Contains(node.Id))
.ToArray());
layerBoundariesByNodeId = ElkLayoutHelpers.BuildLayerBoundariesByNodeId(positionedVisibleNodes, dummyResult.AugmentedLayers);
edgeChannels = ElkEdgeChannels.ComputeEdgeChannels(graph.Edges, positionedVisibleNodes, options.Direction, layerBoundariesByNodeId);
reconstructedEdges = ElkEdgeRouter.ReconstructDummyEdges(
graph.Edges,
dummyResult,
positionedVisibleNodes,
augmentedNodesById,
options.Direction,
graphBounds,
edgeChannels,
layerBoundariesByNodeId);
routedEdges = graph.Edges
.Select(edge => reconstructedEdges.TryGetValue(edge.Id, out var rerouted)
? rerouted
: ElkEdgeRouter.RouteEdge(
edge,
visibleNodesById,
positionedVisibleNodes,
options.Direction,
graphBounds,
ElkSharpLayoutHelpers.ResolveSinkOverride(
edgeChannels.GetValueOrDefault(edge.Id),
edge.Id,
dummyResult,
edgeChannels,
graph.Edges),
layerBoundariesByNodeId))
.ToArray();
}
var finalVisibleNodes = positionedVisibleNodes.Values
.Where(node => !dummyResult.DummyNodeIds.Contains(node.Id))
.OrderBy(node => inputOrder.GetValueOrDefault(node.Id, int.MaxValue))
.ToArray();
routedEdges = ElkEdgePostProcessor.SnapAnchorsToNodeBoundary(routedEdges, finalVisibleNodes);
routedEdges = ElkEdgeRouterIterative.Optimize(routedEdges, finalVisibleNodes, options, cancellationToken);
var compoundNodes = BuildCompoundPositionedNodes(graph.Nodes, hierarchy, positionedVisibleNodes, options);
if (TryResolveNegativeCoordinateShift(compoundNodes, out var shiftX, out var shiftY))
{
compoundNodes = ShiftNodes(graph.Nodes, compoundNodes, shiftX, shiftY, options.Direction);
routedEdges = ShiftEdges(routedEdges, shiftX, shiftY);
}
routedEdges = InsertCompoundBoundaryCrossings(routedEdges, compoundNodes, hierarchy);
ElkLayoutDiagnostics.LogProgress("ElkSharp compound layout optimize returned");
return new ElkLayoutResult
{
GraphId = graph.Id,
Nodes = graph.Nodes.Select(node => compoundNodes[node.Id]).ToArray(),
Edges = routedEdges,
};
}
}