#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 std::tuple> prepareGraph(InternalGraph &Graph, bool OmitClassification); /// Approximates an optimal permutation selection. template LayerContainer selectPermutation(InternalGraph &Graph, RankContainer &Ranks, const MaybeClassifier &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 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 &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 &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 bool computeInternal(InternalGraph &Graph, const Configuration &Configuration) { static_assert(StrictSpecializationOfMutableEdgeNode, "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(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(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; }