Skip to content

Commit 0c436b2

Browse files
committed
forceRadial().angle() sets the preferred angle
as in https://observablehq.com/@fil/forcepolar closes #152
1 parent 6208fe7 commit 0c436b2

File tree

3 files changed

+60
-20
lines changed

3 files changed

+60
-20
lines changed

README.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,11 @@ function y() {
425425

426426
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.
427427

428-
<a name="forceRadial" href="#forceRadial">#</a> d3.<b>forceRadial</b>(<i>radius</i>[, <i>x</i>][, <i>y</i>]) · [Source](https://github.com/d3/d3-force/blob/master/src/radial.js)
428+
<a name="forceRadial" href="#forceRadial">#</a> d3.<b>forceRadial</b>(<i>radius</i>[, <i>x</i>][, <i>y</i>][, <i>angle</i>]) · [Source](https://github.com/d3/d3-force/blob/master/src/radial.js)
429429

430430
[<img alt="Radial Force" src="https://raw.githubusercontent.com/d3/d3-force/master/img/radial.png" width="420" height="219">](https://bl.ocks.org/mbostock/cd98bf52e9067e26945edd95e8cf6ef9)
431431

432-
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⟩.
432+
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.
433433

434434
<a name="radial_strength" href="#radial_strength">#</a> <i>radial</i>.<b>strength</b>([<i>strength</i>]) · [Source](https://github.com/d3/d3-force/blob/master/src/radial.js)
435435

@@ -447,7 +447,7 @@ The strength accessor is invoked for each [node](#simulation_nodes) in the simul
447447

448448
<a name="radial_radius" href="#radial_radius">#</a> <i>radial</i>.<b>radius</b>([<i>radius</i>]) · [Source](https://github.com/d3/d3-force/blob/master/src/radial.js)
449449

450-
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.
450+
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)).
451451

452452
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.
453453

@@ -475,3 +475,9 @@ function y() {
475475

476476
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.
477477

478+
<a name="radial_angle" href="#radial_angle">#</a> <i>radial</i>.<b>angle</b>([<i>angle</i>]) [<>](https://github.com/d3/d3-force/blob/master/src/radial.js "Source")
479+
480+
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.
481+
482+
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.
483+

src/math.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export var pi = Math.PI;
2+
export var radians = pi / 180;

src/radial.js

+49-17
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,69 @@
11
import constant from "./constant.js";
2+
import {radians} from "./math.js";
23

3-
export default function(radius, x, y) {
4+
function value(x) {
5+
if (typeof x === "function") return x;
6+
if (x === null || x === undefined || isNaN(x = +x)) return;
7+
return constant(x);
8+
}
9+
10+
export default function(radius, x, y, angle) {
411
var nodes,
512
strength = constant(0.1),
613
strengths,
7-
radiuses,
14+
radii,
815
xs,
9-
ys;
16+
ys,
17+
angles;
1018

11-
if (typeof radius !== "function") radius = constant(+radius);
12-
if (typeof x !== "function") x = constant(x == null ? 0 : +x);
13-
if (typeof y !== "function") y = constant(y == null ? 0 : +y);
19+
radius = value(radius);
20+
x = value(x) || constant(0);
21+
y = value(y) || constant(0);
22+
angle = value(angle);
1423

1524
function force(alpha) {
1625
for (var i = 0, n = nodes.length; i < n; ++i) {
1726
var node = nodes[i],
1827
dx = node.x - xs[i] || 1e-6,
1928
dy = node.y - ys[i] || 1e-6,
20-
r = Math.sqrt(dx * dx + dy * dy),
21-
k = (radiuses[i] - r) * strengths[i] * alpha / r;
22-
node.vx += dx * k;
23-
node.vy += dy * k;
29+
r = Math.sqrt(dx * dx + dy * dy);
30+
31+
if (radius) {
32+
var k = ((radii[i] - r) * strengths[i] * alpha) / r;
33+
node.vx += dx * k;
34+
node.vy += dy * k;
35+
}
36+
37+
if (angle) {
38+
var a = Math.atan2(dy, dx),
39+
diff = angles[i] - a,
40+
q = r * Math.sin(diff) * (strengths[i] * alpha);
41+
42+
// the factor below augments the "unease" for points that are opposite
43+
// the correct direction: in that case, though sin(diff) is small,
44+
// tan(diff/2) is very high
45+
q *= Math.hypot(1, Math.tan(diff / 2));
46+
47+
node.vx += -q * Math.sin(a);
48+
node.vy += q * Math.cos(a);
49+
}
2450
}
2551
}
2652

2753
function initialize() {
2854
if (!nodes) return;
2955
var i, n = nodes.length;
3056
strengths = new Array(n);
31-
radiuses = new Array(n);
57+
radii = new Array(n);
3258
xs = new Array(n);
3359
ys = new Array(n);
60+
angles = new Array(n);
3461
for (i = 0; i < n; ++i) {
35-
radiuses[i] = +radius(nodes[i], i, nodes);
62+
if (radius) radii[i] = +radius(nodes[i], i, nodes);
3663
xs[i] = +x(nodes[i], i, nodes);
3764
ys[i] = +y(nodes[i], i, nodes);
38-
strengths[i] = isNaN(radiuses[i]) ? 0 : +strength(nodes[i], i, nodes);
65+
if (angle) angles[i] = +angle(nodes[i], i, nodes) * radians;
66+
strengths[i] = isNaN(radii[i]) ? 0 : +strength(nodes[i], i, nodes);
3967
}
4068
}
4169

@@ -44,19 +72,23 @@ export default function(radius, x, y) {
4472
};
4573

4674
force.strength = function(_) {
47-
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
75+
return arguments.length ? (strength = value(_) || constant(1), initialize(), force) : strength;
4876
};
4977

5078
force.radius = function(_) {
51-
return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius;
79+
return arguments.length ? (radius = value(_), initialize(), force) : radius;
5280
};
5381

5482
force.x = function(_) {
55-
return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), initialize(), force) : x;
83+
return arguments.length ? (x = value(_) || constant(0), initialize(), force) : x;
5684
};
5785

5886
force.y = function(_) {
59-
return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), initialize(), force) : y;
87+
return arguments.length ? (y = value(_) || constant(0), initialize(), force) : y;
88+
};
89+
90+
force.angle = function(_) {
91+
return arguments.length ? (angle = value(_), initialize(), force) : y;
6092
};
6193

6294
return force;

0 commit comments

Comments
 (0)