|
// Copyright 2019 Two Six Labs, LLC. v1.0.0 d3-force-sampled https://github.com/twosixlabs/d3-force-sampled/ |
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
|
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
|
(factory((global.d3 = global.d3 || {}))); |
|
}(this, (function (exports) { 'use strict'; |
|
|
|
function constant(x) { |
|
return function() { |
|
return x; |
|
}; |
|
} |
|
|
|
function manyBodySampled() { |
|
var nodes, |
|
alpha, |
|
strength = constant(-30), |
|
strengths, |
|
indicesRepulse, |
|
prevIndex = 0, |
|
distanceMin2 = 1, |
|
distanceMax2 = Infinity, |
|
neighborSize = function () { |
|
return 15; |
|
}, |
|
updateSize = function (nodes) { return Math.pow(nodes.length, 0.75); }, |
|
sampleSize = function (nodes) { return Math.pow(nodes.length, 0.25); }, |
|
numNeighbors, |
|
numUpdate, |
|
numSamples, |
|
chargeMultiplier = function (nodes) { |
|
return nodes.length < 100 ? 1 : nodes.length < 200 ? 3 : Math.sqrt(nodes.length); |
|
}, |
|
cMult, |
|
rand = Math.random; |
|
|
|
function addRandomNode(node) { |
|
var randIdx = Math.floor(rand() * nodes.length), |
|
randNode = nodes[randIdx], |
|
randDist = (node.x - randNode.x) * (node.x - randNode.x) + (node.y - randNode.y) * (node.y - randNode.y), |
|
currIdx, |
|
currNode, |
|
currDist, |
|
maxI, |
|
maxDist = -Infinity, |
|
i = -1; |
|
|
|
// Is this already in the list? |
|
if (node.nearest.indexOf(randIdx) >= 0) return; |
|
|
|
// If there is room for another, add it. |
|
if (node.nearest.length < numNeighbors) { |
|
node.nearest.push(randIdx); |
|
return; |
|
} |
|
|
|
// Replace the farthest away "neighbor" with the new node. |
|
while (++i < node.nearest.length) { |
|
currIdx = node.nearest[i]; |
|
currNode = nodes[currIdx]; |
|
currDist = Math.hypot(node.x - currNode.x, node.y - currNode.y); |
|
if (currDist > maxDist) { |
|
maxI = i; |
|
maxDist = currDist; |
|
} |
|
} |
|
|
|
if (randDist < maxDist) { |
|
node.nearest[maxI] = randIdx; |
|
} |
|
} |
|
|
|
function getRandIndices(indices, num) { |
|
num = Math.floor(num); |
|
var i, |
|
n = nodes.length, |
|
cnt = n - num, |
|
randIdx, |
|
temp; |
|
|
|
// Choose random indices. |
|
for (i = n-1; i >= cnt; --i) { |
|
randIdx = Math.floor(rand() * i); |
|
temp = indices[randIdx]; |
|
indices[randIdx] = indices[i]; |
|
indices[i] = temp; |
|
} |
|
|
|
return indices.slice(cnt); |
|
} |
|
|
|
function approxRepulse(node) { |
|
var i, |
|
randIndices, |
|
currNode, |
|
w, |
|
x, |
|
y, |
|
l; |
|
|
|
// Choose random nodes to update. |
|
randIndices = getRandIndices(indicesRepulse, numSamples); |
|
|
|
for (i = randIndices.length - 1; i >= 0; --i) { |
|
currNode = nodes[randIndices[i]]; |
|
|
|
if (currNode === node) continue; |
|
|
|
x = currNode.x - node.x; |
|
y = currNode.y - node.y; |
|
l = x * x + y * y; |
|
|
|
if (l >= distanceMax2) continue; |
|
|
|
// Limit forces for very close nodes; randomize direction if coincident. |
|
if (x === 0) x = (rand() - 0.5) * 1e-6, l += x * x; |
|
if (y === 0) y = (rand() - 0.5) * 1e-6, l += y * y; |
|
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); |
|
|
|
w = strengths[node.index] * alpha * cMult / l; |
|
node.vx += x * w; |
|
node.vy += y * w; |
|
} |
|
} |
|
|
|
function constantRepulse(node) { |
|
var i, |
|
nearest, |
|
currNode, |
|
w, |
|
x, |
|
y, |
|
l; |
|
|
|
// Update the list of nearest nodes. |
|
if (numNeighbors) addRandomNode(node); |
|
|
|
nearest = node.nearest; |
|
|
|
if (numNeighbors) for (i = nearest.length - 1; i >= 0; --i) { |
|
currNode = nodes[nearest[i]]; |
|
|
|
if (currNode === node) continue; |
|
|
|
x = currNode.x - node.x; |
|
y = currNode.y - node.y; |
|
l = x * x + y * y; |
|
|
|
if (l >= distanceMax2) continue; |
|
|
|
// Limit forces for very close nodes; randomize direction if coincident. |
|
if (x === 0) x = (rand() - 0.5) * 1e-6, l += x * x; |
|
if (y === 0) y = (rand() - 0.5) * 1e-6, l += y * y; |
|
if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); |
|
|
|
w = strengths[node.index] * alpha * cMult / l; |
|
node.vx += x * w; |
|
node.vy += y * w; |
|
} |
|
} |
|
|
|
function force(_) { |
|
var i = 0, j = prevIndex, n = nodes.length, upperIndex = prevIndex + numUpdate; |
|
for (alpha = _; i < n || j < upperIndex; ++i, ++j) { |
|
if (j < upperIndex) approxRepulse(nodes[j%n]); |
|
if (numNeighbors && i < n) constantRepulse(nodes[i]); |
|
} |
|
prevIndex = upperIndex % n; |
|
} |
|
|
|
function initialize() { |
|
if (!nodes) return; |
|
var i, n = nodes.length, node; |
|
indicesRepulse = new Array(n); |
|
for (i = 0; i < n; ++i) indicesRepulse[i] = i; |
|
strengths = new Array(n); |
|
|
|
// Cannot be negative. |
|
numNeighbors = Math.min(Math.ceil(neighborSize(nodes)), n); |
|
numNeighbors = numNeighbors < 0 ? 0 : Math.min(numNeighbors, nodes.length); |
|
numUpdate = Math.ceil(updateSize(nodes)); |
|
numUpdate = numUpdate < 0 ? 0 : Math.min(numUpdate, n); |
|
numSamples = Math.ceil(sampleSize(nodes)); |
|
numSamples = numSamples < 0 ? 0 : Math.min(numSamples, n); |
|
|
|
cMult = chargeMultiplier(nodes); |
|
|
|
alpha = 1; |
|
for (i = 0; i < n; ++i) { |
|
node = nodes[i]; |
|
strengths[node.index] = +strength(node, i, nodes); |
|
node.nearest = []; |
|
while (node.nearest.length < numNeighbors) addRandomNode(node); |
|
} |
|
} |
|
|
|
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.neighborSize = function(_) { |
|
return arguments.length ? (neighborSize = typeof _ === "function" ? _ : constant(+_), initialize(), force) : neighborSize; |
|
}; |
|
|
|
force.updateSize = function(_) { |
|
return arguments.length ? (updateSize = typeof _ === "function" ? _ : constant(+_), initialize(), force) : updateSize; |
|
}; |
|
|
|
force.sampleSize = function(_) { |
|
return arguments.length ? (sampleSize = typeof _ === "function" ? _ : constant(+_), initialize(), force) : sampleSize; |
|
}; |
|
|
|
force.chargeMultiplier = function(_) { |
|
return arguments.length ? (chargeMultiplier = typeof _ === "function" ? _ : constant(+_), initialize(), force) : chargeMultiplier; |
|
}; |
|
|
|
force.source = function(_) { |
|
return arguments.length ? (rand = _, force) : rand; |
|
}; |
|
|
|
return force; |
|
} |
|
|
|
exports.forceManyBodySampled = manyBodySampled; |
|
|
|
Object.defineProperty(exports, '__esModule', { value: true }); |
|
|
|
}))); |