Built with blockbuilder.org
forked from john-guerra's block: d3.forceBoundary Forces no Border
license: mit |
Built with blockbuilder.org
forked from john-guerra's block: d3.forceBoundary Forces no Border
// 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 }); | |
}))); |
<!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 = 100; | |
var boundary = d3.forceBoundary(x0, y0, x1, y1) | |
.strength(strengthBoundary) | |
.border(border); | |
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; | |
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> |