Fish-eye view of a force-directed graph
in
Programming Questions
•
3 months ago
Hi All,
I'm trying to make a 2D fish-eye view of a force-directed graph. The problem I'm having is that whenever I change the position of the nodes to reflect an observer looking from a certain distance, the attraction of the nodes make them flock together, thereby ruining the fish-eye effect. It looks fine for a split second, but then they attract each other very quickly.
Any idea how to fix that?
The code is the following:
- ArrayList<Node> nodes = new ArrayList();
- ArrayList<Spring> springs = new ArrayList();
- int nnodes = 200;
- boolean fishEyeView = false;
- float observerRadius;
- PVector center;
- void setup() {
- size(800, 800);
- center = new PVector(width/2, height/2);
- observerRadius = min(width, height) / 2;
- background(255);
- initNodesAndSprings();
- smooth();
- }
- void draw() {
- background(255);
- for (Node n : nodes) {
- n.attract(nodes);
- n.update();
- n.render();
- }
- for (Spring s : springs) {
- s.update();
- s.render();
- }
- }
- void initNodesAndSprings() {
- for (int i = 0; i < nnodes; i++) {
- nodes.add(new Node(width/2 + random(-200, 200), height/2 + random(-200, 200), floor(random(10, 20))));
- }
- for (int j = 0; j < nodes.size()-1; j++) {
- int rCount = floor(random(1, 2));
- for (int i = 0; i < rCount; i++) {
- int r = floor(random(j+1, nodes.size()));
- Spring newSpring = new Spring(nodes.get(j), nodes.get(r));
- newSpring.length = 20;
- newSpring.stiffness = 1;
- springs.add(newSpring);
- }
- }
- }
- void keyPressed() {
- if (key == ' ') fishEyeView = !fishEyeView;
- }
- class Node {
- String id;
- float rad;
- float scaling = 1;
- float minX, maxX, minY, maxY;
- PVector pos;
- PVector velocity = new PVector(0, 0);
- float maxVelocity = 10;
- float damping = 0.5;
- float radius = 100;
- float strength = -5; // Negative ensures repulsion
- float ramp = 1.0;
- Node(float x, float y, int rad) {
- pos = new PVector(x, y);
- pos = new PVector(x, y);
- this.rad = rad;
- minX = rad;
- maxX = width - rad;
- minY = rad;
- maxY = height - rad;
- }
- void attract(ArrayList<Node> nodes) {
- for (Node n : nodes) {
- if (n == null) break; // stop when empty
- if (n == this) continue; // not with itself
- float d = PVector.dist(pos, n.pos);
- if (d > 0 && d < radius) {
- float s = pow(d / radius, 1 / ramp);
- float f = s * 9 * strength * (1 / (s + 1) + ((s - 3) / 4)) / d;
- PVector df = PVector.sub(pos, n.pos);
- df.mult(f);
- n.velocity.add(df);
- }
- }
- }
- void update() {
- velocity.limit(maxVelocity);
- pos.add(velocity);
- velocity.mult(1 - damping);
- if (fishEyeView) {
- screenPos();
- } else {
- scaling = 1;
- }
- }
- void render() {
- pushMatrix();
- translate(pos.x, pos.y);
- fill(50);
- ellipse(0, 0, (rad + 8) * scaling, (rad + 8) * scaling);
- stroke(255);
- strokeWeight(2);
- fill(0);
- ellipse(0, 0, rad * scaling, rad * scaling);
- fill(255);
- ellipse(0, 0, 0.1 * scaling, 0.1 * scaling);
- popMatrix();
- }
- void screenPos() {
- PVector centerToPos = PVector.sub(pos, center);
- float distanceToCenter = centerToPos.mag();
- float viewingAngle = atan(distanceToCenter / observerRadius);
- float newDistanceToCenter = viewingAngle * observerRadius;
- centerToPos.normalize();
- centerToPos.mult(newDistanceToCenter);
- scaling = map(viewingAngle, 0, HALF_PI, 1, 0.2);
- pos = PVector.add(center, centerToPos);
- }
- }
- class Spring {
- Node from;
- Node to;
- float length = 100;
- float stiffness = 0.6;
- float damping = 0.9;
- Spring(Node from, Node to) {
- this.from = from;
- this.to = to;
- }
- void update() {
- PVector diff = PVector.sub(to.pos, from.pos);
- diff.normalize();
- diff.mult(length);
- PVector target = PVector.add(from.pos, diff);
- PVector force = PVector.sub(target, to.pos);
- force.mult(0.5);
- force.mult(stiffness);
- force.mult(1 - damping);
- to.velocity.add(force);
- from.velocity.add(PVector.mult(force, -1));
- }
- void render() {
- stroke(0, 130, 164);
- strokeWeight(1);
- line(from.pos.x, from.pos.y, to.pos.x, to.pos.y);
- }
- }
1