Files
2025-02-13 13:10:51 +02:00

193 lines
8.3 KiB
C++

#pragma once
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "Helpers.h"
#include "NodeClassification.h"
/// Prepares the graph for further processing.
template<RankingStrategy Strategy>
std::tuple<RankContainer, MaybeClassifier<Strategy>>
prepareGraph(InternalGraph &Graph, bool OmitClassification);
/// Approximates an optimal permutation selection.
template<RankingStrategy Strategy>
LayerContainer selectPermutation(InternalGraph &Graph,
RankContainer &Ranks,
const MaybeClassifier<Strategy> &Classifier);
/// A simplified permutation selection to only be used with simple tree.
LayerContainer selectSimpleTreePermutation(InternalGraph &Graph,
RankContainer &Ranks);
/// Topologically orders nodes of an augmented graph generated based on a
/// layered version of the graph.
std::vector<NodeView>
extractAugmentedTopologicalOrder(InternalGraph &Graph,
const LayerContainer &Layers);
/// Looks for the linear segments and ensures an optimal combination of them
/// is selected. It uses an algorithm from the Sander's paper.
/// The worst case complexity is O(N^2) in the cases where jump table is huge,
/// but the common case is very far from that because normally both
/// entry and exit edge count is low (intuitively, our layouts are tall rather
/// than wide).
///
/// \note: it's probably a good idea to think about loosening the dependence
/// on tall graph layouts since we will want to also lay more generic graphs
/// out.
SegmentContainer selectLinearSegments(InternalGraph &Graph,
const RankContainer &Ranks,
const LayerContainer &Layers,
const std::vector<NodeView> &Order);
/// Builds an empty linear segments map.
SegmentContainer emptyLinearSegments(InternalGraph &Graph);
/// "Levels up" a `LayerContainer` to a `LayoutContainer`.
LayoutContainer convertToLayout(const LayerContainer &Layers);
/// Calculates horizontal coordinates based on a finalized layout and segments.
void setHorizontalCoordinates(const LayerContainer &Layers,
const std::vector<NodeView> &Order,
const SegmentContainer &LinearSegments,
const LayoutContainer &Layout,
float MarginSize,
float VirtualNodeWeight = 0.1f);
/// Simplified horizontal coordinate calculation based on layers only.
void setStaticOffsetHorizontalCoordinates(const LayerContainer &Layers,
float MarginSize);
/// Distributes "touching" edges across lanes to minimize the crossing count.
LaneContainer assignLanes(InternalGraph &Graph,
const SegmentContainer &LinearSegments,
const LayoutContainer &Layout);
/// Calculates vertical coordinates based on layer and lane data.
void setVerticalCoordinates(const LayerContainer &Layers,
const LaneContainer &Lanes,
float MarginSize,
float EdgeDistance);
/// Routes edges that form backwards facing corners. For their indication,
/// V-shaped structures were added to the graph when the backwards edges
/// were partitioned.
CornerContainer routeBackwardsCorners(InternalGraph &Graph,
const RankContainer &Ranks,
const LaneContainer &Lanes,
float MarginSize,
float EdgeDistance);
/// Restores the original edge direction by flipping all the edges marked with
/// `IsBackwards == true`.
void restoreEdgeDirections(InternalGraph &Graph);
/// Produce the optimal routing order.
OrderedEdgeContainer orderEdges(InternalGraph &Graph,
CornerContainer &&Prerouted,
const RankContainer &Ranks,
const LaneContainer &Lanes);
void route(const OrderedEdgeContainer &OrderedListOfEdges,
float MarginSize,
float EdgeDistance);
void routeWithStraightLines(const OrderedEdgeContainer &OrderedListOfEdges);
/// Computes the layout given a graph and the configuration.
///
/// \note: it only works with `MutableEdgeNode`s.
template<yield::layout::sugiyama::RankingStrategy RS>
bool computeInternal(InternalGraph &Graph, const Configuration &Configuration) {
static_assert(StrictSpecializationOfMutableEdgeNode<InternalNode>,
"LayouterSugiyama requires mutable edge nodes.");
// There's nothing we can do for a graph without any nodes.
if (Graph.size() == 0)
return true;
// Prepare the graph for the layouter: this converts `Graph` into
// a DAG, guaranteeing that it's has no loops (some of the edges might have to
// be temporarily inverted to ensure this), with every node having a rank
// assigned based on the strategy specified by the template parameter.
//
// All the long edges (edges where the ranks of `From` and `To` nodes differ
// by more than 1) are split into `1-rank` sized chunks by inserting "virtual"
// nodes in-between.
//
// On top of that, all the backwards facing edges, are also split into
// multiple chunks and are marked by 'v'-shapes in order to be easily noticed
// by the router so they can be treated in a special way.
// For more details on this, see `routeBackwardsCorners` function.
bool ShouldClassify = !Configuration.UseSimpleTreeOptimization;
auto &&[Ranks, Classified] = prepareGraph<RS>(Graph, !ShouldClassify);
// Try to select an optimal node permutation per layer.
// NOTE: since this is the part with the highest complexity, it needs extra
// care for the layouter to perform well.
// Maybe we should consider something more optimal instead of a simple hill
// climbing algorithm.
auto Layers = Configuration.UseSimpleTreeOptimization ?
selectSimpleTreePermutation(Graph, Ranks) :
selectPermutation<RS>(Graph, Ranks, *Classified);
// Compute an augmented topological ordering of the nodes of the graph.
auto Order = extractAugmentedTopologicalOrder(Graph, Layers);
// Decide on which segments of the graph can be made linear, e.g. each edge
// within the same linear segment is a straight line.
SegmentContainer LinearSegments;
if (Configuration.PreserveLinearSegments)
LinearSegments = selectLinearSegments(Graph, Ranks, Layers, Order);
else
LinearSegments = emptyLinearSegments(Graph);
// Finalize the logical positions for each of the nodes.
const auto Final = convertToLayout(Layers);
// Finalize the horizontal node positions.
const auto &Margin = Configuration.NodeMarginSize;
if (Configuration.UseSimpleTreeOptimization) {
size_t MaximumNodeWidth = 0;
for (auto *Node : Graph.nodes())
if (Node->Size.W > MaximumNodeWidth)
MaximumNodeWidth = Node->Size.W;
setStaticOffsetHorizontalCoordinates(Layers, MaximumNodeWidth + Margin);
} else {
const auto &W = Configuration.VirtualNodeWeight;
setHorizontalCoordinates(Layers, Order, LinearSegments, Final, Margin, W);
}
// Distribute edge lanes in a way that minimizes the number of crossings.
auto Lanes = assignLanes(Graph, LinearSegments, Final);
// Set the rest of the coordinates. Node layouting is complete after this.
const auto &EdgeGap = Configuration.EdgeMarginSize;
setVerticalCoordinates(Layers, Lanes, Margin, EdgeGap);
// Route edges forming backwards facing corners.
CornerContainer Prerouted;
if (Configuration.UseOrthogonalBends)
Prerouted = routeBackwardsCorners(Graph, Ranks, Lanes, Margin, EdgeGap);
// Restore the original edge directions.
// The graph will no longer be a DAG.
restoreEdgeDirections(Graph);
// To simplify routing, order the edges first. This consumes the prerouted
// corners to build an ordered list of edges with all the information
// necessary for them to get routed (see `OrderedEdgeContainer`).
auto Edges = orderEdges(Graph, std::move(Prerouted), Ranks, Lanes);
// Route the edges.
if (Configuration.UseOrthogonalBends)
route(Edges, Margin, EdgeGap);
else
routeWithStraightLines(Edges);
return true;
}