Built with blockbuilder.org
Last active
April 18, 2018 22:07
-
-
Save john-guerra/9268633948b4e826e16c02a2e6858094 to your computer and use it in GitHub Desktop.
d3.forceBoundary Forces no Border
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://d3js.org/d3-force/ Version 1.1.0. Copyright 2018 Mike Bostock. | |
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-quadtree'), require('d3-collection'), require('d3-dispatch'), require('d3-timer')) : | |
typeof define === 'function' && define.amd ? define(['exports', 'd3-quadtree', 'd3-collection', 'd3-dispatch', 'd3-timer'], factory) : | |
(factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3,global.d3)); | |
}(this, (function (exports,d3Quadtree,d3Collection,d3Dispatch,d3Timer) { 'use strict'; | |
var center = function(x, y) { | |
var nodes; | |
if (x == null) x = 0; | |
if (y == null) y = 0; | |
function force() { | |
var i, | |
n = nodes.length, | |
node, | |
sx = 0, | |
sy = 0; | |
for (i = 0; i < n; ++i) { | |
node = nodes[i], sx += node.x, sy += node.y; | |
} | |
for (sx = sx / n - x, sy = sy / n - y, i = 0; i < n; ++i) { | |
node = nodes[i], node.x -= sx, node.y -= sy; | |
} | |
} | |
force.initialize = function(_) { | |
nodes = _; | |
}; | |
force.x = function(_) { | |
return arguments.length ? (x = +_, force) : x; | |
}; | |
force.y = function(_) { | |
return arguments.length ? (y = +_, force) : y; | |
}; | |
return force; | |
}; | |
var constant = function(x) { | |
return function() { | |
return x; | |
}; | |
}; | |
var jiggle = function() { | |
return (Math.random() - 0.5) * 1e-6; | |
}; | |
function x(d) { | |
return d.x + d.vx; | |
} | |
function y(d) { | |
return d.y + d.vy; | |
} | |
var collide = function(radius) { | |
var nodes, | |
radii, | |
strength = 1, | |
iterations = 1; | |
if (typeof radius !== "function") radius = constant(radius == null ? 1 : +radius); | |
function force() { | |
var i, n = nodes.length, | |
tree, | |
node, | |
xi, | |
yi, | |
ri, | |
ri2; | |
for (var k = 0; k < iterations; ++k) { | |
tree = d3Quadtree.quadtree(nodes, x, y).visitAfter(prepare); | |
for (i = 0; i < n; ++i) { | |
node = nodes[i]; | |
ri = radii[node.index], ri2 = ri * ri; | |
xi = node.x + node.vx; | |
yi = node.y + node.vy; | |
tree.visit(apply); | |
} | |
} | |
function apply(quad, x0, y0, x1, y1) { | |
var data = quad.data, rj = quad.r, r = ri + rj; | |
if (data) { | |
if (data.index > node.index) { | |
var x = xi - data.x - data.vx, | |
y = yi - data.y - data.vy, | |
l = x * x + y * y; | |
if (l < r * r) { | |
if (x === 0) x = jiggle(), l += x * x; | |
if (y === 0) y = jiggle(), l += y * y; | |
l = (r - (l = Math.sqrt(l))) / l * strength; | |
node.vx += (x *= l) * (r = (rj *= rj) / (ri2 + rj)); | |
node.vy += (y *= l) * r; | |
data.vx -= x * (r = 1 - r); | |
data.vy -= y * r; | |
} | |
} | |
return; | |
} | |
return x0 > xi + r || x1 < xi - r || y0 > yi + r || y1 < yi - r; | |
} | |
} | |
function prepare(quad) { | |
if (quad.data) return quad.r = radii[quad.data.index]; | |
for (var i = quad.r = 0; i < 4; ++i) { | |
if (quad[i] && quad[i].r > quad.r) { | |
quad.r = quad[i].r; | |
} | |
} | |
} | |
function initialize() { | |
if (!nodes) return; | |
var i, n = nodes.length, node; | |
radii = new Array(n); | |
for (i = 0; i < n; ++i) node = nodes[i], radii[node.index] = +radius(node, i, nodes); | |
} | |
force.initialize = function(_) { | |
nodes = _; | |
initialize(); | |
}; | |
force.iterations = function(_) { | |
return arguments.length ? (iterations = +_, force) : iterations; | |
}; | |
force.strength = function(_) { | |
return arguments.length ? (strength = +_, force) : strength; | |
}; | |
force.radius = function(_) { | |
return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius; | |
}; | |
return force; | |
}; | |
function index(d) { | |
return d.index; | |
} | |
function find(nodeById, nodeId) { | |
var node = nodeById.get(nodeId); | |
if (!node) throw new Error("missing: " + nodeId); | |
return node; | |
} | |
var link = function(links) { | |
var id = index, | |
strength = defaultStrength, | |
strengths, | |
distance = constant(30), | |
distances, | |
nodes, | |
count, | |
bias, | |
iterations = 1; | |
if (links == null) links = []; | |
function defaultStrength(link) { | |
return 1 / Math.min(count[link.source.index], count[link.target.index]); | |
} | |
function force(alpha) { | |
for (var k = 0, n = links.length; k < iterations; ++k) { | |
for (var i = 0, link, source, target, x, y, l, b; i < n; ++i) { | |
link = links[i], source = link.source, target = link.target; | |
x = target.x + target.vx - source.x - source.vx || jiggle(); | |
y = target.y + target.vy - source.y - source.vy || jiggle(); | |
l = Math.sqrt(x * x + y * y); | |
l = (l - distances[i]) / l * alpha * strengths[i]; | |
x *= l, y *= l; | |
target.vx -= x * (b = bias[i]); | |
target.vy -= y * b; | |
source.vx += x * (b = 1 - b); | |
source.vy += y * b; | |
} | |
} | |
} | |
function initialize() { | |
if (!nodes) return; | |
var i, | |
n = nodes.length, | |
m = links.length, | |
nodeById = d3Collection.map(nodes, id), | |
link; | |
for (i = 0, count = new Array(n); i < m; ++i) { | |
link = links[i], link.index = i; | |
if (typeof link.source !== "object") link.source = find(nodeById, link.source); | |
if (typeof link.target !== "object") link.target = find(nodeById, link.target); | |
count[link.source.index] = (count[link.source.index] || 0) + 1; | |
count[link.target.index] = (count[link.target.index] || 0) + 1; | |
} | |
for (i = 0, bias = new Array(m); i < m; ++i) { | |
link = links[i], bias[i] = count[link.source.index] / (count[link.source.index] + count[link.target.index]); | |
} | |
strengths = new Array(m), initializeStrength(); | |
distances = new Array(m), initializeDistance(); | |
} | |
function initializeStrength() { | |
if (!nodes) return; | |
for (var i = 0, n = links.length; i < n; ++i) { | |
strengths[i] = +strength(links[i], i, links); | |
} | |
} | |
function initializeDistance() { | |
if (!nodes) return; | |
for (var i = 0, n = links.length; i < n; ++i) { | |
distances[i] = +distance(links[i], i, links); | |
} | |
} | |
force.initialize = function(_) { | |
nodes = _; | |
initialize(); | |
}; | |
force.links = function(_) { | |
return arguments.length ? (links = _, initialize(), force) : links; | |
}; | |
force.id = function(_) { | |
return arguments.length ? (id = _, force) : id; | |
}; | |
force.iterations = function(_) { | |
return arguments.length ? (iterations = +_, force) : iterations; | |
}; | |
force.strength = function(_) { | |
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initializeStrength(), force) : strength; | |
}; | |
force.distance = function(_) { | |
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), initializeDistance(), force) : distance; | |
}; | |
return force; | |
}; | |
function x$1(d) { | |
return d.x; | |
} | |
function y$1(d) { | |
return d.y; | |
} | |
var initialRadius = 10; | |
var initialAngle = Math.PI * (3 - Math.sqrt(5)); | |
var simulation = function(nodes) { | |
var simulation, | |
alpha = 1, | |
alphaMin = 0.001, | |
alphaDecay = 1 - Math.pow(alphaMin, 1 / 300), | |
alphaTarget = 0, | |
velocityDecay = 0.6, | |
forces = d3Collection.map(), | |
stepper = d3Timer.timer(step), | |
event = d3Dispatch.dispatch("tick", "end"); | |
if (nodes == null) nodes = []; | |
function step() { | |
tick(); | |
event.call("tick", simulation); | |
if (alpha < alphaMin) { | |
stepper.stop(); | |
event.call("end", simulation); | |
} | |
} | |
function tick() { | |
var i, n = nodes.length, node; | |
alpha += (alphaTarget - alpha) * alphaDecay; | |
forces.each(function(force) { | |
force(alpha); | |
}); | |
for (i = 0; i < n; ++i) { | |
node = nodes[i]; | |
if (node.fx == null) node.x += node.vx *= velocityDecay; | |
else node.x = node.fx, node.vx = 0; | |
if (node.fy == null) node.y += node.vy *= velocityDecay; | |
else node.y = node.fy, node.vy = 0; | |
} | |
} | |
function initializeNodes() { | |
for (var i = 0, n = nodes.length, node; i < n; ++i) { | |
node = nodes[i], node.index = i; | |
if (isNaN(node.x) || isNaN(node.y)) { | |
var radius = initialRadius * Math.sqrt(i), angle = i * initialAngle; | |
node.x = radius * Math.cos(angle); | |
node.y = radius * Math.sin(angle); | |
} | |
if (isNaN(node.vx) || isNaN(node.vy)) { | |
node.vx = node.vy = 0; | |
} | |
} | |
} | |
function initializeForce(force) { | |
if (force.initialize) force.initialize(nodes); | |
return force; | |
} | |
initializeNodes(); | |
return simulation = { | |
tick: tick, | |
restart: function() { | |
return stepper.restart(step), simulation; | |
}, | |
stop: function() { | |
return stepper.stop(), simulation; | |
}, | |
nodes: function(_) { | |
return arguments.length ? (nodes = _, initializeNodes(), forces.each(initializeForce), simulation) : nodes; | |
}, | |
alpha: function(_) { | |
return arguments.length ? (alpha = +_, simulation) : alpha; | |
}, | |
alphaMin: function(_) { | |
return arguments.length ? (alphaMin = +_, simulation) : alphaMin; | |
}, | |
alphaDecay: function(_) { | |
return arguments.length ? (alphaDecay = +_, simulation) : +alphaDecay; | |
}, | |
alphaTarget: function(_) { | |
return arguments.length ? (alphaTarget = +_, simulation) : alphaTarget; | |
}, | |
velocityDecay: function(_) { | |
return arguments.length ? (velocityDecay = 1 - _, simulation) : 1 - velocityDecay; | |
}, | |
force: function(name, _) { | |
return arguments.length > 1 ? (_ == null ? forces.remove(name) : forces.set(name, initializeForce(_)), simulation) : forces.get(name); | |
}, | |
find: function(x, y, radius) { | |
var i = 0, | |
n = nodes.length, | |
dx, | |
dy, | |
d2, | |
node, | |
closest; | |
if (radius == null) radius = Infinity; | |
else radius *= radius; | |
for (i = 0; i < n; ++i) { | |
node = nodes[i]; | |
dx = x - node.x; | |
dy = y - node.y; | |
d2 = dx * dx + dy * dy; | |
if (d2 < radius) closest = node, radius = d2; | |
} | |
return closest; | |
}, | |
on: function(name, _) { | |
return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name); | |
} | |
}; | |
}; | |
var manyBody = function() { | |
var nodes, | |
node, | |
alpha, | |
strength = constant(-30), | |
strengths, | |
distanceMin2 = 1, | |
distanceMax2 = Infinity, | |
theta2 = 0.81; | |
function force(_) { | |
var i, n = nodes.length, tree = d3Quadtree.quadtree(nodes, x$1, y$1).visitAfter(accumulate); | |
for (alpha = _, i = 0; i < n; ++i) node = nodes[i], tree.visit(apply); | |
} | |
function initialize() { | |
if (!nodes) return; | |
var i, n = nodes.length, node; | |
strengths = new Array(n); | |
for (i = 0; i < n; ++i) node = nodes[i], strengths[node.index] = +strength(node, i, nodes); | |
} | |
function accumulate(quad) { | |
var strength = 0, q, c, weight = 0, x, y, i; | |
// For internal nodes, accumulate forces from child quadrants. | |
if (quad.length) { | |
for (x = y = i = 0; i < 4; ++i) { | |
if ((q = quad[i]) && (c = Math.abs(q.value))) { | |
strength += q.value, weight += c, x += c * q.x, y += c * q.y; | |
} | |
} | |
quad.x = x / weight; | |
quad.y = y / weight; | |
} | |
// For leaf nodes, accumulate forces from coincident quadrants. | |
else { | |
q = quad; | |
q.x = q.data.x; | |
q.y = q.data.y; | |
do strength += strengths[q.data.index]; | |
while (q = q.next); | |
} | |
quad.value = strength; | |
} | |
function apply(quad, x1, _, x2) { | |
if (!quad.value) return true; | |
var x = quad.x - node.x, | |
y = quad.y - node.y, | |
w = x2 - x1, | |
l = x * x + y * y; | |
// Apply the Barnes-Hut approximation if possible. | |
// Limit forces for very close nodes; randomize direction if coincident. | |
if (w * w / theta2 < l) { | |
if (l < distanceMax2) { | |
if (x === 0) x = jiggle(), l += x * x; | |
if (y === 0) y = jiggle(), l += y * y; | |
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); | |
node.vx += x * quad.value * alpha / l; | |
node.vy += y * quad.value * alpha / l; | |
} | |
return true; | |
} | |
// Otherwise, process points directly. | |
else if (quad.length || l >= distanceMax2) return; | |
// Limit forces for very close nodes; randomize direction if coincident. | |
if (quad.data !== node || quad.next) { | |
if (x === 0) x = jiggle(), l += x * x; | |
if (y === 0) y = jiggle(), l += y * y; | |
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); | |
} | |
do if (quad.data !== node) { | |
w = strengths[quad.data.index] * alpha / l; | |
node.vx += x * w; | |
node.vy += y * w; | |
} while (quad = quad.next); | |
} | |
force.initialize = function(_) { | |
nodes = _; | |
initialize(); | |
}; | |
force.strength = function(_) { | |
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength; | |
}; | |
force.distanceMin = function(_) { | |
return arguments.length ? (distanceMin2 = _ * _, force) : Math.sqrt(distanceMin2); | |
}; | |
force.distanceMax = function(_) { | |
return arguments.length ? (distanceMax2 = _ * _, force) : Math.sqrt(distanceMax2); | |
}; | |
force.theta = function(_) { | |
return arguments.length ? (theta2 = _ * _, force) : Math.sqrt(theta2); | |
}; | |
return force; | |
}; | |
var radial = function(radius, x, y) { | |
var nodes, | |
strength = constant(0.1), | |
strengths, | |
radiuses; | |
if (typeof radius !== "function") radius = constant(+radius); | |
if (x == null) x = 0; | |
if (y == null) y = 0; | |
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; | |
} | |
} | |
function initialize() { | |
if (!nodes) return; | |
var i, n = nodes.length; | |
strengths = new Array(n); | |
radiuses = 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); | |
} | |
} | |
force.initialize = function(_) { | |
nodes = _, initialize(); | |
}; | |
force.strength = function(_) { | |
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength; | |
}; | |
force.radius = function(_) { | |
return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius; | |
}; | |
force.x = function(_) { | |
return arguments.length ? (x = +_, force) : x; | |
}; | |
force.y = function(_) { | |
return arguments.length ? (y = +_, force) : y; | |
}; | |
return force; | |
}; | |
var x$2 = function(x) { | |
var strength = constant(0.1), | |
nodes, | |
strengths, | |
xz; | |
if (typeof x !== "function") x = constant(x == null ? 0 : +x); | |
function force(alpha) { | |
for (var i = 0, n = nodes.length, node; i < n; ++i) { | |
node = nodes[i], node.vx += (xz[i] - node.x) * strengths[i] * alpha; | |
} | |
} | |
function initialize() { | |
if (!nodes) return; | |
var i, n = nodes.length; | |
strengths = new Array(n); | |
xz = new Array(n); | |
for (i = 0; i < n; ++i) { | |
strengths[i] = isNaN(xz[i] = +x(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes); | |
} | |
} | |
force.initialize = function(_) { | |
nodes = _; | |
initialize(); | |
}; | |
force.strength = function(_) { | |
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength; | |
}; | |
force.x = function(_) { | |
return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), initialize(), force) : x; | |
}; | |
return force; | |
}; | |
var y$2 = function(y) { | |
var strength = constant(0.1), | |
nodes, | |
strengths, | |
yz; | |
if (typeof y !== "function") y = constant(y == null ? 0 : +y); | |
function force(alpha) { | |
for (var i = 0, n = nodes.length, node; i < n; ++i) { | |
node = nodes[i], node.vy += (yz[i] - node.y) * strengths[i] * alpha; | |
} | |
} | |
function initialize() { | |
if (!nodes) return; | |
var i, n = nodes.length; | |
strengths = new Array(n); | |
yz = new Array(n); | |
for (i = 0; i < n; ++i) { | |
strengths[i] = isNaN(yz[i] = +y(nodes[i], i, nodes)) ? 0 : +strength(nodes[i], i, nodes); | |
} | |
} | |
force.initialize = function(_) { | |
nodes = _; | |
initialize(); | |
}; | |
force.strength = function(_) { | |
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initialize(), force) : strength; | |
}; | |
force.y = function(_) { | |
return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), initialize(), force) : y; | |
}; | |
return force; | |
}; | |
var boundary = 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) { | |
// var targetX = x > halfX ? (x0 + border) : (x1 - border); | |
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]; | |
// node.vx += (halfX[i] - node.x) * strengthsX[i] * alpha; | |
// node.vy += (halfY[i] - node.y) * strengthsY[i] * alpha; | |
// node.vx += (halfX[i] - node.x) * Math.min(2, Math.abs( (halfX[i]-node.x)/halfX[i]*0.1 )) * alpha; | |
// node.vy += (halfY[i] - node.y) * Math.min(2, Math.abs( (halfY[i]-node.y)/halfY[i]*0.1 )) * alpha; | |
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; | |
}; | |
exports.forceCenter = center; | |
exports.forceCollide = collide; | |
exports.forceLink = link; | |
exports.forceManyBody = manyBody; | |
exports.forceRadial = radial; | |
exports.forceSimulation = simulation; | |
exports.forceX = x$2; | |
exports.forceY = y$2; | |
exports.forceBoundary = boundary; | |
Object.defineProperty(exports, '__esModule', { value: true }); | |
}))); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Document</title> | |
</head> | |
<body> | |
<canvas width=960 height=420></canvas> | |
<script src="http://d3js.org/d3.v4.min.js"></script> | |
<script src="d3-force.js"></script> | |
<script> | |
/* global d3 */ | |
var canvas = d3.select("canvas").node(), | |
ctx= canvas.getContext("2d"), | |
width = canvas.width, | |
height = canvas.height, | |
r=2, | |
x0 = 0, | |
y0 = 0, | |
x1 = width-r, | |
y1 = height-r, | |
strengthBoundary = 0.3, | |
border; | |
var boundary = d3.forceBoundary(x0, y0, x1, y1) | |
.strength(strengthBoundary); | |
function getVx(halfX, x, strengthX, border, alpha) { | |
return (halfX - x) * Math.min(2, Math.abs( halfX - x) / halfX) * strengthX * alpha; | |
} | |
boundary.visualizeStrength = function(ctx, _alpha) { | |
var x, y, vx, vy, step=20, | |
line, | |
halfX = x0 + (x1 - x0)/2, | |
halfY = y0 + (y1 - y0)/2, | |
alpha = _alpha !== undefined ? _alpha : 0.7; | |
ctx.save(); | |
ctx.globalAlpha= 0.3; | |
border = Math.min(halfX, halfY); | |
for (x = x0; x < x1 ; x += step) { | |
for (y = y0; y <y1 ; y += step) { | |
if ((x < (x0 + border) || x > (x1 - border)) || | |
(y < (y0 + border) || y > (y1 - border)) ) { | |
vx = getVx(halfX, x, strengthBoundary, border, alpha); | |
vy = getVx(halfY, y, strengthBoundary, border, alpha); | |
line = new Line(x, y, x+vx, y+vy); | |
line.drawWithArrowheads(ctx); | |
} | |
} | |
} | |
ctx.restore(); | |
} | |
function Line(x1,y1,x2,y2){ | |
this.x1=x1; | |
this.y1=y1; | |
this.x2=x2; | |
this.y2=y2; | |
} | |
Line.prototype.drawWithArrowheads=function(ctx){ | |
// arbitrary styling | |
ctx.strokeStyle="steelblue"; | |
ctx.fillStyle="steelblue"; | |
ctx.lineWidth=1; | |
// draw the line | |
ctx.beginPath(); | |
ctx.moveTo(this.x1,this.y1); | |
ctx.lineTo(this.x2,this.y2); | |
ctx.stroke(); | |
// // draw the starting arrowhead | |
// var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1)); | |
// startRadians+=((this.x2>this.x1)?-90:90)*Math.PI/180; | |
// this.drawArrowhead(ctx,this.x1,this.y1,startRadians); | |
// draw the ending arrowhead | |
var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1)); | |
endRadians+=((this.x2 >= this.x1)?90:-90)*Math.PI/180; | |
this.drawArrowhead(ctx,this.x2,this.y2,endRadians); | |
} | |
Line.prototype.drawArrowhead=function(ctx,x,y,radians){ | |
ctx.save(); | |
ctx.beginPath(); | |
ctx.translate(x,y); | |
ctx.rotate(radians); | |
ctx.moveTo(0,0); | |
ctx.lineTo(2,5); | |
ctx.lineTo(-2,5); | |
ctx.closePath(); | |
ctx.restore(); | |
ctx.fill(); | |
} | |
d3.json("https://gist.githubusercontent.com/john-guerra/ef11d9fcf36e4d6a35be583aa69ac95b/raw/0fbedcebdaa1854cc1b89d86e2d8174520f6ec2d/coauthorshipNetwork2015.json", function (err, graph) { | |
if (err) throw err; | |
const simulation = d3.forceSimulation(graph.nodes) | |
// .force("x", d3.forceX(width/2).strength(0.1)) | |
// .force("y", d3.forceY(height/2).strength(0.1)) | |
.force("boundary", boundary) | |
// .force("collide", d3.forceCollide(r)) | |
// .force("charge", d3.forceManyBody().strength(-5)) | |
// .force("link", d3.forceLink(graph.links).distance(10)) | |
.on("tick", ticked); | |
// boundary.visualizeStrength(ctx, simulation.alpha()); | |
function ticked() { | |
ctx.clearRect(0, 0, width, height); | |
boundary.visualizeStrength(ctx, simulation.alpha()); | |
ctx.save(); | |
ctx.strokeStyle="rgba(100,100,100,0.3)"; | |
ctx.beginPath(); | |
for (let l of graph.links) { | |
ctx.moveTo(l.source.x, l.source.y); | |
ctx.lineTo(l.target.x, l.target.y); | |
} | |
ctx.stroke(); | |
ctx.fillStyle="steelblue"; | |
ctx.beginPath(); | |
for (let n of graph.nodes) { | |
ctx.moveTo(n.x, n.y); | |
ctx.arc(n.x+r/2, n.y+r/2, r, 0, 2 * Math.PI); | |
} | |
ctx.fill(); | |
ctx.restore(); | |
} // ticked | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment