import constant from "./constant"; import jiggle from "./jiggle"; import {x, y} from "./xy"; import {quadtree} from "d3-quadtree"; /** * The refinement of the existing Barnes-Hut implementation in D3 * to fit the use case of the project. Previously the algorithm stored * strength as internal node, now the random child is stored as internal * node and the force calculations are done between the node and that internal * object if they are sufficiently far away. * The check to see if the nodes are far away was also changed to the one described in original Barnes-Hut paper. * @return {force} calculated forces. */ export default function() { var nodes, node, alpha, distance = constant(300), theta = 0.5; /** * Constructs a quadtree at every iteration and apply the forces by visiting * each node in a tree. * @param {number} _ - controls the stopping of the * particle simulations. */ function force(_) { var i, n = nodes.length, tree = quadtree(nodes, x, y).visitAfter(accumulate); for (alpha = _, i = 0; i < n; ++i) { node = nodes[i], tree.visit(apply); } } /** * Function used during the tree construction to fill out the nodes with * correct data. Internal nodes acquire the random child while the leaf * nodes accumulate forces from coincident quadrants. * @param {quadrant} quad - node representing the quadrant in quadtree. */ function accumulate(quad) { var q, d, children = []; // For internal nodes, accumulate forces from child quadrants. if (quad.length) { for (var i = 0; i < 4; ++i) { if ((q = quad[i]) && (d = q.data)) { children.push(d); } } // Choose a random child. quad.data = children[Math.floor(Math.random() * children.length)]; quad.x = quad.data.x; quad.y = quad.data.y; } // For leaf nodes, accumulate forces from coincident quadrants. else { q = quad; q.x = q.data.x; q.y = q.data.y; } } /** * Function that applies the forces for each node. If the objects are * far away, the approximation is made. Otherwise, forces are calculated * directly between the nodes. * @param {quadrant} quad - node representing the quadrant in quadtree. * @param {number} x1 - lower x bound of the node. * @param {number} _ - lower y bound of the node. * @param {number} x2 - upper x bound of the node. * @return {boolean} - true if the approximation was applied. */ function apply(quad, x1, _, x2) { var x = quad.data.x + quad.data.vx - node.x - node.vx, y = quad.data.y + quad.data.vy - node.y - node.vy, w = x2 - x1, l = Math.sqrt(x * x + y * y); // Apply the Barnes-Hut approximation if possible. // Limit forces for very close nodes; randomize direction if coincident. if (w / l < theta) { if (x === 0) x = jiggle(), l += x * x; if (y === 0) y = jiggle(), l += y * y; if (quad.data) { l = (l - +distance(node, quad.data)) / l * alpha; x *= l, y *= l; quad.data.vx -= x; quad.data.vy -= y; node.vx += x; node.vy += y; } return true; } // Otherwise, process points directly. else if (quad.length) 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; } do if (quad.data !== node) { l = (l - +distance(node, quad.data)) / l * alpha; x *= l, y *= l; quad.data.vx -= x; quad.data.vy -= y; node.vx += x; node.vy += y; } while (quad = quad.next); } /** * Calculates the stress. Basically, it computes the difference between * high dimensional distance and real distance. The lower the stress is, * the better layout. * @return {number} - stress of the layout. */ function getStress() { var totalDiffSq = 0, totalHighDistSq = 0; for (var i = 0, source, target, realDist, highDist; i < nodes.length; i++) { for (var j = 0; j < nodes.length; j++) { if (i !== j) { source = nodes[i], target = nodes[j]; realDist = Math.hypot(target.x-source.x, target.y-source.y); highDist = +distance(nodes[i], nodes[j]); totalDiffSq += Math.pow(realDist-highDist, 2); totalHighDistSq += highDist * highDist; } } } return Math.sqrt(totalDiffSq/totalHighDistSq); } // API for initializing the algorithm, setting parameters and querying // metrics. force.initialize = function(_) { nodes = _; }; force.distance = function(_) { return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), force) : distance; }; force.theta = function(_) { return arguments.length ? (theta = _, force) : theta; }; force.stress = function() { return getStress(); }; return force; }