Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

force layout: charge and friction are now functors, which allow for prod... #800

Closed
wants to merge 1 commit into from

2 participants

@GerHobbelt

...ucing a per-node friction and/or charge value on every 'tick'; when possible, the charge callback (if one was specified) receives a reference to the constructed quadtree as a third argument: there is no need to have the user construct a costly second quadtree on every tick.

http://bl.ocks.org/3616279 and several other gists of mine can be simplified to using a single force instead of layering two of those on top of one another, when this (and gravity as per-node functor) is done. The interface does not change, thanks to the use of these functors, but the ability of the force layout increases a lot.

The only place where this reduces performance, is in the non-standard case where charge was specifically set to ZERO: now the quadtree is constructed anyhow and the total charge is now calculated (absolute value sum), which is extra work for that situation; anybody else should observe about the same performance.

@GerHobbelt GerHobbelt force layout: charge and friction are now functors, which allow for p…
…roducing a per-node friction and/or charge value on every 'tick'; when possible, the charge callback (if one was specified) receives a reference to the constructed quadtree as a third argument: there is no need to have the user construct a costly second quadtree on every tick.
5feaebc
@mbostock
Owner

Thanks for this pull request, too.

Disabling the construction of the quadtree when no charge force is specified is an important feature for improving performance when using custom forces. And likewise, I prefer to have faster code paths when the charge is defined as a constant (and friction is currently always a constant), rather than upcasting to functors. Another reason is that the upcast to functor should not be exposed externally; see #895 for example.

In general I recommend using the "tick" event if you want to implement custom forces, as in the collision detection example. Alternatively, if you want to customize the behavior completely, I recommend forking the entire force layout and rebuilding it from scratch since it’s not too big. :)

@mbostock mbostock closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 5, 2012
  1. @GerHobbelt

    force layout: charge and friction are now functors, which allow for p…

    GerHobbelt authored
    …roducing a per-node friction and/or charge value on every 'tick'; when possible, the charge callback (if one was specified) receives a reference to the constructed quadtree as a third argument: there is no need to have the user construct a costly second quadtree on every tick.
This page is out of date. Refresh to see the latest.
Showing with 22 additions and 18 deletions.
  1. +22 −18 src/layout/force.js
View
40 src/layout/force.js
@@ -5,10 +5,10 @@ d3.layout.force = function() {
size = [1, 1],
drag,
alpha,
- friction = .9,
+ friction = d3_functor(.9),
linkDistance = d3_layout_forceLinkDistance,
linkStrength = d3_layout_forceLinkStrength,
- charge = -30,
+ charge = d3_functor(-30),
gravity = .1,
theta = .8,
interval,
@@ -53,6 +53,7 @@ d3.layout.force = function() {
var n = nodes.length,
m = links.length,
q,
+ f,
i, // current index
o, // current object
s, // current source
@@ -92,8 +93,16 @@ d3.layout.force = function() {
}
// compute quadtree center of mass and apply charge forces
- if (charge) {
- d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
+ f = 0;
+ q = d3.geom.quadtree(nodes);
+ // recalculate charges on every tick if need be:
+ charges = [];
+ for (i = 0; i < n; ++i) {
+ charges[i] = k = +charge.call(this, nodes[i], i, q);
+ f += Math.abs(k);
+ }
+ if (f != 0) {
+ d3_layout_forceAccumulate(q, alpha, charges);
i = -1; while (++i < n) {
if (!(o = nodes[i]).fixed) {
q.visit(repulse(o));
@@ -108,8 +117,9 @@ d3.layout.force = function() {
o.x = o.px;
o.y = o.py;
} else {
- o.x -= (o.px - (o.px = o.x)) * friction;
- o.y -= (o.py - (o.py = o.y)) * friction;
+ f = friction.call(this, o, i);
+ o.x -= (o.px - (o.px = o.x)) * f;
+ o.y -= (o.py - (o.py = o.y)) * f;
}
}
@@ -151,25 +161,25 @@ d3.layout.force = function() {
force.friction = function(x) {
if (!arguments.length) return friction;
- friction = x;
+ friction = d3_functor(x);
return force;
};
force.charge = function(x) {
if (!arguments.length) return charge;
- charge = typeof x === "function" ? x : +x;
+ charge = d3_functor(x);
return force;
};
force.gravity = function(x) {
if (!arguments.length) return gravity;
- gravity = x;
+ gravity = +x;
return force;
};
force.theta = function(x) {
if (!arguments.length) return theta;
- theta = x;
+ theta = +x;
return force;
};
@@ -223,14 +233,8 @@ d3.layout.force = function() {
}
charges = [];
- if (typeof charge === "function") {
- for (i = 0; i < n; ++i) {
- charges[i] = +charge.call(this, nodes[i], i);
- }
- } else {
- for (i = 0; i < n; ++i) {
- charges[i] = charge;
- }
+ for (i = 0; i < n; ++i) {
+ charges[i] = +charge.call(this, nodes[i], i);
}
// initialize node position based on first neighbor
Something went wrong with that request. Please try again.