Skip to content

Commit

Permalink
Simplify force calculations.
Browse files Browse the repository at this point in the history
We don't actually need the separate `fx` and `fy` attributes on each node;
instead we can modify the `x` and `y` attributes directly, since we're using
position Verlet! This commit also adds the layout's `alpha` parameter to the
tick event, such that normalized external forces can be applied.
  • Loading branch information
mbostock committed Apr 28, 2011
1 parent ee30e29 commit 51b8e02
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 88 deletions.
2 changes: 1 addition & 1 deletion d3.js
@@ -1,4 +1,4 @@
(function(){d3 = {version: "1.13.2"}; // semver (function(){d3 = {version: "1.13.3"}; // semver
if (!Date.now) Date.now = function() { if (!Date.now) Date.now = function() {
return +new Date(); return +new Date();
}; };
Expand Down
77 changes: 35 additions & 42 deletions d3.layout.js
Expand Up @@ -166,27 +166,6 @@ d3.layout.force = function() {
links, links,
distances; distances;


function accumulate(quad) {
var cx = 0,
cy = 0;
quad.count = 0;
if (!quad.leaf) {
quad.nodes.forEach(function(c) {
accumulate(c);
quad.count += c.count;
cx += c.count * c.cx;
cy += c.count * c.cy;
});
}
if (quad.point) {
quad.count++;
cx += quad.point.x;
cy += quad.point.y;
}
quad.cx = cx / quad.count;
quad.cy = cy / quad.count;
}

function repulse(node, kc) { function repulse(node, kc) {
return function(quad, x1, y1, x2, y2) { return function(quad, x1, y1, x2, y2) {
if (quad.point != node) { if (quad.point != node) {
Expand All @@ -197,15 +176,15 @@ d3.layout.force = function() {
/* Barnes-Hut criterion. */ /* Barnes-Hut criterion. */
if ((x2 - x1) * dn < theta) { if ((x2 - x1) * dn < theta) {
var k = kc * quad.count * dn * dn; var k = kc * quad.count * dn * dn;
node.fx += dx * k; node.x += dx * k;
node.fy += dy * k; node.y += dy * k;
return true; return true;
} }


if (quad.point) { if (quad.point) {
var k = kc * dn * dn; var k = kc * dn * dn;
node.fx += dx * k; node.x += dx * k;
node.fy += dy * k; node.y += dy * k;
} }
} }
}; };
Expand All @@ -223,20 +202,15 @@ d3.layout.force = function() {
x, // x-distance x, // x-distance
y; // y-distance y; // y-distance


// reset forces
i = -1; while (++i < n) {
(o = nodes[i]).fx = o.fy = 0;
}

// gauss-seidel relaxation for links // gauss-seidel relaxation for links
for (i = 0; i < m; ++i) { for (i = 0; i < m; ++i) {
o = links[i]; o = links[i];
s = o.source; s = o.source;
t = o.target; t = o.target;
x = t.x - s.x; x = t.x - s.x;
y = t.y - s.y; y = t.y - s.y;
if (l = Math.sqrt(x * x + y * y)) { if (l = (x * x + y * y)) {
l = alpha * (l - distance) / l; l = alpha * ((l = Math.sqrt(l)) - distance) / l;
x *= l; x *= l;
y *= l; y *= l;
t.x -= x; t.x -= x;
Expand All @@ -246,19 +220,19 @@ d3.layout.force = function() {
} }
} }


// compute quadtree center of mass
accumulate(q);

// apply gravity forces // apply gravity forces
var kg = alpha * gravity; var kg = alpha * gravity;
x = size[0] / 2; x = size[0] / 2;
y = size[1] / 2; y = size[1] / 2;
i = -1; while (++i < n) { i = -1; while (++i < n) {
o = nodes[i]; o = nodes[i];
o.fx += (x - o.x) * kg; o.x += (x - o.x) * kg;
o.fy += (y - o.y) * kg; o.y += (y - o.y) * kg;
} }


// compute quadtree center of mass
d3_layout_forceAccumulate(q);

// apply charge forces // apply charge forces
var kc = alpha * charge; var kc = alpha * charge;
i = -1; while (++i < n) { i = -1; while (++i < n) {
Expand All @@ -272,14 +246,12 @@ d3.layout.force = function() {
o.x = o.px; o.x = o.px;
o.y = o.py; o.y = o.py;
} else { } else {
x = o.px - (o.px = o.x); o.x -= (o.px - (o.px = o.x)) * drag;
y = o.py - (o.py = o.y); o.y -= (o.py - (o.py = o.y)) * drag;
o.x += o.fx - x * drag;
o.y += o.fy - y * drag;
} }
} }


event.tick.dispatch({type: "tick"}); event.tick.dispatch({type: "tick", alpha: alpha});


// simulated annealing, basically // simulated annealing, basically
return (alpha *= .99) < .005; return (alpha *= .99) < .005;
Expand Down Expand Up @@ -490,6 +462,27 @@ function d3_layout_forceCancel() {
d3.event.stopPropagation(); d3.event.stopPropagation();
d3.event.preventDefault(); d3.event.preventDefault();
} }

function d3_layout_forceAccumulate(quad) {
var cx = 0,
cy = 0;
quad.count = 0;
if (!quad.leaf) {
quad.nodes.forEach(function(c) {
d3_layout_forceAccumulate(c);
quad.count += c.count;
cx += c.count * c.cx;
cy += c.count * c.cy;
});
}
if (quad.point) {
quad.count++;
cx += quad.point.x;
cy += quad.point.y;
}
quad.cx = cx / quad.count;
quad.cy = cy / quad.count;
}
d3.layout.partition = function() { d3.layout.partition = function() {
var hierarchy = d3.layout.hierarchy(), var hierarchy = d3.layout.hierarchy(),
size = [1, 1]; // width, height size = [1, 1]; // width, height
Expand Down
2 changes: 1 addition & 1 deletion d3.layout.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion d3.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/core/core.js
@@ -1 +1 @@
d3 = {version: "1.13.2"}; // semver d3 = {version: "1.13.3"}; // semver
77 changes: 35 additions & 42 deletions src/layout/force.js
Expand Up @@ -14,27 +14,6 @@ d3.layout.force = function() {
links, links,
distances; distances;


function accumulate(quad) {
var cx = 0,
cy = 0;
quad.count = 0;
if (!quad.leaf) {
quad.nodes.forEach(function(c) {
accumulate(c);
quad.count += c.count;
cx += c.count * c.cx;
cy += c.count * c.cy;
});
}
if (quad.point) {
quad.count++;
cx += quad.point.x;
cy += quad.point.y;
}
quad.cx = cx / quad.count;
quad.cy = cy / quad.count;
}

function repulse(node, kc) { function repulse(node, kc) {
return function(quad, x1, y1, x2, y2) { return function(quad, x1, y1, x2, y2) {
if (quad.point != node) { if (quad.point != node) {
Expand All @@ -45,15 +24,15 @@ d3.layout.force = function() {
/* Barnes-Hut criterion. */ /* Barnes-Hut criterion. */
if ((x2 - x1) * dn < theta) { if ((x2 - x1) * dn < theta) {
var k = kc * quad.count * dn * dn; var k = kc * quad.count * dn * dn;
node.fx += dx * k; node.x += dx * k;
node.fy += dy * k; node.y += dy * k;
return true; return true;
} }


if (quad.point) { if (quad.point) {
var k = kc * dn * dn; var k = kc * dn * dn;
node.fx += dx * k; node.x += dx * k;
node.fy += dy * k; node.y += dy * k;
} }
} }
}; };
Expand All @@ -71,20 +50,15 @@ d3.layout.force = function() {
x, // x-distance x, // x-distance
y; // y-distance y; // y-distance


// reset forces
i = -1; while (++i < n) {
(o = nodes[i]).fx = o.fy = 0;
}

// gauss-seidel relaxation for links // gauss-seidel relaxation for links
for (i = 0; i < m; ++i) { for (i = 0; i < m; ++i) {
o = links[i]; o = links[i];
s = o.source; s = o.source;
t = o.target; t = o.target;
x = t.x - s.x; x = t.x - s.x;
y = t.y - s.y; y = t.y - s.y;
if (l = Math.sqrt(x * x + y * y)) { if (l = (x * x + y * y)) {
l = alpha * (l - distance) / l; l = alpha * ((l = Math.sqrt(l)) - distance) / l;
x *= l; x *= l;
y *= l; y *= l;
t.x -= x; t.x -= x;
Expand All @@ -94,19 +68,19 @@ d3.layout.force = function() {
} }
} }


// compute quadtree center of mass
accumulate(q);

// apply gravity forces // apply gravity forces
var kg = alpha * gravity; var kg = alpha * gravity;
x = size[0] / 2; x = size[0] / 2;
y = size[1] / 2; y = size[1] / 2;
i = -1; while (++i < n) { i = -1; while (++i < n) {
o = nodes[i]; o = nodes[i];
o.fx += (x - o.x) * kg; o.x += (x - o.x) * kg;
o.fy += (y - o.y) * kg; o.y += (y - o.y) * kg;
} }


// compute quadtree center of mass
d3_layout_forceAccumulate(q);

// apply charge forces // apply charge forces
var kc = alpha * charge; var kc = alpha * charge;
i = -1; while (++i < n) { i = -1; while (++i < n) {
Expand All @@ -120,14 +94,12 @@ d3.layout.force = function() {
o.x = o.px; o.x = o.px;
o.y = o.py; o.y = o.py;
} else { } else {
x = o.px - (o.px = o.x); o.x -= (o.px - (o.px = o.x)) * drag;
y = o.py - (o.py = o.y); o.y -= (o.py - (o.py = o.y)) * drag;
o.x += o.fx - x * drag;
o.y += o.fy - y * drag;
} }
} }


event.tick.dispatch({type: "tick"}); event.tick.dispatch({type: "tick", alpha: alpha});


// simulated annealing, basically // simulated annealing, basically
return (alpha *= .99) < .005; return (alpha *= .99) < .005;
Expand Down Expand Up @@ -338,3 +310,24 @@ function d3_layout_forceCancel() {
d3.event.stopPropagation(); d3.event.stopPropagation();
d3.event.preventDefault(); d3.event.preventDefault();
} }

function d3_layout_forceAccumulate(quad) {
var cx = 0,
cy = 0;
quad.count = 0;
if (!quad.leaf) {
quad.nodes.forEach(function(c) {
d3_layout_forceAccumulate(c);
quad.count += c.count;
cx += c.count * c.cx;
cy += c.count * c.cy;
});
}
if (quad.point) {
quad.count++;
cx += quad.point.x;
cy += quad.point.y;
}
quad.cx = cx / quad.count;
quad.cy = cy / quad.count;
}

0 comments on commit 51b8e02

Please sign in to comment.