diff --git a/README.md b/README.md
index 3e1db85..3f08a46 100644
--- a/README.md
+++ b/README.md
@@ -423,11 +423,11 @@ function y() {
The *y*-accessor is invoked for each [node](#simulation_nodes) in the simulation, being passed the *node* and its zero-based *index*. The resulting number is then stored internally, such that the target *y*-coordinate of each node is only recomputed when the force is initialized or when this method is called with a new *y*, and not on every application of the force.
-# d3.forceRadial(radius[, x][, y]) [<>](https://github.com/d3/d3-force/blob/master/src/radial.js "Source")
+# d3.forceRadial(radius[, x][, y][, angle]) [<>](https://github.com/d3/d3-force/blob/master/src/radial.js "Source")
[
](https://bl.ocks.org/mbostock/cd98bf52e9067e26945edd95e8cf6ef9)
-Creates a new positioning force towards a circle of the specified [*radius*](#radial_radius) centered at ⟨[*x*](#radial_x),[*y*](#radial_y)⟩. If *x* and *y* are not specified, they default to ⟨0,0⟩.
+Creates a new positioning force towards a circle of the specified [*radius*](#radial_radius) centered at ⟨[*x*](#radial_x),[*y*](#radial_y)⟩, and with a preferred [*angle*](#radial_angle). If *x* and *y* are not specified, they default to ⟨0,0⟩. If *radius* or *angle* are not specified (or null), they are ignored.
# radial.strength([strength]) [<>](https://github.com/d3/d3-force/blob/master/src/radial.js "Source")
@@ -445,14 +445,37 @@ The strength accessor is invoked for each [node](#simulation_nodes) in the simul
# radial.radius([radius]) [<>](https://github.com/d3/d3-force/blob/master/src/radial.js "Source")
-If *radius* is specified, sets the circle *radius* to the specified number or function, re-evaluates the *radius* accessor for each node, and returns this force. If *radius* is not specified, returns the current *radius* accessor.
+If *radius* is specified, sets the circle *radius* to the specified number or function, re-evaluates the *radius* accessor for each node, and returns this force. If *radius* is not specified, returns the current *radius* accessor. If *angle* is null, the force ignores the radius (see [*radial*.angle](#radial_angle)).
The *radius* accessor is invoked for each [node](#simulation_nodes) in the simulation, being passed the *node* and its zero-based *index*. The resulting number is then stored internally, such that the target radius of each node is only recomputed when the force is initialized or when this method is called with a new *radius*, and not on every application of the force.
# radial.x([x]) [<>](https://github.com/d3/d3-force/blob/master/src/radial.js "Source")
-If *x* is specified, sets the *x*-coordinate of the circle center to the specified number and returns this force. If *x* is not specified, returns the current *x*-coordinate of the center, which defaults to zero.
+If *x* is specified, sets the *x*-coordinate accessor to the specified number or function, re-evaluates the *x*-accessor for each node, and returns this force. If *x* is not specified, returns the current *x*-accessor, which defaults to:
+
+```js
+function x() {
+ return 0;
+}
+```
+
+The *x*-accessor is invoked for each [node](#simulation_nodes) in the simulation, being passed the *node* and its zero-based *index*. The resulting number is then stored internally, such that the target *x*-coordinate of each node is only recomputed when the force is initialized or when this method is called with a new *x*, and not on every application of the force.
# radial.y([y]) [<>](https://github.com/d3/d3-force/blob/master/src/radial.js "Source")
-If *y* is specified, sets the *y*-coordinate of the circle center to the specified number and returns this force. If *y* is not specified, returns the current *y*-coordinate of the center, which defaults to zero.
+If *y* is specified, sets the *y*-coordinate accessor to the specified number or function, re-evaluates the *y*-accessor for each node, and returns this force. If *y* is not specified, returns the current *y*-accessor, which defaults to:
+
+```js
+function y() {
+ return 0;
+}
+```
+
+The *y*-accessor is invoked for each [node](#simulation_nodes) in the simulation, being passed the *node* and its zero-based *index*. The resulting number is then stored internally, such that the target *y*-coordinate of each node is only recomputed when the force is initialized or when this method is called with a new *y*, and not on every application of the force.
+
+# radial.angle([angle]) [<>](https://github.com/d3/d3-force/blob/master/src/radial.js "Source")
+
+If *angle* is specified, sets the preferred *angle* to the specified number or function, re-evaluates the *angle* accessor for each node, and returns this force. If *angle* is not specified, returns the current *angle* accessor. If *angle* is null, the force ignores the preferred angle.
+
+The *angle* accessor is invoked for each [node](#simulation_nodes) in the simulation, being passed the *node* and its zero-based *index*. The resulting number is then stored internally, such that the target angle of each node is only recomputed when the force is initialized or when this method is called with a new *angle*, and not on every application of the force.
+
diff --git a/src/math.js b/src/math.js
new file mode 100644
index 0000000..d38afca
--- /dev/null
+++ b/src/math.js
@@ -0,0 +1,2 @@
+export var pi = Math.PI;
+export var radians = pi / 180;
diff --git a/src/radial.js b/src/radial.js
index 609516b..3fe3ff1 100644
--- a/src/radial.js
+++ b/src/radial.js
@@ -1,24 +1,52 @@
import constant from "./constant.js";
+import {radians} from "./math.js";
-export default function(radius, x, y) {
+function value(x) {
+ if (typeof x === "function") return x;
+ if (x === null || x === undefined || isNaN(x = +x)) return;
+ return constant(x);
+}
+
+export default function(radius, x, y, angle) {
var nodes,
strength = constant(0.1),
strengths,
- radiuses;
+ radii,
+ xs,
+ ys,
+ angles;
- if (typeof radius !== "function") radius = constant(+radius);
- if (x == null) x = 0;
- if (y == null) y = 0;
+ radius = value(radius);
+ x = value(x) || constant(0);
+ y = value(y) || constant(0);
+ angle = value(angle);
function force(alpha) {
for (var i = 0, n = nodes.length; i < n; ++i) {
var node = nodes[i],
- dx = node.x - x || 1e-6,
- dy = node.y - y || 1e-6,
- r = Math.sqrt(dx * dx + dy * dy),
- k = (radiuses[i] - r) * strengths[i] * alpha / r;
- node.vx += dx * k;
- node.vy += dy * k;
+ dx = node.x - xs[i] || 1e-6,
+ dy = node.y - ys[i] || 1e-6,
+ r = Math.sqrt(dx * dx + dy * dy);
+
+ if (radius) {
+ var k = ((radii[i] - r) * strengths[i] * alpha) / r;
+ node.vx += dx * k;
+ node.vy += dy * k;
+ }
+
+ if (angle) {
+ var a = Math.atan2(dy, dx),
+ diff = angles[i] - a,
+ q = r * Math.sin(diff) * (strengths[i] * alpha);
+
+ // the factor below augments the "unease" for points that are opposite
+ // the correct direction: in that case, though sin(diff) is small,
+ // tan(diff/2) is very high
+ q *= Math.hypot(1, Math.tan(diff / 2));
+
+ node.vx += -q * Math.sin(a);
+ node.vy += q * Math.cos(a);
+ }
}
}
@@ -26,10 +54,16 @@ export default function(radius, x, y) {
if (!nodes) return;
var i, n = nodes.length;
strengths = new Array(n);
- radiuses = new Array(n);
+ radii = new Array(n);
+ xs = new Array(n);
+ ys = new Array(n);
+ angles = new Array(n);
for (i = 0; i < n; ++i) {
- radiuses[i] = +radius(nodes[i], i, nodes);
- strengths[i] = isNaN(radiuses[i]) ? 0 : +strength(nodes[i], i, nodes);
+ if (radius) radii[i] = +radius(nodes[i], i, nodes);
+ xs[i] = +x(nodes[i], i, nodes);
+ ys[i] = +y(nodes[i], i, nodes);
+ if (angle) angles[i] = +angle(nodes[i], i, nodes) * radians;
+ strengths[i] = isNaN(radii[i]) ? 0 : +strength(nodes[i], i, nodes);
}
}
@@ -38,19 +72,23 @@ export default function(radius, x, y) {
};
force.strength = function(_) {
- return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
+ return arguments.length ? (strength = value(_) || constant(1), initialize(), force) : strength;
};
force.radius = function(_) {
- return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius;
+ return arguments.length ? (radius = value(_), initialize(), force) : radius;
};
force.x = function(_) {
- return arguments.length ? (x = +_, force) : x;
+ return arguments.length ? (x = value(_) || constant(0), initialize(), force) : x;
};
force.y = function(_) {
- return arguments.length ? (y = +_, force) : y;
+ return arguments.length ? (y = value(_) || constant(0), initialize(), force) : y;
+ };
+
+force.angle = function(_) {
+ return arguments.length ? (angle = value(_), initialize(), force) : y;
};
return force;