Skip to content

Commit

Permalink
Use MAAR packer instead of TileToRowsCC packer
Browse files Browse the repository at this point in the history
Should fix #93
  • Loading branch information
asl committed Jul 17, 2022
1 parent 958f49d commit b38b201
Showing 1 changed file with 196 additions and 149 deletions.
345 changes: 196 additions & 149 deletions layout/graphlayoutworker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
#include "ogdf/basic/GraphCopy.h"
#include "ogdf/basic/simple_graph_alg.h"
#include "ogdf/energybased/FMMMLayout.h"
#include "ogdf/energybased/fmmm/MAARPacking.h"
#include "ogdf/energybased/FastMultipoleEmbedder.h"
#include "ogdf/energybased/fmmm/FMMMOptions.h"
#include "ogdf/graphalg/ConvexHull.h"
#include "ogdf/packing/TileToRowsCCPacker.h"

#include <QFutureSynchronizer>
#include <QtConcurrent>
Expand Down Expand Up @@ -321,176 +320,224 @@ static void buildGraph(ogdf::Graph &ogdfGraph,
}
}

static void reassembleDrawings(ogdf::GraphAttributes &GA,
double graphLayoutComponentSeparation, double aspectRatio,
const ogdf::Array<ogdf::List<ogdf::node> > &nodesInCC) {
ogdf::TileToRowsCCPacker packer;
int numberOfComponents = nodesInCC.size();

ogdf::Array<ogdf::IPoint> box;
ogdf::Array<ogdf::IPoint> offset;
ogdf::Array<ogdf::DPoint> oldOffset;
ogdf::Array<double> rotation;
ogdf::ConvexHull CH;

// rotate components and create bounding rectangles

//iterate through all components and compute convex hull
for (int j = 0; j < numberOfComponents; j++) {
std::vector<ogdf::DPoint> points;

//collect node positions and at the same time center average
// at origin
double avg_x = 0.0;
double avg_y = 0.0;
for (ogdf::node v: nodesInCC[j]) {
ogdf::DPoint dp(GA.x(v), GA.y(v));
avg_x += dp.m_x;
avg_y += dp.m_y;
points.push_back(dp);
}
avg_x /= nodesInCC[j].size();
avg_y /= nodesInCC[j].size();

//adapt positions to origin
int count = 0;
//assume same order of vertices and positions
for (ogdf::node v: nodesInCC[j]) {
GA.x(v) = GA.x(v) - avg_x;
GA.y(v) = GA.y(v) - avg_y;
points.at(count).m_x -= avg_x;
points.at(count).m_y -= avg_y;

count++;
}
using namespace ogdf::energybased;

static fmmm::Rectangle
calculateBoundingRectangle(const ogdf::GraphAttributes &GA,
const ogdf::List<ogdf::node> &nodesInCC,
double graphLayoutComponentSeparation,
int componentIndex) {
ogdf::node first = nodesInCC.front();
fmmm::Rectangle r;

// max_boundary is the maximum of half of the width and half of the
// height of each node; (needed to be able to tip rectangles over
// without having access to the height and width of each node)
double max_boundary = std::max(GA.width(first) / 2, GA.height(first) / 2);
double x_min = GA.x(first) - max_boundary,
x_max = GA.x(first) + max_boundary,
y_min = GA.y(first) - max_boundary,
y_max = GA.y(first) + max_boundary;

for (ogdf::node v: nodesInCC) {
max_boundary = std::max(GA.width(v) / 2, GA.height(v) / 2);
double act_x_min = GA.x(v) - max_boundary,
act_x_max = GA.x(v) + max_boundary,
act_y_min = GA.y(v) - max_boundary,
act_y_max = GA.y(v) + max_boundary;
if (act_x_min < x_min) x_min = act_x_min;
if (act_x_max > x_max) x_max = act_x_max;
if (act_y_min < y_min) y_min = act_y_min;
if (act_y_max > y_max) y_max = act_y_max;
}

// calculate convex hull
ogdf::DPolygon hull = CH.call(points);

double best_area = std::numeric_limits<double>::max();
ogdf::DPoint best_normal;
double best_width = 0.0;
double best_height = 0.0;

// find best rotation by using every face as rectangle border once.
for (ogdf::DPolygon::iterator iter = hull.begin(); iter != hull.end(); ++iter) {
ogdf::DPolygon::iterator k = hull.cyclicSucc(iter);

double dist = 0.0;
ogdf::DPoint norm = CH.calcNormal(*k, *iter);
for (const ogdf::DPoint &z: hull) {
double d = CH.leftOfLine(norm, z, *k);
if (d > dist) {
dist = d;
}
}
// add offset
x_min -= graphLayoutComponentSeparation / 2;
x_max += graphLayoutComponentSeparation / 2;
y_min -= graphLayoutComponentSeparation / 2;
y_max += graphLayoutComponentSeparation / 2;

double left = 0.0;
double right = 0.0;
norm = CH.calcNormal(ogdf::DPoint(0, 0), norm);
for (const ogdf::DPoint &z: hull) {
double d = CH.leftOfLine(norm, z, *k);
if (d > left) {
left = d;
} else if (d < right) {
right = d;
}
}
double width = left - right;
r.set_rectangle(x_max - x_min, y_max - y_min, x_min, y_min, componentIndex);

ogdf::Math::updateMax(dist, 1.0);
ogdf::Math::updateMax(width, 1.0);
return r;
}

double area = dist * width;
static ogdf::List<fmmm::Rectangle>
calculateBoundingRectanglesOfComponents(const ogdf::GraphAttributes &GA,
const ogdf::Array<ogdf::List<ogdf::node> > &nodesInCC,
double graphLayoutComponentSeparation) {
int numCCs = nodesInCC.size();
ogdf::List<fmmm::Rectangle> R;

for (int i = 0; i < numCCs; ++i) {
auto r = calculateBoundingRectangle(GA, nodesInCC[i],
graphLayoutComponentSeparation, i);
R.pushBack(r);
}

if (area <= best_area) {
best_height = dist;
best_width = width;
best_area = area;
best_normal = CH.calcNormal(*k, *iter);
}
}
return R;
}

if (hull.size() <= 1) {
best_height = 1.0;
best_width = 1.0;
best_normal = ogdf::DPoint(1.0, 1.0);
static double calculateArea(double width, double height, int comp_nr,
double aspectRatio) {
double scaling = 1.0;
if (comp_nr == 1) { //calculate aspect ratio area of the rectangle
double ratio = width / height;
if (ratio < aspectRatio) { //scale width
scaling = aspectRatio / ratio;
} else { //scale height
scaling = ratio / aspectRatio;
}
}
return width * height * scaling;
}

double angle = -atan2(best_normal.m_y, best_normal.m_x) + 1.5 * ogdf::Math::pi;
if (best_width < best_height) {
angle += 0.5f * ogdf::Math::pi;
double temp = best_height;
best_height = best_width;
best_width = temp;
}
rotation.grow(1, angle);
double left = hull.front().m_x;
double top = hull.front().m_y;
double bottom = hull.front().m_y;
// apply rotation to hull and calc offset
for (ogdf::DPoint tempP: hull) {
double ang = atan2(tempP.m_y, tempP.m_x);
double len = sqrt(tempP.m_x * tempP.m_x + tempP.m_y * tempP.m_y);
ang += angle;
tempP.m_x = cos(ang) * len;
tempP.m_y = sin(ang) * len;

if (tempP.m_x < left) {
left = tempP.m_x;
static ogdf::List<fmmm::Rectangle>
rotateComponentsAndCalculateBoundingRectangles(
ogdf::GraphAttributes &GA,
const ogdf::Array<ogdf::List<ogdf::node> > &nodesInCC,
double graphLayoutComponentSeparation, double aspectRatio,
int stepsForRotatingComponents = 50) {
int numCCs = nodesInCC.size();
const ogdf::Graph &G = const_cast<ogdf::Graph &>(GA.constGraph());

ogdf::Array<ogdf::NodeArray<ogdf::DPoint> > best_coords(numCCs);
ogdf::Array<ogdf::NodeArray<ogdf::DPoint> > old_coords(numCCs);

fmmm::Rectangle r_act, r_best;
ogdf::DPoint new_pos, new_dlc;

ogdf::List<fmmm::Rectangle> R;

for (int i = 0; i < numCCs; i++) {
//init r_best, best_area and best_(old)coords
r_best = calculateBoundingRectangle(GA, nodesInCC[i],
graphLayoutComponentSeparation, i);
double best_area = calculateArea(r_best.get_width(), r_best.get_height(),
numCCs, aspectRatio);
best_coords[i].init(G);
old_coords[i].init(G);

for (ogdf::node v: nodesInCC[i])
old_coords[i][v] = best_coords[i][v] = GA.point(v);

//rotate the components
for (int j = 1; j <= stepsForRotatingComponents; j++) {
//calculate new positions for the nodes, the new rectangle and area
double angle = ogdf::Math::pi_2 * (double(j) / double(stepsForRotatingComponents + 1));
double sin_j = sin(angle);
double cos_j = cos(angle);
for (ogdf::node v: nodesInCC[i]) {
new_pos.m_x = cos_j * old_coords[i][v].m_x
- sin_j * old_coords[i][v].m_y;
new_pos.m_y = sin_j * old_coords[i][v].m_x
+ cos_j * old_coords[i][v].m_y;
GA.x(v) = new_pos.m_x;
GA.y(v) = new_pos.m_y;
}
if (tempP.m_y < top) {
top = tempP.m_y;
}
if (tempP.m_y > bottom) {
bottom = tempP.m_y;

r_act = calculateBoundingRectangle(GA, nodesInCC[i],
graphLayoutComponentSeparation, i);
double act_area = calculateArea(r_act.get_width(), r_act.get_height(),
numCCs, aspectRatio);

double act_area_PI_half_rotated;
if (numCCs == 1)
act_area_PI_half_rotated = calculateArea(r_act.get_height(),
r_act.get_width(),
numCCs, aspectRatio);

// store placement of the nodes with minimal area (in case that
// number_of_components >1) else store placement with minimal aspect ratio area
if (act_area < best_area) {
r_best = r_act;
best_area = act_area;
for (ogdf::node v: nodesInCC[i])
best_coords[i][v] = GA.point(v);
} else if ((numCCs == 1) && (act_area_PI_half_rotated <
best_area)) { //test if rotating further with PI_half would be an improvement
r_best = r_act;
best_area = act_area_PI_half_rotated;
for (ogdf::node v: nodesInCC[i])
best_coords[i][v] = GA.point(v);
//the needed rotation step follows in the next if statement
}
}
oldOffset.grow(1, ogdf::DPoint(left + 0.5 * graphLayoutComponentSeparation,
-1.0 * best_height + 1.0 * bottom + 0.0 * top +
0.5 * graphLayoutComponentSeparation));

// save rect
int w = static_cast<int>(best_width);
int h = static_cast<int>(best_height);
box.grow(1, ogdf::IPoint(w + graphLayoutComponentSeparation, h + graphLayoutComponentSeparation));
}

offset.init(box.size());
//tipp the smallest rectangle over by angle PI/2 around the origin if it makes the
//aspect_ratio of r_best more similar to the desired aspect_ratio
double ratio = r_best.get_width() / r_best.get_height();

// call packer
packer.call(box, offset, aspectRatio);
if ((aspectRatio < 1 && ratio > 1) || (aspectRatio >= 1 && ratio < 1)) {
for (ogdf::node v: nodesInCC[i]) {
new_pos.m_x = best_coords[i][v].m_y * (-1);
new_pos.m_y = best_coords[i][v].m_x;
best_coords[i][v] = new_pos;
}

int index = 0;
// Apply offset and rebuild Graph
for (int j = 0; j < numberOfComponents; j++) {
double angle = rotation[index];
// apply rotation and offset to all nodes
//calculate new rectangle
new_dlc.m_x = r_best.get_old_dlc_position().m_y * (-1) - r_best.get_height();
new_dlc.m_y = r_best.get_old_dlc_position().m_x;

for (ogdf::node v: nodesInCC[j]) {
double x = GA.x(v);
double y = GA.y(v);
double ang = atan2(y, x);
double len = sqrt(x * x + y * y);
ang += angle;
x = cos(ang) * len;
y = sin(ang) * len;
double new_width = r_best.get_height();
double new_height = r_best.get_width();
r_best.set_width(new_width);
r_best.set_height(new_height);
r_best.set_old_dlc_position(new_dlc);
}

x += static_cast<double>(offset[index].m_x);
y += static_cast<double>(offset[index].m_y);
//save the computed information in A_sub and R
for (ogdf::node v: nodesInCC[i]) {
GA.x(v) = best_coords[i][v].m_x;
GA.y(v) = best_coords[i][v].m_y;
}

x -= oldOffset[index].m_x;
y -= oldOffset[index].m_y;
R.pushBack(r_best);
}

GA.x(v) = x;
GA.y(v) = y;
return R;
}

static void reassembleDrawings(ogdf::GraphAttributes &GA,
double graphLayoutComponentSeparation, double aspectRatio,
const ogdf::Array<ogdf::List<ogdf::node> > &nodesInCC) {
#if 0
auto R =
calculateBoundingRectanglesOfComponents(GA, nodesInCC,
graphLayoutComponentSeparation);
#else
auto R =
rotateComponentsAndCalculateBoundingRectangles(GA, nodesInCC,
graphLayoutComponentSeparation, aspectRatio);
#endif

double aspect_ratio_area, bounding_rectangles_area;
fmmm::MAARPacking().pack_rectangles_using_Best_Fit_strategy(R, aspectRatio,
ogdf::FMMMOptions::PreSort::DecreasingHeight,
ogdf::FMMMOptions::TipOver::NoGrowingRow,
aspect_ratio_area, bounding_rectangles_area);

for (const auto &r: R) {
int i = r.get_component_index();
if (r.is_tipped_over()) {
// calculate tipped coordinates of the nodes
for (auto v: nodesInCC[i]) {
ogdf::DPoint tipped_pos(-GA.y(v), GA.x(v));
GA.x(v) = tipped_pos.m_x;
GA.y(v) = tipped_pos.m_y;
}
}

index++;
for (auto v: nodesInCC[i]) {
ogdf::DPoint newpos = GA.point(v);
newpos += r.get_new_dlc_position();
newpos -= r.get_old_dlc_position();
GA.x(v) = newpos.m_x;
GA.y(v) = newpos.m_y;
}
}
}


GraphLayout GraphLayoutWorker::layoutGraph(const AssemblyGraph &graph) {
ogdf::Graph G;
ogdf::EdgeArray<double> edgeLengths(G);
Expand Down

0 comments on commit b38b201

Please sign in to comment.