diff --git a/README.md b/README.md index a042ac7..6ba4f7d 100644 --- a/README.md +++ b/README.md @@ -456,3 +456,55 @@ If *x* is specified, sets the *x*-coordinate of the circle center to the specifi # 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. + +# d3.forceBoundary(x0, y0, x1, x1) [<>](https://github.com/d3/d3-force/blob/master/src/boundary.js "Source") + +[Boundary Force](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. + +# boundary.strength([strength]) [<>](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. + +# boundary.x0([x0]) [<>](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. + +# boundary.y0([y0]) [<>](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. + +# boundary.x1([x1]) [<>](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. + +# boundary.y1([y1]) [<>](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. + +# boundary.border([border]) [<>](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 + +[Boundary Forces with Border](http://bl.ocks.org/john-guerra/a7cb6691ab063726ffc1c7f29b9a6578) + +Boundary Forces with default border + +[Boundary Forces without Border](http://bl.ocks.org/john-guerra/9268633948b4e826e16c02a2e6858094) + +# boundary.hardBoundary([hardBoundary]) [<>](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. \ No newline at end of file diff --git a/img/boundaryBorder.gif b/img/boundaryBorder.gif new file mode 100644 index 0000000..b79b04e Binary files /dev/null and b/img/boundaryBorder.gif differ diff --git a/img/boundaryForcesWithBorder.png b/img/boundaryForcesWithBorder.png new file mode 100644 index 0000000..d38106c Binary files /dev/null and b/img/boundaryForcesWithBorder.png differ diff --git a/img/boundaryForcesWithoutBorder.png b/img/boundaryForcesWithoutBorder.png new file mode 100644 index 0000000..e36f42a Binary files /dev/null and b/img/boundaryForcesWithoutBorder.png differ diff --git a/src/boundary.js b/src/boundary.js new file mode 100644 index 0000000..7cc5dc4 --- /dev/null +++ b/src/boundary.js @@ -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; + } + + 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; +} \ No newline at end of file