Files
revng-revng/lib/GraphLayout/SugiyamaStyle/HorizontalPositions.cpp
Ivan Krysak 7d235f4fd0 Enforce licence header consistency
Also do some basic cleanup: capitalize first letters, add `.`
at the end of the sentences, and so on.
2023-07-03 15:23:10 +00:00

310 lines
12 KiB
C++

/// \file HorizontalPositions.cpp
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <map>
#include "InternalCompute.h"
/// To determine the coordinates, we need to solve this equation system:
/// X_n : X_n > X_p(n) + W_n / 2 + W_p(n) / 2 + margin X_n : X_n = X_s(n)
///
/// It can be done iteratively until convergence, see the paper for the proof.
///
/// \note: this function is too long. It should probably be split up.
void setHorizontalCoordinates(const LayerContainer &Layers,
const std::vector<NodeView> &Order,
const SegmentContainer &LinearSegments,
const LayoutContainer &Layout,
float MarginSize,
float VirtualNodeWeight) {
// `IterationCount` can be tuned depending on the types of nodes and edges
// NOTE: BFS layouts have wider layers, so it might make sense to make
// `IterationCount` larger for them.
size_t IterationCount = 10 + 5 * std::log2(Layers.size());
// Clear the existing coordinates so they don't interfere
for (auto Node : Order)
Node->Center = { 0, 0 };
// First, the minimal horizontal width for each layer is computed.
//
// Starting each layer at a proportional negative offset helps
// with centering the layout.
std::vector<double> MinimalLayerWidths;
MinimalLayerWidths.resize(Layers.size());
for (size_t Index = 0; Index < Layers.size(); ++Index)
for (auto Node : Layers[Index])
MinimalLayerWidths[Index] += Node->Size.W + MarginSize;
for (size_t Iteration = 0; Iteration < IterationCount; ++Iteration) {
for (auto [Child, Parent] : LinearSegments)
if (Child->Center.X > Parent->Center.X)
Parent->Center.X = Child->Center.X;
else
Child->Center.X = Parent->Center.X;
// NOTE: I feel like this loop can be A LOT simpler.
std::map<Rank, double> MinimumX;
for (auto Node : Order) {
auto LayerIndex = Layout.at(Node).Layer;
auto Iterator = MinimumX.lower_bound(LayerIndex);
if (Iterator == MinimumX.end() || Iterator->first > LayerIndex) {
auto Offset = -MinimalLayerWidths[LayerIndex] / 2;
Iterator = MinimumX.insert(Iterator, { LayerIndex, Offset });
}
auto LeftX = Iterator->second + Node->Size.W / 2 + MarginSize;
if (LeftX > Node->Center.X)
Node->Center.X = LeftX;
Iterator->second = Node->Center.X + Node->Size.W / 2 + MarginSize;
}
}
// At this point, the layout is very left heavy.
// To counteract this, nodes are pushed to the right if their neighbours are
// very far away and the successors are also on the right.
// The positions are weighted based on the successor positions.
for (size_t Iteration = 0; Iteration < IterationCount; ++Iteration) {
// First of all, a safe right margin is computed for each segment.
// A given coordinate is safe to use if no node in the segment would
// intersect a neighbour
std::map<NodeView, double> RightSegments;
{
// Check for segments that are on the rightmost edge of the
// graph, these are the segments that can be moved freely.
std::map<NodeView, bool> FreeSegments;
for (auto [Child, Parent] : LinearSegments) {
auto const &ChildPosition = Layout.at(Child);
auto ChildLayerSize = Layers.at(ChildPosition.Layer).size();
if (ChildLayerSize == ChildPosition.Index + 1) {
auto Iterator = FreeSegments.lower_bound(Parent);
if (Iterator == FreeSegments.end()
|| Iterator->first->index() > Parent->index())
FreeSegments.insert(Iterator, { Parent, true });
} else {
FreeSegments[Parent] = false;
}
}
std::vector<double> Barycenters(Layers.size());
for (size_t Index = 0; Index < Layers.size(); ++Index) {
if (Layers[Index].size() != 0) {
for (auto Node : Layers[Index])
Barycenters[Index] += Node->Center.X;
Barycenters[Index] /= Layers[Index].size();
}
}
for (size_t Index = 0; Index < Layers.size(); ++Index) {
if (Layers[Index].size() != 0) {
for (size_t J = 0; J < Layers[Index].size() - 1; ++J) {
auto Current = Layers[Index][J];
auto Next = Layers[Index][J + 1];
double RightMargin = Next->Center.X - Next->Size.W / 2
- MarginSize * 2 - Current->Size.W / 2;
auto Parent = LinearSegments.at(Current);
auto Iterator = RightSegments.lower_bound(Parent);
if (Iterator == RightSegments.end()
|| Iterator->first->index() > Parent->index())
RightSegments.insert(Iterator, { Parent, RightMargin });
else if (RightMargin < Iterator->second)
Iterator->second = RightMargin;
}
// Set the bounds for the rightmost node.
//
// Any coordinate is safe for the rightmost node, but the barycenters
// of both layers should be taken into the account, so that
// the layout does not become skewed
float RightMargin = Layers[Index].back()->Center.X;
if (Index + 1 < Layers.size())
if (auto Dlt = Barycenters[Index + 1] - Barycenters[Index]; Dlt > 0)
RightMargin += Dlt;
auto Parent = LinearSegments.at(Layers[Index].back());
auto Iterator = RightSegments.lower_bound(Parent);
if (Iterator == RightSegments.end()
|| Iterator->first->index() > Parent->index())
RightSegments.insert(Iterator, { Parent, RightMargin });
else if (FreeSegments.at(Parent)) {
if (RightMargin > Iterator->second)
Iterator->second = RightMargin;
} else if (RightMargin < Iterator->second) {
Iterator->second = RightMargin;
}
}
}
}
// Balance the graph to the right while keeping the safe right margin
for (size_t Index = Layers.size() - 1; Index != size_t(-1); --Index) {
if (Layers[Index].empty())
continue;
// First move the rightmost node.
if (auto Node = Layers[Index].back(); !Node->IsVirtual) {
if (Node->successorCount() > 0) {
double Barycenter = 0, TotalWeight = 0;
for (auto *Next : Node->successors()) {
float Weight = Next->IsVirtual ? 1 : VirtualNodeWeight;
// This weight as an arbitrary number used to help the algorithm
// prioritize putting a node closer to its non-virtual successors.
Barycenter += Next->Center.X / Weight;
TotalWeight += 1.f / Weight;
}
Barycenter /= TotalWeight;
if (Barycenter > Node->Center.X)
Node->Center.X = Barycenter;
}
}
// Then move the rest.
for (size_t J = Layers[Index].size() - 1; J != size_t(-1); --J) {
auto Node = Layers[Index][J];
float LayerMargin = RightSegments.at(LinearSegments.at(Node));
if (Node->successorCount() > 0) {
double Barycenter = 0;
double TotalWeight = 0;
for (auto *Next : Node->successors()) {
float Weight = Next->IsVirtual ? 1 : VirtualNodeWeight;
// This weight as an arbitrary number used to help the algorithm
// prioritize putting a node closer to its non-virtual successors.
Barycenter += Next->Center.X / Weight;
TotalWeight += 1.f / Weight;
}
Barycenter /= TotalWeight;
if (Barycenter < LayerMargin)
LayerMargin = Barycenter;
}
if (LayerMargin > Node->Center.X)
Node->Center.X = LayerMargin;
}
}
}
}
void setStaticOffsetHorizontalCoordinates(const LayerContainer &Layers,
float MarginSize) {
struct Subtree {
/// Represents the required width of a subtree where `1` unit represents
/// a width of single node.
size_t LogicalWidth = 0;
/// Represents the index of the node in it's layer (it's position relative
/// to its sibling nodes).
size_t LogicalPosition = 0;
/// A place to store final node position, after tree widths are taken into
/// the account.
size_t ActualPosition = 0;
};
// Build a table of sub-trees for each node, where a subtree represents
// the logical space required to fit all the successors of the node as well
// it's logical position in relation to its siblings.
//
// NOTE: this only computes logical placement, so `ActualPosition` is left
// unset.
std::unordered_map<NodeView, Subtree> LookupTable;
for (auto LayerInd = Layers.size() - 1; LayerInd != size_t(-1); --LayerInd) {
for (size_t Index = 0; Index < Layers[LayerInd].size(); ++Index) {
auto NodeView = Layers[LayerInd][Index];
auto SubtreeIterator = LookupTable.find(NodeView);
if (SubtreeIterator == LookupTable.end()) {
// Only add new lookup entry if there is not one present already.
bool Success = false;
std::tie(SubtreeIterator, Success) = LookupTable.try_emplace(NodeView);
revng_assert(Success);
SubtreeIterator->second.LogicalWidth = 1;
}
// Fill logical positions based on the input layer structure.
auto &CurrentSubtree = SubtreeIterator->second;
CurrentSubtree.LogicalPosition = Index;
// This relies on the fact that every node has at most one predecessor.
revng_assert(NodeView->predecessorCount() <= 1);
if (NodeView->hasPredecessors()) {
auto *Predecessor = *NodeView->predecessors().begin();
auto Iterator = LookupTable.find(Predecessor);
if (Iterator == LookupTable.end()) {
bool Success = false;
std::tie(Iterator, Success) = LookupTable.try_emplace(Predecessor);
revng_assert(Success);
// If the only predecessor of this node is not in the lookup table,
// add it while setting its width to that of the current node.
auto &PredecessorSubtree = Iterator->second;
PredecessorSubtree.LogicalWidth = CurrentSubtree.LogicalWidth;
} else {
// If there predecessor was already added (it has multiple
// successors), just add the current width to it.
auto &PredecessorSubtree = Iterator->second;
PredecessorSubtree.LogicalWidth += CurrentSubtree.LogicalWidth;
}
}
}
}
// Now that all the logical positions we decided by going over the layers
// backwards, one more forwards facing pass is used to decide on their actual
// placement.
for (size_t LayerIndex = 0; LayerIndex < Layers.size(); ++LayerIndex) {
size_t CurrentPosition = 0;
for (size_t Index = 0; Index < Layers[LayerIndex].size(); ++Index) {
auto &NodeView = Layers[LayerIndex][Index];
auto SubtreeIterator = LookupTable.find(NodeView);
revng_assert(SubtreeIterator != LookupTable.end());
auto &CurrentSubtree = SubtreeIterator->second;
// This relies on the fact that every node has at most one predecessor.
revng_assert(NodeView->predecessorCount() <= 1);
// If this node has to predecessors, consider it's `PredecessorPosition`
// to be the first available space to the right.
//
// NOTE: this also sets the position of the first entry node to 0.
size_t PredecessorPosition = CurrentPosition;
if (NodeView->hasPredecessors()) {
auto *Predecessor = *NodeView->predecessors().begin();
auto Iterator = LookupTable.find(Predecessor);
revng_assert(Iterator != LookupTable.end());
// Otherwise, if there is a predecessor, fill the position based on
// that.
auto &PredecessorSubtree = Iterator->second;
PredecessorPosition = PredecessorSubtree.ActualPosition;
}
// Make sure the current node is never to the left of its predecessor
// as that space might be occupied by the predecessor's sibling's tree.
CurrentPosition = std::max(CurrentPosition, PredecessorPosition);
// Export the computed position into both the lookup map (for
// the successors of this node to use) and as its final horizontal
// coordinate.
CurrentSubtree.ActualPosition = CurrentPosition;
auto Width = CurrentSubtree.LogicalWidth;
NodeView->Center.X = (Width / 2 + CurrentPosition) * MarginSize;
// Update the current position, so that no sibling tree occupies the same
// space.
CurrentPosition += CurrentSubtree.LogicalWidth;
}
}
}