Files
SAPFOR/Sapfor/_src/VisualizerCalls/graphLayout/fruchterman_reingold.cpp

84 lines
2.3 KiB
C++
Raw Normal View History

2023-09-14 19:43:13 +03:00
#include "fruchterman_reingold.hpp"
#include "algebra.hpp"
#include <algorithm>
#include <cmath>
#include <iostream>
namespace nodesoup {
using std::vector;
FruchtermanReingold::FruchtermanReingold(const adj_list_t& g, double k)
: g_(g)
, k_(k)
, k_squared_(k * k)
, temp_(10 * sqrt(g.size()))
, mvmts_(g_.size()) {}
void FruchtermanReingold::operator()(vector<Point2D>& positions) {
Vector2D zero = { 0.0, 0.0 };
fill(mvmts_.begin(), mvmts_.end(), zero);
// Repulsion force between vertice pairs
for (vertex_id_t v_id = 0; v_id < g_.size(); v_id++) {
for (vertex_id_t other_id = v_id + 1; other_id < g_.size(); other_id++) {
if (v_id == other_id) {
continue;
}
Vector2D delta = positions[v_id] - positions[other_id];
double distance = delta.norm();
// TODO: handle distance == 0.0
// > 1000.0: not worth computing
if (distance > 1000.0) {
continue;
}
double repulsion = k_squared_ / distance;
mvmts_[v_id] += delta / distance * repulsion;
mvmts_[other_id] -= delta / distance * repulsion;
}
// Attraction force between edges
for (vertex_id_t adj_id : g_[v_id]) {
if (adj_id > v_id) {
continue;
}
Vector2D delta = positions[v_id] - positions[adj_id];
double distance = delta.norm();
if (distance == 0.0) {
continue;
}
double attraction = distance * distance / k_;
mvmts_[v_id] -= delta / distance * attraction;
mvmts_[adj_id] += delta / distance * attraction;
}
}
// Max movement capped by current temperature
for (vertex_id_t v_id = 0; v_id < g_.size(); v_id++) {
double mvmt_norm = mvmts_[v_id].norm();
// < 1.0: not worth computing
if (mvmt_norm < 1.0) {
continue;
}
double capped_mvmt_norm = std::min(mvmt_norm, temp_);
Vector2D capped_mvmt = mvmts_[v_id] / mvmt_norm * capped_mvmt_norm;
positions[v_id] += capped_mvmt;
}
// Cool down fast until we reach 1.5, then stay at low temperature
if (temp_ > 1.5) {
temp_ *= 0.85;
} else {
temp_ = 1.5;
}
}
}