d3-force-magnetic
Version:
A natural attraction/repulsion force type for the d3-force simulation engine.
242 lines (221 loc) • 7.65 kB
JavaScript
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 };