Force-directed graph drawing

c¼h 2019-05-16

Merovius

Goal:

(1,2),(1,3),(1,5),(1,7),
(2,4),(2,6),
(3,6),(3,8),
(4,5),(4,9),
(5,11),
(6,9),(6,10),
(7,11),(7,8),
(8,10),
(9,11),
(10,11)
          

Idea

  1. Do physics simulation, with edges being springs
  2. ???
  3. Profit

Boilerplate

<div id="graph"></div>
<script>
  let graph = [[1,2], [0,2], [0,1]];
  let g = new Graph(
    document.getElementById("graph"),
    graph,
    function(g) { /* compute forces */ }
  );
</script>
// Reset forces to 0
this.nodes.forEach(function(n) {
  [n.ax, n.ay] = [0, 0];
})

// Run provided function to calculate forces
update(this);

// Step simulation
for (let i = 0; i < n; i++) {
  let n = this.nodes[i];
  // v' = a
  n.vx += n.ax;
  n.vy += n.ay;
  // x' = v
  n.x += n.vx;
  n.y += n.vy;

  // boundary-conditions
  if (n.x < 0) {
    n.x = 0;
    n.vx = Math.max(n.vx, -n.vx);
  }
  if (n.x > c.width-1) {
    n.x = c.width-1;
    n.vx = Math.min(n.vx, -n.vx);
  }
  if (n.y < 0) {
    n.y = 0;
    n.vy = Math.max(n.vy, -n.vy);
  }
  if (n.y > c.height-1) {
    n.y = c.height-1;
    n.vy = Math.min(n.vy, -n.vy);
  }
}

Spring forces

Hooke's law: \(\vec F = -k\vec x\)

function(g) {
  const springK = 0.001;
  for (let i = 0; i < g.nodes.length; i++) {
    let n = g.nodes[i];
    g.edges[i].forEach(function(j) {
      let m = g.nodes[j];
      let [dx, dy] = [n.x-m.x, n.y-m.y];
      n.ax -= springK * dx;
      n.ay -= springK * dy;
    })
  }
}

Drag

Drag force: \(\vec F = -c\vec v\)

function(g) {
  const springK = 0.001;
  const drag = 0.1;
  for (let i = 0; i < g.nodes.length; i++) {
    let n = g.nodes[i];
    g.edges[i].forEach(function(j) {
      let m = g.nodes[j];
      let [dx, dy] = [n.x-m.x, n.y-m.y];
      n.ax -= springK * dx;
      n.ay -= springK * dy;
    })
    n.ax -= n.vx * drag;
    n.ay -= n.vy * drag;
  }
}

Electrostatic forces

Coulomb's law: \(\vec F = k_e \frac{q_1q_2}{|r|^2}\frac{\vec r}{|r|}\)

function(g) {
  const springK = 0.001;
  const drag = 0.1;
  const electricK = 1000;
  for (let i = 0; i < g.nodes.length; i++) {
    let n = g.nodes[i];
    g.edges[i].forEach(function(j) {
      let m = g.nodes[j];
      let [dx, dy] = [n.x-m.x, n.y-m.y];
      n.ax -= springK * dx;
      n.ay -= springK * dy;
    })
    for (let j = 0; j < g.nodes.length; j++) {
      if (i == j) { continue; }
      let m = g.nodes[j];
      let [dx, dy] = [n.x-m.x, n.y-m.y];
      let d = Math.sqrt(dx*dx+dy*dy);
      if (d == 0) { continue; }
      let F = electricK / d**3;
      n.ax += dx * F;
      n.ay += dy * F;
    }
    n.ax -= n.vx * drag;
    n.ay -= n.vy * drag;
  }
}

Herschel Graph

Gravitation

Centering

Mouse attraction

Fin