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

Creating a new boundary force #115

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,55 @@ If *x* is specified, sets the *x*-coordinate of the circle center to the specifi
<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.

<a name="forceBoundary" href="#forceBoundary">#</a> d3.<b>forceBoundary</b>(<i>x0</i>, <i>y0</i>, <i>x1</i>, <i>x1</i>) [<>](https://github.com/d3/d3-force/blob/master/src/boundary.js "Source")

[<img alt="Boundary Force" src="https://raw.githubusercontent.com/john-guerra/d3-force/master/img/boundaryBorder.gif" width="420" height="219">](http://bl.ocks.org/john-guerra/784de2b8b15590f7e568029142075ad3/b61279c87e92bfe2ab6fd56371a1301a6d2f47ad)

Creates a new positioning force that tries to keep elements inside a defined boundary defined by [*x0*](#boundary_x0),[*y0*](#boundary_y0) and [*x1*](#boundary_x1),[*y1*](#boundary_y1). This is useful when you want to guarantee that all nodes remain visible inside the visualization.

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

If *strength* is specified, sets the strength accessor to the specified number or function, re-evaluates the strength accessor for each node, and returns this force. The *strength* determines how much to increment the node’s *x*- and *y*-velocity. For example, a value of 0.1 indicates that the node should move a tenth of the way from its current position to the closest point on the circle with each application. Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints. A value outside the range [0,1] is not recommended.

If *strength* is not specified, returns the current strength accessor, which defaults to:

```js
function strength() {
return 0.1;
}
```

The strength 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 strength of each node is only recomputed when the force is initialized or when this method is called with a new *strength*, and not on every application of the force.

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

If *x0* is specified, sets the starting *x*-coordinate of the boundary as an accessor (can be a function). If *x0* is not specified, returns the current starting *x*-coordinate of the boundary.

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

If *y0* is specified, sets the starting *y*-coordinate of the boundary as an accessor (can be a function). If *y0* is not specified, returns the current starting *y*-coordinate of the boundary.

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

If *x1* is specified, sets the ending *x*-coordinate of the boundary as an accessor (can be a function). If *x* is not specified, returns the current ending *x*-coordinate of the boundary.

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

If *y1* is specified, sets the ending *y*-coordinate of the boundary as an accessor (can be a function). If *y1* is not specified, returns the current ending *y*-coordinate of the boundary.

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

If *border* is specified, the force would work only at a *border* distance from the boundary. If *border* is not specified, returns the current *border* that defaults to the middle point of the border. Setting a border is useful when you only want to apply the force on the nodes that are reaching the boundary.

Boundary Forces with a border of 100 pixels

[<img alt="Boundary Forces with Border" src="https://raw.githubusercontent.com/john-guerra/d3-force/master/img/boundaryForcesWithBorder.png" width="420" height="219">](http://bl.ocks.org/john-guerra/a7cb6691ab063726ffc1c7f29b9a6578)

Boundary Forces with default border

[<img alt="Boundary Forces without Border" src="https://raw.githubusercontent.com/john-guerra/d3-force/master/img/boundaryForcesWithoutBorder.png" width="420" height="219">](http://bl.ocks.org/john-guerra/9268633948b4e826e16c02a2e6858094)

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

If *hardBoundary* is specified and is a falsifiable value it will determine if the nodes are going to be forced to be inside the boundary. A false value will try to send the nodes inside the boundary, but won't force them. If *hardBoundary* is not specified, returns the current *hardBoundary* that defaults to true.
Binary file added img/boundaryBorder.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/boundaryForcesWithBorder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/boundaryForcesWithoutBorder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export {default as forceRadial} from "./src/radial";
export {default as forceSimulation} from "./src/simulation";
export {default as forceX} from "./src/x";
export {default as forceY} from "./src/y";
export {default as forceBoundary} from "./src/boundary";
105 changes: 105 additions & 0 deletions src/boundary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import constant from "./constant";

export default function(x0, y0, x1, y1) {
var strength = constant(0.1),
hardBoundary = true,
border = constant( Math.min((x1 - x0)/2, (y1 - y0)/2) ),
nodes,
strengthsX,
strengthsY,
x0z, x1z,
y0z, y1z,
borderz,
halfX, halfY;


if (typeof x0 !== "function") x0 = constant(x0 == null ? -100 : +x0);
if (typeof x1 !== "function") x1 = constant(x1 == null ? 100 : +x1);
if (typeof y0 !== "function") y0 = constant(y0 == null ? -100 : +y0);
if (typeof y1 !== "function") y1 = constant(y1 == null ? 100 : +y1);

function getVx(halfX, x, strengthX, border, alpha) {
return (halfX - x) * Math.min(2, Math.abs( halfX - x) / halfX) * strengthX * alpha;
}

function force(alpha) {
for (var i = 0, n = nodes.length, node; i < n; ++i) {
node = nodes[i];

if ((node.x < (x0z[i] + borderz[i]) || node.x > (x1z[i] - borderz[i])) ||
(node.y < (y0z[i] + borderz[i]) || node.y > (y1z[i] - borderz[i])) ) {
node.vx += getVx(halfX[i], node.x, strengthsX[i], borderz[i], alpha);
node.vy += getVx(halfY[i], node.y, strengthsY[i], borderz[i], alpha);
} else {
node.vx = 0;
node.vy = 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this and found that setting vx/vy to zero broke my simulation (i'm applying forceBoundry last).
most likely this else should be removed

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may be what @hornj encountered

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @kumavis, I'll check it out

}

if (hardBoundary) {
if (node.x >= x1z[i]) node.vx += x1z[i] - node.x;
if (node.x <= x0z[i]) node.vx += x0z[i] - node.x;
if (node.y >= y1z[i]) node.vy += y1z[i] - node.y;
if (node.y <= y0z[i]) node.vy += y0z[i] - node.y;
}
}
}

function initialize() {
if (!nodes) return;
var i, n = nodes.length;
strengthsX = new Array(n);
strengthsY = new Array(n);
x0z = new Array(n);
y0z = new Array(n);
x1z = new Array(n);
y1z = new Array(n);
halfY = new Array(n);
halfX = new Array(n);
borderz = new Array(n);

for (i = 0; i < n; ++i) {
strengthsX[i] = (isNaN(x0z[i] = +x0(nodes[i], i, nodes)) ||
isNaN(x1z[i] = +x1(nodes[i], i, nodes))) ? 0 : +strength(nodes[i], i, nodes);
strengthsY[i] = (isNaN(y0z[i] = +y0(nodes[i], i, nodes)) ||
isNaN(y1z[i] = +y1(nodes[i], i, nodes))) ? 0 : +strength(nodes[i], i, nodes);
halfX[i] = x0z[i] + (x1z[i] - x0z[i])/2,
halfY[i] = y0z[i] + (y1z[i] - y0z[i])/2;
borderz[i] = +border(nodes[i], i, nodes)
}
}

force.initialize = function(_) {
nodes = _;
initialize();
};

force.x0 = function(_) {
return arguments.length ? (x0 = typeof _ === "function" ? _ : constant(+_), initialize(), force) : x0;
};

force.x1 = function(_) {
return arguments.length ? (x1 = typeof _ === "function" ? _ : constant(+_), initialize(), force) : x1;
};

force.y0 = function(_) {
return arguments.length ? (y0 = typeof _ === "function" ? _ : constant(+_), initialize(), force) : y0;
};

force.y1 = function(_) {
return arguments.length ? (y1 = typeof _ === "function" ? _ : constant(+_), initialize(), force) : y1;
};

force.strength = function(_) {
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength;
};

force.border = function(_) {
return arguments.length ? (border = typeof _ === "function" ? _ : constant(+_), initialize(), force) : border;
};

force.hardBoundary = function(_) {
return arguments.length ? (hardBoundary = _, force) : hardBoundary;
};

return force;
}