Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

forceRadial x, y and angle accessors #167

Merged
merged 3 commits into from
Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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

[<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)

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.

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

Expand All @@ -445,14 +445,37 @@ The strength accessor is invoked for each [node](#simulation_nodes) in the simul

<a name="radial_radius" href="#radial_radius">#</a> <i>radial</i>.<b>radius</b>([<i>radius</i>]) [<>](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.

<a name="radial_x" href="#radial_x">#</a> <i>radial</i>.<b>x</b>([<i>x</i>]) [<>](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.

<a name="radial_y" href="#radial_y">#</a> <i>radial</i>.<b>y</b>([<i>y</i>]) [<>](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.

<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")

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.

2 changes: 2 additions & 0 deletions src/math.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export var pi = Math.PI;
export var radians = pi / 180;
74 changes: 56 additions & 18 deletions src/radial.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,69 @@
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);
}
}
}

function initialize() {
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);
}
}

Expand All @@ -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;
Expand Down