import {binarytree} from "d3-binarytree"; import {quadtree} from "d3-quadtree"; import {octree} from "d3-octree"; import constant from "./constant.js"; import jiggle from "./jiggle.js"; import {x, y, z} from "./simulation.js"; export default function() { var nodes, nDim, node, random, alpha, strength = constant(-30), strengths, distanceMin2 = 1, distanceMax2 = Infinity, theta2 = 0.81; function force(_) { var i, n = nodes.length, tree = (nDim === 1 ? binarytree(nodes, x) :(nDim === 2 ? quadtree(nodes, x, y) :(nDim === 3 ? octree(nodes, x, y, z) :null ))).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(treeNode) { var strength = 0, q, c, weight = 0, x, y, z, i; var numChildren = treeNode.length; // For internal nodes, accumulate forces from children. if (numChildren) { for (x = y = z = i = 0; i < numChildren; ++i) { if ((q = treeNode[i]) && (c = Math.abs(q.value))) { strength += q.value, weight += c, x += c * (q.x || 0), y += c * (q.y || 0), z += c * (q.z || 0); } } strength *= Math.sqrt(4 / numChildren); // scale accumulated strength according to number of dimensions treeNode.x = x / weight; if (nDim > 1) { treeNode.y = y / weight; } if (nDim > 2) { treeNode.z = z / weight; } } // For leaf nodes, accumulate forces from coincident nodes. else { q = treeNode; q.x = q.data.x; if (nDim > 1) { q.y = q.data.y; } if (nDim > 2) { q.z = q.data.z; } do strength += strengths[q.data.index]; while (q = q.next); } treeNode.value = strength; } function apply(treeNode, x1, arg1, arg2, arg3) { if (!treeNode.value) return true; var x2 = [arg1, arg2, arg3][nDim-1]; var x = treeNode.x - node.x, y = (nDim > 1 ? treeNode.y - node.y : 0), z = (nDim > 2 ? treeNode.z - node.z : 0), w = x2 - x1, l = x * x + y * y + z * z; // 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(random), l += x * x; if (nDim > 1 && y === 0) y = jiggle(random), l += y * y; if (nDim > 2 && z === 0) z = jiggle(random), l += z * z; if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); node.vx += x * treeNode.value * alpha / l; if (nDim > 1) { node.vy += y * treeNode.value * alpha / l; } if (nDim > 2) { node.vz += z * treeNode.value * alpha / l; } } return true; } // Otherwise, process points directly. else if (treeNode.length || l >= distanceMax2) return; // Limit forces for very close nodes; randomize direction if coincident. if (treeNode.data !== node || treeNode.next) { if (x === 0) x = jiggle(random), l += x * x; if (nDim > 1 && y === 0) y = jiggle(random), l += y * y; if (nDim > 2 && z === 0) z = jiggle(random), l += z * z; if (l < distanceMin2) l = Math.sqrt(distanceMin2 * l); } do if (treeNode.data !== node) { w = strengths[treeNode.data.index] * alpha / l; node.vx += x * w; if (nDim > 1) { node.vy += y * w; } if (nDim > 2) { node.vz += z * w; } } while (treeNode = treeNode.next); } force.initialize = function(_nodes, ...args) { nodes = _nodes; random = args.find(arg => typeof arg === 'function') || Math.random; nDim = args.find(arg => [1, 2, 3].includes(arg)) || 2; 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; }