Another attempt at better bounding box collision for d3 force.
forked from emeeks's block: More bounding box collide
license: |
Another attempt at better bounding box collision for d3 force.
forked from emeeks's block: More bounding box collide
// https://d3js.org/d3-force/ Version 1.0.0. Copyright 2016 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'; | |
function center(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; | |
} | |
function constant(x) { | |
return function() { | |
return x; | |
}; | |
} | |
function jiggle() { | |
return (Math.random() - 0.5) * 1e-6; | |
} | |
function x(d) { | |
return d.x + d.vx; | |
} | |
function y(d) { | |
return d.y + d.vy; | |
} | |
function collide(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[i], 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 > i) { | |
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; | |
} | |
} | |
} | |
force.initialize = function(_) { | |
var i, n = (nodes = _).length; radii = new Array(n); | |
for (i = 0; i < n; ++i) radii[i] = +radius(nodes[i], i, nodes); | |
}; | |
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(+_), force) : radius; | |
}; | |
return force; | |
} | |
function x$1(d) { | |
return d.x + d.vx; | |
} | |
function y$1(d) { | |
return d.y + d.vy; | |
} | |
function rectCollide(bbox) { | |
var nodes, | |
boundingBoxes, | |
strength = 1, | |
iterations = 1; | |
if (typeof bbox !== "function") bbox = constant(bbox == null ? [[0,0][1,1]] : bbox); | |
function force() { | |
var i, | |
n = nodes.length, | |
tree, | |
node, | |
xi, | |
yi, | |
bbi, | |
nx1, | |
ny1, | |
nx2, | |
ny2 | |
for (var k = 0; k < iterations; ++k) { | |
tree = d3Quadtree.quadtree(nodes, x$1, y$1).visitAfter(prepare); | |
for (i = 0; i < n; ++i) { | |
var node = nodes[i]; | |
bbi = boundingBoxes[i], | |
xi = node.x + node.vx; | |
yi = node.y + node.vy; | |
nx1 = xi + bbi[0][0] | |
ny1 = yi + bbi[0][1] | |
nx2 = xi + bbi[1][0] | |
ny2 = yi + bbi[1][1] | |
tree.visit(apply); | |
} | |
} | |
function apply(quad, x0, y0, x1, y1) { | |
var data = quad.data, | |
bWidth = bbLength(bbi, 0), | |
bHeight = bbLength(bbi, 1); | |
if (data) { | |
if (data.index > ~~(i / 4)) { | |
var bbj = boundingBoxes[data.index], | |
dnx1 = data.x + data.vx + bbj[0][0], | |
dny1 = data.y + data.vy + bbj[0][1], | |
dnx2 = data.x + data.vx + bbj[1][0], | |
dny2 = data.y + data.vy + bbj[1][1], | |
dWidth = bbLength(bbj, 0), | |
dHeight = bbLength(bbj, 1) | |
if (nx1 <= dnx2 && dnx1 <= nx2 && ny1 <= dny2 && dny1 <= ny2) { | |
var xSize = d3.extent([dnx1, dnx2, nx1, nx2]) | |
var ySize = d3.extent([dny1, dny2, ny1, ny2]) | |
var xOverlap = (bWidth + dWidth) - (xSize[1] - xSize[0]) | |
var yOverlap = (bHeight + dHeight) - (ySize[1] - ySize[0]) | |
var xBPush = xOverlap * strength * (xOverlap / bWidth) | |
var yBPush = yOverlap * strength * (yOverlap / bHeight) | |
var xDPush = xOverlap * strength * (xOverlap / dWidth) | |
var yDPush = yOverlap * strength * (yOverlap / dHeight) | |
if (xOverlap / bWidth < xOverlap / dWidth) { | |
node.vx -= xBPush | |
data.vx += xDPush | |
} | |
else { | |
node.vx += xBPush | |
data.vx -= xDPush | |
} | |
if ( yOverlap / bHeight < yOverlap / dHeight) { | |
node.vy -= yBPush | |
data.vy += yDPush | |
} | |
else { | |
node.vy += yBPush | |
data.vy -= yDPush | |
} | |
} | |
} | |
return; | |
} | |
return x0 > nx2 || x1 < nx1 || y0 > ny2 || y1 < ny1; | |
} | |
} | |
function prepare(quad) { | |
if (quad.data) return quad.bb = boundingBoxes[quad.data.index]; | |
for (var i = quad.bb = [[0,0],[0,0]]; i < 4; ++i) { | |
if (quad[i] && bbArea(quad[i].bb) > bbArea(quad.bb)) { | |
quad.bb = quad[i].bb; | |
} | |
} | |
} | |
function bbArea(bbox) { | |
return (bbox[1][0] - bbox[0][0]) * (bbox[1][1] - bbox[0][1]) | |
} | |
function bbLength(bbox, heightWidth) { | |
return (bbox[1][heightWidth] - bbox[0][heightWidth]) | |
} | |
force.initialize = function(_) { | |
var i, n = (nodes = _).length; boundingBoxes = new Array(n); | |
for (i = 0; i < n; ++i) boundingBoxes[i] = bbox(nodes[i], i, nodes); | |
}; | |
force.iterations = function(_) { | |
return arguments.length ? (iterations = +_, force) : iterations; | |
}; | |
force.strength = function(_) { | |
return arguments.length ? (strength = +_, force) : strength; | |
}; | |
force.bbox = function(_) { | |
return arguments.length ? (bbox = typeof _ === "function" ? _ : constant(+_), force) : bbox; | |
}; | |
return force; | |
} | |
function index(d, i) { | |
return i; | |
} | |
function link(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 < n; ++i) { | |
count[i] = 0; | |
} | |
for (i = 0; i < m; ++i) { | |
link = links[i], link.index = i; | |
if (typeof link.source !== "object") link.source = nodeById.get(link.source); | |
if (typeof link.target !== "object") link.target = nodeById.get(link.target); | |
++count[link.source.index], ++count[link.target.index]; | |
} | |
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$2(d) { | |
return d.x; | |
} | |
function y$2(d) { | |
return d.y; | |
} | |
var initialRadius = 10; | |
var initialAngle = Math.PI * (3 - Math.sqrt(5)); | |
function simulation(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); | |
} | |
}; | |
} | |
function manyBody() { | |
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$2, y$2).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; | |
strengths = new Array(n); | |
for (i = 0; i < n; ++i) strengths[i] = +strength(nodes[i], i, nodes); | |
} | |
function accumulate(quad) { | |
var strength = 0, q, c, 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 = q.value)) { | |
strength += c, x += c * q.x, y += c * q.y; | |
} | |
} | |
quad.x = x / strength; | |
quad.y = y / strength; | |
} | |
// 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; | |
} | |
function x$3(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; | |
} | |
function y$3(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; | |
} | |
exports.forceCenter = center; | |
exports.forceCollide = collide; | |
exports.forceRectCollide = rectCollide; | |
exports.forceLink = link; | |
exports.forceManyBody = manyBody; | |
exports.forceSimulation = simulation; | |
exports.forceX = x$3; | |
exports.forceY = y$3; | |
Object.defineProperty(exports, '__esModule', { value: true }); | |
})); |
<html> | |
<head> | |
<title>d3v4 Simple Word Cloud</title> | |
<meta charset="utf-8" /> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="d3-force.js"></script> | |
</head> | |
<style> | |
svg { | |
height: 768px; | |
width: 768px; | |
border: 1px solid gray; | |
} | |
</style> | |
<body> | |
<div id="viz"> | |
<svg class="main"> | |
<defs> | |
<linearGradient id="green"> | |
<stop offset="0%" stop-color="#92E6CB"/> | |
<stop offset="100%" stop-color="#61D3A5"/> | |
</linearGradient> | |
<linearGradient id="yellow"> | |
<stop offset="0%" stop-color="#FFB140"/> | |
<stop offset="100%" stop-color="#E19930"/> | |
</linearGradient> | |
<linearGradient id="red"> | |
<stop offset="0%" stop-color="#EE6767"/> | |
<stop offset="100%" stop-color="#D14C4C"/> | |
</linearGradient> | |
<linearGradient id="gray"> | |
<stop offset="0%" stop-color="#8D98A2"/> | |
<stop offset="100%" stop-color="#76818C"/> | |
</linearGradient> | |
<marker id="green-end" viewBox="0 -5 10 10" refX="-30" refY="0" markerWidth="6" markerHeight="6" orient="auto"> | |
<path d="M0,-5L10,0L0,5" fill="#8BE3C5"></path> | |
</marker> | |
<marker id="yellow-end" viewBox="0 -5 10 10" refX="-30" refY="0" markerWidth="6" markerHeight="6" orient="auto"> | |
<path d="M0,-5L10,0L0,5" fill="#FFB140"></path> | |
</marker> | |
<marker id="red-end" viewBox="0 -5 10 10" refX="-30" refY="0" markerWidth="6" markerHeight="6" orient="auto"> | |
<path d="M0,-5L10,0L0,5" fill="#E55B5A"></path> | |
</marker> | |
<marker id="green-start" viewBox="0 -5 10 10" refX="-30" refY="0" markerWidth="6" markerHeight="6" orient="auto"> | |
<path d="M0,-5L10,0L0,5" fill="#8BE3C5"></path> | |
</marker> | |
<filter id="dropshadow" filterUnits="userSpaceOnUse"> | |
<feGaussianBlur in="SourceAlpha" stdDeviation="20"> | |
</feGaussianBlur> | |
<feOffset dx="0" dy="20"></feOffset> | |
<feComponentTransfer> | |
<feFuncA type="linear" slope="0.3"></feFuncA> | |
</feComponentTransfer> | |
<feMerge> | |
<feMergeNode></feMergeNode> | |
<feMergeNode in="SourceGraphic"></feMergeNode> | |
</feMerge> | |
</filter> | |
</defs> | |
</svg> | |
</div> | |
</body> | |
<footer> | |
<script> | |
MARKER_GREEN = '#8BE3C5'; | |
MARKER_YELLOW = '#FFB140' | |
MARKER_RED = '#E55B5A' | |
function startMarkerColor(idx) { | |
switch (idx % 4) { | |
case 1: return 'url(#green-start)' | |
case 2: return null | |
case 3: return null | |
default: return null | |
} | |
} | |
function endMarkerColor(idx) { | |
switch (idx % 4) { | |
case 1: return null | |
case 2: return 'url(#yellow-end)' | |
case 3: return 'url(#red-end)' | |
default: return null | |
} | |
} | |
function lineColor(idx) { | |
switch (idx % 4) { | |
case 1: return MARKER_GREEN | |
case 2: return MARKER_YELLOW | |
case 3: return MARKER_RED | |
default: return null | |
} | |
} | |
function nodeColor(node, idx) { | |
// console.log('node=', node) | |
switch (idx % 4) { | |
case 1: return 'url(#green)' | |
case 2: return 'url(#yellow)' | |
case 3: return 'url(#red)' | |
default: return 'url(#gray)' | |
} | |
} | |
const HEIGHT = 768; | |
const WIDTH = 768; | |
const STRONG = 390; | |
const USER_CENTER_X = WIDTH / 2 | |
const USER_CENTER_Y = 10 + STRONG / 2 | |
const GREEN = '#61D3A5'; | |
const GRAY = '#8D98A2'; | |
const FONT = 'Verdana'; | |
const USER_RADIUS = 40; | |
const svg = d3.select("svg.main") | |
const colors = d3.scaleOrdinal(d3.schemeCategory20) | |
const total = 20 | |
const nodes = d3.range(total).map(i => { | |
let n = { index: i, r: 30, x: WIDTH / 2, y: HEIGHT, name: `N${i}` } | |
if (i == 0) n.r = USER_RADIUS | |
return n | |
}); | |
const links = d3.range(total).map(i => { | |
let n | |
if ((i % 4) == 1) { | |
n = { source: i, target: 0, value: i } | |
} else { | |
n = { source: 0, target: i, value: i } | |
} | |
return n | |
}); | |
// scales | |
const vScale = d3.scaleLinear() | |
.domain([0, total]) | |
.range([USER_RADIUS, WIDTH / 2]) | |
// svg | |
const outerG = svg.append('g') | |
.attr('transform',`translate(${WIDTH/2},${HEIGHT/2})`) | |
const outerCirle = outerG.append('circle') | |
.attr('x', WIDTH / 2) | |
.attr('y', HEIGHT / 2) | |
.attr('r', WIDTH / 2) | |
.attr('fill', 'none') | |
.attr('stroke', '#8D98A2') | |
.attr('stroke-opacity', '0.2') | |
const outerText = outerG.append('text') | |
.text('Weak') | |
.attr('text-anchor', 'middle') | |
.attr('y', HEIGHT / 6) | |
.attr('fill', GRAY) | |
.attr('style', `font-family: ${FONT}; font-size: 16px;`) | |
// | |
const innerG = svg.append('g') | |
.attr('transform', `translate(${USER_CENTER_X},${USER_CENTER_Y})`) | |
const innerCircle = innerG.append('circle') | |
.attr('x', WIDTH / 2) | |
.attr('y', HEIGHT / 2) | |
.attr('r', STRONG / 2) | |
.attr('fill', GREEN) | |
.attr('fill-opacity', '0.1') | |
.attr('stroke', GREEN) | |
.attr('stroke-opacity', '0.8') | |
const innerText = innerG.append('text') | |
.text('Strong') | |
.attr('text-anchor', 'middle') | |
.attr('y', 110) | |
.attr('fill', GREEN) | |
.attr('style', `font-family: ${FONT}; font-size: 16px;`) | |
// | |
const centerForce = d3.forceCenter(WIDTH / 2, HEIGHT / 2) | |
const forceX = d3.forceX(USER_CENTER_X).strength(0.005) | |
const forceY = d3.forceY(USER_CENTER_Y).strength(0.005) | |
const collideForce = d3.forceCollide().radius(70).strength(0.2).iterations(100) | |
const bodyForce = d3.forceManyBody().strength(-0.2) | |
const linkForce = d3.forceLink().strength(0.3).iterations(100).distance(l => vScale(l.value)) | |
const simulation = d3.forceSimulation() | |
.velocityDecay(0.9) | |
.force('charge', bodyForce) | |
.force('collide', collideForce) | |
.force('center', centerForce) | |
.force('links', linkForce) | |
.force('forceX', forceX) | |
.force('forceY', forceY) | |
const nodeCirles = svg.append("g") | |
.attr("class", "nodes") | |
.selectAll('circle') | |
.filter('.friend') | |
.data(nodes) | |
.enter().append('circle') | |
.attr('r', d => d.r) | |
.attr('class', 'friend') | |
.attr('fill', (d, i) => nodeColor(d, i)) | |
.attr('style', 'filter: url("#dropshadow")') | |
const nodeLinks = svg.append("g") | |
.attr("class", "links") | |
.selectAll("line") | |
.data(links) | |
.enter().append("line") | |
.attr("marker-start", (d, i) => startMarkerColor(i)) | |
.attr("marker-end", (d, i) => endMarkerColor(i)) | |
.attr("stroke-width", d => 2) | |
// .style("stroke", (d, i) => lineColor(i)) | |
const nodeLabels = svg.selectAll("text") | |
.filter('.node-label') | |
.data(nodes) | |
.enter() | |
.append("text") | |
.attr('class', 'node-label') | |
.text(function (d) { return d.name; }) | |
.attr('fill', '#fff') | |
.attr('style', `font-family: ${FONT}; font-size: 16px;`) | |
simulation | |
.nodes(nodes) | |
.on('tick', ticked) | |
simulation.force("links").links(links) | |
// let mutex = true | |
function ticked (e) { | |
nodeLinks | |
.attr("x1", function(d) { return d.source.x }) | |
.attr("y1", function(d) { return d.source.y }) | |
.attr("x2", function(d) { return d.target.x }) | |
.attr("y2", function(d) { return d.target.y }); | |
nodeCirles | |
.attr('cx', d => { | |
// if (d.index == 1 && mutex) { | |
// console.log(d) | |
// mutex = false; | |
// } | |
return d.x | |
}) | |
.attr('cy', d => d.y) | |
nodeLabels | |
.attr("x", function(d){ return d.x }) | |
.attr("y", function (d) {return d.y + 5.5; }) | |
.style("text-anchor", "middle") | |
nodes[0].x = USER_CENTER_X | |
nodes[0].y = USER_CENTER_Y | |
} | |
</script> | |
</footer> | |
</html> |
text | frequency | |
---|---|---|
layout | 63 | |
function | 61 | |
data | 47 | |
return | 36 | |
attr | 29 | |
chart | 28 | |
array | 24 | |
style | 24 | |
layouts | 22 | |
values | 22 | |
need | 21 | |
nodes | 21 | |
pie | 21 | |
use | 21 | |
figure | 20 | |
circle | 19 | |
we'll | 19 | |
zoom | 19 | |
append | 17 | |
elements | 17 |