UNPKG

d3-force-magnetic

Version:

A natural attraction/repulsion force type for the d3-force simulation engine.

242 lines (221 loc) 7.65 kB
import { binarytree } from 'd3-binarytree'; import { quadtree } from 'd3-quadtree'; import { octree } from 'd3-octree'; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function constant (x) { return function () { return x; }; } function magnetic () { var nDim, nodes = [], links = [], id = function id(node) { return node.index; }, // accessor: node unique id charge = function charge(node) { return 100; }, // accessor: number (equivalent to node mass) strength = function strength(link) { return 1; }, // accessor: number (equivalent to G constant) polarity = function polarity(q1, q2) { return null; }, // boolean or null (asymmetrical) distanceWeight = function distanceWeight(d) { return 1 / (d * d); }, // Intensity falls with the square of the distance (inverse-square law) theta = 0.9; function force(alpha) { if (links.length) { // Pre-set node pairs for (var i = 0; i < links.length; i++) { var link = links[i], dx = link.target.x - link.source.x, dy = link.target.y - link.source.y || 0, dz = link.target.z - link.source.z || 0, d = distance(dx, dy, dz); if (d === 0) continue; var relStrength = alpha * strength(link) * distanceWeight(d); var qSrc = charge(link.source), qTgt = charge(link.target); // Set attract/repel polarity var linkPolarity = polarity(qSrc, qTgt); var sourceAcceleration = signedCharge(qTgt, linkPolarity) * relStrength; var targetAcceleration = signedCharge(qSrc, linkPolarity) * relStrength; link.source.vx += dx / d * sourceAcceleration; link.target.vx -= dx / d * targetAcceleration; if (nDim > 1) { link.source.vy += dy / d * sourceAcceleration; link.target.vy -= dy / d * targetAcceleration; } if (nDim > 2) { link.source.vz += dz / d * sourceAcceleration; link.target.vz -= dz / d * targetAcceleration; } } } else { // Assume full node mesh if no links specified var tree = (nDim === 1 ? binarytree(nodes, function (d) { return d.x; }) : nDim === 2 ? quadtree(nodes, function (d) { return d.x; }, function (d) { return d.y; }) : nDim === 3 ? octree(nodes, function (d) { return d.x; }, function (d) { return d.y; }, function (d) { return d.z; }) : null).visitAfter(accumulate); var etherStrength = alpha * strength(); var _loop = function _loop() { var node = nodes[_i], nodeQ = charge(node); tree.visit(function (treeNode, x1, arg1, arg2, arg3) { if (!treeNode.value) return true; var x2 = [arg1, arg2, arg3][nDim - 1]; var dx = treeNode.x - node.x, dy = treeNode.y - node.y || 0, dz = treeNode.z - node.z || 0, d = distance(dx, dy, dz); // Apply the Barnes-Hut approximation if possible. if ((x2 - x1) / d < theta) { if (d > 0) { applyAcceleration(); } return true; } // Otherwise, process points directly. else if (treeNode.length || d === 0) return; do if (treeNode.data !== node) { applyAcceleration(); } while (treeNode = treeNode.next); // function applyAcceleration() { var acceleration = signedCharge(treeNode.value, polarity(nodeQ, treeNode.value)) * etherStrength * distanceWeight(d); node.vx += dx / d * acceleration; if (nDim > 1) { node.vy += dy / d * acceleration; } if (nDim > 2) { node.vz += dz / d * acceleration; } } }); }; for (var _i = 0; _i < nodes.length; _i++) { _loop(); } } // function accumulate(treeNode) { var localCharge = 0, q, c, weight = 0, x, y, z, i; // For internal nodes, accumulate forces from child tree-nodes (segments/quadrants/octants). if (treeNode.length) { for (x = y = z = i = 0; i < Math.pow(2, nDim); ++i) { if ((q = treeNode[i]) && (c = Math.abs(q.value))) { localCharge += q.value, weight += c, x += c * (q.x || 0), y += c * (q.y || 0), z += c * (q.z || 0); } } 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 tree 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 localCharge += charge(q.data); while (q = q.next); } treeNode.value = localCharge; } function signedCharge(q, polarity) { if (polarity === null) return q; // No change with null polarity return Math.abs(q) * (polarity ? 1 : -1); } function distance(x, y, z) { return Math.sqrt(x * x + y * y + z * z); } } function initialize() { var nodesById = {}; nodes.forEach(function (node) { nodesById[id(node)] = node; }); links.forEach(function (link) { if (_typeof(link.source) !== "object") link.source = nodesById[link.source] || link.source; if (_typeof(link.target) !== "object") link.target = nodesById[link.target] || link.target; }); } force.initialize = function (initNodes) { nodes = initNodes; for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } nDim = args.find(function (arg) { return [1, 2, 3].includes(arg); }) || 2; initialize(); }; force.links = function (_) { return arguments.length ? (links = _, initialize(), force) : links; }; // Node id force.id = function (_) { return arguments.length ? (id = _, force) : id; }; // Node capacity to attract (positive) or repel (negative) force.charge = function (_) { return arguments.length ? (charge = typeof _ === "function" ? _ : constant(+_), force) : charge; }; // Link strength (ability of the medium to propagate charges) force.strength = function (_) { return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), force) : strength; }; // How force direction is determined (whether nodes should attract each other (boolean), or asymmetrical based on opposite node's charge sign (null)) force.polarity = function (_) { return arguments.length ? (polarity = typeof _ === "function" ? _ : constant(+_), force) : polarity; }; // How the force intensity relates to the distance between nodes force.distanceWeight = function (_) { return arguments.length ? (distanceWeight = _, force) : distanceWeight; }; // Barnes-Hut approximation tetha threshold (for full-mesh mode) force.theta = function (_) { return arguments.length ? (theta = _, force) : theta; }; return force; } export { magnetic as default };