8477 lines
270 KiB
JavaScript
Executable file
8477 lines
270 KiB
JavaScript
Executable file
// Version 1.41.14 three-forcegraph - https://github.com/vasturiano/three-forcegraph
|
||
(function (global, factory) {
|
||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('three')) :
|
||
typeof define === 'function' && define.amd ? define(['three'], factory) :
|
||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ThreeForceGraph = factory(global.THREE));
|
||
})(this, (function (three$2) { 'use strict';
|
||
|
||
function _callSuper(t, o, e) {
|
||
return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e));
|
||
}
|
||
function _construct(t, e, r) {
|
||
if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments);
|
||
var o = [null];
|
||
o.push.apply(o, e);
|
||
var p = new (t.bind.apply(t, o))();
|
||
return r && _setPrototypeOf(p, r.prototype), p;
|
||
}
|
||
function _isNativeReflectConstruct() {
|
||
try {
|
||
var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
|
||
} catch (t) {}
|
||
return (_isNativeReflectConstruct = function () {
|
||
return !!t;
|
||
})();
|
||
}
|
||
function _iterableToArrayLimit$3(r, l) {
|
||
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
||
if (null != t) {
|
||
var e,
|
||
n,
|
||
i,
|
||
u,
|
||
a = [],
|
||
f = !0,
|
||
o = !1;
|
||
try {
|
||
if (i = (t = t.call(r)).next, 0 === l) {
|
||
if (Object(t) !== t) return;
|
||
f = !1;
|
||
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
||
} catch (r) {
|
||
o = !0, n = r;
|
||
} finally {
|
||
try {
|
||
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
||
} finally {
|
||
if (o) throw n;
|
||
}
|
||
}
|
||
return a;
|
||
}
|
||
}
|
||
function ownKeys$1(e, r) {
|
||
var t = Object.keys(e);
|
||
if (Object.getOwnPropertySymbols) {
|
||
var o = Object.getOwnPropertySymbols(e);
|
||
r && (o = o.filter(function (r) {
|
||
return Object.getOwnPropertyDescriptor(e, r).enumerable;
|
||
})), t.push.apply(t, o);
|
||
}
|
||
return t;
|
||
}
|
||
function _objectSpread2$1(e) {
|
||
for (var r = 1; r < arguments.length; r++) {
|
||
var t = null != arguments[r] ? arguments[r] : {};
|
||
r % 2 ? ownKeys$1(Object(t), !0).forEach(function (r) {
|
||
_defineProperty$1(e, r, t[r]);
|
||
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) {
|
||
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
|
||
});
|
||
}
|
||
return e;
|
||
}
|
||
function _toPrimitive$3(t, r) {
|
||
if ("object" != typeof t || !t) return t;
|
||
var e = t[Symbol.toPrimitive];
|
||
if (void 0 !== e) {
|
||
var i = e.call(t, r || "default");
|
||
if ("object" != typeof i) return i;
|
||
throw new TypeError("@@toPrimitive must return a primitive value.");
|
||
}
|
||
return ("string" === r ? String : Number)(t);
|
||
}
|
||
function _toPropertyKey$3(t) {
|
||
var i = _toPrimitive$3(t, "string");
|
||
return "symbol" == typeof i ? i : String(i);
|
||
}
|
||
function _typeof$1(o) {
|
||
"@babel/helpers - typeof";
|
||
|
||
return _typeof$1 = "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$1(o);
|
||
}
|
||
function _classCallCheck$1(instance, Constructor) {
|
||
if (!(instance instanceof Constructor)) {
|
||
throw new TypeError("Cannot call a class as a function");
|
||
}
|
||
}
|
||
function _defineProperties$1(target, props) {
|
||
for (var i = 0; i < props.length; i++) {
|
||
var descriptor = props[i];
|
||
descriptor.enumerable = descriptor.enumerable || false;
|
||
descriptor.configurable = true;
|
||
if ("value" in descriptor) descriptor.writable = true;
|
||
Object.defineProperty(target, _toPropertyKey$3(descriptor.key), descriptor);
|
||
}
|
||
}
|
||
function _createClass$1(Constructor, protoProps, staticProps) {
|
||
if (protoProps) _defineProperties$1(Constructor.prototype, protoProps);
|
||
if (staticProps) _defineProperties$1(Constructor, staticProps);
|
||
Object.defineProperty(Constructor, "prototype", {
|
||
writable: false
|
||
});
|
||
return Constructor;
|
||
}
|
||
function _defineProperty$1(obj, key, value) {
|
||
key = _toPropertyKey$3(key);
|
||
if (key in obj) {
|
||
Object.defineProperty(obj, key, {
|
||
value: value,
|
||
enumerable: true,
|
||
configurable: true,
|
||
writable: true
|
||
});
|
||
} else {
|
||
obj[key] = value;
|
||
}
|
||
return obj;
|
||
}
|
||
function _inherits(subClass, superClass) {
|
||
if (typeof superClass !== "function" && superClass !== null) {
|
||
throw new TypeError("Super expression must either be null or a function");
|
||
}
|
||
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
||
constructor: {
|
||
value: subClass,
|
||
writable: true,
|
||
configurable: true
|
||
}
|
||
});
|
||
Object.defineProperty(subClass, "prototype", {
|
||
writable: false
|
||
});
|
||
if (superClass) _setPrototypeOf(subClass, superClass);
|
||
}
|
||
function _getPrototypeOf(o) {
|
||
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
|
||
return o.__proto__ || Object.getPrototypeOf(o);
|
||
};
|
||
return _getPrototypeOf(o);
|
||
}
|
||
function _setPrototypeOf(o, p) {
|
||
_setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
|
||
o.__proto__ = p;
|
||
return o;
|
||
};
|
||
return _setPrototypeOf(o, p);
|
||
}
|
||
function _objectWithoutPropertiesLoose$2(source, excluded) {
|
||
if (source == null) return {};
|
||
var target = {};
|
||
var sourceKeys = Object.keys(source);
|
||
var key, i;
|
||
for (i = 0; i < sourceKeys.length; i++) {
|
||
key = sourceKeys[i];
|
||
if (excluded.indexOf(key) >= 0) continue;
|
||
target[key] = source[key];
|
||
}
|
||
return target;
|
||
}
|
||
function _objectWithoutProperties$2(source, excluded) {
|
||
if (source == null) return {};
|
||
var target = _objectWithoutPropertiesLoose$2(source, excluded);
|
||
var key, i;
|
||
if (Object.getOwnPropertySymbols) {
|
||
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
||
for (i = 0; i < sourceSymbolKeys.length; i++) {
|
||
key = sourceSymbolKeys[i];
|
||
if (excluded.indexOf(key) >= 0) continue;
|
||
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
||
target[key] = source[key];
|
||
}
|
||
}
|
||
return target;
|
||
}
|
||
function _assertThisInitialized(self) {
|
||
if (self === void 0) {
|
||
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
||
}
|
||
return self;
|
||
}
|
||
function _possibleConstructorReturn(self, call) {
|
||
if (call && (typeof call === "object" || typeof call === "function")) {
|
||
return call;
|
||
} else if (call !== void 0) {
|
||
throw new TypeError("Derived constructors may only return object or undefined");
|
||
}
|
||
return _assertThisInitialized(self);
|
||
}
|
||
function _slicedToArray$3(arr, i) {
|
||
return _arrayWithHoles$3(arr) || _iterableToArrayLimit$3(arr, i) || _unsupportedIterableToArray$3(arr, i) || _nonIterableRest$3();
|
||
}
|
||
function _toConsumableArray$2(arr) {
|
||
return _arrayWithoutHoles$2(arr) || _iterableToArray$2(arr) || _unsupportedIterableToArray$3(arr) || _nonIterableSpread$2();
|
||
}
|
||
function _arrayWithoutHoles$2(arr) {
|
||
if (Array.isArray(arr)) return _arrayLikeToArray$3(arr);
|
||
}
|
||
function _arrayWithHoles$3(arr) {
|
||
if (Array.isArray(arr)) return arr;
|
||
}
|
||
function _iterableToArray$2(iter) {
|
||
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
||
}
|
||
function _unsupportedIterableToArray$3(o, minLen) {
|
||
if (!o) return;
|
||
if (typeof o === "string") return _arrayLikeToArray$3(o, minLen);
|
||
var n = Object.prototype.toString.call(o).slice(8, -1);
|
||
if (n === "Object" && o.constructor) n = o.constructor.name;
|
||
if (n === "Map" || n === "Set") return Array.from(o);
|
||
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$3(o, minLen);
|
||
}
|
||
function _arrayLikeToArray$3(arr, len) {
|
||
if (len == null || len > arr.length) len = arr.length;
|
||
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
||
return arr2;
|
||
}
|
||
function _nonIterableSpread$2() {
|
||
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
||
}
|
||
function _nonIterableRest$3() {
|
||
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
||
}
|
||
|
||
function d3ForceCenter(x, y, z) {
|
||
var nodes, strength = 1;
|
||
|
||
if (x == null) x = 0;
|
||
if (y == null) y = 0;
|
||
if (z == null) z = 0;
|
||
|
||
function force() {
|
||
var i,
|
||
n = nodes.length,
|
||
node,
|
||
sx = 0,
|
||
sy = 0,
|
||
sz = 0;
|
||
|
||
for (i = 0; i < n; ++i) {
|
||
node = nodes[i], sx += node.x || 0, sy += node.y || 0, sz += node.z || 0;
|
||
}
|
||
|
||
for (sx = (sx / n - x) * strength, sy = (sy / n - y) * strength, sz = (sz / n - z) * strength, i = 0; i < n; ++i) {
|
||
node = nodes[i];
|
||
if (sx) { node.x -= sx; }
|
||
if (sy) { node.y -= sy; }
|
||
if (sz) { node.z -= sz; }
|
||
}
|
||
}
|
||
|
||
force.initialize = function(_) {
|
||
nodes = _;
|
||
};
|
||
|
||
force.x = function(_) {
|
||
return arguments.length ? (x = +_, force) : x;
|
||
};
|
||
|
||
force.y = function(_) {
|
||
return arguments.length ? (y = +_, force) : y;
|
||
};
|
||
|
||
force.z = function(_) {
|
||
return arguments.length ? (z = +_, force) : z;
|
||
};
|
||
|
||
force.strength = function(_) {
|
||
return arguments.length ? (strength = +_, force) : strength;
|
||
};
|
||
|
||
return force;
|
||
}
|
||
|
||
function tree_add$2(d) {
|
||
const x = +this._x.call(null, d);
|
||
return add$2(this.cover(x), x, d);
|
||
}
|
||
|
||
function add$2(tree, x, d) {
|
||
if (isNaN(x)) return tree; // ignore invalid points
|
||
|
||
var parent,
|
||
node = tree._root,
|
||
leaf = {data: d},
|
||
x0 = tree._x0,
|
||
x1 = tree._x1,
|
||
xm,
|
||
xp,
|
||
right,
|
||
i,
|
||
j;
|
||
|
||
// If the tree is empty, initialize the root as a leaf.
|
||
if (!node) return tree._root = leaf, tree;
|
||
|
||
// Find the existing leaf for the new point, or add it.
|
||
while (node.length) {
|
||
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
||
if (parent = node, !(node = node[i = +right])) return parent[i] = leaf, tree;
|
||
}
|
||
|
||
// Is the new point is exactly coincident with the existing point?
|
||
xp = +tree._x.call(null, node.data);
|
||
if (x === xp) return leaf.next = node, parent ? parent[i] = leaf : tree._root = leaf, tree;
|
||
|
||
// Otherwise, split the leaf node until the old and new point are separated.
|
||
do {
|
||
parent = parent ? parent[i] = new Array(2) : tree._root = new Array(2);
|
||
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
||
} while ((i = +right) === (j = +(xp >= xm)));
|
||
return parent[j] = node, parent[i] = leaf, tree;
|
||
}
|
||
|
||
function addAll$2(data) {
|
||
if (!Array.isArray(data)) data = Array.from(data);
|
||
const n = data.length;
|
||
const xz = new Float64Array(n);
|
||
let x0 = Infinity,
|
||
x1 = -Infinity;
|
||
|
||
// Compute the points and their extent.
|
||
for (let i = 0, x; i < n; ++i) {
|
||
if (isNaN(x = +this._x.call(null, data[i]))) continue;
|
||
xz[i] = x;
|
||
if (x < x0) x0 = x;
|
||
if (x > x1) x1 = x;
|
||
}
|
||
|
||
// If there were no (valid) points, abort.
|
||
if (x0 > x1) return this;
|
||
|
||
// Expand the tree to cover the new points.
|
||
this.cover(x0).cover(x1);
|
||
|
||
// Add the new points.
|
||
for (let i = 0; i < n; ++i) {
|
||
add$2(this, xz[i], data[i]);
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
function tree_cover$2(x) {
|
||
if (isNaN(x = +x)) return this; // ignore invalid points
|
||
|
||
var x0 = this._x0,
|
||
x1 = this._x1;
|
||
|
||
// If the binarytree has no extent, initialize them.
|
||
// Integer extent are necessary so that if we later double the extent,
|
||
// the existing half boundaries don’t change due to floating point error!
|
||
if (isNaN(x0)) {
|
||
x1 = (x0 = Math.floor(x)) + 1;
|
||
}
|
||
|
||
// Otherwise, double repeatedly to cover.
|
||
else {
|
||
var z = x1 - x0 || 1,
|
||
node = this._root,
|
||
parent,
|
||
i;
|
||
|
||
while (x0 > x || x >= x1) {
|
||
i = +(x < x0);
|
||
parent = new Array(2), parent[i] = node, node = parent, z *= 2;
|
||
switch (i) {
|
||
case 0: x1 = x0 + z; break;
|
||
case 1: x0 = x1 - z; break;
|
||
}
|
||
}
|
||
|
||
if (this._root && this._root.length) this._root = node;
|
||
}
|
||
|
||
this._x0 = x0;
|
||
this._x1 = x1;
|
||
return this;
|
||
}
|
||
|
||
function tree_data$2() {
|
||
var data = [];
|
||
this.visit(function(node) {
|
||
if (!node.length) do data.push(node.data); while (node = node.next)
|
||
});
|
||
return data;
|
||
}
|
||
|
||
function tree_extent$2(_) {
|
||
return arguments.length
|
||
? this.cover(+_[0][0]).cover(+_[1][0])
|
||
: isNaN(this._x0) ? undefined : [[this._x0], [this._x1]];
|
||
}
|
||
|
||
function Half(node, x0, x1) {
|
||
this.node = node;
|
||
this.x0 = x0;
|
||
this.x1 = x1;
|
||
}
|
||
|
||
function tree_find$2(x, radius) {
|
||
var data,
|
||
x0 = this._x0,
|
||
x1,
|
||
x2,
|
||
x3 = this._x1,
|
||
halves = [],
|
||
node = this._root,
|
||
q,
|
||
i;
|
||
|
||
if (node) halves.push(new Half(node, x0, x3));
|
||
if (radius == null) radius = Infinity;
|
||
else {
|
||
x0 = x - radius;
|
||
x3 = x + radius;
|
||
}
|
||
|
||
while (q = halves.pop()) {
|
||
|
||
// Stop searching if this half can’t contain a closer node.
|
||
if (!(node = q.node)
|
||
|| (x1 = q.x0) > x3
|
||
|| (x2 = q.x1) < x0) continue;
|
||
|
||
// Bisect the current half.
|
||
if (node.length) {
|
||
var xm = (x1 + x2) / 2;
|
||
|
||
halves.push(
|
||
new Half(node[1], xm, x2),
|
||
new Half(node[0], x1, xm)
|
||
);
|
||
|
||
// Visit the closest half first.
|
||
if (i = +(x >= xm)) {
|
||
q = halves[halves.length - 1];
|
||
halves[halves.length - 1] = halves[halves.length - 1 - i];
|
||
halves[halves.length - 1 - i] = q;
|
||
}
|
||
}
|
||
|
||
// Visit this point. (Visiting coincident points isn’t necessary!)
|
||
else {
|
||
var d = Math.abs(x - +this._x.call(null, node.data));
|
||
if (d < radius) {
|
||
radius = d;
|
||
x0 = x - d;
|
||
x3 = x + d;
|
||
data = node.data;
|
||
}
|
||
}
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
function tree_remove$2(d) {
|
||
if (isNaN(x = +this._x.call(null, d))) return this; // ignore invalid points
|
||
|
||
var parent,
|
||
node = this._root,
|
||
retainer,
|
||
previous,
|
||
next,
|
||
x0 = this._x0,
|
||
x1 = this._x1,
|
||
x,
|
||
xm,
|
||
right,
|
||
i,
|
||
j;
|
||
|
||
// If the tree is empty, initialize the root as a leaf.
|
||
if (!node) return this;
|
||
|
||
// Find the leaf node for the point.
|
||
// While descending, also retain the deepest parent with a non-removed sibling.
|
||
if (node.length) while (true) {
|
||
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
||
if (!(parent = node, node = node[i = +right])) return this;
|
||
if (!node.length) break;
|
||
if (parent[(i + 1) & 1]) retainer = parent, j = i;
|
||
}
|
||
|
||
// Find the point to remove.
|
||
while (node.data !== d) if (!(previous = node, node = node.next)) return this;
|
||
if (next = node.next) delete node.next;
|
||
|
||
// If there are multiple coincident points, remove just the point.
|
||
if (previous) return (next ? previous.next = next : delete previous.next), this;
|
||
|
||
// If this is the root point, remove it.
|
||
if (!parent) return this._root = next, this;
|
||
|
||
// Remove this leaf.
|
||
next ? parent[i] = next : delete parent[i];
|
||
|
||
// If the parent now contains exactly one leaf, collapse superfluous parents.
|
||
if ((node = parent[0] || parent[1])
|
||
&& node === (parent[1] || parent[0])
|
||
&& !node.length) {
|
||
if (retainer) retainer[j] = node;
|
||
else this._root = node;
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
function removeAll$2(data) {
|
||
for (var i = 0, n = data.length; i < n; ++i) this.remove(data[i]);
|
||
return this;
|
||
}
|
||
|
||
function tree_root$2() {
|
||
return this._root;
|
||
}
|
||
|
||
function tree_size$2() {
|
||
var size = 0;
|
||
this.visit(function(node) {
|
||
if (!node.length) do ++size; while (node = node.next)
|
||
});
|
||
return size;
|
||
}
|
||
|
||
function tree_visit$2(callback) {
|
||
var halves = [], q, node = this._root, child, x0, x1;
|
||
if (node) halves.push(new Half(node, this._x0, this._x1));
|
||
while (q = halves.pop()) {
|
||
if (!callback(node = q.node, x0 = q.x0, x1 = q.x1) && node.length) {
|
||
var xm = (x0 + x1) / 2;
|
||
if (child = node[1]) halves.push(new Half(child, xm, x1));
|
||
if (child = node[0]) halves.push(new Half(child, x0, xm));
|
||
}
|
||
}
|
||
return this;
|
||
}
|
||
|
||
function tree_visitAfter$2(callback) {
|
||
var halves = [], next = [], q;
|
||
if (this._root) halves.push(new Half(this._root, this._x0, this._x1));
|
||
while (q = halves.pop()) {
|
||
var node = q.node;
|
||
if (node.length) {
|
||
var child, x0 = q.x0, x1 = q.x1, xm = (x0 + x1) / 2;
|
||
if (child = node[0]) halves.push(new Half(child, x0, xm));
|
||
if (child = node[1]) halves.push(new Half(child, xm, x1));
|
||
}
|
||
next.push(q);
|
||
}
|
||
while (q = next.pop()) {
|
||
callback(q.node, q.x0, q.x1);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
function defaultX$2(d) {
|
||
return d[0];
|
||
}
|
||
|
||
function tree_x$2(_) {
|
||
return arguments.length ? (this._x = _, this) : this._x;
|
||
}
|
||
|
||
function binarytree(nodes, x) {
|
||
var tree = new Binarytree(x == null ? defaultX$2 : x, NaN, NaN);
|
||
return nodes == null ? tree : tree.addAll(nodes);
|
||
}
|
||
|
||
function Binarytree(x, x0, x1) {
|
||
this._x = x;
|
||
this._x0 = x0;
|
||
this._x1 = x1;
|
||
this._root = undefined;
|
||
}
|
||
|
||
function leaf_copy$2(leaf) {
|
||
var copy = {data: leaf.data}, next = copy;
|
||
while (leaf = leaf.next) next = next.next = {data: leaf.data};
|
||
return copy;
|
||
}
|
||
|
||
var treeProto$2 = binarytree.prototype = Binarytree.prototype;
|
||
|
||
treeProto$2.copy = function() {
|
||
var copy = new Binarytree(this._x, this._x0, this._x1),
|
||
node = this._root,
|
||
nodes,
|
||
child;
|
||
|
||
if (!node) return copy;
|
||
|
||
if (!node.length) return copy._root = leaf_copy$2(node), copy;
|
||
|
||
nodes = [{source: node, target: copy._root = new Array(2)}];
|
||
while (node = nodes.pop()) {
|
||
for (var i = 0; i < 2; ++i) {
|
||
if (child = node.source[i]) {
|
||
if (child.length) nodes.push({source: child, target: node.target[i] = new Array(2)});
|
||
else node.target[i] = leaf_copy$2(child);
|
||
}
|
||
}
|
||
}
|
||
|
||
return copy;
|
||
};
|
||
|
||
treeProto$2.add = tree_add$2;
|
||
treeProto$2.addAll = addAll$2;
|
||
treeProto$2.cover = tree_cover$2;
|
||
treeProto$2.data = tree_data$2;
|
||
treeProto$2.extent = tree_extent$2;
|
||
treeProto$2.find = tree_find$2;
|
||
treeProto$2.remove = tree_remove$2;
|
||
treeProto$2.removeAll = removeAll$2;
|
||
treeProto$2.root = tree_root$2;
|
||
treeProto$2.size = tree_size$2;
|
||
treeProto$2.visit = tree_visit$2;
|
||
treeProto$2.visitAfter = tree_visitAfter$2;
|
||
treeProto$2.x = tree_x$2;
|
||
|
||
function tree_add$1(d) {
|
||
const x = +this._x.call(null, d),
|
||
y = +this._y.call(null, d);
|
||
return add$1(this.cover(x, y), x, y, d);
|
||
}
|
||
|
||
function add$1(tree, x, y, d) {
|
||
if (isNaN(x) || isNaN(y)) return tree; // ignore invalid points
|
||
|
||
var parent,
|
||
node = tree._root,
|
||
leaf = {data: d},
|
||
x0 = tree._x0,
|
||
y0 = tree._y0,
|
||
x1 = tree._x1,
|
||
y1 = tree._y1,
|
||
xm,
|
||
ym,
|
||
xp,
|
||
yp,
|
||
right,
|
||
bottom,
|
||
i,
|
||
j;
|
||
|
||
// If the tree is empty, initialize the root as a leaf.
|
||
if (!node) return tree._root = leaf, tree;
|
||
|
||
// Find the existing leaf for the new point, or add it.
|
||
while (node.length) {
|
||
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
||
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
||
if (parent = node, !(node = node[i = bottom << 1 | right])) return parent[i] = leaf, tree;
|
||
}
|
||
|
||
// Is the new point is exactly coincident with the existing point?
|
||
xp = +tree._x.call(null, node.data);
|
||
yp = +tree._y.call(null, node.data);
|
||
if (x === xp && y === yp) return leaf.next = node, parent ? parent[i] = leaf : tree._root = leaf, tree;
|
||
|
||
// Otherwise, split the leaf node until the old and new point are separated.
|
||
do {
|
||
parent = parent ? parent[i] = new Array(4) : tree._root = new Array(4);
|
||
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
||
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
||
} while ((i = bottom << 1 | right) === (j = (yp >= ym) << 1 | (xp >= xm)));
|
||
return parent[j] = node, parent[i] = leaf, tree;
|
||
}
|
||
|
||
function addAll$1(data) {
|
||
var d, i, n = data.length,
|
||
x,
|
||
y,
|
||
xz = new Array(n),
|
||
yz = new Array(n),
|
||
x0 = Infinity,
|
||
y0 = Infinity,
|
||
x1 = -Infinity,
|
||
y1 = -Infinity;
|
||
|
||
// Compute the points and their extent.
|
||
for (i = 0; i < n; ++i) {
|
||
if (isNaN(x = +this._x.call(null, d = data[i])) || isNaN(y = +this._y.call(null, d))) continue;
|
||
xz[i] = x;
|
||
yz[i] = y;
|
||
if (x < x0) x0 = x;
|
||
if (x > x1) x1 = x;
|
||
if (y < y0) y0 = y;
|
||
if (y > y1) y1 = y;
|
||
}
|
||
|
||
// If there were no (valid) points, abort.
|
||
if (x0 > x1 || y0 > y1) return this;
|
||
|
||
// Expand the tree to cover the new points.
|
||
this.cover(x0, y0).cover(x1, y1);
|
||
|
||
// Add the new points.
|
||
for (i = 0; i < n; ++i) {
|
||
add$1(this, xz[i], yz[i], data[i]);
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
function tree_cover$1(x, y) {
|
||
if (isNaN(x = +x) || isNaN(y = +y)) return this; // ignore invalid points
|
||
|
||
var x0 = this._x0,
|
||
y0 = this._y0,
|
||
x1 = this._x1,
|
||
y1 = this._y1;
|
||
|
||
// If the quadtree has no extent, initialize them.
|
||
// Integer extent are necessary so that if we later double the extent,
|
||
// the existing quadrant boundaries don’t change due to floating point error!
|
||
if (isNaN(x0)) {
|
||
x1 = (x0 = Math.floor(x)) + 1;
|
||
y1 = (y0 = Math.floor(y)) + 1;
|
||
}
|
||
|
||
// Otherwise, double repeatedly to cover.
|
||
else {
|
||
var z = x1 - x0 || 1,
|
||
node = this._root,
|
||
parent,
|
||
i;
|
||
|
||
while (x0 > x || x >= x1 || y0 > y || y >= y1) {
|
||
i = (y < y0) << 1 | (x < x0);
|
||
parent = new Array(4), parent[i] = node, node = parent, z *= 2;
|
||
switch (i) {
|
||
case 0: x1 = x0 + z, y1 = y0 + z; break;
|
||
case 1: x0 = x1 - z, y1 = y0 + z; break;
|
||
case 2: x1 = x0 + z, y0 = y1 - z; break;
|
||
case 3: x0 = x1 - z, y0 = y1 - z; break;
|
||
}
|
||
}
|
||
|
||
if (this._root && this._root.length) this._root = node;
|
||
}
|
||
|
||
this._x0 = x0;
|
||
this._y0 = y0;
|
||
this._x1 = x1;
|
||
this._y1 = y1;
|
||
return this;
|
||
}
|
||
|
||
function tree_data$1() {
|
||
var data = [];
|
||
this.visit(function(node) {
|
||
if (!node.length) do data.push(node.data); while (node = node.next)
|
||
});
|
||
return data;
|
||
}
|
||
|
||
function tree_extent$1(_) {
|
||
return arguments.length
|
||
? this.cover(+_[0][0], +_[0][1]).cover(+_[1][0], +_[1][1])
|
||
: isNaN(this._x0) ? undefined : [[this._x0, this._y0], [this._x1, this._y1]];
|
||
}
|
||
|
||
function Quad(node, x0, y0, x1, y1) {
|
||
this.node = node;
|
||
this.x0 = x0;
|
||
this.y0 = y0;
|
||
this.x1 = x1;
|
||
this.y1 = y1;
|
||
}
|
||
|
||
function tree_find$1(x, y, radius) {
|
||
var data,
|
||
x0 = this._x0,
|
||
y0 = this._y0,
|
||
x1,
|
||
y1,
|
||
x2,
|
||
y2,
|
||
x3 = this._x1,
|
||
y3 = this._y1,
|
||
quads = [],
|
||
node = this._root,
|
||
q,
|
||
i;
|
||
|
||
if (node) quads.push(new Quad(node, x0, y0, x3, y3));
|
||
if (radius == null) radius = Infinity;
|
||
else {
|
||
x0 = x - radius, y0 = y - radius;
|
||
x3 = x + radius, y3 = y + radius;
|
||
radius *= radius;
|
||
}
|
||
|
||
while (q = quads.pop()) {
|
||
|
||
// Stop searching if this quadrant can’t contain a closer node.
|
||
if (!(node = q.node)
|
||
|| (x1 = q.x0) > x3
|
||
|| (y1 = q.y0) > y3
|
||
|| (x2 = q.x1) < x0
|
||
|| (y2 = q.y1) < y0) continue;
|
||
|
||
// Bisect the current quadrant.
|
||
if (node.length) {
|
||
var xm = (x1 + x2) / 2,
|
||
ym = (y1 + y2) / 2;
|
||
|
||
quads.push(
|
||
new Quad(node[3], xm, ym, x2, y2),
|
||
new Quad(node[2], x1, ym, xm, y2),
|
||
new Quad(node[1], xm, y1, x2, ym),
|
||
new Quad(node[0], x1, y1, xm, ym)
|
||
);
|
||
|
||
// Visit the closest quadrant first.
|
||
if (i = (y >= ym) << 1 | (x >= xm)) {
|
||
q = quads[quads.length - 1];
|
||
quads[quads.length - 1] = quads[quads.length - 1 - i];
|
||
quads[quads.length - 1 - i] = q;
|
||
}
|
||
}
|
||
|
||
// Visit this point. (Visiting coincident points isn’t necessary!)
|
||
else {
|
||
var dx = x - +this._x.call(null, node.data),
|
||
dy = y - +this._y.call(null, node.data),
|
||
d2 = dx * dx + dy * dy;
|
||
if (d2 < radius) {
|
||
var d = Math.sqrt(radius = d2);
|
||
x0 = x - d, y0 = y - d;
|
||
x3 = x + d, y3 = y + d;
|
||
data = node.data;
|
||
}
|
||
}
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
function tree_remove$1(d) {
|
||
if (isNaN(x = +this._x.call(null, d)) || isNaN(y = +this._y.call(null, d))) return this; // ignore invalid points
|
||
|
||
var parent,
|
||
node = this._root,
|
||
retainer,
|
||
previous,
|
||
next,
|
||
x0 = this._x0,
|
||
y0 = this._y0,
|
||
x1 = this._x1,
|
||
y1 = this._y1,
|
||
x,
|
||
y,
|
||
xm,
|
||
ym,
|
||
right,
|
||
bottom,
|
||
i,
|
||
j;
|
||
|
||
// If the tree is empty, initialize the root as a leaf.
|
||
if (!node) return this;
|
||
|
||
// Find the leaf node for the point.
|
||
// While descending, also retain the deepest parent with a non-removed sibling.
|
||
if (node.length) while (true) {
|
||
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
||
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
||
if (!(parent = node, node = node[i = bottom << 1 | right])) return this;
|
||
if (!node.length) break;
|
||
if (parent[(i + 1) & 3] || parent[(i + 2) & 3] || parent[(i + 3) & 3]) retainer = parent, j = i;
|
||
}
|
||
|
||
// Find the point to remove.
|
||
while (node.data !== d) if (!(previous = node, node = node.next)) return this;
|
||
if (next = node.next) delete node.next;
|
||
|
||
// If there are multiple coincident points, remove just the point.
|
||
if (previous) return (next ? previous.next = next : delete previous.next), this;
|
||
|
||
// If this is the root point, remove it.
|
||
if (!parent) return this._root = next, this;
|
||
|
||
// Remove this leaf.
|
||
next ? parent[i] = next : delete parent[i];
|
||
|
||
// If the parent now contains exactly one leaf, collapse superfluous parents.
|
||
if ((node = parent[0] || parent[1] || parent[2] || parent[3])
|
||
&& node === (parent[3] || parent[2] || parent[1] || parent[0])
|
||
&& !node.length) {
|
||
if (retainer) retainer[j] = node;
|
||
else this._root = node;
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
function removeAll$1(data) {
|
||
for (var i = 0, n = data.length; i < n; ++i) this.remove(data[i]);
|
||
return this;
|
||
}
|
||
|
||
function tree_root$1() {
|
||
return this._root;
|
||
}
|
||
|
||
function tree_size$1() {
|
||
var size = 0;
|
||
this.visit(function(node) {
|
||
if (!node.length) do ++size; while (node = node.next)
|
||
});
|
||
return size;
|
||
}
|
||
|
||
function tree_visit$1(callback) {
|
||
var quads = [], q, node = this._root, child, x0, y0, x1, y1;
|
||
if (node) quads.push(new Quad(node, this._x0, this._y0, this._x1, this._y1));
|
||
while (q = quads.pop()) {
|
||
if (!callback(node = q.node, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1) && node.length) {
|
||
var xm = (x0 + x1) / 2, ym = (y0 + y1) / 2;
|
||
if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1));
|
||
if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1));
|
||
if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym));
|
||
if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym));
|
||
}
|
||
}
|
||
return this;
|
||
}
|
||
|
||
function tree_visitAfter$1(callback) {
|
||
var quads = [], next = [], q;
|
||
if (this._root) quads.push(new Quad(this._root, this._x0, this._y0, this._x1, this._y1));
|
||
while (q = quads.pop()) {
|
||
var node = q.node;
|
||
if (node.length) {
|
||
var child, x0 = q.x0, y0 = q.y0, x1 = q.x1, y1 = q.y1, xm = (x0 + x1) / 2, ym = (y0 + y1) / 2;
|
||
if (child = node[0]) quads.push(new Quad(child, x0, y0, xm, ym));
|
||
if (child = node[1]) quads.push(new Quad(child, xm, y0, x1, ym));
|
||
if (child = node[2]) quads.push(new Quad(child, x0, ym, xm, y1));
|
||
if (child = node[3]) quads.push(new Quad(child, xm, ym, x1, y1));
|
||
}
|
||
next.push(q);
|
||
}
|
||
while (q = next.pop()) {
|
||
callback(q.node, q.x0, q.y0, q.x1, q.y1);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
function defaultX$1(d) {
|
||
return d[0];
|
||
}
|
||
|
||
function tree_x$1(_) {
|
||
return arguments.length ? (this._x = _, this) : this._x;
|
||
}
|
||
|
||
function defaultY$1(d) {
|
||
return d[1];
|
||
}
|
||
|
||
function tree_y$1(_) {
|
||
return arguments.length ? (this._y = _, this) : this._y;
|
||
}
|
||
|
||
function quadtree(nodes, x, y) {
|
||
var tree = new Quadtree(x == null ? defaultX$1 : x, y == null ? defaultY$1 : y, NaN, NaN, NaN, NaN);
|
||
return nodes == null ? tree : tree.addAll(nodes);
|
||
}
|
||
|
||
function Quadtree(x, y, x0, y0, x1, y1) {
|
||
this._x = x;
|
||
this._y = y;
|
||
this._x0 = x0;
|
||
this._y0 = y0;
|
||
this._x1 = x1;
|
||
this._y1 = y1;
|
||
this._root = undefined;
|
||
}
|
||
|
||
function leaf_copy$1(leaf) {
|
||
var copy = {data: leaf.data}, next = copy;
|
||
while (leaf = leaf.next) next = next.next = {data: leaf.data};
|
||
return copy;
|
||
}
|
||
|
||
var treeProto$1 = quadtree.prototype = Quadtree.prototype;
|
||
|
||
treeProto$1.copy = function() {
|
||
var copy = new Quadtree(this._x, this._y, this._x0, this._y0, this._x1, this._y1),
|
||
node = this._root,
|
||
nodes,
|
||
child;
|
||
|
||
if (!node) return copy;
|
||
|
||
if (!node.length) return copy._root = leaf_copy$1(node), copy;
|
||
|
||
nodes = [{source: node, target: copy._root = new Array(4)}];
|
||
while (node = nodes.pop()) {
|
||
for (var i = 0; i < 4; ++i) {
|
||
if (child = node.source[i]) {
|
||
if (child.length) nodes.push({source: child, target: node.target[i] = new Array(4)});
|
||
else node.target[i] = leaf_copy$1(child);
|
||
}
|
||
}
|
||
}
|
||
|
||
return copy;
|
||
};
|
||
|
||
treeProto$1.add = tree_add$1;
|
||
treeProto$1.addAll = addAll$1;
|
||
treeProto$1.cover = tree_cover$1;
|
||
treeProto$1.data = tree_data$1;
|
||
treeProto$1.extent = tree_extent$1;
|
||
treeProto$1.find = tree_find$1;
|
||
treeProto$1.remove = tree_remove$1;
|
||
treeProto$1.removeAll = removeAll$1;
|
||
treeProto$1.root = tree_root$1;
|
||
treeProto$1.size = tree_size$1;
|
||
treeProto$1.visit = tree_visit$1;
|
||
treeProto$1.visitAfter = tree_visitAfter$1;
|
||
treeProto$1.x = tree_x$1;
|
||
treeProto$1.y = tree_y$1;
|
||
|
||
function tree_add(d) {
|
||
const x = +this._x.call(null, d),
|
||
y = +this._y.call(null, d),
|
||
z = +this._z.call(null, d);
|
||
return add(this.cover(x, y, z), x, y, z, d);
|
||
}
|
||
|
||
function add(tree, x, y, z, d) {
|
||
if (isNaN(x) || isNaN(y) || isNaN(z)) return tree; // ignore invalid points
|
||
|
||
var parent,
|
||
node = tree._root,
|
||
leaf = {data: d},
|
||
x0 = tree._x0,
|
||
y0 = tree._y0,
|
||
z0 = tree._z0,
|
||
x1 = tree._x1,
|
||
y1 = tree._y1,
|
||
z1 = tree._z1,
|
||
xm,
|
||
ym,
|
||
zm,
|
||
xp,
|
||
yp,
|
||
zp,
|
||
right,
|
||
bottom,
|
||
deep,
|
||
i,
|
||
j;
|
||
|
||
// If the tree is empty, initialize the root as a leaf.
|
||
if (!node) return tree._root = leaf, tree;
|
||
|
||
// Find the existing leaf for the new point, or add it.
|
||
while (node.length) {
|
||
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
||
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
||
if (deep = z >= (zm = (z0 + z1) / 2)) z0 = zm; else z1 = zm;
|
||
if (parent = node, !(node = node[i = deep << 2 | bottom << 1 | right])) return parent[i] = leaf, tree;
|
||
}
|
||
|
||
// Is the new point is exactly coincident with the existing point?
|
||
xp = +tree._x.call(null, node.data);
|
||
yp = +tree._y.call(null, node.data);
|
||
zp = +tree._z.call(null, node.data);
|
||
if (x === xp && y === yp && z === zp) return leaf.next = node, parent ? parent[i] = leaf : tree._root = leaf, tree;
|
||
|
||
// Otherwise, split the leaf node until the old and new point are separated.
|
||
do {
|
||
parent = parent ? parent[i] = new Array(8) : tree._root = new Array(8);
|
||
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
||
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
||
if (deep = z >= (zm = (z0 + z1) / 2)) z0 = zm; else z1 = zm;
|
||
} while ((i = deep << 2 | bottom << 1 | right) === (j = (zp >= zm) << 2 | (yp >= ym) << 1 | (xp >= xm)));
|
||
return parent[j] = node, parent[i] = leaf, tree;
|
||
}
|
||
|
||
function addAll(data) {
|
||
if (!Array.isArray(data)) data = Array.from(data);
|
||
const n = data.length;
|
||
const xz = new Float64Array(n);
|
||
const yz = new Float64Array(n);
|
||
const zz = new Float64Array(n);
|
||
let x0 = Infinity,
|
||
y0 = Infinity,
|
||
z0 = Infinity,
|
||
x1 = -Infinity,
|
||
y1 = -Infinity,
|
||
z1 = -Infinity;
|
||
|
||
// Compute the points and their extent.
|
||
for (let i = 0, d, x, y, z; i < n; ++i) {
|
||
if (isNaN(x = +this._x.call(null, d = data[i])) || isNaN(y = +this._y.call(null, d)) || isNaN(z = +this._z.call(null, d))) continue;
|
||
xz[i] = x;
|
||
yz[i] = y;
|
||
zz[i] = z;
|
||
if (x < x0) x0 = x;
|
||
if (x > x1) x1 = x;
|
||
if (y < y0) y0 = y;
|
||
if (y > y1) y1 = y;
|
||
if (z < z0) z0 = z;
|
||
if (z > z1) z1 = z;
|
||
}
|
||
|
||
// If there were no (valid) points, abort.
|
||
if (x0 > x1 || y0 > y1 || z0 > z1) return this;
|
||
|
||
// Expand the tree to cover the new points.
|
||
this.cover(x0, y0, z0).cover(x1, y1, z1);
|
||
|
||
// Add the new points.
|
||
for (let i = 0; i < n; ++i) {
|
||
add(this, xz[i], yz[i], zz[i], data[i]);
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
function tree_cover(x, y, z) {
|
||
if (isNaN(x = +x) || isNaN(y = +y) || isNaN(z = +z)) return this; // ignore invalid points
|
||
|
||
var x0 = this._x0,
|
||
y0 = this._y0,
|
||
z0 = this._z0,
|
||
x1 = this._x1,
|
||
y1 = this._y1,
|
||
z1 = this._z1;
|
||
|
||
// If the octree has no extent, initialize them.
|
||
// Integer extent are necessary so that if we later double the extent,
|
||
// the existing octant boundaries don’t change due to floating point error!
|
||
if (isNaN(x0)) {
|
||
x1 = (x0 = Math.floor(x)) + 1;
|
||
y1 = (y0 = Math.floor(y)) + 1;
|
||
z1 = (z0 = Math.floor(z)) + 1;
|
||
}
|
||
|
||
// Otherwise, double repeatedly to cover.
|
||
else {
|
||
var t = x1 - x0 || 1,
|
||
node = this._root,
|
||
parent,
|
||
i;
|
||
|
||
while (x0 > x || x >= x1 || y0 > y || y >= y1 || z0 > z || z >= z1) {
|
||
i = (z < z0) << 2 | (y < y0) << 1 | (x < x0);
|
||
parent = new Array(8), parent[i] = node, node = parent, t *= 2;
|
||
switch (i) {
|
||
case 0: x1 = x0 + t, y1 = y0 + t, z1 = z0 + t; break;
|
||
case 1: x0 = x1 - t, y1 = y0 + t, z1 = z0 + t; break;
|
||
case 2: x1 = x0 + t, y0 = y1 - t, z1 = z0 + t; break;
|
||
case 3: x0 = x1 - t, y0 = y1 - t, z1 = z0 + t; break;
|
||
case 4: x1 = x0 + t, y1 = y0 + t, z0 = z1 - t; break;
|
||
case 5: x0 = x1 - t, y1 = y0 + t, z0 = z1 - t; break;
|
||
case 6: x1 = x0 + t, y0 = y1 - t, z0 = z1 - t; break;
|
||
case 7: x0 = x1 - t, y0 = y1 - t, z0 = z1 - t; break;
|
||
}
|
||
}
|
||
|
||
if (this._root && this._root.length) this._root = node;
|
||
}
|
||
|
||
this._x0 = x0;
|
||
this._y0 = y0;
|
||
this._z0 = z0;
|
||
this._x1 = x1;
|
||
this._y1 = y1;
|
||
this._z1 = z1;
|
||
return this;
|
||
}
|
||
|
||
function tree_data() {
|
||
var data = [];
|
||
this.visit(function(node) {
|
||
if (!node.length) do data.push(node.data); while (node = node.next)
|
||
});
|
||
return data;
|
||
}
|
||
|
||
function tree_extent(_) {
|
||
return arguments.length
|
||
? this.cover(+_[0][0], +_[0][1], +_[0][2]).cover(+_[1][0], +_[1][1], +_[1][2])
|
||
: isNaN(this._x0) ? undefined : [[this._x0, this._y0, this._z0], [this._x1, this._y1, this._z1]];
|
||
}
|
||
|
||
function Octant(node, x0, y0, z0, x1, y1, z1) {
|
||
this.node = node;
|
||
this.x0 = x0;
|
||
this.y0 = y0;
|
||
this.z0 = z0;
|
||
this.x1 = x1;
|
||
this.y1 = y1;
|
||
this.z1 = z1;
|
||
}
|
||
|
||
function tree_find(x, y, z, radius) {
|
||
var data,
|
||
x0 = this._x0,
|
||
y0 = this._y0,
|
||
z0 = this._z0,
|
||
x1,
|
||
y1,
|
||
z1,
|
||
x2,
|
||
y2,
|
||
z2,
|
||
x3 = this._x1,
|
||
y3 = this._y1,
|
||
z3 = this._z1,
|
||
octs = [],
|
||
node = this._root,
|
||
q,
|
||
i;
|
||
|
||
if (node) octs.push(new Octant(node, x0, y0, z0, x3, y3, z3));
|
||
if (radius == null) radius = Infinity;
|
||
else {
|
||
x0 = x - radius, y0 = y - radius, z0 = z - radius;
|
||
x3 = x + radius, y3 = y + radius, z3 = z + radius;
|
||
radius *= radius;
|
||
}
|
||
|
||
while (q = octs.pop()) {
|
||
|
||
// Stop searching if this octant can’t contain a closer node.
|
||
if (!(node = q.node)
|
||
|| (x1 = q.x0) > x3
|
||
|| (y1 = q.y0) > y3
|
||
|| (z1 = q.z0) > z3
|
||
|| (x2 = q.x1) < x0
|
||
|| (y2 = q.y1) < y0
|
||
|| (z2 = q.z1) < z0) continue;
|
||
|
||
// Bisect the current octant.
|
||
if (node.length) {
|
||
var xm = (x1 + x2) / 2,
|
||
ym = (y1 + y2) / 2,
|
||
zm = (z1 + z2) / 2;
|
||
|
||
octs.push(
|
||
new Octant(node[7], xm, ym, zm, x2, y2, z2),
|
||
new Octant(node[6], x1, ym, zm, xm, y2, z2),
|
||
new Octant(node[5], xm, y1, zm, x2, ym, z2),
|
||
new Octant(node[4], x1, y1, zm, xm, ym, z2),
|
||
new Octant(node[3], xm, ym, z1, x2, y2, zm),
|
||
new Octant(node[2], x1, ym, z1, xm, y2, zm),
|
||
new Octant(node[1], xm, y1, z1, x2, ym, zm),
|
||
new Octant(node[0], x1, y1, z1, xm, ym, zm)
|
||
);
|
||
|
||
// Visit the closest octant first.
|
||
if (i = (z >= zm) << 2 | (y >= ym) << 1 | (x >= xm)) {
|
||
q = octs[octs.length - 1];
|
||
octs[octs.length - 1] = octs[octs.length - 1 - i];
|
||
octs[octs.length - 1 - i] = q;
|
||
}
|
||
}
|
||
|
||
// Visit this point. (Visiting coincident points isn’t necessary!)
|
||
else {
|
||
var dx = x - +this._x.call(null, node.data),
|
||
dy = y - +this._y.call(null, node.data),
|
||
dz = z - +this._z.call(null, node.data),
|
||
d2 = dx * dx + dy * dy + dz * dz;
|
||
if (d2 < radius) {
|
||
var d = Math.sqrt(radius = d2);
|
||
x0 = x - d, y0 = y - d, z0 = z - d;
|
||
x3 = x + d, y3 = y + d, z3 = z + d;
|
||
data = node.data;
|
||
}
|
||
}
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
function tree_remove(d) {
|
||
if (isNaN(x = +this._x.call(null, d)) || isNaN(y = +this._y.call(null, d)) || isNaN(z = +this._z.call(null, d))) return this; // ignore invalid points
|
||
|
||
var parent,
|
||
node = this._root,
|
||
retainer,
|
||
previous,
|
||
next,
|
||
x0 = this._x0,
|
||
y0 = this._y0,
|
||
z0 = this._z0,
|
||
x1 = this._x1,
|
||
y1 = this._y1,
|
||
z1 = this._z1,
|
||
x,
|
||
y,
|
||
z,
|
||
xm,
|
||
ym,
|
||
zm,
|
||
right,
|
||
bottom,
|
||
deep,
|
||
i,
|
||
j;
|
||
|
||
// If the tree is empty, initialize the root as a leaf.
|
||
if (!node) return this;
|
||
|
||
// Find the leaf node for the point.
|
||
// While descending, also retain the deepest parent with a non-removed sibling.
|
||
if (node.length) while (true) {
|
||
if (right = x >= (xm = (x0 + x1) / 2)) x0 = xm; else x1 = xm;
|
||
if (bottom = y >= (ym = (y0 + y1) / 2)) y0 = ym; else y1 = ym;
|
||
if (deep = z >= (zm = (z0 + z1) / 2)) z0 = zm; else z1 = zm;
|
||
if (!(parent = node, node = node[i = deep << 2 | bottom << 1 | right])) return this;
|
||
if (!node.length) break;
|
||
if (parent[(i + 1) & 7] || parent[(i + 2) & 7] || parent[(i + 3) & 7] || parent[(i + 4) & 7] || parent[(i + 5) & 7] || parent[(i + 6) & 7] || parent[(i + 7) & 7]) retainer = parent, j = i;
|
||
}
|
||
|
||
// Find the point to remove.
|
||
while (node.data !== d) if (!(previous = node, node = node.next)) return this;
|
||
if (next = node.next) delete node.next;
|
||
|
||
// If there are multiple coincident points, remove just the point.
|
||
if (previous) return (next ? previous.next = next : delete previous.next), this;
|
||
|
||
// If this is the root point, remove it.
|
||
if (!parent) return this._root = next, this;
|
||
|
||
// Remove this leaf.
|
||
next ? parent[i] = next : delete parent[i];
|
||
|
||
// If the parent now contains exactly one leaf, collapse superfluous parents.
|
||
if ((node = parent[0] || parent[1] || parent[2] || parent[3] || parent[4] || parent[5] || parent[6] || parent[7])
|
||
&& node === (parent[7] || parent[6] || parent[5] || parent[4] || parent[3] || parent[2] || parent[1] || parent[0])
|
||
&& !node.length) {
|
||
if (retainer) retainer[j] = node;
|
||
else this._root = node;
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
function removeAll(data) {
|
||
for (var i = 0, n = data.length; i < n; ++i) this.remove(data[i]);
|
||
return this;
|
||
}
|
||
|
||
function tree_root() {
|
||
return this._root;
|
||
}
|
||
|
||
function tree_size() {
|
||
var size = 0;
|
||
this.visit(function(node) {
|
||
if (!node.length) do ++size; while (node = node.next)
|
||
});
|
||
return size;
|
||
}
|
||
|
||
function tree_visit(callback) {
|
||
var octs = [], q, node = this._root, child, x0, y0, z0, x1, y1, z1;
|
||
if (node) octs.push(new Octant(node, this._x0, this._y0, this._z0, this._x1, this._y1, this._z1));
|
||
while (q = octs.pop()) {
|
||
if (!callback(node = q.node, x0 = q.x0, y0 = q.y0, z0 = q.z0, x1 = q.x1, y1 = q.y1, z1 = q.z1) && node.length) {
|
||
var xm = (x0 + x1) / 2, ym = (y0 + y1) / 2, zm = (z0 + z1) / 2;
|
||
if (child = node[7]) octs.push(new Octant(child, xm, ym, zm, x1, y1, z1));
|
||
if (child = node[6]) octs.push(new Octant(child, x0, ym, zm, xm, y1, z1));
|
||
if (child = node[5]) octs.push(new Octant(child, xm, y0, zm, x1, ym, z1));
|
||
if (child = node[4]) octs.push(new Octant(child, x0, y0, zm, xm, ym, z1));
|
||
if (child = node[3]) octs.push(new Octant(child, xm, ym, z0, x1, y1, zm));
|
||
if (child = node[2]) octs.push(new Octant(child, x0, ym, z0, xm, y1, zm));
|
||
if (child = node[1]) octs.push(new Octant(child, xm, y0, z0, x1, ym, zm));
|
||
if (child = node[0]) octs.push(new Octant(child, x0, y0, z0, xm, ym, zm));
|
||
}
|
||
}
|
||
return this;
|
||
}
|
||
|
||
function tree_visitAfter(callback) {
|
||
var octs = [], next = [], q;
|
||
if (this._root) octs.push(new Octant(this._root, this._x0, this._y0, this._z0, this._x1, this._y1, this._z1));
|
||
while (q = octs.pop()) {
|
||
var node = q.node;
|
||
if (node.length) {
|
||
var child, x0 = q.x0, y0 = q.y0, z0 = q.z0, x1 = q.x1, y1 = q.y1, z1 = q.z1, xm = (x0 + x1) / 2, ym = (y0 + y1) / 2, zm = (z0 + z1) / 2;
|
||
if (child = node[0]) octs.push(new Octant(child, x0, y0, z0, xm, ym, zm));
|
||
if (child = node[1]) octs.push(new Octant(child, xm, y0, z0, x1, ym, zm));
|
||
if (child = node[2]) octs.push(new Octant(child, x0, ym, z0, xm, y1, zm));
|
||
if (child = node[3]) octs.push(new Octant(child, xm, ym, z0, x1, y1, zm));
|
||
if (child = node[4]) octs.push(new Octant(child, x0, y0, zm, xm, ym, z1));
|
||
if (child = node[5]) octs.push(new Octant(child, xm, y0, zm, x1, ym, z1));
|
||
if (child = node[6]) octs.push(new Octant(child, x0, ym, zm, xm, y1, z1));
|
||
if (child = node[7]) octs.push(new Octant(child, xm, ym, zm, x1, y1, z1));
|
||
}
|
||
next.push(q);
|
||
}
|
||
while (q = next.pop()) {
|
||
callback(q.node, q.x0, q.y0, q.z0, q.x1, q.y1, q.z1);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
function defaultX(d) {
|
||
return d[0];
|
||
}
|
||
|
||
function tree_x(_) {
|
||
return arguments.length ? (this._x = _, this) : this._x;
|
||
}
|
||
|
||
function defaultY(d) {
|
||
return d[1];
|
||
}
|
||
|
||
function tree_y(_) {
|
||
return arguments.length ? (this._y = _, this) : this._y;
|
||
}
|
||
|
||
function defaultZ(d) {
|
||
return d[2];
|
||
}
|
||
|
||
function tree_z(_) {
|
||
return arguments.length ? (this._z = _, this) : this._z;
|
||
}
|
||
|
||
function octree(nodes, x, y, z) {
|
||
var tree = new Octree(x == null ? defaultX : x, y == null ? defaultY : y, z == null ? defaultZ : z, NaN, NaN, NaN, NaN, NaN, NaN);
|
||
return nodes == null ? tree : tree.addAll(nodes);
|
||
}
|
||
|
||
function Octree(x, y, z, x0, y0, z0, x1, y1, z1) {
|
||
this._x = x;
|
||
this._y = y;
|
||
this._z = z;
|
||
this._x0 = x0;
|
||
this._y0 = y0;
|
||
this._z0 = z0;
|
||
this._x1 = x1;
|
||
this._y1 = y1;
|
||
this._z1 = z1;
|
||
this._root = undefined;
|
||
}
|
||
|
||
function leaf_copy(leaf) {
|
||
var copy = {data: leaf.data}, next = copy;
|
||
while (leaf = leaf.next) next = next.next = {data: leaf.data};
|
||
return copy;
|
||
}
|
||
|
||
var treeProto = octree.prototype = Octree.prototype;
|
||
|
||
treeProto.copy = function() {
|
||
var copy = new Octree(this._x, this._y, this._z, this._x0, this._y0, this._z0, this._x1, this._y1, this._z1),
|
||
node = this._root,
|
||
nodes,
|
||
child;
|
||
|
||
if (!node) return copy;
|
||
|
||
if (!node.length) return copy._root = leaf_copy(node), copy;
|
||
|
||
nodes = [{source: node, target: copy._root = new Array(8)}];
|
||
while (node = nodes.pop()) {
|
||
for (var i = 0; i < 8; ++i) {
|
||
if (child = node.source[i]) {
|
||
if (child.length) nodes.push({source: child, target: node.target[i] = new Array(8)});
|
||
else node.target[i] = leaf_copy(child);
|
||
}
|
||
}
|
||
}
|
||
|
||
return copy;
|
||
};
|
||
|
||
treeProto.add = tree_add;
|
||
treeProto.addAll = addAll;
|
||
treeProto.cover = tree_cover;
|
||
treeProto.data = tree_data;
|
||
treeProto.extent = tree_extent;
|
||
treeProto.find = tree_find;
|
||
treeProto.remove = tree_remove;
|
||
treeProto.removeAll = removeAll;
|
||
treeProto.root = tree_root;
|
||
treeProto.size = tree_size;
|
||
treeProto.visit = tree_visit;
|
||
treeProto.visitAfter = tree_visitAfter;
|
||
treeProto.x = tree_x;
|
||
treeProto.y = tree_y;
|
||
treeProto.z = tree_z;
|
||
|
||
function constant(x) {
|
||
return function() {
|
||
return x;
|
||
};
|
||
}
|
||
|
||
function jiggle(random) {
|
||
return (random() - 0.5) * 1e-6;
|
||
}
|
||
|
||
function index$3(d) {
|
||
return d.index;
|
||
}
|
||
|
||
function find(nodeById, nodeId) {
|
||
var node = nodeById.get(nodeId);
|
||
if (!node) throw new Error("node not found: " + nodeId);
|
||
return node;
|
||
}
|
||
|
||
function d3ForceLink(links) {
|
||
var id = index$3,
|
||
strength = defaultStrength,
|
||
strengths,
|
||
distance = constant(30),
|
||
distances,
|
||
nodes,
|
||
nDim,
|
||
count,
|
||
bias,
|
||
random,
|
||
iterations = 1;
|
||
|
||
if (links == null) links = [];
|
||
|
||
function defaultStrength(link) {
|
||
return 1 / Math.min(count[link.source.index], count[link.target.index]);
|
||
}
|
||
|
||
function force(alpha) {
|
||
for (var k = 0, n = links.length; k < iterations; ++k) {
|
||
for (var i = 0, link, source, target, x = 0, y = 0, z = 0, l, b; i < n; ++i) {
|
||
link = links[i], source = link.source, target = link.target;
|
||
x = target.x + target.vx - source.x - source.vx || jiggle(random);
|
||
if (nDim > 1) { y = target.y + target.vy - source.y - source.vy || jiggle(random); }
|
||
if (nDim > 2) { z = target.z + target.vz - source.z - source.vz || jiggle(random); }
|
||
l = Math.sqrt(x * x + y * y + z * z);
|
||
l = (l - distances[i]) / l * alpha * strengths[i];
|
||
x *= l, y *= l, z *= l;
|
||
|
||
target.vx -= x * (b = bias[i]);
|
||
if (nDim > 1) { target.vy -= y * b; }
|
||
if (nDim > 2) { target.vz -= z * b; }
|
||
|
||
source.vx += x * (b = 1 - b);
|
||
if (nDim > 1) { source.vy += y * b; }
|
||
if (nDim > 2) { source.vz += z * b; }
|
||
}
|
||
}
|
||
}
|
||
|
||
function initialize() {
|
||
if (!nodes) return;
|
||
|
||
var i,
|
||
n = nodes.length,
|
||
m = links.length,
|
||
nodeById = new Map(nodes.map((d, i) => [id(d, i, nodes), d])),
|
||
link;
|
||
|
||
for (i = 0, count = new Array(n); i < m; ++i) {
|
||
link = links[i], link.index = i;
|
||
if (typeof link.source !== "object") link.source = find(nodeById, link.source);
|
||
if (typeof link.target !== "object") link.target = find(nodeById, link.target);
|
||
count[link.source.index] = (count[link.source.index] || 0) + 1;
|
||
count[link.target.index] = (count[link.target.index] || 0) + 1;
|
||
}
|
||
|
||
for (i = 0, bias = new Array(m); i < m; ++i) {
|
||
link = links[i], bias[i] = count[link.source.index] / (count[link.source.index] + count[link.target.index]);
|
||
}
|
||
|
||
strengths = new Array(m), initializeStrength();
|
||
distances = new Array(m), initializeDistance();
|
||
}
|
||
|
||
function initializeStrength() {
|
||
if (!nodes) return;
|
||
|
||
for (var i = 0, n = links.length; i < n; ++i) {
|
||
strengths[i] = +strength(links[i], i, links);
|
||
}
|
||
}
|
||
|
||
function initializeDistance() {
|
||
if (!nodes) return;
|
||
|
||
for (var i = 0, n = links.length; i < n; ++i) {
|
||
distances[i] = +distance(links[i], i, links);
|
||
}
|
||
}
|
||
|
||
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.links = function(_) {
|
||
return arguments.length ? (links = _, initialize(), force) : links;
|
||
};
|
||
|
||
force.id = function(_) {
|
||
return arguments.length ? (id = _, force) : id;
|
||
};
|
||
|
||
force.iterations = function(_) {
|
||
return arguments.length ? (iterations = +_, force) : iterations;
|
||
};
|
||
|
||
force.strength = function(_) {
|
||
return arguments.length ? (strength = typeof _ === "function" ? _ : constant(+_), initializeStrength(), force) : strength;
|
||
};
|
||
|
||
force.distance = function(_) {
|
||
return arguments.length ? (distance = typeof _ === "function" ? _ : constant(+_), initializeDistance(), force) : distance;
|
||
};
|
||
|
||
return force;
|
||
}
|
||
|
||
var noop$1 = {value: () => {}};
|
||
|
||
function dispatch() {
|
||
for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
|
||
if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t);
|
||
_[t] = [];
|
||
}
|
||
return new Dispatch(_);
|
||
}
|
||
|
||
function Dispatch(_) {
|
||
this._ = _;
|
||
}
|
||
|
||
function parseTypenames(typenames, types) {
|
||
return typenames.trim().split(/^|\s+/).map(function(t) {
|
||
var name = "", i = t.indexOf(".");
|
||
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
|
||
if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
|
||
return {type: t, name: name};
|
||
});
|
||
}
|
||
|
||
Dispatch.prototype = dispatch.prototype = {
|
||
constructor: Dispatch,
|
||
on: function(typename, callback) {
|
||
var _ = this._,
|
||
T = parseTypenames(typename + "", _),
|
||
t,
|
||
i = -1,
|
||
n = T.length;
|
||
|
||
// If no callback was specified, return the callback of the given type and name.
|
||
if (arguments.length < 2) {
|
||
while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
|
||
return;
|
||
}
|
||
|
||
// If a type was specified, set the callback for the given type and name.
|
||
// Otherwise, if a null callback was specified, remove callbacks of the given name.
|
||
if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
|
||
while (++i < n) {
|
||
if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
|
||
else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);
|
||
}
|
||
|
||
return this;
|
||
},
|
||
copy: function() {
|
||
var copy = {}, _ = this._;
|
||
for (var t in _) copy[t] = _[t].slice();
|
||
return new Dispatch(copy);
|
||
},
|
||
call: function(type, that) {
|
||
if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
|
||
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
|
||
for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
|
||
},
|
||
apply: function(type, that, args) {
|
||
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
|
||
for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
|
||
}
|
||
};
|
||
|
||
function get(type, name) {
|
||
for (var i = 0, n = type.length, c; i < n; ++i) {
|
||
if ((c = type[i]).name === name) {
|
||
return c.value;
|
||
}
|
||
}
|
||
}
|
||
|
||
function set(type, name, callback) {
|
||
for (var i = 0, n = type.length; i < n; ++i) {
|
||
if (type[i].name === name) {
|
||
type[i] = noop$1, type = type.slice(0, i).concat(type.slice(i + 1));
|
||
break;
|
||
}
|
||
}
|
||
if (callback != null) type.push({name: name, value: callback});
|
||
return type;
|
||
}
|
||
|
||
var frame = 0, // is an animation frame pending?
|
||
timeout = 0, // is a timeout pending?
|
||
interval = 0, // are any timers active?
|
||
pokeDelay = 1000, // how frequently we check for clock skew
|
||
taskHead,
|
||
taskTail,
|
||
clockLast = 0,
|
||
clockNow = 0,
|
||
clockSkew = 0,
|
||
clock = typeof performance === "object" && performance.now ? performance : Date,
|
||
setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); };
|
||
|
||
function now$1() {
|
||
return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew);
|
||
}
|
||
|
||
function clearNow() {
|
||
clockNow = 0;
|
||
}
|
||
|
||
function Timer() {
|
||
this._call =
|
||
this._time =
|
||
this._next = null;
|
||
}
|
||
|
||
Timer.prototype = timer.prototype = {
|
||
constructor: Timer,
|
||
restart: function(callback, delay, time) {
|
||
if (typeof callback !== "function") throw new TypeError("callback is not a function");
|
||
time = (time == null ? now$1() : +time) + (delay == null ? 0 : +delay);
|
||
if (!this._next && taskTail !== this) {
|
||
if (taskTail) taskTail._next = this;
|
||
else taskHead = this;
|
||
taskTail = this;
|
||
}
|
||
this._call = callback;
|
||
this._time = time;
|
||
sleep();
|
||
},
|
||
stop: function() {
|
||
if (this._call) {
|
||
this._call = null;
|
||
this._time = Infinity;
|
||
sleep();
|
||
}
|
||
}
|
||
};
|
||
|
||
function timer(callback, delay, time) {
|
||
var t = new Timer;
|
||
t.restart(callback, delay, time);
|
||
return t;
|
||
}
|
||
|
||
function timerFlush() {
|
||
now$1(); // Get the current time, if not already set.
|
||
++frame; // Pretend we’ve set an alarm, if we haven’t already.
|
||
var t = taskHead, e;
|
||
while (t) {
|
||
if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e);
|
||
t = t._next;
|
||
}
|
||
--frame;
|
||
}
|
||
|
||
function wake() {
|
||
clockNow = (clockLast = clock.now()) + clockSkew;
|
||
frame = timeout = 0;
|
||
try {
|
||
timerFlush();
|
||
} finally {
|
||
frame = 0;
|
||
nap();
|
||
clockNow = 0;
|
||
}
|
||
}
|
||
|
||
function poke() {
|
||
var now = clock.now(), delay = now - clockLast;
|
||
if (delay > pokeDelay) clockSkew -= delay, clockLast = now;
|
||
}
|
||
|
||
function nap() {
|
||
var t0, t1 = taskHead, t2, time = Infinity;
|
||
while (t1) {
|
||
if (t1._call) {
|
||
if (time > t1._time) time = t1._time;
|
||
t0 = t1, t1 = t1._next;
|
||
} else {
|
||
t2 = t1._next, t1._next = null;
|
||
t1 = t0 ? t0._next = t2 : taskHead = t2;
|
||
}
|
||
}
|
||
taskTail = t0;
|
||
sleep(time);
|
||
}
|
||
|
||
function sleep(time) {
|
||
if (frame) return; // Soonest alarm already set, or will be.
|
||
if (timeout) timeout = clearTimeout(timeout);
|
||
var delay = time - clockNow; // Strictly less than if we recomputed clockNow.
|
||
if (delay > 24) {
|
||
if (time < Infinity) timeout = setTimeout(wake, time - clock.now() - clockSkew);
|
||
if (interval) interval = clearInterval(interval);
|
||
} else {
|
||
if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay);
|
||
frame = 1, setFrame(wake);
|
||
}
|
||
}
|
||
|
||
// https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use
|
||
const a = 1664525;
|
||
const c = 1013904223;
|
||
const m = 4294967296; // 2^32
|
||
|
||
function lcg() {
|
||
let s = 1;
|
||
return () => (s = (a * s + c) % m) / m;
|
||
}
|
||
|
||
var MAX_DIMENSIONS = 3;
|
||
|
||
function x(d) {
|
||
return d.x;
|
||
}
|
||
|
||
function y(d) {
|
||
return d.y;
|
||
}
|
||
|
||
function z(d) {
|
||
return d.z;
|
||
}
|
||
|
||
var initialRadius = 10,
|
||
initialAngleRoll = Math.PI * (3 - Math.sqrt(5)), // Golden ratio angle
|
||
initialAngleYaw = Math.PI * 20 / (9 + Math.sqrt(221)); // Markov irrational number
|
||
|
||
function d3ForceSimulation(nodes, numDimensions) {
|
||
numDimensions = numDimensions || 2;
|
||
|
||
var nDim = Math.min(MAX_DIMENSIONS, Math.max(1, Math.round(numDimensions))),
|
||
simulation,
|
||
alpha = 1,
|
||
alphaMin = 0.001,
|
||
alphaDecay = 1 - Math.pow(alphaMin, 1 / 300),
|
||
alphaTarget = 0,
|
||
velocityDecay = 0.6,
|
||
forces = new Map(),
|
||
stepper = timer(step),
|
||
event = dispatch("tick", "end"),
|
||
random = lcg();
|
||
|
||
if (nodes == null) nodes = [];
|
||
|
||
function step() {
|
||
tick();
|
||
event.call("tick", simulation);
|
||
if (alpha < alphaMin) {
|
||
stepper.stop();
|
||
event.call("end", simulation);
|
||
}
|
||
}
|
||
|
||
function tick(iterations) {
|
||
var i, n = nodes.length, node;
|
||
|
||
if (iterations === undefined) iterations = 1;
|
||
|
||
for (var k = 0; k < iterations; ++k) {
|
||
alpha += (alphaTarget - alpha) * alphaDecay;
|
||
|
||
forces.forEach(function (force) {
|
||
force(alpha);
|
||
});
|
||
|
||
for (i = 0; i < n; ++i) {
|
||
node = nodes[i];
|
||
if (node.fx == null) node.x += node.vx *= velocityDecay;
|
||
else node.x = node.fx, node.vx = 0;
|
||
if (nDim > 1) {
|
||
if (node.fy == null) node.y += node.vy *= velocityDecay;
|
||
else node.y = node.fy, node.vy = 0;
|
||
}
|
||
if (nDim > 2) {
|
||
if (node.fz == null) node.z += node.vz *= velocityDecay;
|
||
else node.z = node.fz, node.vz = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
return simulation;
|
||
}
|
||
|
||
function initializeNodes() {
|
||
for (var i = 0, n = nodes.length, node; i < n; ++i) {
|
||
node = nodes[i], node.index = i;
|
||
if (node.fx != null) node.x = node.fx;
|
||
if (node.fy != null) node.y = node.fy;
|
||
if (node.fz != null) node.z = node.fz;
|
||
if (isNaN(node.x) || (nDim > 1 && isNaN(node.y)) || (nDim > 2 && isNaN(node.z))) {
|
||
var radius = initialRadius * (nDim > 2 ? Math.cbrt(0.5 + i) : (nDim > 1 ? Math.sqrt(0.5 + i) : i)),
|
||
rollAngle = i * initialAngleRoll,
|
||
yawAngle = i * initialAngleYaw;
|
||
|
||
if (nDim === 1) {
|
||
node.x = radius;
|
||
} else if (nDim === 2) {
|
||
node.x = radius * Math.cos(rollAngle);
|
||
node.y = radius * Math.sin(rollAngle);
|
||
} else { // 3 dimensions: use spherical distribution along 2 irrational number angles
|
||
node.x = radius * Math.sin(rollAngle) * Math.cos(yawAngle);
|
||
node.y = radius * Math.cos(rollAngle);
|
||
node.z = radius * Math.sin(rollAngle) * Math.sin(yawAngle);
|
||
}
|
||
}
|
||
if (isNaN(node.vx) || (nDim > 1 && isNaN(node.vy)) || (nDim > 2 && isNaN(node.vz))) {
|
||
node.vx = 0;
|
||
if (nDim > 1) { node.vy = 0; }
|
||
if (nDim > 2) { node.vz = 0; }
|
||
}
|
||
}
|
||
}
|
||
|
||
function initializeForce(force) {
|
||
if (force.initialize) force.initialize(nodes, random, nDim);
|
||
return force;
|
||
}
|
||
|
||
initializeNodes();
|
||
|
||
return simulation = {
|
||
tick: tick,
|
||
|
||
restart: function() {
|
||
return stepper.restart(step), simulation;
|
||
},
|
||
|
||
stop: function() {
|
||
return stepper.stop(), simulation;
|
||
},
|
||
|
||
numDimensions: function(_) {
|
||
return arguments.length
|
||
? (nDim = Math.min(MAX_DIMENSIONS, Math.max(1, Math.round(_))), forces.forEach(initializeForce), simulation)
|
||
: nDim;
|
||
},
|
||
|
||
nodes: function(_) {
|
||
return arguments.length ? (nodes = _, initializeNodes(), forces.forEach(initializeForce), simulation) : nodes;
|
||
},
|
||
|
||
alpha: function(_) {
|
||
return arguments.length ? (alpha = +_, simulation) : alpha;
|
||
},
|
||
|
||
alphaMin: function(_) {
|
||
return arguments.length ? (alphaMin = +_, simulation) : alphaMin;
|
||
},
|
||
|
||
alphaDecay: function(_) {
|
||
return arguments.length ? (alphaDecay = +_, simulation) : +alphaDecay;
|
||
},
|
||
|
||
alphaTarget: function(_) {
|
||
return arguments.length ? (alphaTarget = +_, simulation) : alphaTarget;
|
||
},
|
||
|
||
velocityDecay: function(_) {
|
||
return arguments.length ? (velocityDecay = 1 - _, simulation) : 1 - velocityDecay;
|
||
},
|
||
|
||
randomSource: function(_) {
|
||
return arguments.length ? (random = _, forces.forEach(initializeForce), simulation) : random;
|
||
},
|
||
|
||
force: function(name, _) {
|
||
return arguments.length > 1 ? ((_ == null ? forces.delete(name) : forces.set(name, initializeForce(_))), simulation) : forces.get(name);
|
||
},
|
||
|
||
find: function() {
|
||
var args = Array.prototype.slice.call(arguments);
|
||
var x = args.shift() || 0,
|
||
y = (nDim > 1 ? args.shift() : null) || 0,
|
||
z = (nDim > 2 ? args.shift() : null) || 0,
|
||
radius = args.shift() || Infinity;
|
||
|
||
var i = 0,
|
||
n = nodes.length,
|
||
dx,
|
||
dy,
|
||
dz,
|
||
d2,
|
||
node,
|
||
closest;
|
||
|
||
radius *= radius;
|
||
|
||
for (i = 0; i < n; ++i) {
|
||
node = nodes[i];
|
||
dx = x - node.x;
|
||
dy = y - (node.y || 0);
|
||
dz = z - (node.z ||0);
|
||
d2 = dx * dx + dy * dy + dz * dz;
|
||
if (d2 < radius) closest = node, radius = d2;
|
||
}
|
||
|
||
return closest;
|
||
},
|
||
|
||
on: function(name, _) {
|
||
return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name);
|
||
}
|
||
};
|
||
}
|
||
|
||
function d3ForceManyBody() {
|
||
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;
|
||
}
|
||
|
||
function d3ForceRadial(radius, x, y, z) {
|
||
var nodes,
|
||
nDim,
|
||
strength = constant(0.1),
|
||
strengths,
|
||
radiuses;
|
||
|
||
if (typeof radius !== "function") radius = constant(+radius);
|
||
if (x == null) x = 0;
|
||
if (y == null) y = 0;
|
||
if (z == null) z = 0;
|
||
|
||
function force(alpha) {
|
||
for (var i = 0, n = nodes.length; i < n; ++i) {
|
||
var node = nodes[i],
|
||
dx = node.x - x || 1e-6,
|
||
dy = (node.y || 0) - y || 1e-6,
|
||
dz = (node.z || 0) - z || 1e-6,
|
||
r = Math.sqrt(dx * dx + dy * dy + dz * dz),
|
||
k = (radiuses[i] - r) * strengths[i] * alpha / r;
|
||
node.vx += dx * k;
|
||
if (nDim>1) { node.vy += dy * k; }
|
||
if (nDim>2) { node.vz += dz * k; }
|
||
}
|
||
}
|
||
|
||
function initialize() {
|
||
if (!nodes) return;
|
||
var i, n = nodes.length;
|
||
strengths = new Array(n);
|
||
radiuses = new Array(n);
|
||
for (i = 0; i < n; ++i) {
|
||
radiuses[i] = +radius(nodes[i], i, nodes);
|
||
strengths[i] = isNaN(radiuses[i]) ? 0 : +strength(nodes[i], i, nodes);
|
||
}
|
||
}
|
||
|
||
force.initialize = function(initNodes, ...args) {
|
||
nodes = initNodes;
|
||
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.radius = function(_) {
|
||
return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius;
|
||
};
|
||
|
||
force.x = function(_) {
|
||
return arguments.length ? (x = +_, force) : x;
|
||
};
|
||
|
||
force.y = function(_) {
|
||
return arguments.length ? (y = +_, force) : y;
|
||
};
|
||
|
||
force.z = function(_) {
|
||
return arguments.length ? (z = +_, force) : z;
|
||
};
|
||
|
||
return force;
|
||
}
|
||
|
||
function getDefaultExportFromCjs (x) {
|
||
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
||
}
|
||
|
||
var ngraph_events = function eventify(subject) {
|
||
validateSubject(subject);
|
||
|
||
var eventsStorage = createEventsStorage(subject);
|
||
subject.on = eventsStorage.on;
|
||
subject.off = eventsStorage.off;
|
||
subject.fire = eventsStorage.fire;
|
||
return subject;
|
||
};
|
||
|
||
function createEventsStorage(subject) {
|
||
// Store all event listeners to this hash. Key is event name, value is array
|
||
// of callback records.
|
||
//
|
||
// A callback record consists of callback function and its optional context:
|
||
// { 'eventName' => [{callback: function, ctx: object}] }
|
||
var registeredEvents = Object.create(null);
|
||
|
||
return {
|
||
on: function (eventName, callback, ctx) {
|
||
if (typeof callback !== 'function') {
|
||
throw new Error('callback is expected to be a function');
|
||
}
|
||
var handlers = registeredEvents[eventName];
|
||
if (!handlers) {
|
||
handlers = registeredEvents[eventName] = [];
|
||
}
|
||
handlers.push({callback: callback, ctx: ctx});
|
||
|
||
return subject;
|
||
},
|
||
|
||
off: function (eventName, callback) {
|
||
var wantToRemoveAll = (typeof eventName === 'undefined');
|
||
if (wantToRemoveAll) {
|
||
// Killing old events storage should be enough in this case:
|
||
registeredEvents = Object.create(null);
|
||
return subject;
|
||
}
|
||
|
||
if (registeredEvents[eventName]) {
|
||
var deleteAllCallbacksForEvent = (typeof callback !== 'function');
|
||
if (deleteAllCallbacksForEvent) {
|
||
delete registeredEvents[eventName];
|
||
} else {
|
||
var callbacks = registeredEvents[eventName];
|
||
for (var i = 0; i < callbacks.length; ++i) {
|
||
if (callbacks[i].callback === callback) {
|
||
callbacks.splice(i, 1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return subject;
|
||
},
|
||
|
||
fire: function (eventName) {
|
||
var callbacks = registeredEvents[eventName];
|
||
if (!callbacks) {
|
||
return subject;
|
||
}
|
||
|
||
var fireArguments;
|
||
if (arguments.length > 1) {
|
||
fireArguments = Array.prototype.splice.call(arguments, 1);
|
||
}
|
||
for(var i = 0; i < callbacks.length; ++i) {
|
||
var callbackInfo = callbacks[i];
|
||
callbackInfo.callback.apply(callbackInfo.ctx, fireArguments);
|
||
}
|
||
|
||
return subject;
|
||
}
|
||
};
|
||
}
|
||
|
||
function validateSubject(subject) {
|
||
if (!subject) {
|
||
throw new Error('Eventify cannot use falsy object as events subject');
|
||
}
|
||
var reservedWords = ['on', 'fire', 'off'];
|
||
for (var i = 0; i < reservedWords.length; ++i) {
|
||
if (subject.hasOwnProperty(reservedWords[i])) {
|
||
throw new Error("Subject cannot be eventified, since it already has property '" + reservedWords[i] + "'");
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @fileOverview Contains definition of the core graph object.
|
||
*/
|
||
|
||
// TODO: need to change storage layer:
|
||
// 1. Be able to get all nodes O(1)
|
||
// 2. Be able to get number of links O(1)
|
||
|
||
/**
|
||
* @example
|
||
* var graph = require('ngraph.graph')();
|
||
* graph.addNode(1); // graph has one node.
|
||
* graph.addLink(2, 3); // now graph contains three nodes and one link.
|
||
*
|
||
*/
|
||
var ngraph_graph = createGraph;
|
||
|
||
var eventify$1 = ngraph_events;
|
||
|
||
/**
|
||
* Creates a new graph
|
||
*/
|
||
function createGraph(options) {
|
||
// Graph structure is maintained as dictionary of nodes
|
||
// and array of links. Each node has 'links' property which
|
||
// hold all links related to that node. And general links
|
||
// array is used to speed up all links enumeration. This is inefficient
|
||
// in terms of memory, but simplifies coding.
|
||
options = options || {};
|
||
if ('uniqueLinkId' in options) {
|
||
console.warn(
|
||
'ngraph.graph: Starting from version 0.14 `uniqueLinkId` is deprecated.\n' +
|
||
'Use `multigraph` option instead\n',
|
||
'\n',
|
||
'Note: there is also change in default behavior: From now on each graph\n'+
|
||
'is considered to be not a multigraph by default (each edge is unique).'
|
||
);
|
||
|
||
options.multigraph = options.uniqueLinkId;
|
||
}
|
||
|
||
// Dear reader, the non-multigraphs do not guarantee that there is only
|
||
// one link for a given pair of node. When this option is set to false
|
||
// we can save some memory and CPU (18% faster for non-multigraph);
|
||
if (options.multigraph === undefined) options.multigraph = false;
|
||
|
||
if (typeof Map !== 'function') {
|
||
// TODO: Should we polyfill it ourselves? We don't use much operations there..
|
||
throw new Error('ngraph.graph requires `Map` to be defined. Please polyfill it before using ngraph');
|
||
}
|
||
|
||
var nodes = new Map(); // nodeId => Node
|
||
var links = new Map(); // linkId => Link
|
||
// Hash of multi-edges. Used to track ids of edges between same nodes
|
||
var multiEdges = {};
|
||
var suspendEvents = 0;
|
||
|
||
var createLink = options.multigraph ? createUniqueLink : createSingleLink,
|
||
|
||
// Our graph API provides means to listen to graph changes. Users can subscribe
|
||
// to be notified about changes in the graph by using `on` method. However
|
||
// in some cases they don't use it. To avoid unnecessary memory consumption
|
||
// we will not record graph changes until we have at least one subscriber.
|
||
// Code below supports this optimization.
|
||
//
|
||
// Accumulates all changes made during graph updates.
|
||
// Each change element contains:
|
||
// changeType - one of the strings: 'add', 'remove' or 'update';
|
||
// node - if change is related to node this property is set to changed graph's node;
|
||
// link - if change is related to link this property is set to changed graph's link;
|
||
changes = [],
|
||
recordLinkChange = noop,
|
||
recordNodeChange = noop,
|
||
enterModification = noop,
|
||
exitModification = noop;
|
||
|
||
// this is our public API:
|
||
var graphPart = {
|
||
/**
|
||
* Sometimes duck typing could be slow. Giving clients a hint about data structure
|
||
* via explicit version number here:
|
||
*/
|
||
version: 20.0,
|
||
|
||
/**
|
||
* Adds node to the graph. If node with given id already exists in the graph
|
||
* its data is extended with whatever comes in 'data' argument.
|
||
*
|
||
* @param nodeId the node's identifier. A string or number is preferred.
|
||
* @param [data] additional data for the node being added. If node already
|
||
* exists its data object is augmented with the new one.
|
||
*
|
||
* @return {node} The newly added node or node with given id if it already exists.
|
||
*/
|
||
addNode: addNode,
|
||
|
||
/**
|
||
* Adds a link to the graph. The function always create a new
|
||
* link between two nodes. If one of the nodes does not exists
|
||
* a new node is created.
|
||
*
|
||
* @param fromId link start node id;
|
||
* @param toId link end node id;
|
||
* @param [data] additional data to be set on the new link;
|
||
*
|
||
* @return {link} The newly created link
|
||
*/
|
||
addLink: addLink,
|
||
|
||
/**
|
||
* Removes link from the graph. If link does not exist does nothing.
|
||
*
|
||
* @param link - object returned by addLink() or getLinks() methods.
|
||
*
|
||
* @returns true if link was removed; false otherwise.
|
||
*/
|
||
removeLink: removeLink,
|
||
|
||
/**
|
||
* Removes node with given id from the graph. If node does not exist in the graph
|
||
* does nothing.
|
||
*
|
||
* @param nodeId node's identifier passed to addNode() function.
|
||
*
|
||
* @returns true if node was removed; false otherwise.
|
||
*/
|
||
removeNode: removeNode,
|
||
|
||
/**
|
||
* Gets node with given identifier. If node does not exist undefined value is returned.
|
||
*
|
||
* @param nodeId requested node identifier;
|
||
*
|
||
* @return {node} in with requested identifier or undefined if no such node exists.
|
||
*/
|
||
getNode: getNode,
|
||
|
||
/**
|
||
* Gets number of nodes in this graph.
|
||
*
|
||
* @return number of nodes in the graph.
|
||
*/
|
||
getNodeCount: getNodeCount,
|
||
|
||
/**
|
||
* Gets total number of links in the graph.
|
||
*/
|
||
getLinkCount: getLinkCount,
|
||
|
||
/**
|
||
* Gets total number of links in the graph.
|
||
*/
|
||
getEdgeCount: getLinkCount,
|
||
|
||
/**
|
||
* Synonym for `getLinkCount()`
|
||
*/
|
||
getLinksCount: getLinkCount,
|
||
|
||
/**
|
||
* Synonym for `getNodeCount()`
|
||
*/
|
||
getNodesCount: getNodeCount,
|
||
|
||
/**
|
||
* Gets all links (inbound and outbound) from the node with given id.
|
||
* If node with given id is not found null is returned.
|
||
*
|
||
* @param nodeId requested node identifier.
|
||
*
|
||
* @return Set of links from and to requested node if such node exists;
|
||
* otherwise null is returned.
|
||
*/
|
||
getLinks: getLinks,
|
||
|
||
/**
|
||
* Invokes callback on each node of the graph.
|
||
*
|
||
* @param {Function(node)} callback Function to be invoked. The function
|
||
* is passed one argument: visited node.
|
||
*/
|
||
forEachNode: forEachNode,
|
||
|
||
/**
|
||
* Invokes callback on every linked (adjacent) node to the given one.
|
||
*
|
||
* @param nodeId Identifier of the requested node.
|
||
* @param {Function(node, link)} callback Function to be called on all linked nodes.
|
||
* The function is passed two parameters: adjacent node and link object itself.
|
||
* @param oriented if true graph treated as oriented.
|
||
*/
|
||
forEachLinkedNode: forEachLinkedNode,
|
||
|
||
/**
|
||
* Enumerates all links in the graph
|
||
*
|
||
* @param {Function(link)} callback Function to be called on all links in the graph.
|
||
* The function is passed one parameter: graph's link object.
|
||
*
|
||
* Link object contains at least the following fields:
|
||
* fromId - node id where link starts;
|
||
* toId - node id where link ends,
|
||
* data - additional data passed to graph.addLink() method.
|
||
*/
|
||
forEachLink: forEachLink,
|
||
|
||
/**
|
||
* Suspend all notifications about graph changes until
|
||
* endUpdate is called.
|
||
*/
|
||
beginUpdate: enterModification,
|
||
|
||
/**
|
||
* Resumes all notifications about graph changes and fires
|
||
* graph 'changed' event in case there are any pending changes.
|
||
*/
|
||
endUpdate: exitModification,
|
||
|
||
/**
|
||
* Removes all nodes and links from the graph.
|
||
*/
|
||
clear: clear,
|
||
|
||
/**
|
||
* Detects whether there is a link between two nodes.
|
||
* Operation complexity is O(n) where n - number of links of a node.
|
||
* NOTE: this function is synonym for getLink()
|
||
*
|
||
* @returns link if there is one. null otherwise.
|
||
*/
|
||
hasLink: getLink,
|
||
|
||
/**
|
||
* Detects whether there is a node with given id
|
||
*
|
||
* Operation complexity is O(1)
|
||
* NOTE: this function is synonym for getNode()
|
||
*
|
||
* @returns node if there is one; Falsy value otherwise.
|
||
*/
|
||
hasNode: getNode,
|
||
|
||
/**
|
||
* Gets an edge between two nodes.
|
||
* Operation complexity is O(n) where n - number of links of a node.
|
||
*
|
||
* @param {string} fromId link start identifier
|
||
* @param {string} toId link end identifier
|
||
*
|
||
* @returns link if there is one; undefined otherwise.
|
||
*/
|
||
getLink: getLink
|
||
};
|
||
|
||
// this will add `on()` and `fire()` methods.
|
||
eventify$1(graphPart);
|
||
|
||
monitorSubscribers();
|
||
|
||
return graphPart;
|
||
|
||
function monitorSubscribers() {
|
||
var realOn = graphPart.on;
|
||
|
||
// replace real `on` with our temporary on, which will trigger change
|
||
// modification monitoring:
|
||
graphPart.on = on;
|
||
|
||
function on() {
|
||
// now it's time to start tracking stuff:
|
||
graphPart.beginUpdate = enterModification = enterModificationReal;
|
||
graphPart.endUpdate = exitModification = exitModificationReal;
|
||
recordLinkChange = recordLinkChangeReal;
|
||
recordNodeChange = recordNodeChangeReal;
|
||
|
||
// this will replace current `on` method with real pub/sub from `eventify`.
|
||
graphPart.on = realOn;
|
||
// delegate to real `on` handler:
|
||
return realOn.apply(graphPart, arguments);
|
||
}
|
||
}
|
||
|
||
function recordLinkChangeReal(link, changeType) {
|
||
changes.push({
|
||
link: link,
|
||
changeType: changeType
|
||
});
|
||
}
|
||
|
||
function recordNodeChangeReal(node, changeType) {
|
||
changes.push({
|
||
node: node,
|
||
changeType: changeType
|
||
});
|
||
}
|
||
|
||
function addNode(nodeId, data) {
|
||
if (nodeId === undefined) {
|
||
throw new Error('Invalid node identifier');
|
||
}
|
||
|
||
enterModification();
|
||
|
||
var node = getNode(nodeId);
|
||
if (!node) {
|
||
node = new Node(nodeId, data);
|
||
recordNodeChange(node, 'add');
|
||
} else {
|
||
node.data = data;
|
||
recordNodeChange(node, 'update');
|
||
}
|
||
|
||
nodes.set(nodeId, node);
|
||
|
||
exitModification();
|
||
return node;
|
||
}
|
||
|
||
function getNode(nodeId) {
|
||
return nodes.get(nodeId);
|
||
}
|
||
|
||
function removeNode(nodeId) {
|
||
var node = getNode(nodeId);
|
||
if (!node) {
|
||
return false;
|
||
}
|
||
|
||
enterModification();
|
||
|
||
var prevLinks = node.links;
|
||
if (prevLinks) {
|
||
prevLinks.forEach(removeLinkInstance);
|
||
node.links = null;
|
||
}
|
||
|
||
nodes.delete(nodeId);
|
||
|
||
recordNodeChange(node, 'remove');
|
||
|
||
exitModification();
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
function addLink(fromId, toId, data) {
|
||
enterModification();
|
||
|
||
var fromNode = getNode(fromId) || addNode(fromId);
|
||
var toNode = getNode(toId) || addNode(toId);
|
||
|
||
var link = createLink(fromId, toId, data);
|
||
var isUpdate = links.has(link.id);
|
||
|
||
links.set(link.id, link);
|
||
|
||
// TODO: this is not cool. On large graphs potentially would consume more memory.
|
||
addLinkToNode(fromNode, link);
|
||
if (fromId !== toId) {
|
||
// make sure we are not duplicating links for self-loops
|
||
addLinkToNode(toNode, link);
|
||
}
|
||
|
||
recordLinkChange(link, isUpdate ? 'update' : 'add');
|
||
|
||
exitModification();
|
||
|
||
return link;
|
||
}
|
||
|
||
function createSingleLink(fromId, toId, data) {
|
||
var linkId = makeLinkId(fromId, toId);
|
||
var prevLink = links.get(linkId);
|
||
if (prevLink) {
|
||
prevLink.data = data;
|
||
return prevLink;
|
||
}
|
||
|
||
return new Link(fromId, toId, data, linkId);
|
||
}
|
||
|
||
function createUniqueLink(fromId, toId, data) {
|
||
// TODO: Find a better/faster way to store multigraphs
|
||
var linkId = makeLinkId(fromId, toId);
|
||
var isMultiEdge = multiEdges.hasOwnProperty(linkId);
|
||
if (isMultiEdge || getLink(fromId, toId)) {
|
||
if (!isMultiEdge) {
|
||
multiEdges[linkId] = 0;
|
||
}
|
||
var suffix = '@' + (++multiEdges[linkId]);
|
||
linkId = makeLinkId(fromId + suffix, toId + suffix);
|
||
}
|
||
|
||
return new Link(fromId, toId, data, linkId);
|
||
}
|
||
|
||
function getNodeCount() {
|
||
return nodes.size;
|
||
}
|
||
|
||
function getLinkCount() {
|
||
return links.size;
|
||
}
|
||
|
||
function getLinks(nodeId) {
|
||
var node = getNode(nodeId);
|
||
return node ? node.links : null;
|
||
}
|
||
|
||
function removeLink(link, otherId) {
|
||
if (otherId !== undefined) {
|
||
link = getLink(link, otherId);
|
||
}
|
||
return removeLinkInstance(link);
|
||
}
|
||
|
||
function removeLinkInstance(link) {
|
||
if (!link) {
|
||
return false;
|
||
}
|
||
if (!links.get(link.id)) return false;
|
||
|
||
enterModification();
|
||
|
||
links.delete(link.id);
|
||
|
||
var fromNode = getNode(link.fromId);
|
||
var toNode = getNode(link.toId);
|
||
|
||
if (fromNode) {
|
||
fromNode.links.delete(link);
|
||
}
|
||
|
||
if (toNode) {
|
||
toNode.links.delete(link);
|
||
}
|
||
|
||
recordLinkChange(link, 'remove');
|
||
|
||
exitModification();
|
||
|
||
return true;
|
||
}
|
||
|
||
function getLink(fromNodeId, toNodeId) {
|
||
if (fromNodeId === undefined || toNodeId === undefined) return undefined;
|
||
return links.get(makeLinkId(fromNodeId, toNodeId));
|
||
}
|
||
|
||
function clear() {
|
||
enterModification();
|
||
forEachNode(function(node) {
|
||
removeNode(node.id);
|
||
});
|
||
exitModification();
|
||
}
|
||
|
||
function forEachLink(callback) {
|
||
if (typeof callback === 'function') {
|
||
var valuesIterator = links.values();
|
||
var nextValue = valuesIterator.next();
|
||
while (!nextValue.done) {
|
||
if (callback(nextValue.value)) {
|
||
return true; // client doesn't want to proceed. Return.
|
||
}
|
||
nextValue = valuesIterator.next();
|
||
}
|
||
}
|
||
}
|
||
|
||
function forEachLinkedNode(nodeId, callback, oriented) {
|
||
var node = getNode(nodeId);
|
||
|
||
if (node && node.links && typeof callback === 'function') {
|
||
if (oriented) {
|
||
return forEachOrientedLink(node.links, nodeId, callback);
|
||
} else {
|
||
return forEachNonOrientedLink(node.links, nodeId, callback);
|
||
}
|
||
}
|
||
}
|
||
|
||
// eslint-disable-next-line no-shadow
|
||
function forEachNonOrientedLink(links, nodeId, callback) {
|
||
var quitFast;
|
||
|
||
var valuesIterator = links.values();
|
||
var nextValue = valuesIterator.next();
|
||
while (!nextValue.done) {
|
||
var link = nextValue.value;
|
||
var linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId;
|
||
quitFast = callback(nodes.get(linkedNodeId), link);
|
||
if (quitFast) {
|
||
return true; // Client does not need more iterations. Break now.
|
||
}
|
||
nextValue = valuesIterator.next();
|
||
}
|
||
}
|
||
|
||
// eslint-disable-next-line no-shadow
|
||
function forEachOrientedLink(links, nodeId, callback) {
|
||
var quitFast;
|
||
var valuesIterator = links.values();
|
||
var nextValue = valuesIterator.next();
|
||
while (!nextValue.done) {
|
||
var link = nextValue.value;
|
||
if (link.fromId === nodeId) {
|
||
quitFast = callback(nodes.get(link.toId), link);
|
||
if (quitFast) {
|
||
return true; // Client does not need more iterations. Break now.
|
||
}
|
||
}
|
||
nextValue = valuesIterator.next();
|
||
}
|
||
}
|
||
|
||
// we will not fire anything until users of this library explicitly call `on()`
|
||
// method.
|
||
function noop() {}
|
||
|
||
// Enter, Exit modification allows bulk graph updates without firing events.
|
||
function enterModificationReal() {
|
||
suspendEvents += 1;
|
||
}
|
||
|
||
function exitModificationReal() {
|
||
suspendEvents -= 1;
|
||
if (suspendEvents === 0 && changes.length > 0) {
|
||
graphPart.fire('changed', changes);
|
||
changes.length = 0;
|
||
}
|
||
}
|
||
|
||
function forEachNode(callback) {
|
||
if (typeof callback !== 'function') {
|
||
throw new Error('Function is expected to iterate over graph nodes. You passed ' + callback);
|
||
}
|
||
|
||
var valuesIterator = nodes.values();
|
||
var nextValue = valuesIterator.next();
|
||
while (!nextValue.done) {
|
||
if (callback(nextValue.value)) {
|
||
return true; // client doesn't want to proceed. Return.
|
||
}
|
||
nextValue = valuesIterator.next();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Internal structure to represent node;
|
||
*/
|
||
function Node(id, data) {
|
||
this.id = id;
|
||
this.links = null;
|
||
this.data = data;
|
||
}
|
||
|
||
function addLinkToNode(node, link) {
|
||
if (node.links) {
|
||
node.links.add(link);
|
||
} else {
|
||
node.links = new Set([link]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Internal structure to represent links;
|
||
*/
|
||
function Link(fromId, toId, data, id) {
|
||
this.fromId = fromId;
|
||
this.toId = toId;
|
||
this.data = data;
|
||
this.id = id;
|
||
}
|
||
|
||
function makeLinkId(fromId, toId) {
|
||
return fromId.toString() + '👉 ' + toId.toString();
|
||
}
|
||
|
||
var graph = /*@__PURE__*/getDefaultExportFromCjs(ngraph_graph);
|
||
|
||
var ngraph_forcelayout = {exports: {}};
|
||
|
||
var generateCreateBody = {exports: {}};
|
||
|
||
var getVariableName$2 = function getVariableName(index) {
|
||
if (index === 0) return 'x';
|
||
if (index === 1) return 'y';
|
||
if (index === 2) return 'z';
|
||
return 'c' + (index + 1);
|
||
};
|
||
|
||
const getVariableName$1 = getVariableName$2;
|
||
|
||
var createPatternBuilder$6 = function createPatternBuilder(dimension) {
|
||
|
||
return pattern;
|
||
|
||
function pattern(template, config) {
|
||
let indent = (config && config.indent) || 0;
|
||
let join = (config && config.join !== undefined) ? config.join : '\n';
|
||
let indentString = Array(indent + 1).join(' ');
|
||
let buffer = [];
|
||
for (let i = 0; i < dimension; ++i) {
|
||
let variableName = getVariableName$1(i);
|
||
let prefix = (i === 0) ? '' : indentString;
|
||
buffer.push(prefix + template.replace(/{var}/g, variableName));
|
||
}
|
||
return buffer.join(join);
|
||
}
|
||
};
|
||
|
||
const createPatternBuilder$5 = createPatternBuilder$6;
|
||
|
||
generateCreateBody.exports = generateCreateBodyFunction$1;
|
||
generateCreateBody.exports.generateCreateBodyFunctionBody = generateCreateBodyFunctionBody;
|
||
|
||
// InlineTransform: getVectorCode
|
||
generateCreateBody.exports.getVectorCode = getVectorCode;
|
||
// InlineTransform: getBodyCode
|
||
generateCreateBody.exports.getBodyCode = getBodyCode;
|
||
// InlineTransformExport: module.exports = function() { return Body; }
|
||
|
||
function generateCreateBodyFunction$1(dimension, debugSetters) {
|
||
let code = generateCreateBodyFunctionBody(dimension, debugSetters);
|
||
let {Body} = (new Function(code))();
|
||
return Body;
|
||
}
|
||
|
||
function generateCreateBodyFunctionBody(dimension, debugSetters) {
|
||
let code = `
|
||
${getVectorCode(dimension, debugSetters)}
|
||
${getBodyCode(dimension)}
|
||
return {Body: Body, Vector: Vector};
|
||
`;
|
||
return code;
|
||
}
|
||
|
||
function getBodyCode(dimension) {
|
||
let pattern = createPatternBuilder$5(dimension);
|
||
let variableList = pattern('{var}', {join: ', '});
|
||
return `
|
||
function Body(${variableList}) {
|
||
this.isPinned = false;
|
||
this.pos = new Vector(${variableList});
|
||
this.force = new Vector();
|
||
this.velocity = new Vector();
|
||
this.mass = 1;
|
||
|
||
this.springCount = 0;
|
||
this.springLength = 0;
|
||
}
|
||
|
||
Body.prototype.reset = function() {
|
||
this.force.reset();
|
||
this.springCount = 0;
|
||
this.springLength = 0;
|
||
}
|
||
|
||
Body.prototype.setPosition = function (${variableList}) {
|
||
${pattern('this.pos.{var} = {var} || 0;', {indent: 2})}
|
||
};`;
|
||
}
|
||
|
||
function getVectorCode(dimension, debugSetters) {
|
||
let pattern = createPatternBuilder$5(dimension);
|
||
let setters = '';
|
||
if (debugSetters) {
|
||
setters = `${pattern("\n\
|
||
var v{var};\n\
|
||
Object.defineProperty(this, '{var}', {\n\
|
||
set: function(v) { \n\
|
||
if (!Number.isFinite(v)) throw new Error('Cannot set non-numbers to {var}');\n\
|
||
v{var} = v; \n\
|
||
},\n\
|
||
get: function() { return v{var}; }\n\
|
||
});")}`;
|
||
}
|
||
|
||
let variableList = pattern('{var}', {join: ', '});
|
||
return `function Vector(${variableList}) {
|
||
${setters}
|
||
if (typeof arguments[0] === 'object') {
|
||
// could be another vector
|
||
let v = arguments[0];
|
||
${pattern('if (!Number.isFinite(v.{var})) throw new Error("Expected value is not a finite number at Vector constructor ({var})");', {indent: 4})}
|
||
${pattern('this.{var} = v.{var};', {indent: 4})}
|
||
} else {
|
||
${pattern('this.{var} = typeof {var} === "number" ? {var} : 0;', {indent: 4})}
|
||
}
|
||
}
|
||
|
||
Vector.prototype.reset = function () {
|
||
${pattern('this.{var} = ', {join: ''})}0;
|
||
};`;
|
||
}
|
||
|
||
var generateCreateBodyExports = generateCreateBody.exports;
|
||
|
||
var generateQuadTree = {exports: {}};
|
||
|
||
const createPatternBuilder$4 = createPatternBuilder$6;
|
||
const getVariableName = getVariableName$2;
|
||
|
||
generateQuadTree.exports = generateQuadTreeFunction$1;
|
||
generateQuadTree.exports.generateQuadTreeFunctionBody = generateQuadTreeFunctionBody;
|
||
|
||
// These exports are for InlineTransform tool.
|
||
// InlineTransform: getInsertStackCode
|
||
generateQuadTree.exports.getInsertStackCode = getInsertStackCode;
|
||
// InlineTransform: getQuadNodeCode
|
||
generateQuadTree.exports.getQuadNodeCode = getQuadNodeCode;
|
||
// InlineTransform: isSamePosition
|
||
generateQuadTree.exports.isSamePosition = isSamePosition;
|
||
// InlineTransform: getChildBodyCode
|
||
generateQuadTree.exports.getChildBodyCode = getChildBodyCode;
|
||
// InlineTransform: setChildBodyCode
|
||
generateQuadTree.exports.setChildBodyCode = setChildBodyCode;
|
||
|
||
function generateQuadTreeFunction$1(dimension) {
|
||
let code = generateQuadTreeFunctionBody(dimension);
|
||
return (new Function(code))();
|
||
}
|
||
|
||
function generateQuadTreeFunctionBody(dimension) {
|
||
let pattern = createPatternBuilder$4(dimension);
|
||
let quadCount = Math.pow(2, dimension);
|
||
|
||
let code = `
|
||
${getInsertStackCode()}
|
||
${getQuadNodeCode(dimension)}
|
||
${isSamePosition(dimension)}
|
||
${getChildBodyCode(dimension)}
|
||
${setChildBodyCode(dimension)}
|
||
|
||
function createQuadTree(options, random) {
|
||
options = options || {};
|
||
options.gravity = typeof options.gravity === 'number' ? options.gravity : -1;
|
||
options.theta = typeof options.theta === 'number' ? options.theta : 0.8;
|
||
|
||
var gravity = options.gravity;
|
||
var updateQueue = [];
|
||
var insertStack = new InsertStack();
|
||
var theta = options.theta;
|
||
|
||
var nodesCache = [];
|
||
var currentInCache = 0;
|
||
var root = newNode();
|
||
|
||
return {
|
||
insertBodies: insertBodies,
|
||
|
||
/**
|
||
* Gets root node if it is present
|
||
*/
|
||
getRoot: function() {
|
||
return root;
|
||
},
|
||
|
||
updateBodyForce: update,
|
||
|
||
options: function(newOptions) {
|
||
if (newOptions) {
|
||
if (typeof newOptions.gravity === 'number') {
|
||
gravity = newOptions.gravity;
|
||
}
|
||
if (typeof newOptions.theta === 'number') {
|
||
theta = newOptions.theta;
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
return {
|
||
gravity: gravity,
|
||
theta: theta
|
||
};
|
||
}
|
||
};
|
||
|
||
function newNode() {
|
||
// To avoid pressure on GC we reuse nodes.
|
||
var node = nodesCache[currentInCache];
|
||
if (node) {
|
||
${assignQuads(' node.')}
|
||
node.body = null;
|
||
node.mass = ${pattern('node.mass_{var} = ', {join: ''})}0;
|
||
${pattern('node.min_{var} = node.max_{var} = ', {join: ''})}0;
|
||
} else {
|
||
node = new QuadNode();
|
||
nodesCache[currentInCache] = node;
|
||
}
|
||
|
||
++currentInCache;
|
||
return node;
|
||
}
|
||
|
||
function update(sourceBody) {
|
||
var queue = updateQueue;
|
||
var v;
|
||
${pattern('var d{var};', {indent: 4})}
|
||
var r;
|
||
${pattern('var f{var} = 0;', {indent: 4})}
|
||
var queueLength = 1;
|
||
var shiftIdx = 0;
|
||
var pushIdx = 1;
|
||
|
||
queue[0] = root;
|
||
|
||
while (queueLength) {
|
||
var node = queue[shiftIdx];
|
||
var body = node.body;
|
||
|
||
queueLength -= 1;
|
||
shiftIdx += 1;
|
||
var differentBody = (body !== sourceBody);
|
||
if (body && differentBody) {
|
||
// If the current node is a leaf node (and it is not source body),
|
||
// calculate the force exerted by the current node on body, and add this
|
||
// amount to body's net force.
|
||
${pattern('d{var} = body.pos.{var} - sourceBody.pos.{var};', {indent: 8})}
|
||
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
|
||
|
||
if (r === 0) {
|
||
// Poor man's protection against zero distance.
|
||
${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 10})}
|
||
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
|
||
}
|
||
|
||
// This is standard gravitation force calculation but we divide
|
||
// by r^3 to save two operations when normalizing force vector.
|
||
v = gravity * body.mass * sourceBody.mass / (r * r * r);
|
||
${pattern('f{var} += v * d{var};', {indent: 8})}
|
||
} else if (differentBody) {
|
||
// Otherwise, calculate the ratio s / r, where s is the width of the region
|
||
// represented by the internal node, and r is the distance between the body
|
||
// and the node's center-of-mass
|
||
${pattern('d{var} = node.mass_{var} / node.mass - sourceBody.pos.{var};', {indent: 8})}
|
||
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
|
||
|
||
if (r === 0) {
|
||
// Sorry about code duplication. I don't want to create many functions
|
||
// right away. Just want to see performance first.
|
||
${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 10})}
|
||
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
|
||
}
|
||
// If s / r < θ, treat this internal node as a single body, and calculate the
|
||
// force it exerts on sourceBody, and add this amount to sourceBody's net force.
|
||
if ((node.max_${getVariableName(0)} - node.min_${getVariableName(0)}) / r < theta) {
|
||
// in the if statement above we consider node's width only
|
||
// because the region was made into square during tree creation.
|
||
// Thus there is no difference between using width or height.
|
||
v = gravity * node.mass * sourceBody.mass / (r * r * r);
|
||
${pattern('f{var} += v * d{var};', {indent: 10})}
|
||
} else {
|
||
// Otherwise, run the procedure recursively on each of the current node's children.
|
||
|
||
// I intentionally unfolded this loop, to save several CPU cycles.
|
||
${runRecursiveOnChildren()}
|
||
}
|
||
}
|
||
}
|
||
|
||
${pattern('sourceBody.force.{var} += f{var};', {indent: 4})}
|
||
}
|
||
|
||
function insertBodies(bodies) {
|
||
${pattern('var {var}min = Number.MAX_VALUE;', {indent: 4})}
|
||
${pattern('var {var}max = Number.MIN_VALUE;', {indent: 4})}
|
||
var i = bodies.length;
|
||
|
||
// To reduce quad tree depth we are looking for exact bounding box of all particles.
|
||
while (i--) {
|
||
var pos = bodies[i].pos;
|
||
${pattern('if (pos.{var} < {var}min) {var}min = pos.{var};', {indent: 6})}
|
||
${pattern('if (pos.{var} > {var}max) {var}max = pos.{var};', {indent: 6})}
|
||
}
|
||
|
||
// Makes the bounds square.
|
||
var maxSideLength = -Infinity;
|
||
${pattern('if ({var}max - {var}min > maxSideLength) maxSideLength = {var}max - {var}min ;', {indent: 4})}
|
||
|
||
currentInCache = 0;
|
||
root = newNode();
|
||
${pattern('root.min_{var} = {var}min;', {indent: 4})}
|
||
${pattern('root.max_{var} = {var}min + maxSideLength;', {indent: 4})}
|
||
|
||
i = bodies.length - 1;
|
||
if (i >= 0) {
|
||
root.body = bodies[i];
|
||
}
|
||
while (i--) {
|
||
insert(bodies[i], root);
|
||
}
|
||
}
|
||
|
||
function insert(newBody) {
|
||
insertStack.reset();
|
||
insertStack.push(root, newBody);
|
||
|
||
while (!insertStack.isEmpty()) {
|
||
var stackItem = insertStack.pop();
|
||
var node = stackItem.node;
|
||
var body = stackItem.body;
|
||
|
||
if (!node.body) {
|
||
// This is internal node. Update the total mass of the node and center-of-mass.
|
||
${pattern('var {var} = body.pos.{var};', {indent: 8})}
|
||
node.mass += body.mass;
|
||
${pattern('node.mass_{var} += body.mass * {var};', {indent: 8})}
|
||
|
||
// Recursively insert the body in the appropriate quadrant.
|
||
// But first find the appropriate quadrant.
|
||
var quadIdx = 0; // Assume we are in the 0's quad.
|
||
${pattern('var min_{var} = node.min_{var};', {indent: 8})}
|
||
${pattern('var max_{var} = (min_{var} + node.max_{var}) / 2;', {indent: 8})}
|
||
|
||
${assignInsertionQuadIndex(8)}
|
||
|
||
var child = getChild(node, quadIdx);
|
||
|
||
if (!child) {
|
||
// The node is internal but this quadrant is not taken. Add
|
||
// subnode to it.
|
||
child = newNode();
|
||
${pattern('child.min_{var} = min_{var};', {indent: 10})}
|
||
${pattern('child.max_{var} = max_{var};', {indent: 10})}
|
||
child.body = body;
|
||
|
||
setChild(node, quadIdx, child);
|
||
} else {
|
||
// continue searching in this quadrant.
|
||
insertStack.push(child, body);
|
||
}
|
||
} else {
|
||
// We are trying to add to the leaf node.
|
||
// We have to convert current leaf into internal node
|
||
// and continue adding two nodes.
|
||
var oldBody = node.body;
|
||
node.body = null; // internal nodes do not cary bodies
|
||
|
||
if (isSamePosition(oldBody.pos, body.pos)) {
|
||
// Prevent infinite subdivision by bumping one node
|
||
// anywhere in this quadrant
|
||
var retriesCount = 3;
|
||
do {
|
||
var offset = random.nextDouble();
|
||
${pattern('var d{var} = (node.max_{var} - node.min_{var}) * offset;', {indent: 12})}
|
||
|
||
${pattern('oldBody.pos.{var} = node.min_{var} + d{var};', {indent: 12})}
|
||
retriesCount -= 1;
|
||
// Make sure we don't bump it out of the box. If we do, next iteration should fix it
|
||
} while (retriesCount > 0 && isSamePosition(oldBody.pos, body.pos));
|
||
|
||
if (retriesCount === 0 && isSamePosition(oldBody.pos, body.pos)) {
|
||
// This is very bad, we ran out of precision.
|
||
// if we do not return from the method we'll get into
|
||
// infinite loop here. So we sacrifice correctness of layout, and keep the app running
|
||
// Next layout iteration should get larger bounding box in the first step and fix this
|
||
return;
|
||
}
|
||
}
|
||
// Next iteration should subdivide node further.
|
||
insertStack.push(node, oldBody);
|
||
insertStack.push(node, body);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return createQuadTree;
|
||
|
||
`;
|
||
return code;
|
||
|
||
|
||
function assignInsertionQuadIndex(indentCount) {
|
||
let insertionCode = [];
|
||
let indent = Array(indentCount + 1).join(' ');
|
||
for (let i = 0; i < dimension; ++i) {
|
||
insertionCode.push(indent + `if (${getVariableName(i)} > max_${getVariableName(i)}) {`);
|
||
insertionCode.push(indent + ` quadIdx = quadIdx + ${Math.pow(2, i)};`);
|
||
insertionCode.push(indent + ` min_${getVariableName(i)} = max_${getVariableName(i)};`);
|
||
insertionCode.push(indent + ` max_${getVariableName(i)} = node.max_${getVariableName(i)};`);
|
||
insertionCode.push(indent + `}`);
|
||
}
|
||
return insertionCode.join('\n');
|
||
// if (x > max_x) { // somewhere in the eastern part.
|
||
// quadIdx = quadIdx + 1;
|
||
// left = right;
|
||
// right = node.right;
|
||
// }
|
||
}
|
||
|
||
function runRecursiveOnChildren() {
|
||
let indent = Array(11).join(' ');
|
||
let recursiveCode = [];
|
||
for (let i = 0; i < quadCount; ++i) {
|
||
recursiveCode.push(indent + `if (node.quad${i}) {`);
|
||
recursiveCode.push(indent + ` queue[pushIdx] = node.quad${i};`);
|
||
recursiveCode.push(indent + ` queueLength += 1;`);
|
||
recursiveCode.push(indent + ` pushIdx += 1;`);
|
||
recursiveCode.push(indent + `}`);
|
||
}
|
||
return recursiveCode.join('\n');
|
||
// if (node.quad0) {
|
||
// queue[pushIdx] = node.quad0;
|
||
// queueLength += 1;
|
||
// pushIdx += 1;
|
||
// }
|
||
}
|
||
|
||
function assignQuads(indent) {
|
||
// this.quad0 = null;
|
||
// this.quad1 = null;
|
||
// this.quad2 = null;
|
||
// this.quad3 = null;
|
||
let quads = [];
|
||
for (let i = 0; i < quadCount; ++i) {
|
||
quads.push(`${indent}quad${i} = null;`);
|
||
}
|
||
return quads.join('\n');
|
||
}
|
||
}
|
||
|
||
function isSamePosition(dimension) {
|
||
let pattern = createPatternBuilder$4(dimension);
|
||
return `
|
||
function isSamePosition(point1, point2) {
|
||
${pattern('var d{var} = Math.abs(point1.{var} - point2.{var});', {indent: 2})}
|
||
|
||
return ${pattern('d{var} < 1e-8', {join: ' && '})};
|
||
}
|
||
`;
|
||
}
|
||
|
||
function setChildBodyCode(dimension) {
|
||
var quadCount = Math.pow(2, dimension);
|
||
return `
|
||
function setChild(node, idx, child) {
|
||
${setChildBody()}
|
||
}`;
|
||
function setChildBody() {
|
||
let childBody = [];
|
||
for (let i = 0; i < quadCount; ++i) {
|
||
let prefix = (i === 0) ? ' ' : ' else ';
|
||
childBody.push(`${prefix}if (idx === ${i}) node.quad${i} = child;`);
|
||
}
|
||
|
||
return childBody.join('\n');
|
||
// if (idx === 0) node.quad0 = child;
|
||
// else if (idx === 1) node.quad1 = child;
|
||
// else if (idx === 2) node.quad2 = child;
|
||
// else if (idx === 3) node.quad3 = child;
|
||
}
|
||
}
|
||
|
||
function getChildBodyCode(dimension) {
|
||
return `function getChild(node, idx) {
|
||
${getChildBody()}
|
||
return null;
|
||
}`;
|
||
|
||
function getChildBody() {
|
||
let childBody = [];
|
||
let quadCount = Math.pow(2, dimension);
|
||
for (let i = 0; i < quadCount; ++i) {
|
||
childBody.push(` if (idx === ${i}) return node.quad${i};`);
|
||
}
|
||
|
||
return childBody.join('\n');
|
||
// if (idx === 0) return node.quad0;
|
||
// if (idx === 1) return node.quad1;
|
||
// if (idx === 2) return node.quad2;
|
||
// if (idx === 3) return node.quad3;
|
||
}
|
||
}
|
||
|
||
function getQuadNodeCode(dimension) {
|
||
let pattern = createPatternBuilder$4(dimension);
|
||
let quadCount = Math.pow(2, dimension);
|
||
var quadNodeCode = `
|
||
function QuadNode() {
|
||
// body stored inside this node. In quad tree only leaf nodes (by construction)
|
||
// contain bodies:
|
||
this.body = null;
|
||
|
||
// Child nodes are stored in quads. Each quad is presented by number:
|
||
// 0 | 1
|
||
// -----
|
||
// 2 | 3
|
||
${assignQuads(' this.')}
|
||
|
||
// Total mass of current node
|
||
this.mass = 0;
|
||
|
||
// Center of mass coordinates
|
||
${pattern('this.mass_{var} = 0;', {indent: 2})}
|
||
|
||
// bounding box coordinates
|
||
${pattern('this.min_{var} = 0;', {indent: 2})}
|
||
${pattern('this.max_{var} = 0;', {indent: 2})}
|
||
}
|
||
`;
|
||
return quadNodeCode;
|
||
|
||
function assignQuads(indent) {
|
||
// this.quad0 = null;
|
||
// this.quad1 = null;
|
||
// this.quad2 = null;
|
||
// this.quad3 = null;
|
||
let quads = [];
|
||
for (let i = 0; i < quadCount; ++i) {
|
||
quads.push(`${indent}quad${i} = null;`);
|
||
}
|
||
return quads.join('\n');
|
||
}
|
||
}
|
||
|
||
function getInsertStackCode() {
|
||
return `
|
||
/**
|
||
* Our implementation of QuadTree is non-recursive to avoid GC hit
|
||
* This data structure represent stack of elements
|
||
* which we are trying to insert into quad tree.
|
||
*/
|
||
function InsertStack () {
|
||
this.stack = [];
|
||
this.popIdx = 0;
|
||
}
|
||
|
||
InsertStack.prototype = {
|
||
isEmpty: function() {
|
||
return this.popIdx === 0;
|
||
},
|
||
push: function (node, body) {
|
||
var item = this.stack[this.popIdx];
|
||
if (!item) {
|
||
// we are trying to avoid memory pressure: create new element
|
||
// only when absolutely necessary
|
||
this.stack[this.popIdx] = new InsertStackElement(node, body);
|
||
} else {
|
||
item.node = node;
|
||
item.body = body;
|
||
}
|
||
++this.popIdx;
|
||
},
|
||
pop: function () {
|
||
if (this.popIdx > 0) {
|
||
return this.stack[--this.popIdx];
|
||
}
|
||
},
|
||
reset: function () {
|
||
this.popIdx = 0;
|
||
}
|
||
};
|
||
|
||
function InsertStackElement(node, body) {
|
||
this.node = node; // QuadTree node
|
||
this.body = body; // physical body which needs to be inserted to node
|
||
}
|
||
`;
|
||
}
|
||
|
||
var generateQuadTreeExports = generateQuadTree.exports;
|
||
|
||
var generateBounds = {exports: {}};
|
||
|
||
generateBounds.exports = generateBoundsFunction$1;
|
||
generateBounds.exports.generateFunctionBody = generateBoundsFunctionBody;
|
||
|
||
const createPatternBuilder$3 = createPatternBuilder$6;
|
||
|
||
function generateBoundsFunction$1(dimension) {
|
||
let code = generateBoundsFunctionBody(dimension);
|
||
return new Function('bodies', 'settings', 'random', code);
|
||
}
|
||
|
||
function generateBoundsFunctionBody(dimension) {
|
||
let pattern = createPatternBuilder$3(dimension);
|
||
|
||
let code = `
|
||
var boundingBox = {
|
||
${pattern('min_{var}: 0, max_{var}: 0,', {indent: 4})}
|
||
};
|
||
|
||
return {
|
||
box: boundingBox,
|
||
|
||
update: updateBoundingBox,
|
||
|
||
reset: resetBoundingBox,
|
||
|
||
getBestNewPosition: function (neighbors) {
|
||
var ${pattern('base_{var} = 0', {join: ', '})};
|
||
|
||
if (neighbors.length) {
|
||
for (var i = 0; i < neighbors.length; ++i) {
|
||
let neighborPos = neighbors[i].pos;
|
||
${pattern('base_{var} += neighborPos.{var};', {indent: 10})}
|
||
}
|
||
|
||
${pattern('base_{var} /= neighbors.length;', {indent: 8})}
|
||
} else {
|
||
${pattern('base_{var} = (boundingBox.min_{var} + boundingBox.max_{var}) / 2;', {indent: 8})}
|
||
}
|
||
|
||
var springLength = settings.springLength;
|
||
return {
|
||
${pattern('{var}: base_{var} + (random.nextDouble() - 0.5) * springLength,', {indent: 8})}
|
||
};
|
||
}
|
||
};
|
||
|
||
function updateBoundingBox() {
|
||
var i = bodies.length;
|
||
if (i === 0) return; // No bodies - no borders.
|
||
|
||
${pattern('var max_{var} = -Infinity;', {indent: 4})}
|
||
${pattern('var min_{var} = Infinity;', {indent: 4})}
|
||
|
||
while(i--) {
|
||
// this is O(n), it could be done faster with quadtree, if we check the root node bounds
|
||
var bodyPos = bodies[i].pos;
|
||
${pattern('if (bodyPos.{var} < min_{var}) min_{var} = bodyPos.{var};', {indent: 6})}
|
||
${pattern('if (bodyPos.{var} > max_{var}) max_{var} = bodyPos.{var};', {indent: 6})}
|
||
}
|
||
|
||
${pattern('boundingBox.min_{var} = min_{var};', {indent: 4})}
|
||
${pattern('boundingBox.max_{var} = max_{var};', {indent: 4})}
|
||
}
|
||
|
||
function resetBoundingBox() {
|
||
${pattern('boundingBox.min_{var} = boundingBox.max_{var} = 0;', {indent: 4})}
|
||
}
|
||
`;
|
||
return code;
|
||
}
|
||
|
||
var generateBoundsExports = generateBounds.exports;
|
||
|
||
var generateCreateDragForce = {exports: {}};
|
||
|
||
const createPatternBuilder$2 = createPatternBuilder$6;
|
||
|
||
generateCreateDragForce.exports = generateCreateDragForceFunction$1;
|
||
generateCreateDragForce.exports.generateCreateDragForceFunctionBody = generateCreateDragForceFunctionBody;
|
||
|
||
function generateCreateDragForceFunction$1(dimension) {
|
||
let code = generateCreateDragForceFunctionBody(dimension);
|
||
return new Function('options', code);
|
||
}
|
||
|
||
function generateCreateDragForceFunctionBody(dimension) {
|
||
let pattern = createPatternBuilder$2(dimension);
|
||
let code = `
|
||
if (!Number.isFinite(options.dragCoefficient)) throw new Error('dragCoefficient is not a finite number');
|
||
|
||
return {
|
||
update: function(body) {
|
||
${pattern('body.force.{var} -= options.dragCoefficient * body.velocity.{var};', {indent: 6})}
|
||
}
|
||
};
|
||
`;
|
||
return code;
|
||
}
|
||
|
||
var generateCreateDragForceExports = generateCreateDragForce.exports;
|
||
|
||
var generateCreateSpringForce = {exports: {}};
|
||
|
||
const createPatternBuilder$1 = createPatternBuilder$6;
|
||
|
||
generateCreateSpringForce.exports = generateCreateSpringForceFunction$1;
|
||
generateCreateSpringForce.exports.generateCreateSpringForceFunctionBody = generateCreateSpringForceFunctionBody;
|
||
|
||
function generateCreateSpringForceFunction$1(dimension) {
|
||
let code = generateCreateSpringForceFunctionBody(dimension);
|
||
return new Function('options', 'random', code);
|
||
}
|
||
|
||
function generateCreateSpringForceFunctionBody(dimension) {
|
||
let pattern = createPatternBuilder$1(dimension);
|
||
let code = `
|
||
if (!Number.isFinite(options.springCoefficient)) throw new Error('Spring coefficient is not a number');
|
||
if (!Number.isFinite(options.springLength)) throw new Error('Spring length is not a number');
|
||
|
||
return {
|
||
/**
|
||
* Updates forces acting on a spring
|
||
*/
|
||
update: function (spring) {
|
||
var body1 = spring.from;
|
||
var body2 = spring.to;
|
||
var length = spring.length < 0 ? options.springLength : spring.length;
|
||
${pattern('var d{var} = body2.pos.{var} - body1.pos.{var};', {indent: 6})}
|
||
var r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
|
||
|
||
if (r === 0) {
|
||
${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 8})}
|
||
r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
|
||
}
|
||
|
||
var d = r - length;
|
||
var coefficient = ((spring.coefficient > 0) ? spring.coefficient : options.springCoefficient) * d / r;
|
||
|
||
${pattern('body1.force.{var} += coefficient * d{var}', {indent: 6})};
|
||
body1.springCount += 1;
|
||
body1.springLength += r;
|
||
|
||
${pattern('body2.force.{var} -= coefficient * d{var}', {indent: 6})};
|
||
body2.springCount += 1;
|
||
body2.springLength += r;
|
||
}
|
||
};
|
||
`;
|
||
return code;
|
||
}
|
||
|
||
var generateCreateSpringForceExports = generateCreateSpringForce.exports;
|
||
|
||
var generateIntegrator = {exports: {}};
|
||
|
||
const createPatternBuilder = createPatternBuilder$6;
|
||
|
||
generateIntegrator.exports = generateIntegratorFunction$1;
|
||
generateIntegrator.exports.generateIntegratorFunctionBody = generateIntegratorFunctionBody;
|
||
|
||
function generateIntegratorFunction$1(dimension) {
|
||
let code = generateIntegratorFunctionBody(dimension);
|
||
return new Function('bodies', 'timeStep', 'adaptiveTimeStepWeight', code);
|
||
}
|
||
|
||
function generateIntegratorFunctionBody(dimension) {
|
||
let pattern = createPatternBuilder(dimension);
|
||
let code = `
|
||
var length = bodies.length;
|
||
if (length === 0) return 0;
|
||
|
||
${pattern('var d{var} = 0, t{var} = 0;', {indent: 2})}
|
||
|
||
for (var i = 0; i < length; ++i) {
|
||
var body = bodies[i];
|
||
if (body.isPinned) continue;
|
||
|
||
if (adaptiveTimeStepWeight && body.springCount) {
|
||
timeStep = (adaptiveTimeStepWeight * body.springLength/body.springCount);
|
||
}
|
||
|
||
var coeff = timeStep / body.mass;
|
||
|
||
${pattern('body.velocity.{var} += coeff * body.force.{var};', {indent: 4})}
|
||
${pattern('var v{var} = body.velocity.{var};', {indent: 4})}
|
||
var v = Math.sqrt(${pattern('v{var} * v{var}', {join: ' + '})});
|
||
|
||
if (v > 1) {
|
||
// We normalize it so that we move within timeStep range.
|
||
// for the case when v <= 1 - we let velocity to fade out.
|
||
${pattern('body.velocity.{var} = v{var} / v;', {indent: 6})}
|
||
}
|
||
|
||
${pattern('d{var} = timeStep * body.velocity.{var};', {indent: 4})}
|
||
|
||
${pattern('body.pos.{var} += d{var};', {indent: 4})}
|
||
|
||
${pattern('t{var} += Math.abs(d{var});', {indent: 4})}
|
||
}
|
||
|
||
return (${pattern('t{var} * t{var}', {join: ' + '})})/length;
|
||
`;
|
||
return code;
|
||
}
|
||
|
||
var generateIntegratorExports = generateIntegrator.exports;
|
||
|
||
var spring;
|
||
var hasRequiredSpring;
|
||
|
||
function requireSpring () {
|
||
if (hasRequiredSpring) return spring;
|
||
hasRequiredSpring = 1;
|
||
spring = Spring;
|
||
|
||
/**
|
||
* Represents a physical spring. Spring connects two bodies, has rest length
|
||
* stiffness coefficient and optional weight
|
||
*/
|
||
function Spring(fromBody, toBody, length, springCoefficient) {
|
||
this.from = fromBody;
|
||
this.to = toBody;
|
||
this.length = length;
|
||
this.coefficient = springCoefficient;
|
||
}
|
||
return spring;
|
||
}
|
||
|
||
var ngraph_merge;
|
||
var hasRequiredNgraph_merge;
|
||
|
||
function requireNgraph_merge () {
|
||
if (hasRequiredNgraph_merge) return ngraph_merge;
|
||
hasRequiredNgraph_merge = 1;
|
||
ngraph_merge = merge;
|
||
|
||
/**
|
||
* Augments `target` with properties in `options`. Does not override
|
||
* target's properties if they are defined and matches expected type in
|
||
* options
|
||
*
|
||
* @returns {Object} merged object
|
||
*/
|
||
function merge(target, options) {
|
||
var key;
|
||
if (!target) { target = {}; }
|
||
if (options) {
|
||
for (key in options) {
|
||
if (options.hasOwnProperty(key)) {
|
||
var targetHasIt = target.hasOwnProperty(key),
|
||
optionsValueType = typeof options[key],
|
||
shouldReplace = !targetHasIt || (typeof target[key] !== optionsValueType);
|
||
|
||
if (shouldReplace) {
|
||
target[key] = options[key];
|
||
} else if (optionsValueType === 'object') {
|
||
// go deep, don't care about loops here, we are simple API!:
|
||
target[key] = merge(target[key], options[key]);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return target;
|
||
}
|
||
return ngraph_merge;
|
||
}
|
||
|
||
var ngraph_random = {exports: {}};
|
||
|
||
var hasRequiredNgraph_random;
|
||
|
||
function requireNgraph_random () {
|
||
if (hasRequiredNgraph_random) return ngraph_random.exports;
|
||
hasRequiredNgraph_random = 1;
|
||
ngraph_random.exports = random;
|
||
|
||
// TODO: Deprecate?
|
||
ngraph_random.exports.random = random,
|
||
ngraph_random.exports.randomIterator = randomIterator;
|
||
|
||
/**
|
||
* Creates seeded PRNG with two methods:
|
||
* next() and nextDouble()
|
||
*/
|
||
function random(inputSeed) {
|
||
var seed = typeof inputSeed === 'number' ? inputSeed : (+new Date());
|
||
return new Generator(seed)
|
||
}
|
||
|
||
function Generator(seed) {
|
||
this.seed = seed;
|
||
}
|
||
|
||
/**
|
||
* Generates random integer number in the range from 0 (inclusive) to maxValue (exclusive)
|
||
*
|
||
* @param maxValue Number REQUIRED. Omitting this number will result in NaN values from PRNG.
|
||
*/
|
||
Generator.prototype.next = next;
|
||
|
||
/**
|
||
* Generates random double number in the range from 0 (inclusive) to 1 (exclusive)
|
||
* This function is the same as Math.random() (except that it could be seeded)
|
||
*/
|
||
Generator.prototype.nextDouble = nextDouble;
|
||
|
||
/**
|
||
* Returns a random real number from uniform distribution in [0, 1)
|
||
*/
|
||
Generator.prototype.uniform = nextDouble;
|
||
|
||
/**
|
||
* Returns a random real number from a Gaussian distribution
|
||
* with 0 as a mean, and 1 as standard deviation u ~ N(0,1)
|
||
*/
|
||
Generator.prototype.gaussian = gaussian;
|
||
|
||
function gaussian() {
|
||
// use the polar form of the Box-Muller transform
|
||
// based on https://introcs.cs.princeton.edu/java/23recursion/StdRandom.java
|
||
var r, x, y;
|
||
do {
|
||
x = this.nextDouble() * 2 - 1;
|
||
y = this.nextDouble() * 2 - 1;
|
||
r = x * x + y * y;
|
||
} while (r >= 1 || r === 0);
|
||
|
||
return x * Math.sqrt(-2 * Math.log(r)/r);
|
||
}
|
||
|
||
/**
|
||
* See https://twitter.com/anvaka/status/1296182534150135808
|
||
*/
|
||
Generator.prototype.levy = levy;
|
||
|
||
function levy() {
|
||
var beta = 3 / 2;
|
||
var sigma = Math.pow(
|
||
gamma( 1 + beta ) * Math.sin(Math.PI * beta / 2) /
|
||
(gamma((1 + beta) / 2) * beta * Math.pow(2, (beta - 1) / 2)),
|
||
1/beta
|
||
);
|
||
return this.gaussian() * sigma / Math.pow(Math.abs(this.gaussian()), 1/beta);
|
||
}
|
||
|
||
// gamma function approximation
|
||
function gamma(z) {
|
||
return Math.sqrt(2 * Math.PI / z) * Math.pow((1 / Math.E) * (z + 1 / (12 * z - 1 / (10 * z))), z);
|
||
}
|
||
|
||
function nextDouble() {
|
||
var seed = this.seed;
|
||
// Robert Jenkins' 32 bit integer hash function.
|
||
seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff;
|
||
seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
|
||
seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff;
|
||
seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff;
|
||
seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff;
|
||
seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
|
||
this.seed = seed;
|
||
return (seed & 0xfffffff) / 0x10000000;
|
||
}
|
||
|
||
function next(maxValue) {
|
||
return Math.floor(this.nextDouble() * maxValue);
|
||
}
|
||
|
||
/*
|
||
* Creates iterator over array, which returns items of array in random order
|
||
* Time complexity is guaranteed to be O(n);
|
||
*/
|
||
function randomIterator(array, customRandom) {
|
||
var localRandom = customRandom || random();
|
||
if (typeof localRandom.next !== 'function') {
|
||
throw new Error('customRandom does not match expected API: next() function is missing');
|
||
}
|
||
|
||
return {
|
||
forEach: forEach,
|
||
|
||
/**
|
||
* Shuffles array randomly, in place.
|
||
*/
|
||
shuffle: shuffle
|
||
};
|
||
|
||
function shuffle() {
|
||
var i, j, t;
|
||
for (i = array.length - 1; i > 0; --i) {
|
||
j = localRandom.next(i + 1); // i inclusive
|
||
t = array[j];
|
||
array[j] = array[i];
|
||
array[i] = t;
|
||
}
|
||
|
||
return array;
|
||
}
|
||
|
||
function forEach(callback) {
|
||
var i, j, t;
|
||
for (i = array.length - 1; i > 0; --i) {
|
||
j = localRandom.next(i + 1); // i inclusive
|
||
t = array[j];
|
||
array[j] = array[i];
|
||
array[i] = t;
|
||
|
||
callback(t);
|
||
}
|
||
|
||
if (array.length) {
|
||
callback(array[0]);
|
||
}
|
||
}
|
||
}
|
||
return ngraph_random.exports;
|
||
}
|
||
|
||
/**
|
||
* Manages a simulation of physical forces acting on bodies and springs.
|
||
*/
|
||
|
||
var createPhysicsSimulator_1 = createPhysicsSimulator;
|
||
|
||
var generateCreateBodyFunction = generateCreateBodyExports;
|
||
var generateQuadTreeFunction = generateQuadTreeExports;
|
||
var generateBoundsFunction = generateBoundsExports;
|
||
var generateCreateDragForceFunction = generateCreateDragForceExports;
|
||
var generateCreateSpringForceFunction = generateCreateSpringForceExports;
|
||
var generateIntegratorFunction = generateIntegratorExports;
|
||
|
||
var dimensionalCache = {};
|
||
|
||
function createPhysicsSimulator(settings) {
|
||
var Spring = requireSpring();
|
||
var merge = requireNgraph_merge();
|
||
var eventify = ngraph_events;
|
||
if (settings) {
|
||
// Check for names from older versions of the layout
|
||
if (settings.springCoeff !== undefined) throw new Error('springCoeff was renamed to springCoefficient');
|
||
if (settings.dragCoeff !== undefined) throw new Error('dragCoeff was renamed to dragCoefficient');
|
||
}
|
||
|
||
settings = merge(settings, {
|
||
/**
|
||
* Ideal length for links (springs in physical model).
|
||
*/
|
||
springLength: 10,
|
||
|
||
/**
|
||
* Hook's law coefficient. 1 - solid spring.
|
||
*/
|
||
springCoefficient: 0.8,
|
||
|
||
/**
|
||
* Coulomb's law coefficient. It's used to repel nodes thus should be negative
|
||
* if you make it positive nodes start attract each other :).
|
||
*/
|
||
gravity: -12,
|
||
|
||
/**
|
||
* Theta coefficient from Barnes Hut simulation. Ranged between (0, 1).
|
||
* The closer it's to 1 the more nodes algorithm will have to go through.
|
||
* Setting it to one makes Barnes Hut simulation no different from
|
||
* brute-force forces calculation (each node is considered).
|
||
*/
|
||
theta: 0.8,
|
||
|
||
/**
|
||
* Drag force coefficient. Used to slow down system, thus should be less than 1.
|
||
* The closer it is to 0 the less tight system will be.
|
||
*/
|
||
dragCoefficient: 0.9, // TODO: Need to rename this to something better. E.g. `dragCoefficient`
|
||
|
||
/**
|
||
* Default time step (dt) for forces integration
|
||
*/
|
||
timeStep : 0.5,
|
||
|
||
/**
|
||
* Adaptive time step uses average spring length to compute actual time step:
|
||
* See: https://twitter.com/anvaka/status/1293067160755957760
|
||
*/
|
||
adaptiveTimeStepWeight: 0,
|
||
|
||
/**
|
||
* This parameter defines number of dimensions of the space where simulation
|
||
* is performed.
|
||
*/
|
||
dimensions: 2,
|
||
|
||
/**
|
||
* In debug mode more checks are performed, this will help you catch errors
|
||
* quickly, however for production build it is recommended to turn off this flag
|
||
* to speed up computation.
|
||
*/
|
||
debug: false
|
||
});
|
||
|
||
var factory = dimensionalCache[settings.dimensions];
|
||
if (!factory) {
|
||
var dimensions = settings.dimensions;
|
||
factory = {
|
||
Body: generateCreateBodyFunction(dimensions, settings.debug),
|
||
createQuadTree: generateQuadTreeFunction(dimensions),
|
||
createBounds: generateBoundsFunction(dimensions),
|
||
createDragForce: generateCreateDragForceFunction(dimensions),
|
||
createSpringForce: generateCreateSpringForceFunction(dimensions),
|
||
integrate: generateIntegratorFunction(dimensions),
|
||
};
|
||
dimensionalCache[dimensions] = factory;
|
||
}
|
||
|
||
var Body = factory.Body;
|
||
var createQuadTree = factory.createQuadTree;
|
||
var createBounds = factory.createBounds;
|
||
var createDragForce = factory.createDragForce;
|
||
var createSpringForce = factory.createSpringForce;
|
||
var integrate = factory.integrate;
|
||
var createBody = pos => new Body(pos);
|
||
|
||
var random = requireNgraph_random().random(42);
|
||
var bodies = []; // Bodies in this simulation.
|
||
var springs = []; // Springs in this simulation.
|
||
|
||
var quadTree = createQuadTree(settings, random);
|
||
var bounds = createBounds(bodies, settings, random);
|
||
var springForce = createSpringForce(settings, random);
|
||
var dragForce = createDragForce(settings);
|
||
|
||
var totalMovement = 0; // how much movement we made on last step
|
||
var forces = [];
|
||
var forceMap = new Map();
|
||
var iterationNumber = 0;
|
||
|
||
addForce('nbody', nbodyForce);
|
||
addForce('spring', updateSpringForce);
|
||
|
||
var publicApi = {
|
||
/**
|
||
* Array of bodies, registered with current simulator
|
||
*
|
||
* Note: To add new body, use addBody() method. This property is only
|
||
* exposed for testing/performance purposes.
|
||
*/
|
||
bodies: bodies,
|
||
|
||
quadTree: quadTree,
|
||
|
||
/**
|
||
* Array of springs, registered with current simulator
|
||
*
|
||
* Note: To add new spring, use addSpring() method. This property is only
|
||
* exposed for testing/performance purposes.
|
||
*/
|
||
springs: springs,
|
||
|
||
/**
|
||
* Returns settings with which current simulator was initialized
|
||
*/
|
||
settings: settings,
|
||
|
||
/**
|
||
* Adds a new force to simulation
|
||
*/
|
||
addForce: addForce,
|
||
|
||
/**
|
||
* Removes a force from the simulation.
|
||
*/
|
||
removeForce: removeForce,
|
||
|
||
/**
|
||
* Returns a map of all registered forces.
|
||
*/
|
||
getForces: getForces,
|
||
|
||
/**
|
||
* Performs one step of force simulation.
|
||
*
|
||
* @returns {boolean} true if system is considered stable; False otherwise.
|
||
*/
|
||
step: function () {
|
||
for (var i = 0; i < forces.length; ++i) {
|
||
forces[i](iterationNumber);
|
||
}
|
||
var movement = integrate(bodies, settings.timeStep, settings.adaptiveTimeStepWeight);
|
||
iterationNumber += 1;
|
||
return movement;
|
||
},
|
||
|
||
/**
|
||
* Adds body to the system
|
||
*
|
||
* @param {ngraph.physics.primitives.Body} body physical body
|
||
*
|
||
* @returns {ngraph.physics.primitives.Body} added body
|
||
*/
|
||
addBody: function (body) {
|
||
if (!body) {
|
||
throw new Error('Body is required');
|
||
}
|
||
bodies.push(body);
|
||
|
||
return body;
|
||
},
|
||
|
||
/**
|
||
* Adds body to the system at given position
|
||
*
|
||
* @param {Object} pos position of a body
|
||
*
|
||
* @returns {ngraph.physics.primitives.Body} added body
|
||
*/
|
||
addBodyAt: function (pos) {
|
||
if (!pos) {
|
||
throw new Error('Body position is required');
|
||
}
|
||
var body = createBody(pos);
|
||
bodies.push(body);
|
||
|
||
return body;
|
||
},
|
||
|
||
/**
|
||
* Removes body from the system
|
||
*
|
||
* @param {ngraph.physics.primitives.Body} body to remove
|
||
*
|
||
* @returns {Boolean} true if body found and removed. falsy otherwise;
|
||
*/
|
||
removeBody: function (body) {
|
||
if (!body) { return; }
|
||
|
||
var idx = bodies.indexOf(body);
|
||
if (idx < 0) { return; }
|
||
|
||
bodies.splice(idx, 1);
|
||
if (bodies.length === 0) {
|
||
bounds.reset();
|
||
}
|
||
return true;
|
||
},
|
||
|
||
/**
|
||
* Adds a spring to this simulation.
|
||
*
|
||
* @returns {Object} - a handle for a spring. If you want to later remove
|
||
* spring pass it to removeSpring() method.
|
||
*/
|
||
addSpring: function (body1, body2, springLength, springCoefficient) {
|
||
if (!body1 || !body2) {
|
||
throw new Error('Cannot add null spring to force simulator');
|
||
}
|
||
|
||
if (typeof springLength !== 'number') {
|
||
springLength = -1; // assume global configuration
|
||
}
|
||
|
||
var spring = new Spring(body1, body2, springLength, springCoefficient >= 0 ? springCoefficient : -1);
|
||
springs.push(spring);
|
||
|
||
// TODO: could mark simulator as dirty.
|
||
return spring;
|
||
},
|
||
|
||
/**
|
||
* Returns amount of movement performed on last step() call
|
||
*/
|
||
getTotalMovement: function () {
|
||
return totalMovement;
|
||
},
|
||
|
||
/**
|
||
* Removes spring from the system
|
||
*
|
||
* @param {Object} spring to remove. Spring is an object returned by addSpring
|
||
*
|
||
* @returns {Boolean} true if spring found and removed. falsy otherwise;
|
||
*/
|
||
removeSpring: function (spring) {
|
||
if (!spring) { return; }
|
||
var idx = springs.indexOf(spring);
|
||
if (idx > -1) {
|
||
springs.splice(idx, 1);
|
||
return true;
|
||
}
|
||
},
|
||
|
||
getBestNewBodyPosition: function (neighbors) {
|
||
return bounds.getBestNewPosition(neighbors);
|
||
},
|
||
|
||
/**
|
||
* Returns bounding box which covers all bodies
|
||
*/
|
||
getBBox: getBoundingBox,
|
||
getBoundingBox: getBoundingBox,
|
||
|
||
invalidateBBox: function () {
|
||
console.warn('invalidateBBox() is deprecated, bounds always recomputed on `getBBox()` call');
|
||
},
|
||
|
||
// TODO: Move the force specific stuff to force
|
||
gravity: function (value) {
|
||
if (value !== undefined) {
|
||
settings.gravity = value;
|
||
quadTree.options({gravity: value});
|
||
return this;
|
||
} else {
|
||
return settings.gravity;
|
||
}
|
||
},
|
||
|
||
theta: function (value) {
|
||
if (value !== undefined) {
|
||
settings.theta = value;
|
||
quadTree.options({theta: value});
|
||
return this;
|
||
} else {
|
||
return settings.theta;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Returns pseudo-random number generator instance.
|
||
*/
|
||
random: random
|
||
};
|
||
|
||
// allow settings modification via public API:
|
||
expose(settings, publicApi);
|
||
|
||
eventify(publicApi);
|
||
|
||
return publicApi;
|
||
|
||
function getBoundingBox() {
|
||
bounds.update();
|
||
return bounds.box;
|
||
}
|
||
|
||
function addForce(forceName, forceFunction) {
|
||
if (forceMap.has(forceName)) throw new Error('Force ' + forceName + ' is already added');
|
||
|
||
forceMap.set(forceName, forceFunction);
|
||
forces.push(forceFunction);
|
||
}
|
||
|
||
function removeForce(forceName) {
|
||
var forceIndex = forces.indexOf(forceMap.get(forceName));
|
||
if (forceIndex < 0) return;
|
||
forces.splice(forceIndex, 1);
|
||
forceMap.delete(forceName);
|
||
}
|
||
|
||
function getForces() {
|
||
// TODO: Should I trust them or clone the forces?
|
||
return forceMap;
|
||
}
|
||
|
||
function nbodyForce(/* iterationUmber */) {
|
||
if (bodies.length === 0) return;
|
||
|
||
quadTree.insertBodies(bodies);
|
||
var i = bodies.length;
|
||
while (i--) {
|
||
var body = bodies[i];
|
||
if (!body.isPinned) {
|
||
body.reset();
|
||
quadTree.updateBodyForce(body);
|
||
dragForce.update(body);
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateSpringForce() {
|
||
var i = springs.length;
|
||
while (i--) {
|
||
springForce.update(springs[i]);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
function expose(settings, target) {
|
||
for (var key in settings) {
|
||
augment(settings, target, key);
|
||
}
|
||
}
|
||
|
||
function augment(source, target, key) {
|
||
if (!source.hasOwnProperty(key)) return;
|
||
if (typeof target[key] === 'function') {
|
||
// this accessor is already defined. Ignore it
|
||
return;
|
||
}
|
||
var sourceIsNumber = Number.isFinite(source[key]);
|
||
|
||
if (sourceIsNumber) {
|
||
target[key] = function (value) {
|
||
if (value !== undefined) {
|
||
if (!Number.isFinite(value)) throw new Error('Value of ' + key + ' should be a valid number.');
|
||
source[key] = value;
|
||
return target;
|
||
}
|
||
return source[key];
|
||
};
|
||
} else {
|
||
target[key] = function (value) {
|
||
if (value !== undefined) {
|
||
source[key] = value;
|
||
return target;
|
||
}
|
||
return source[key];
|
||
};
|
||
}
|
||
}
|
||
|
||
ngraph_forcelayout.exports = createLayout;
|
||
ngraph_forcelayout.exports.simulator = createPhysicsSimulator_1;
|
||
|
||
var eventify = ngraph_events;
|
||
|
||
/**
|
||
* Creates force based layout for a given graph.
|
||
*
|
||
* @param {ngraph.graph} graph which needs to be laid out
|
||
* @param {object} physicsSettings if you need custom settings
|
||
* for physics simulator you can pass your own settings here. If it's not passed
|
||
* a default one will be created.
|
||
*/
|
||
function createLayout(graph, physicsSettings) {
|
||
if (!graph) {
|
||
throw new Error('Graph structure cannot be undefined');
|
||
}
|
||
|
||
var createSimulator = (physicsSettings && physicsSettings.createSimulator) || createPhysicsSimulator_1;
|
||
var physicsSimulator = createSimulator(physicsSettings);
|
||
if (Array.isArray(physicsSettings)) throw new Error('Physics settings is expected to be an object');
|
||
|
||
var nodeMass = graph.version > 19 ? defaultSetNodeMass : defaultArrayNodeMass;
|
||
if (physicsSettings && typeof physicsSettings.nodeMass === 'function') {
|
||
nodeMass = physicsSettings.nodeMass;
|
||
}
|
||
|
||
var nodeBodies = new Map();
|
||
var springs = {};
|
||
var bodiesCount = 0;
|
||
|
||
var springTransform = physicsSimulator.settings.springTransform || noop;
|
||
|
||
// Initialize physics with what we have in the graph:
|
||
initPhysics();
|
||
listenToEvents();
|
||
|
||
var wasStable = false;
|
||
|
||
var api = {
|
||
/**
|
||
* Performs one step of iterative layout algorithm
|
||
*
|
||
* @returns {boolean} true if the system should be considered stable; False otherwise.
|
||
* The system is stable if no further call to `step()` can improve the layout.
|
||
*/
|
||
step: function() {
|
||
if (bodiesCount === 0) {
|
||
updateStableStatus(true);
|
||
return true;
|
||
}
|
||
|
||
var lastMove = physicsSimulator.step();
|
||
|
||
// Save the movement in case if someone wants to query it in the step
|
||
// callback.
|
||
api.lastMove = lastMove;
|
||
|
||
// Allow listeners to perform low-level actions after nodes are updated.
|
||
api.fire('step');
|
||
|
||
var ratio = lastMove/bodiesCount;
|
||
var isStableNow = ratio <= 0.01; // TODO: The number is somewhat arbitrary...
|
||
updateStableStatus(isStableNow);
|
||
|
||
|
||
return isStableNow;
|
||
},
|
||
|
||
/**
|
||
* For a given `nodeId` returns position
|
||
*/
|
||
getNodePosition: function (nodeId) {
|
||
return getInitializedBody(nodeId).pos;
|
||
},
|
||
|
||
/**
|
||
* Sets position of a node to a given coordinates
|
||
* @param {string} nodeId node identifier
|
||
* @param {number} x position of a node
|
||
* @param {number} y position of a node
|
||
* @param {number=} z position of node (only if applicable to body)
|
||
*/
|
||
setNodePosition: function (nodeId) {
|
||
var body = getInitializedBody(nodeId);
|
||
body.setPosition.apply(body, Array.prototype.slice.call(arguments, 1));
|
||
},
|
||
|
||
/**
|
||
* @returns {Object} Link position by link id
|
||
* @returns {Object.from} {x, y} coordinates of link start
|
||
* @returns {Object.to} {x, y} coordinates of link end
|
||
*/
|
||
getLinkPosition: function (linkId) {
|
||
var spring = springs[linkId];
|
||
if (spring) {
|
||
return {
|
||
from: spring.from.pos,
|
||
to: spring.to.pos
|
||
};
|
||
}
|
||
},
|
||
|
||
/**
|
||
* @returns {Object} area required to fit in the graph. Object contains
|
||
* `x1`, `y1` - top left coordinates
|
||
* `x2`, `y2` - bottom right coordinates
|
||
*/
|
||
getGraphRect: function () {
|
||
return physicsSimulator.getBBox();
|
||
},
|
||
|
||
/**
|
||
* Iterates over each body in the layout simulator and performs a callback(body, nodeId)
|
||
*/
|
||
forEachBody: forEachBody,
|
||
|
||
/*
|
||
* Requests layout algorithm to pin/unpin node to its current position
|
||
* Pinned nodes should not be affected by layout algorithm and always
|
||
* remain at their position
|
||
*/
|
||
pinNode: function (node, isPinned) {
|
||
var body = getInitializedBody(node.id);
|
||
body.isPinned = !!isPinned;
|
||
},
|
||
|
||
/**
|
||
* Checks whether given graph's node is currently pinned
|
||
*/
|
||
isNodePinned: function (node) {
|
||
return getInitializedBody(node.id).isPinned;
|
||
},
|
||
|
||
/**
|
||
* Request to release all resources
|
||
*/
|
||
dispose: function() {
|
||
graph.off('changed', onGraphChanged);
|
||
api.fire('disposed');
|
||
},
|
||
|
||
/**
|
||
* Gets physical body for a given node id. If node is not found undefined
|
||
* value is returned.
|
||
*/
|
||
getBody: getBody,
|
||
|
||
/**
|
||
* Gets spring for a given edge.
|
||
*
|
||
* @param {string} linkId link identifer. If two arguments are passed then
|
||
* this argument is treated as formNodeId
|
||
* @param {string=} toId when defined this parameter denotes head of the link
|
||
* and first argument is treated as tail of the link (fromId)
|
||
*/
|
||
getSpring: getSpring,
|
||
|
||
/**
|
||
* Returns length of cumulative force vector. The closer this to zero - the more stable the system is
|
||
*/
|
||
getForceVectorLength: getForceVectorLength,
|
||
|
||
/**
|
||
* [Read only] Gets current physics simulator
|
||
*/
|
||
simulator: physicsSimulator,
|
||
|
||
/**
|
||
* Gets the graph that was used for layout
|
||
*/
|
||
graph: graph,
|
||
|
||
/**
|
||
* Gets amount of movement performed during last step operation
|
||
*/
|
||
lastMove: 0
|
||
};
|
||
|
||
eventify(api);
|
||
|
||
return api;
|
||
|
||
function updateStableStatus(isStableNow) {
|
||
if (wasStable !== isStableNow) {
|
||
wasStable = isStableNow;
|
||
onStableChanged(isStableNow);
|
||
}
|
||
}
|
||
|
||
function forEachBody(cb) {
|
||
nodeBodies.forEach(cb);
|
||
}
|
||
|
||
function getForceVectorLength() {
|
||
var fx = 0, fy = 0;
|
||
forEachBody(function(body) {
|
||
fx += Math.abs(body.force.x);
|
||
fy += Math.abs(body.force.y);
|
||
});
|
||
return Math.sqrt(fx * fx + fy * fy);
|
||
}
|
||
|
||
function getSpring(fromId, toId) {
|
||
var linkId;
|
||
if (toId === undefined) {
|
||
if (typeof fromId !== 'object') {
|
||
// assume fromId as a linkId:
|
||
linkId = fromId;
|
||
} else {
|
||
// assume fromId to be a link object:
|
||
linkId = fromId.id;
|
||
}
|
||
} else {
|
||
// toId is defined, should grab link:
|
||
var link = graph.hasLink(fromId, toId);
|
||
if (!link) return;
|
||
linkId = link.id;
|
||
}
|
||
|
||
return springs[linkId];
|
||
}
|
||
|
||
function getBody(nodeId) {
|
||
return nodeBodies.get(nodeId);
|
||
}
|
||
|
||
function listenToEvents() {
|
||
graph.on('changed', onGraphChanged);
|
||
}
|
||
|
||
function onStableChanged(isStable) {
|
||
api.fire('stable', isStable);
|
||
}
|
||
|
||
function onGraphChanged(changes) {
|
||
for (var i = 0; i < changes.length; ++i) {
|
||
var change = changes[i];
|
||
if (change.changeType === 'add') {
|
||
if (change.node) {
|
||
initBody(change.node.id);
|
||
}
|
||
if (change.link) {
|
||
initLink(change.link);
|
||
}
|
||
} else if (change.changeType === 'remove') {
|
||
if (change.node) {
|
||
releaseNode(change.node);
|
||
}
|
||
if (change.link) {
|
||
releaseLink(change.link);
|
||
}
|
||
}
|
||
}
|
||
bodiesCount = graph.getNodesCount();
|
||
}
|
||
|
||
function initPhysics() {
|
||
bodiesCount = 0;
|
||
|
||
graph.forEachNode(function (node) {
|
||
initBody(node.id);
|
||
bodiesCount += 1;
|
||
});
|
||
|
||
graph.forEachLink(initLink);
|
||
}
|
||
|
||
function initBody(nodeId) {
|
||
var body = nodeBodies.get(nodeId);
|
||
if (!body) {
|
||
var node = graph.getNode(nodeId);
|
||
if (!node) {
|
||
throw new Error('initBody() was called with unknown node id');
|
||
}
|
||
|
||
var pos = node.position;
|
||
if (!pos) {
|
||
var neighbors = getNeighborBodies(node);
|
||
pos = physicsSimulator.getBestNewBodyPosition(neighbors);
|
||
}
|
||
|
||
body = physicsSimulator.addBodyAt(pos);
|
||
body.id = nodeId;
|
||
|
||
nodeBodies.set(nodeId, body);
|
||
updateBodyMass(nodeId);
|
||
|
||
if (isNodeOriginallyPinned(node)) {
|
||
body.isPinned = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
function releaseNode(node) {
|
||
var nodeId = node.id;
|
||
var body = nodeBodies.get(nodeId);
|
||
if (body) {
|
||
nodeBodies.delete(nodeId);
|
||
physicsSimulator.removeBody(body);
|
||
}
|
||
}
|
||
|
||
function initLink(link) {
|
||
updateBodyMass(link.fromId);
|
||
updateBodyMass(link.toId);
|
||
|
||
var fromBody = nodeBodies.get(link.fromId),
|
||
toBody = nodeBodies.get(link.toId),
|
||
spring = physicsSimulator.addSpring(fromBody, toBody, link.length);
|
||
|
||
springTransform(link, spring);
|
||
|
||
springs[link.id] = spring;
|
||
}
|
||
|
||
function releaseLink(link) {
|
||
var spring = springs[link.id];
|
||
if (spring) {
|
||
var from = graph.getNode(link.fromId),
|
||
to = graph.getNode(link.toId);
|
||
|
||
if (from) updateBodyMass(from.id);
|
||
if (to) updateBodyMass(to.id);
|
||
|
||
delete springs[link.id];
|
||
|
||
physicsSimulator.removeSpring(spring);
|
||
}
|
||
}
|
||
|
||
function getNeighborBodies(node) {
|
||
// TODO: Could probably be done better on memory
|
||
var neighbors = [];
|
||
if (!node.links) {
|
||
return neighbors;
|
||
}
|
||
var maxNeighbors = Math.min(node.links.length, 2);
|
||
for (var i = 0; i < maxNeighbors; ++i) {
|
||
var link = node.links[i];
|
||
var otherBody = link.fromId !== node.id ? nodeBodies.get(link.fromId) : nodeBodies.get(link.toId);
|
||
if (otherBody && otherBody.pos) {
|
||
neighbors.push(otherBody);
|
||
}
|
||
}
|
||
|
||
return neighbors;
|
||
}
|
||
|
||
function updateBodyMass(nodeId) {
|
||
var body = nodeBodies.get(nodeId);
|
||
body.mass = nodeMass(nodeId);
|
||
if (Number.isNaN(body.mass)) {
|
||
throw new Error('Node mass should be a number');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Checks whether graph node has in its settings pinned attribute,
|
||
* which means layout algorithm cannot move it. Node can be marked
|
||
* as pinned, if it has "isPinned" attribute, or when node.data has it.
|
||
*
|
||
* @param {Object} node a graph node to check
|
||
* @return {Boolean} true if node should be treated as pinned; false otherwise.
|
||
*/
|
||
function isNodeOriginallyPinned(node) {
|
||
return (node && (node.isPinned || (node.data && node.data.isPinned)));
|
||
}
|
||
|
||
function getInitializedBody(nodeId) {
|
||
var body = nodeBodies.get(nodeId);
|
||
if (!body) {
|
||
initBody(nodeId);
|
||
body = nodeBodies.get(nodeId);
|
||
}
|
||
return body;
|
||
}
|
||
|
||
/**
|
||
* Calculates mass of a body, which corresponds to node with given id.
|
||
*
|
||
* @param {String|Number} nodeId identifier of a node, for which body mass needs to be calculated
|
||
* @returns {Number} recommended mass of the body;
|
||
*/
|
||
function defaultArrayNodeMass(nodeId) {
|
||
// This function is for older versions of ngraph.graph.
|
||
var links = graph.getLinks(nodeId);
|
||
if (!links) return 1;
|
||
return 1 + links.length / 3.0;
|
||
}
|
||
|
||
function defaultSetNodeMass(nodeId) {
|
||
var links = graph.getLinks(nodeId);
|
||
if (!links) return 1;
|
||
return 1 + links.size / 3.0;
|
||
}
|
||
}
|
||
|
||
function noop() { }
|
||
|
||
var ngraph_forcelayoutExports = ngraph_forcelayout.exports;
|
||
var forcelayout = /*@__PURE__*/getDefaultExportFromCjs(ngraph_forcelayoutExports);
|
||
|
||
/**
|
||
* Checks if `value` is the
|
||
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
|
||
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
||
*
|
||
* @static
|
||
* @memberOf _
|
||
* @since 0.1.0
|
||
* @category Lang
|
||
* @param {*} value The value to check.
|
||
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
||
* @example
|
||
*
|
||
* _.isObject({});
|
||
* // => true
|
||
*
|
||
* _.isObject([1, 2, 3]);
|
||
* // => true
|
||
*
|
||
* _.isObject(_.noop);
|
||
* // => true
|
||
*
|
||
* _.isObject(null);
|
||
* // => false
|
||
*/
|
||
function isObject(value) {
|
||
var type = typeof value;
|
||
return value != null && (type == 'object' || type == 'function');
|
||
}
|
||
|
||
/** Detect free variable `global` from Node.js. */
|
||
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
|
||
|
||
/** Detect free variable `self`. */
|
||
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
|
||
|
||
/** Used as a reference to the global object. */
|
||
var root = freeGlobal || freeSelf || Function('return this')();
|
||
|
||
/**
|
||
* Gets the timestamp of the number of milliseconds that have elapsed since
|
||
* the Unix epoch (1 January 1970 00:00:00 UTC).
|
||
*
|
||
* @static
|
||
* @memberOf _
|
||
* @since 2.4.0
|
||
* @category Date
|
||
* @returns {number} Returns the timestamp.
|
||
* @example
|
||
*
|
||
* _.defer(function(stamp) {
|
||
* console.log(_.now() - stamp);
|
||
* }, _.now());
|
||
* // => Logs the number of milliseconds it took for the deferred invocation.
|
||
*/
|
||
var now = function() {
|
||
return root.Date.now();
|
||
};
|
||
|
||
/** Used to match a single whitespace character. */
|
||
var reWhitespace = /\s/;
|
||
|
||
/**
|
||
* Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
|
||
* character of `string`.
|
||
*
|
||
* @private
|
||
* @param {string} string The string to inspect.
|
||
* @returns {number} Returns the index of the last non-whitespace character.
|
||
*/
|
||
function trimmedEndIndex(string) {
|
||
var index = string.length;
|
||
|
||
while (index-- && reWhitespace.test(string.charAt(index))) {}
|
||
return index;
|
||
}
|
||
|
||
/** Used to match leading whitespace. */
|
||
var reTrimStart = /^\s+/;
|
||
|
||
/**
|
||
* The base implementation of `_.trim`.
|
||
*
|
||
* @private
|
||
* @param {string} string The string to trim.
|
||
* @returns {string} Returns the trimmed string.
|
||
*/
|
||
function baseTrim(string) {
|
||
return string
|
||
? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')
|
||
: string;
|
||
}
|
||
|
||
/** Built-in value references. */
|
||
var Symbol$1 = root.Symbol;
|
||
|
||
/** Used for built-in method references. */
|
||
var objectProto$1 = Object.prototype;
|
||
|
||
/** Used to check objects for own properties. */
|
||
var hasOwnProperty = objectProto$1.hasOwnProperty;
|
||
|
||
/**
|
||
* Used to resolve the
|
||
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
|
||
* of values.
|
||
*/
|
||
var nativeObjectToString$1 = objectProto$1.toString;
|
||
|
||
/** Built-in value references. */
|
||
var symToStringTag$1 = Symbol$1 ? Symbol$1.toStringTag : undefined;
|
||
|
||
/**
|
||
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
|
||
*
|
||
* @private
|
||
* @param {*} value The value to query.
|
||
* @returns {string} Returns the raw `toStringTag`.
|
||
*/
|
||
function getRawTag(value) {
|
||
var isOwn = hasOwnProperty.call(value, symToStringTag$1),
|
||
tag = value[symToStringTag$1];
|
||
|
||
try {
|
||
value[symToStringTag$1] = undefined;
|
||
var unmasked = true;
|
||
} catch (e) {}
|
||
|
||
var result = nativeObjectToString$1.call(value);
|
||
if (unmasked) {
|
||
if (isOwn) {
|
||
value[symToStringTag$1] = tag;
|
||
} else {
|
||
delete value[symToStringTag$1];
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/** Used for built-in method references. */
|
||
var objectProto = Object.prototype;
|
||
|
||
/**
|
||
* Used to resolve the
|
||
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
|
||
* of values.
|
||
*/
|
||
var nativeObjectToString = objectProto.toString;
|
||
|
||
/**
|
||
* Converts `value` to a string using `Object.prototype.toString`.
|
||
*
|
||
* @private
|
||
* @param {*} value The value to convert.
|
||
* @returns {string} Returns the converted string.
|
||
*/
|
||
function objectToString(value) {
|
||
return nativeObjectToString.call(value);
|
||
}
|
||
|
||
/** `Object#toString` result references. */
|
||
var nullTag = '[object Null]',
|
||
undefinedTag = '[object Undefined]';
|
||
|
||
/** Built-in value references. */
|
||
var symToStringTag = Symbol$1 ? Symbol$1.toStringTag : undefined;
|
||
|
||
/**
|
||
* The base implementation of `getTag` without fallbacks for buggy environments.
|
||
*
|
||
* @private
|
||
* @param {*} value The value to query.
|
||
* @returns {string} Returns the `toStringTag`.
|
||
*/
|
||
function baseGetTag(value) {
|
||
if (value == null) {
|
||
return value === undefined ? undefinedTag : nullTag;
|
||
}
|
||
return (symToStringTag && symToStringTag in Object(value))
|
||
? getRawTag(value)
|
||
: objectToString(value);
|
||
}
|
||
|
||
/**
|
||
* Checks if `value` is object-like. A value is object-like if it's not `null`
|
||
* and has a `typeof` result of "object".
|
||
*
|
||
* @static
|
||
* @memberOf _
|
||
* @since 4.0.0
|
||
* @category Lang
|
||
* @param {*} value The value to check.
|
||
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
||
* @example
|
||
*
|
||
* _.isObjectLike({});
|
||
* // => true
|
||
*
|
||
* _.isObjectLike([1, 2, 3]);
|
||
* // => true
|
||
*
|
||
* _.isObjectLike(_.noop);
|
||
* // => false
|
||
*
|
||
* _.isObjectLike(null);
|
||
* // => false
|
||
*/
|
||
function isObjectLike(value) {
|
||
return value != null && typeof value == 'object';
|
||
}
|
||
|
||
/** `Object#toString` result references. */
|
||
var symbolTag = '[object Symbol]';
|
||
|
||
/**
|
||
* Checks if `value` is classified as a `Symbol` primitive or object.
|
||
*
|
||
* @static
|
||
* @memberOf _
|
||
* @since 4.0.0
|
||
* @category Lang
|
||
* @param {*} value The value to check.
|
||
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
|
||
* @example
|
||
*
|
||
* _.isSymbol(Symbol.iterator);
|
||
* // => true
|
||
*
|
||
* _.isSymbol('abc');
|
||
* // => false
|
||
*/
|
||
function isSymbol(value) {
|
||
return typeof value == 'symbol' ||
|
||
(isObjectLike(value) && baseGetTag(value) == symbolTag);
|
||
}
|
||
|
||
/** Used as references for various `Number` constants. */
|
||
var NAN = 0 / 0;
|
||
|
||
/** Used to detect bad signed hexadecimal string values. */
|
||
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
|
||
|
||
/** Used to detect binary string values. */
|
||
var reIsBinary = /^0b[01]+$/i;
|
||
|
||
/** Used to detect octal string values. */
|
||
var reIsOctal = /^0o[0-7]+$/i;
|
||
|
||
/** Built-in method references without a dependency on `root`. */
|
||
var freeParseInt = parseInt;
|
||
|
||
/**
|
||
* Converts `value` to a number.
|
||
*
|
||
* @static
|
||
* @memberOf _
|
||
* @since 4.0.0
|
||
* @category Lang
|
||
* @param {*} value The value to process.
|
||
* @returns {number} Returns the number.
|
||
* @example
|
||
*
|
||
* _.toNumber(3.2);
|
||
* // => 3.2
|
||
*
|
||
* _.toNumber(Number.MIN_VALUE);
|
||
* // => 5e-324
|
||
*
|
||
* _.toNumber(Infinity);
|
||
* // => Infinity
|
||
*
|
||
* _.toNumber('3.2');
|
||
* // => 3.2
|
||
*/
|
||
function toNumber(value) {
|
||
if (typeof value == 'number') {
|
||
return value;
|
||
}
|
||
if (isSymbol(value)) {
|
||
return NAN;
|
||
}
|
||
if (isObject(value)) {
|
||
var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
|
||
value = isObject(other) ? (other + '') : other;
|
||
}
|
||
if (typeof value != 'string') {
|
||
return value === 0 ? value : +value;
|
||
}
|
||
value = baseTrim(value);
|
||
var isBinary = reIsBinary.test(value);
|
||
return (isBinary || reIsOctal.test(value))
|
||
? freeParseInt(value.slice(2), isBinary ? 2 : 8)
|
||
: (reIsBadHex.test(value) ? NAN : +value);
|
||
}
|
||
|
||
/** Error message constants. */
|
||
var FUNC_ERROR_TEXT = 'Expected a function';
|
||
|
||
/* Built-in method references for those with the same name as other `lodash` methods. */
|
||
var nativeMax = Math.max,
|
||
nativeMin = Math.min;
|
||
|
||
/**
|
||
* Creates a debounced function that delays invoking `func` until after `wait`
|
||
* milliseconds have elapsed since the last time the debounced function was
|
||
* invoked. The debounced function comes with a `cancel` method to cancel
|
||
* delayed `func` invocations and a `flush` method to immediately invoke them.
|
||
* Provide `options` to indicate whether `func` should be invoked on the
|
||
* leading and/or trailing edge of the `wait` timeout. The `func` is invoked
|
||
* with the last arguments provided to the debounced function. Subsequent
|
||
* calls to the debounced function return the result of the last `func`
|
||
* invocation.
|
||
*
|
||
* **Note:** If `leading` and `trailing` options are `true`, `func` is
|
||
* invoked on the trailing edge of the timeout only if the debounced function
|
||
* is invoked more than once during the `wait` timeout.
|
||
*
|
||
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
|
||
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
|
||
*
|
||
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
||
* for details over the differences between `_.debounce` and `_.throttle`.
|
||
*
|
||
* @static
|
||
* @memberOf _
|
||
* @since 0.1.0
|
||
* @category Function
|
||
* @param {Function} func The function to debounce.
|
||
* @param {number} [wait=0] The number of milliseconds to delay.
|
||
* @param {Object} [options={}] The options object.
|
||
* @param {boolean} [options.leading=false]
|
||
* Specify invoking on the leading edge of the timeout.
|
||
* @param {number} [options.maxWait]
|
||
* The maximum time `func` is allowed to be delayed before it's invoked.
|
||
* @param {boolean} [options.trailing=true]
|
||
* Specify invoking on the trailing edge of the timeout.
|
||
* @returns {Function} Returns the new debounced function.
|
||
* @example
|
||
*
|
||
* // Avoid costly calculations while the window size is in flux.
|
||
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
|
||
*
|
||
* // Invoke `sendMail` when clicked, debouncing subsequent calls.
|
||
* jQuery(element).on('click', _.debounce(sendMail, 300, {
|
||
* 'leading': true,
|
||
* 'trailing': false
|
||
* }));
|
||
*
|
||
* // Ensure `batchLog` is invoked once after 1 second of debounced calls.
|
||
* var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
|
||
* var source = new EventSource('/stream');
|
||
* jQuery(source).on('message', debounced);
|
||
*
|
||
* // Cancel the trailing debounced invocation.
|
||
* jQuery(window).on('popstate', debounced.cancel);
|
||
*/
|
||
function debounce(func, wait, options) {
|
||
var lastArgs,
|
||
lastThis,
|
||
maxWait,
|
||
result,
|
||
timerId,
|
||
lastCallTime,
|
||
lastInvokeTime = 0,
|
||
leading = false,
|
||
maxing = false,
|
||
trailing = true;
|
||
|
||
if (typeof func != 'function') {
|
||
throw new TypeError(FUNC_ERROR_TEXT);
|
||
}
|
||
wait = toNumber(wait) || 0;
|
||
if (isObject(options)) {
|
||
leading = !!options.leading;
|
||
maxing = 'maxWait' in options;
|
||
maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
|
||
trailing = 'trailing' in options ? !!options.trailing : trailing;
|
||
}
|
||
|
||
function invokeFunc(time) {
|
||
var args = lastArgs,
|
||
thisArg = lastThis;
|
||
|
||
lastArgs = lastThis = undefined;
|
||
lastInvokeTime = time;
|
||
result = func.apply(thisArg, args);
|
||
return result;
|
||
}
|
||
|
||
function leadingEdge(time) {
|
||
// Reset any `maxWait` timer.
|
||
lastInvokeTime = time;
|
||
// Start the timer for the trailing edge.
|
||
timerId = setTimeout(timerExpired, wait);
|
||
// Invoke the leading edge.
|
||
return leading ? invokeFunc(time) : result;
|
||
}
|
||
|
||
function remainingWait(time) {
|
||
var timeSinceLastCall = time - lastCallTime,
|
||
timeSinceLastInvoke = time - lastInvokeTime,
|
||
timeWaiting = wait - timeSinceLastCall;
|
||
|
||
return maxing
|
||
? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
|
||
: timeWaiting;
|
||
}
|
||
|
||
function shouldInvoke(time) {
|
||
var timeSinceLastCall = time - lastCallTime,
|
||
timeSinceLastInvoke = time - lastInvokeTime;
|
||
|
||
// Either this is the first call, activity has stopped and we're at the
|
||
// trailing edge, the system time has gone backwards and we're treating
|
||
// it as the trailing edge, or we've hit the `maxWait` limit.
|
||
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
|
||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
|
||
}
|
||
|
||
function timerExpired() {
|
||
var time = now();
|
||
if (shouldInvoke(time)) {
|
||
return trailingEdge(time);
|
||
}
|
||
// Restart the timer.
|
||
timerId = setTimeout(timerExpired, remainingWait(time));
|
||
}
|
||
|
||
function trailingEdge(time) {
|
||
timerId = undefined;
|
||
|
||
// Only invoke if we have `lastArgs` which means `func` has been
|
||
// debounced at least once.
|
||
if (trailing && lastArgs) {
|
||
return invokeFunc(time);
|
||
}
|
||
lastArgs = lastThis = undefined;
|
||
return result;
|
||
}
|
||
|
||
function cancel() {
|
||
if (timerId !== undefined) {
|
||
clearTimeout(timerId);
|
||
}
|
||
lastInvokeTime = 0;
|
||
lastArgs = lastCallTime = lastThis = timerId = undefined;
|
||
}
|
||
|
||
function flush() {
|
||
return timerId === undefined ? result : trailingEdge(now());
|
||
}
|
||
|
||
function debounced() {
|
||
var time = now(),
|
||
isInvoking = shouldInvoke(time);
|
||
|
||
lastArgs = arguments;
|
||
lastThis = this;
|
||
lastCallTime = time;
|
||
|
||
if (isInvoking) {
|
||
if (timerId === undefined) {
|
||
return leadingEdge(lastCallTime);
|
||
}
|
||
if (maxing) {
|
||
// Handle invocations in a tight loop.
|
||
clearTimeout(timerId);
|
||
timerId = setTimeout(timerExpired, wait);
|
||
return invokeFunc(lastCallTime);
|
||
}
|
||
}
|
||
if (timerId === undefined) {
|
||
timerId = setTimeout(timerExpired, wait);
|
||
}
|
||
return result;
|
||
}
|
||
debounced.cancel = cancel;
|
||
debounced.flush = flush;
|
||
return debounced;
|
||
}
|
||
|
||
function _iterableToArrayLimit$2(r, l) {
|
||
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
||
if (null != t) {
|
||
var e,
|
||
n,
|
||
i,
|
||
u,
|
||
a = [],
|
||
f = !0,
|
||
o = !1;
|
||
try {
|
||
if (i = (t = t.call(r)).next, 0 === l) {
|
||
if (Object(t) !== t) return;
|
||
f = !1;
|
||
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
||
} catch (r) {
|
||
o = !0, n = r;
|
||
} finally {
|
||
try {
|
||
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
||
} finally {
|
||
if (o) throw n;
|
||
}
|
||
}
|
||
return a;
|
||
}
|
||
}
|
||
function _classCallCheck(instance, Constructor) {
|
||
if (!(instance instanceof Constructor)) {
|
||
throw new TypeError("Cannot call a class as a function");
|
||
}
|
||
}
|
||
function _defineProperties(target, props) {
|
||
for (var i = 0; i < props.length; i++) {
|
||
var descriptor = props[i];
|
||
descriptor.enumerable = descriptor.enumerable || false;
|
||
descriptor.configurable = true;
|
||
if ("value" in descriptor) descriptor.writable = true;
|
||
Object.defineProperty(target, _toPropertyKey$2(descriptor.key), descriptor);
|
||
}
|
||
}
|
||
function _createClass(Constructor, protoProps, staticProps) {
|
||
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
||
if (staticProps) _defineProperties(Constructor, staticProps);
|
||
Object.defineProperty(Constructor, "prototype", {
|
||
writable: false
|
||
});
|
||
return Constructor;
|
||
}
|
||
function _slicedToArray$2(arr, i) {
|
||
return _arrayWithHoles$2(arr) || _iterableToArrayLimit$2(arr, i) || _unsupportedIterableToArray$2(arr, i) || _nonIterableRest$2();
|
||
}
|
||
function _arrayWithHoles$2(arr) {
|
||
if (Array.isArray(arr)) return arr;
|
||
}
|
||
function _unsupportedIterableToArray$2(o, minLen) {
|
||
if (!o) return;
|
||
if (typeof o === "string") return _arrayLikeToArray$2(o, minLen);
|
||
var n = Object.prototype.toString.call(o).slice(8, -1);
|
||
if (n === "Object" && o.constructor) n = o.constructor.name;
|
||
if (n === "Map" || n === "Set") return Array.from(o);
|
||
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$2(o, minLen);
|
||
}
|
||
function _arrayLikeToArray$2(arr, len) {
|
||
if (len == null || len > arr.length) len = arr.length;
|
||
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
||
return arr2;
|
||
}
|
||
function _nonIterableRest$2() {
|
||
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
||
}
|
||
function _toPrimitive$2(input, hint) {
|
||
if (typeof input !== "object" || input === null) return input;
|
||
var prim = input[Symbol.toPrimitive];
|
||
if (prim !== undefined) {
|
||
var res = prim.call(input, hint || "default");
|
||
if (typeof res !== "object") return res;
|
||
throw new TypeError("@@toPrimitive must return a primitive value.");
|
||
}
|
||
return (hint === "string" ? String : Number)(input);
|
||
}
|
||
function _toPropertyKey$2(arg) {
|
||
var key = _toPrimitive$2(arg, "string");
|
||
return typeof key === "symbol" ? key : String(key);
|
||
}
|
||
|
||
var Prop = /*#__PURE__*/_createClass(function Prop(name, _ref) {
|
||
var _ref$default = _ref["default"],
|
||
defaultVal = _ref$default === void 0 ? null : _ref$default,
|
||
_ref$triggerUpdate = _ref.triggerUpdate,
|
||
triggerUpdate = _ref$triggerUpdate === void 0 ? true : _ref$triggerUpdate,
|
||
_ref$onChange = _ref.onChange,
|
||
onChange = _ref$onChange === void 0 ? function (newVal, state) {} : _ref$onChange;
|
||
_classCallCheck(this, Prop);
|
||
this.name = name;
|
||
this.defaultVal = defaultVal;
|
||
this.triggerUpdate = triggerUpdate;
|
||
this.onChange = onChange;
|
||
});
|
||
function index$2 (_ref2) {
|
||
var _ref2$stateInit = _ref2.stateInit,
|
||
stateInit = _ref2$stateInit === void 0 ? function () {
|
||
return {};
|
||
} : _ref2$stateInit,
|
||
_ref2$props = _ref2.props,
|
||
rawProps = _ref2$props === void 0 ? {} : _ref2$props,
|
||
_ref2$methods = _ref2.methods,
|
||
methods = _ref2$methods === void 0 ? {} : _ref2$methods,
|
||
_ref2$aliases = _ref2.aliases,
|
||
aliases = _ref2$aliases === void 0 ? {} : _ref2$aliases,
|
||
_ref2$init = _ref2.init,
|
||
initFn = _ref2$init === void 0 ? function () {} : _ref2$init,
|
||
_ref2$update = _ref2.update,
|
||
updateFn = _ref2$update === void 0 ? function () {} : _ref2$update;
|
||
// Parse props into Prop instances
|
||
var props = Object.keys(rawProps).map(function (propName) {
|
||
return new Prop(propName, rawProps[propName]);
|
||
});
|
||
return function () {
|
||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||
// Holds component state
|
||
var state = Object.assign({}, stateInit instanceof Function ? stateInit(options) : stateInit,
|
||
// Support plain objects for backwards compatibility
|
||
{
|
||
initialised: false
|
||
});
|
||
|
||
// keeps track of which props triggered an update
|
||
var changedProps = {};
|
||
|
||
// Component constructor
|
||
function comp(nodeElement) {
|
||
initStatic(nodeElement, options);
|
||
digest();
|
||
return comp;
|
||
}
|
||
var initStatic = function initStatic(nodeElement, options) {
|
||
initFn.call(comp, nodeElement, state, options);
|
||
state.initialised = true;
|
||
};
|
||
var digest = debounce(function () {
|
||
if (!state.initialised) {
|
||
return;
|
||
}
|
||
updateFn.call(comp, state, changedProps);
|
||
changedProps = {};
|
||
}, 1);
|
||
|
||
// Getter/setter methods
|
||
props.forEach(function (prop) {
|
||
comp[prop.name] = getSetProp(prop);
|
||
function getSetProp(_ref3) {
|
||
var prop = _ref3.name,
|
||
_ref3$triggerUpdate = _ref3.triggerUpdate,
|
||
redigest = _ref3$triggerUpdate === void 0 ? false : _ref3$triggerUpdate,
|
||
_ref3$onChange = _ref3.onChange,
|
||
onChange = _ref3$onChange === void 0 ? function (newVal, state) {} : _ref3$onChange,
|
||
_ref3$defaultVal = _ref3.defaultVal,
|
||
defaultVal = _ref3$defaultVal === void 0 ? null : _ref3$defaultVal;
|
||
return function (_) {
|
||
var curVal = state[prop];
|
||
if (!arguments.length) {
|
||
return curVal;
|
||
} // Getter mode
|
||
|
||
var val = _ === undefined ? defaultVal : _; // pick default if value passed is undefined
|
||
state[prop] = val;
|
||
onChange.call(comp, val, state, curVal);
|
||
|
||
// track changed props
|
||
!changedProps.hasOwnProperty(prop) && (changedProps[prop] = curVal);
|
||
if (redigest) {
|
||
digest();
|
||
}
|
||
return comp;
|
||
};
|
||
}
|
||
});
|
||
|
||
// Other methods
|
||
Object.keys(methods).forEach(function (methodName) {
|
||
comp[methodName] = function () {
|
||
var _methods$methodName;
|
||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
||
args[_key] = arguments[_key];
|
||
}
|
||
return (_methods$methodName = methods[methodName]).call.apply(_methods$methodName, [comp, state].concat(args));
|
||
};
|
||
});
|
||
|
||
// Link aliases
|
||
Object.entries(aliases).forEach(function (_ref4) {
|
||
var _ref5 = _slicedToArray$2(_ref4, 2),
|
||
alias = _ref5[0],
|
||
target = _ref5[1];
|
||
return comp[alias] = comp[target];
|
||
});
|
||
|
||
// Reset all component props to their default value
|
||
comp.resetProps = function () {
|
||
props.forEach(function (prop) {
|
||
comp[prop.name](prop.defaultVal);
|
||
});
|
||
return comp;
|
||
};
|
||
|
||
//
|
||
|
||
comp.resetProps(); // Apply all prop defaults
|
||
state._rerender = digest; // Expose digest method
|
||
|
||
return comp;
|
||
};
|
||
}
|
||
|
||
var index$1 = (function (p) {
|
||
return typeof p === 'function' ? p // fn
|
||
: typeof p === 'string' ? function (obj) {
|
||
return obj[p];
|
||
} // property name
|
||
: function (obj) {
|
||
return p;
|
||
};
|
||
}); // constant
|
||
|
||
class InternMap extends Map {
|
||
constructor(entries, key = keyof) {
|
||
super();
|
||
Object.defineProperties(this, {_intern: {value: new Map()}, _key: {value: key}});
|
||
if (entries != null) for (const [key, value] of entries) this.set(key, value);
|
||
}
|
||
get(key) {
|
||
return super.get(intern_get(this, key));
|
||
}
|
||
has(key) {
|
||
return super.has(intern_get(this, key));
|
||
}
|
||
set(key, value) {
|
||
return super.set(intern_set(this, key), value);
|
||
}
|
||
delete(key) {
|
||
return super.delete(intern_delete(this, key));
|
||
}
|
||
}
|
||
|
||
function intern_get({_intern, _key}, value) {
|
||
const key = _key(value);
|
||
return _intern.has(key) ? _intern.get(key) : value;
|
||
}
|
||
|
||
function intern_set({_intern, _key}, value) {
|
||
const key = _key(value);
|
||
if (_intern.has(key)) return _intern.get(key);
|
||
_intern.set(key, value);
|
||
return value;
|
||
}
|
||
|
||
function intern_delete({_intern, _key}, value) {
|
||
const key = _key(value);
|
||
if (_intern.has(key)) {
|
||
value = _intern.get(key);
|
||
_intern.delete(key);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
function keyof(value) {
|
||
return value !== null && typeof value === "object" ? value.valueOf() : value;
|
||
}
|
||
|
||
function max(values, valueof) {
|
||
let max;
|
||
if (valueof === undefined) {
|
||
for (const value of values) {
|
||
if (value != null
|
||
&& (max < value || (max === undefined && value >= value))) {
|
||
max = value;
|
||
}
|
||
}
|
||
} else {
|
||
let index = -1;
|
||
for (let value of values) {
|
||
if ((value = valueof(value, ++index, values)) != null
|
||
&& (max < value || (max === undefined && value >= value))) {
|
||
max = value;
|
||
}
|
||
}
|
||
}
|
||
return max;
|
||
}
|
||
|
||
function min(values, valueof) {
|
||
let min;
|
||
if (valueof === undefined) {
|
||
for (const value of values) {
|
||
if (value != null
|
||
&& (min > value || (min === undefined && value >= value))) {
|
||
min = value;
|
||
}
|
||
}
|
||
} else {
|
||
let index = -1;
|
||
for (let value of values) {
|
||
if ((value = valueof(value, ++index, values)) != null
|
||
&& (min > value || (min === undefined && value >= value))) {
|
||
min = value;
|
||
}
|
||
}
|
||
}
|
||
return min;
|
||
}
|
||
|
||
function _iterableToArrayLimit$1(arr, i) {
|
||
var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"];
|
||
if (null != _i) {
|
||
var _s,
|
||
_e,
|
||
_x,
|
||
_r,
|
||
_arr = [],
|
||
_n = !0,
|
||
_d = !1;
|
||
try {
|
||
if (_x = (_i = _i.call(arr)).next, 0 === i) {
|
||
if (Object(_i) !== _i) return;
|
||
_n = !1;
|
||
} else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);
|
||
} catch (err) {
|
||
_d = !0, _e = err;
|
||
} finally {
|
||
try {
|
||
if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;
|
||
} finally {
|
||
if (_d) throw _e;
|
||
}
|
||
}
|
||
return _arr;
|
||
}
|
||
}
|
||
function _objectWithoutPropertiesLoose$1(source, excluded) {
|
||
if (source == null) return {};
|
||
var target = {};
|
||
var sourceKeys = Object.keys(source);
|
||
var key, i;
|
||
for (i = 0; i < sourceKeys.length; i++) {
|
||
key = sourceKeys[i];
|
||
if (excluded.indexOf(key) >= 0) continue;
|
||
target[key] = source[key];
|
||
}
|
||
return target;
|
||
}
|
||
function _objectWithoutProperties$1(source, excluded) {
|
||
if (source == null) return {};
|
||
var target = _objectWithoutPropertiesLoose$1(source, excluded);
|
||
var key, i;
|
||
if (Object.getOwnPropertySymbols) {
|
||
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
||
for (i = 0; i < sourceSymbolKeys.length; i++) {
|
||
key = sourceSymbolKeys[i];
|
||
if (excluded.indexOf(key) >= 0) continue;
|
||
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
||
target[key] = source[key];
|
||
}
|
||
}
|
||
return target;
|
||
}
|
||
function _slicedToArray$1(arr, i) {
|
||
return _arrayWithHoles$1(arr) || _iterableToArrayLimit$1(arr, i) || _unsupportedIterableToArray$1(arr, i) || _nonIterableRest$1();
|
||
}
|
||
function _toConsumableArray$1(arr) {
|
||
return _arrayWithoutHoles$1(arr) || _iterableToArray$1(arr) || _unsupportedIterableToArray$1(arr) || _nonIterableSpread$1();
|
||
}
|
||
function _arrayWithoutHoles$1(arr) {
|
||
if (Array.isArray(arr)) return _arrayLikeToArray$1(arr);
|
||
}
|
||
function _arrayWithHoles$1(arr) {
|
||
if (Array.isArray(arr)) return arr;
|
||
}
|
||
function _iterableToArray$1(iter) {
|
||
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
||
}
|
||
function _unsupportedIterableToArray$1(o, minLen) {
|
||
if (!o) return;
|
||
if (typeof o === "string") return _arrayLikeToArray$1(o, minLen);
|
||
var n = Object.prototype.toString.call(o).slice(8, -1);
|
||
if (n === "Object" && o.constructor) n = o.constructor.name;
|
||
if (n === "Map" || n === "Set") return Array.from(o);
|
||
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$1(o, minLen);
|
||
}
|
||
function _arrayLikeToArray$1(arr, len) {
|
||
if (len == null || len > arr.length) len = arr.length;
|
||
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
||
return arr2;
|
||
}
|
||
function _nonIterableSpread$1() {
|
||
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
||
}
|
||
function _nonIterableRest$1() {
|
||
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
||
}
|
||
function _toPrimitive$1(input, hint) {
|
||
if (typeof input !== "object" || input === null) return input;
|
||
var prim = input[Symbol.toPrimitive];
|
||
if (prim !== undefined) {
|
||
var res = prim.call(input, hint || "default");
|
||
if (typeof res !== "object") return res;
|
||
throw new TypeError("@@toPrimitive must return a primitive value.");
|
||
}
|
||
return (hint === "string" ? String : Number)(input);
|
||
}
|
||
function _toPropertyKey$1(arg) {
|
||
var key = _toPrimitive$1(arg, "string");
|
||
return typeof key === "symbol" ? key : String(key);
|
||
}
|
||
|
||
var index = (function () {
|
||
var list = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
||
var keyAccessors = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
||
var multiItem = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
||
var flattenKeys = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
|
||
var keys = (keyAccessors instanceof Array ? keyAccessors.length ? keyAccessors : [undefined] : [keyAccessors]).map(function (key) {
|
||
return {
|
||
keyAccessor: key,
|
||
isProp: !(key instanceof Function)
|
||
};
|
||
});
|
||
var indexedResult = list.reduce(function (res, item) {
|
||
var iterObj = res;
|
||
var itemVal = item;
|
||
keys.forEach(function (_ref, idx) {
|
||
var keyAccessor = _ref.keyAccessor,
|
||
isProp = _ref.isProp;
|
||
var key;
|
||
if (isProp) {
|
||
var _itemVal = itemVal,
|
||
propVal = _itemVal[keyAccessor],
|
||
rest = _objectWithoutProperties$1(_itemVal, [keyAccessor].map(_toPropertyKey$1));
|
||
key = propVal;
|
||
itemVal = rest;
|
||
} else {
|
||
key = keyAccessor(itemVal, idx);
|
||
}
|
||
if (idx + 1 < keys.length) {
|
||
if (!iterObj.hasOwnProperty(key)) {
|
||
iterObj[key] = {};
|
||
}
|
||
iterObj = iterObj[key];
|
||
} else {
|
||
// Leaf key
|
||
if (multiItem) {
|
||
if (!iterObj.hasOwnProperty(key)) {
|
||
iterObj[key] = [];
|
||
}
|
||
iterObj[key].push(itemVal);
|
||
} else {
|
||
iterObj[key] = itemVal;
|
||
}
|
||
}
|
||
});
|
||
return res;
|
||
}, {});
|
||
if (multiItem instanceof Function) {
|
||
// Reduce leaf multiple values
|
||
(function reduce(node) {
|
||
var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
||
if (level === keys.length) {
|
||
Object.keys(node).forEach(function (k) {
|
||
return node[k] = multiItem(node[k]);
|
||
});
|
||
} else {
|
||
Object.values(node).forEach(function (child) {
|
||
return reduce(child, level + 1);
|
||
});
|
||
}
|
||
})(indexedResult); // IIFE
|
||
}
|
||
|
||
var result = indexedResult;
|
||
if (flattenKeys) {
|
||
// flatten into array
|
||
result = [];
|
||
(function flatten(node) {
|
||
var accKeys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
||
if (accKeys.length === keys.length) {
|
||
result.push({
|
||
keys: accKeys,
|
||
vals: node
|
||
});
|
||
} else {
|
||
Object.entries(node).forEach(function (_ref2) {
|
||
var _ref3 = _slicedToArray$1(_ref2, 2),
|
||
key = _ref3[0],
|
||
val = _ref3[1];
|
||
return flatten(val, [].concat(_toConsumableArray$1(accKeys), [key]));
|
||
});
|
||
}
|
||
})(indexedResult); //IIFE
|
||
|
||
if (keyAccessors instanceof Array && keyAccessors.length === 0 && result.length === 1) {
|
||
// clear keys if there's no key accessors (single result)
|
||
result[0].keys = [];
|
||
}
|
||
}
|
||
return result;
|
||
});
|
||
|
||
function _iterableToArrayLimit(arr, i) {
|
||
var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"];
|
||
if (null != _i) {
|
||
var _s,
|
||
_e,
|
||
_x,
|
||
_r,
|
||
_arr = [],
|
||
_n = !0,
|
||
_d = !1;
|
||
try {
|
||
if (_x = (_i = _i.call(arr)).next, 0 === i) {
|
||
if (Object(_i) !== _i) return;
|
||
_n = !1;
|
||
} else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);
|
||
} catch (err) {
|
||
_d = !0, _e = err;
|
||
} finally {
|
||
try {
|
||
if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;
|
||
} finally {
|
||
if (_d) throw _e;
|
||
}
|
||
}
|
||
return _arr;
|
||
}
|
||
}
|
||
function ownKeys(object, enumerableOnly) {
|
||
var keys = Object.keys(object);
|
||
if (Object.getOwnPropertySymbols) {
|
||
var symbols = Object.getOwnPropertySymbols(object);
|
||
enumerableOnly && (symbols = symbols.filter(function (sym) {
|
||
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
||
})), keys.push.apply(keys, symbols);
|
||
}
|
||
return keys;
|
||
}
|
||
function _objectSpread2(target) {
|
||
for (var i = 1; i < arguments.length; i++) {
|
||
var source = null != arguments[i] ? arguments[i] : {};
|
||
i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
|
||
_defineProperty(target, key, source[key]);
|
||
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
|
||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
||
});
|
||
}
|
||
return target;
|
||
}
|
||
function _defineProperty(obj, key, value) {
|
||
key = _toPropertyKey(key);
|
||
if (key in obj) {
|
||
Object.defineProperty(obj, key, {
|
||
value: value,
|
||
enumerable: true,
|
||
configurable: true,
|
||
writable: true
|
||
});
|
||
} else {
|
||
obj[key] = value;
|
||
}
|
||
return obj;
|
||
}
|
||
function _objectWithoutPropertiesLoose(source, excluded) {
|
||
if (source == null) return {};
|
||
var target = {};
|
||
var sourceKeys = Object.keys(source);
|
||
var key, i;
|
||
for (i = 0; i < sourceKeys.length; i++) {
|
||
key = sourceKeys[i];
|
||
if (excluded.indexOf(key) >= 0) continue;
|
||
target[key] = source[key];
|
||
}
|
||
return target;
|
||
}
|
||
function _objectWithoutProperties(source, excluded) {
|
||
if (source == null) return {};
|
||
var target = _objectWithoutPropertiesLoose(source, excluded);
|
||
var key, i;
|
||
if (Object.getOwnPropertySymbols) {
|
||
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
||
for (i = 0; i < sourceSymbolKeys.length; i++) {
|
||
key = sourceSymbolKeys[i];
|
||
if (excluded.indexOf(key) >= 0) continue;
|
||
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
||
target[key] = source[key];
|
||
}
|
||
}
|
||
return target;
|
||
}
|
||
function _slicedToArray(arr, i) {
|
||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
||
}
|
||
function _toConsumableArray(arr) {
|
||
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
|
||
}
|
||
function _arrayWithoutHoles(arr) {
|
||
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
|
||
}
|
||
function _arrayWithHoles(arr) {
|
||
if (Array.isArray(arr)) return arr;
|
||
}
|
||
function _iterableToArray(iter) {
|
||
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
||
}
|
||
function _unsupportedIterableToArray(o, minLen) {
|
||
if (!o) return;
|
||
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
|
||
var n = Object.prototype.toString.call(o).slice(8, -1);
|
||
if (n === "Object" && o.constructor) n = o.constructor.name;
|
||
if (n === "Map" || n === "Set") return Array.from(o);
|
||
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
|
||
}
|
||
function _arrayLikeToArray(arr, len) {
|
||
if (len == null || len > arr.length) len = arr.length;
|
||
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
||
return arr2;
|
||
}
|
||
function _nonIterableSpread() {
|
||
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
||
}
|
||
function _nonIterableRest() {
|
||
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
||
}
|
||
function _toPrimitive(input, hint) {
|
||
if (typeof input !== "object" || input === null) return input;
|
||
var prim = input[Symbol.toPrimitive];
|
||
if (prim !== undefined) {
|
||
var res = prim.call(input, hint || "default");
|
||
if (typeof res !== "object") return res;
|
||
throw new TypeError("@@toPrimitive must return a primitive value.");
|
||
}
|
||
return (hint === "string" ? String : Number)(input);
|
||
}
|
||
function _toPropertyKey(arg) {
|
||
var key = _toPrimitive(arg, "string");
|
||
return typeof key === "symbol" ? key : String(key);
|
||
}
|
||
|
||
var _excluded$1 = ["createObj", "updateObj", "exitObj", "objBindAttr", "dataBindAttr"];
|
||
function diffArrays(prev, next, idAccessor) {
|
||
var result = {
|
||
enter: [],
|
||
update: [],
|
||
exit: []
|
||
};
|
||
if (!idAccessor) {
|
||
// use object references for comparison
|
||
var prevSet = new Set(prev);
|
||
var nextSet = new Set(next);
|
||
new Set([].concat(_toConsumableArray(prevSet), _toConsumableArray(nextSet))).forEach(function (item) {
|
||
var type = !prevSet.has(item) ? 'enter' : !nextSet.has(item) ? 'exit' : 'update';
|
||
result[type].push(type === 'update' ? [item, item] : item);
|
||
});
|
||
} else {
|
||
// compare by id (duplicate keys are ignored)
|
||
var prevById = index(prev, idAccessor, false);
|
||
var nextById = index(next, idAccessor, false);
|
||
var byId = Object.assign({}, prevById, nextById);
|
||
Object.entries(byId).forEach(function (_ref) {
|
||
var _ref2 = _slicedToArray(_ref, 2),
|
||
id = _ref2[0],
|
||
item = _ref2[1];
|
||
var type = !prevById.hasOwnProperty(id) ? 'enter' : !nextById.hasOwnProperty(id) ? 'exit' : 'update';
|
||
result[type].push(type === 'update' ? [prevById[id], nextById[id]] : item);
|
||
});
|
||
}
|
||
return result;
|
||
}
|
||
function dataBindDiff(data, existingObjs, _ref3) {
|
||
var _ref3$objBindAttr = _ref3.objBindAttr,
|
||
objBindAttr = _ref3$objBindAttr === void 0 ? '__obj' : _ref3$objBindAttr,
|
||
_ref3$dataBindAttr = _ref3.dataBindAttr,
|
||
dataBindAttr = _ref3$dataBindAttr === void 0 ? '__data' : _ref3$dataBindAttr,
|
||
idAccessor = _ref3.idAccessor,
|
||
_ref3$purge = _ref3.purge,
|
||
purge = _ref3$purge === void 0 ? false : _ref3$purge;
|
||
var isObjValid = function isObjValid(obj) {
|
||
return obj.hasOwnProperty(dataBindAttr);
|
||
};
|
||
var removeObjs = existingObjs.filter(function (obj) {
|
||
return !isObjValid(obj);
|
||
});
|
||
var prevD = existingObjs.filter(isObjValid).map(function (obj) {
|
||
return obj[dataBindAttr];
|
||
});
|
||
var nextD = data;
|
||
var diff = purge ? {
|
||
enter: nextD,
|
||
exit: prevD,
|
||
update: []
|
||
} // don't diff data in purge mode
|
||
: diffArrays(prevD, nextD, idAccessor);
|
||
diff.update = diff.update.map(function (_ref4) {
|
||
var _ref5 = _slicedToArray(_ref4, 2),
|
||
prevD = _ref5[0],
|
||
nextD = _ref5[1];
|
||
if (prevD !== nextD) {
|
||
// transfer obj to new data point (if different)
|
||
nextD[objBindAttr] = prevD[objBindAttr];
|
||
nextD[objBindAttr][dataBindAttr] = nextD;
|
||
}
|
||
return nextD;
|
||
});
|
||
diff.exit = diff.exit.concat(removeObjs.map(function (obj) {
|
||
return _defineProperty({}, objBindAttr, obj);
|
||
}));
|
||
return diff;
|
||
}
|
||
function viewDigest(data, existingObjs,
|
||
// list
|
||
appendObj,
|
||
// item => {...} function
|
||
removeObj, // item => {...} function
|
||
_ref7) {
|
||
var _ref7$createObj = _ref7.createObj,
|
||
createObj = _ref7$createObj === void 0 ? function (d) {
|
||
return {};
|
||
} : _ref7$createObj,
|
||
_ref7$updateObj = _ref7.updateObj,
|
||
updateObj = _ref7$updateObj === void 0 ? function (obj, d) {} : _ref7$updateObj,
|
||
_ref7$exitObj = _ref7.exitObj,
|
||
exitObj = _ref7$exitObj === void 0 ? function (obj) {} : _ref7$exitObj,
|
||
_ref7$objBindAttr = _ref7.objBindAttr,
|
||
objBindAttr = _ref7$objBindAttr === void 0 ? '__obj' : _ref7$objBindAttr,
|
||
_ref7$dataBindAttr = _ref7.dataBindAttr,
|
||
dataBindAttr = _ref7$dataBindAttr === void 0 ? '__data' : _ref7$dataBindAttr,
|
||
dataDiffOptions = _objectWithoutProperties(_ref7, _excluded$1);
|
||
var _dataBindDiff = dataBindDiff(data, existingObjs, _objectSpread2({
|
||
objBindAttr: objBindAttr,
|
||
dataBindAttr: dataBindAttr
|
||
}, dataDiffOptions)),
|
||
enter = _dataBindDiff.enter,
|
||
update = _dataBindDiff.update,
|
||
exit = _dataBindDiff.exit;
|
||
|
||
// Remove exiting points
|
||
exit.forEach(function (d) {
|
||
var obj = d[objBindAttr];
|
||
delete d[objBindAttr]; // unbind obj
|
||
|
||
exitObj(obj);
|
||
removeObj(obj);
|
||
});
|
||
var newObjs = createObjs(enter);
|
||
var pointsData = [].concat(_toConsumableArray(enter), _toConsumableArray(update));
|
||
updateObjs(pointsData);
|
||
|
||
// Add new points
|
||
newObjs.forEach(appendObj);
|
||
|
||
//
|
||
|
||
function createObjs(data) {
|
||
var newObjs = [];
|
||
data.forEach(function (d) {
|
||
var obj = createObj(d);
|
||
if (obj) {
|
||
obj[dataBindAttr] = d;
|
||
d[objBindAttr] = obj;
|
||
newObjs.push(obj);
|
||
}
|
||
});
|
||
return newObjs;
|
||
}
|
||
function updateObjs(data) {
|
||
data.forEach(function (d) {
|
||
var obj = d[objBindAttr];
|
||
if (obj) {
|
||
obj[dataBindAttr] = d;
|
||
updateObj(obj, d);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
var materialDispose = function materialDispose(material) {
|
||
if (material instanceof Array) {
|
||
material.forEach(materialDispose);
|
||
} else {
|
||
if (material.map) {
|
||
material.map.dispose();
|
||
}
|
||
material.dispose();
|
||
}
|
||
};
|
||
var deallocate = function deallocate(obj) {
|
||
if (obj.geometry) {
|
||
obj.geometry.dispose();
|
||
}
|
||
if (obj.material) {
|
||
materialDispose(obj.material);
|
||
}
|
||
if (obj.texture) {
|
||
obj.texture.dispose();
|
||
}
|
||
if (obj.children) {
|
||
obj.children.forEach(deallocate);
|
||
}
|
||
};
|
||
var emptyObject = function emptyObject(obj) {
|
||
while (obj.children.length) {
|
||
var childObj = obj.children[0];
|
||
obj.remove(childObj);
|
||
deallocate(childObj);
|
||
}
|
||
};
|
||
|
||
var _excluded = ["objFilter"];
|
||
function threeDigest(data, scene) {
|
||
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
|
||
_ref$objFilter = _ref.objFilter,
|
||
objFilter = _ref$objFilter === void 0 ? function () {
|
||
return true;
|
||
} : _ref$objFilter,
|
||
options = _objectWithoutProperties$2(_ref, _excluded);
|
||
return viewDigest(data, scene.children.filter(objFilter), function (obj) {
|
||
return scene.add(obj);
|
||
}, function (obj) {
|
||
scene.remove(obj);
|
||
emptyObject(obj);
|
||
}, _objectSpread2$1({
|
||
objBindAttr: '__threeObj'
|
||
}, options));
|
||
}
|
||
|
||
function initRange(domain, range) {
|
||
switch (arguments.length) {
|
||
case 0: break;
|
||
case 1: this.range(domain); break;
|
||
default: this.range(range).domain(domain); break;
|
||
}
|
||
return this;
|
||
}
|
||
|
||
const implicit = Symbol("implicit");
|
||
|
||
function ordinal() {
|
||
var index = new InternMap(),
|
||
domain = [],
|
||
range = [],
|
||
unknown = implicit;
|
||
|
||
function scale(d) {
|
||
let i = index.get(d);
|
||
if (i === undefined) {
|
||
if (unknown !== implicit) return unknown;
|
||
index.set(d, i = domain.push(d) - 1);
|
||
}
|
||
return range[i % range.length];
|
||
}
|
||
|
||
scale.domain = function(_) {
|
||
if (!arguments.length) return domain.slice();
|
||
domain = [], index = new InternMap();
|
||
for (const value of _) {
|
||
if (index.has(value)) continue;
|
||
index.set(value, domain.push(value) - 1);
|
||
}
|
||
return scale;
|
||
};
|
||
|
||
scale.range = function(_) {
|
||
return arguments.length ? (range = Array.from(_), scale) : range.slice();
|
||
};
|
||
|
||
scale.unknown = function(_) {
|
||
return arguments.length ? (unknown = _, scale) : unknown;
|
||
};
|
||
|
||
scale.copy = function() {
|
||
return ordinal(domain, range).unknown(unknown);
|
||
};
|
||
|
||
initRange.apply(scale, arguments);
|
||
|
||
return scale;
|
||
}
|
||
|
||
function colors(specifier) {
|
||
var n = specifier.length / 6 | 0, colors = new Array(n), i = 0;
|
||
while (i < n) colors[i] = "#" + specifier.slice(i * 6, ++i * 6);
|
||
return colors;
|
||
}
|
||
|
||
var schemePaired = colors("a6cee31f78b4b2df8a33a02cfb9a99e31a1cfdbf6fff7f00cab2d66a3d9affff99b15928");
|
||
|
||
// This file is autogenerated. It's used to publish ESM to npm.
|
||
function _typeof(obj) {
|
||
"@babel/helpers - typeof";
|
||
|
||
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
|
||
return typeof obj;
|
||
} : function (obj) {
|
||
return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
||
}, _typeof(obj);
|
||
}
|
||
|
||
// https://github.com/bgrins/TinyColor
|
||
// Brian Grinstead, MIT License
|
||
|
||
var trimLeft = /^\s+/;
|
||
var trimRight = /\s+$/;
|
||
function tinycolor(color, opts) {
|
||
color = color ? color : "";
|
||
opts = opts || {};
|
||
|
||
// If input is already a tinycolor, return itself
|
||
if (color instanceof tinycolor) {
|
||
return color;
|
||
}
|
||
// If we are called as a function, call using new instead
|
||
if (!(this instanceof tinycolor)) {
|
||
return new tinycolor(color, opts);
|
||
}
|
||
var rgb = inputToRGB(color);
|
||
this._originalInput = color, this._r = rgb.r, this._g = rgb.g, this._b = rgb.b, this._a = rgb.a, this._roundA = Math.round(100 * this._a) / 100, this._format = opts.format || rgb.format;
|
||
this._gradientType = opts.gradientType;
|
||
|
||
// Don't let the range of [0,255] come back in [0,1].
|
||
// Potentially lose a little bit of precision here, but will fix issues where
|
||
// .5 gets interpreted as half of the total, instead of half of 1
|
||
// If it was supposed to be 128, this was already taken care of by `inputToRgb`
|
||
if (this._r < 1) this._r = Math.round(this._r);
|
||
if (this._g < 1) this._g = Math.round(this._g);
|
||
if (this._b < 1) this._b = Math.round(this._b);
|
||
this._ok = rgb.ok;
|
||
}
|
||
tinycolor.prototype = {
|
||
isDark: function isDark() {
|
||
return this.getBrightness() < 128;
|
||
},
|
||
isLight: function isLight() {
|
||
return !this.isDark();
|
||
},
|
||
isValid: function isValid() {
|
||
return this._ok;
|
||
},
|
||
getOriginalInput: function getOriginalInput() {
|
||
return this._originalInput;
|
||
},
|
||
getFormat: function getFormat() {
|
||
return this._format;
|
||
},
|
||
getAlpha: function getAlpha() {
|
||
return this._a;
|
||
},
|
||
getBrightness: function getBrightness() {
|
||
//http://www.w3.org/TR/AERT#color-contrast
|
||
var rgb = this.toRgb();
|
||
return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
|
||
},
|
||
getLuminance: function getLuminance() {
|
||
//http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
||
var rgb = this.toRgb();
|
||
var RsRGB, GsRGB, BsRGB, R, G, B;
|
||
RsRGB = rgb.r / 255;
|
||
GsRGB = rgb.g / 255;
|
||
BsRGB = rgb.b / 255;
|
||
if (RsRGB <= 0.03928) R = RsRGB / 12.92;else R = Math.pow((RsRGB + 0.055) / 1.055, 2.4);
|
||
if (GsRGB <= 0.03928) G = GsRGB / 12.92;else G = Math.pow((GsRGB + 0.055) / 1.055, 2.4);
|
||
if (BsRGB <= 0.03928) B = BsRGB / 12.92;else B = Math.pow((BsRGB + 0.055) / 1.055, 2.4);
|
||
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
||
},
|
||
setAlpha: function setAlpha(value) {
|
||
this._a = boundAlpha(value);
|
||
this._roundA = Math.round(100 * this._a) / 100;
|
||
return this;
|
||
},
|
||
toHsv: function toHsv() {
|
||
var hsv = rgbToHsv(this._r, this._g, this._b);
|
||
return {
|
||
h: hsv.h * 360,
|
||
s: hsv.s,
|
||
v: hsv.v,
|
||
a: this._a
|
||
};
|
||
},
|
||
toHsvString: function toHsvString() {
|
||
var hsv = rgbToHsv(this._r, this._g, this._b);
|
||
var h = Math.round(hsv.h * 360),
|
||
s = Math.round(hsv.s * 100),
|
||
v = Math.round(hsv.v * 100);
|
||
return this._a == 1 ? "hsv(" + h + ", " + s + "%, " + v + "%)" : "hsva(" + h + ", " + s + "%, " + v + "%, " + this._roundA + ")";
|
||
},
|
||
toHsl: function toHsl() {
|
||
var hsl = rgbToHsl(this._r, this._g, this._b);
|
||
return {
|
||
h: hsl.h * 360,
|
||
s: hsl.s,
|
||
l: hsl.l,
|
||
a: this._a
|
||
};
|
||
},
|
||
toHslString: function toHslString() {
|
||
var hsl = rgbToHsl(this._r, this._g, this._b);
|
||
var h = Math.round(hsl.h * 360),
|
||
s = Math.round(hsl.s * 100),
|
||
l = Math.round(hsl.l * 100);
|
||
return this._a == 1 ? "hsl(" + h + ", " + s + "%, " + l + "%)" : "hsla(" + h + ", " + s + "%, " + l + "%, " + this._roundA + ")";
|
||
},
|
||
toHex: function toHex(allow3Char) {
|
||
return rgbToHex(this._r, this._g, this._b, allow3Char);
|
||
},
|
||
toHexString: function toHexString(allow3Char) {
|
||
return "#" + this.toHex(allow3Char);
|
||
},
|
||
toHex8: function toHex8(allow4Char) {
|
||
return rgbaToHex(this._r, this._g, this._b, this._a, allow4Char);
|
||
},
|
||
toHex8String: function toHex8String(allow4Char) {
|
||
return "#" + this.toHex8(allow4Char);
|
||
},
|
||
toRgb: function toRgb() {
|
||
return {
|
||
r: Math.round(this._r),
|
||
g: Math.round(this._g),
|
||
b: Math.round(this._b),
|
||
a: this._a
|
||
};
|
||
},
|
||
toRgbString: function toRgbString() {
|
||
return this._a == 1 ? "rgb(" + Math.round(this._r) + ", " + Math.round(this._g) + ", " + Math.round(this._b) + ")" : "rgba(" + Math.round(this._r) + ", " + Math.round(this._g) + ", " + Math.round(this._b) + ", " + this._roundA + ")";
|
||
},
|
||
toPercentageRgb: function toPercentageRgb() {
|
||
return {
|
||
r: Math.round(bound01(this._r, 255) * 100) + "%",
|
||
g: Math.round(bound01(this._g, 255) * 100) + "%",
|
||
b: Math.round(bound01(this._b, 255) * 100) + "%",
|
||
a: this._a
|
||
};
|
||
},
|
||
toPercentageRgbString: function toPercentageRgbString() {
|
||
return this._a == 1 ? "rgb(" + Math.round(bound01(this._r, 255) * 100) + "%, " + Math.round(bound01(this._g, 255) * 100) + "%, " + Math.round(bound01(this._b, 255) * 100) + "%)" : "rgba(" + Math.round(bound01(this._r, 255) * 100) + "%, " + Math.round(bound01(this._g, 255) * 100) + "%, " + Math.round(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
|
||
},
|
||
toName: function toName() {
|
||
if (this._a === 0) {
|
||
return "transparent";
|
||
}
|
||
if (this._a < 1) {
|
||
return false;
|
||
}
|
||
return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
|
||
},
|
||
toFilter: function toFilter(secondColor) {
|
||
var hex8String = "#" + rgbaToArgbHex(this._r, this._g, this._b, this._a);
|
||
var secondHex8String = hex8String;
|
||
var gradientType = this._gradientType ? "GradientType = 1, " : "";
|
||
if (secondColor) {
|
||
var s = tinycolor(secondColor);
|
||
secondHex8String = "#" + rgbaToArgbHex(s._r, s._g, s._b, s._a);
|
||
}
|
||
return "progid:DXImageTransform.Microsoft.gradient(" + gradientType + "startColorstr=" + hex8String + ",endColorstr=" + secondHex8String + ")";
|
||
},
|
||
toString: function toString(format) {
|
||
var formatSet = !!format;
|
||
format = format || this._format;
|
||
var formattedString = false;
|
||
var hasAlpha = this._a < 1 && this._a >= 0;
|
||
var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "hex4" || format === "hex8" || format === "name");
|
||
if (needsAlphaFormat) {
|
||
// Special case for "transparent", all other non-alpha formats
|
||
// will return rgba when there is transparency.
|
||
if (format === "name" && this._a === 0) {
|
||
return this.toName();
|
||
}
|
||
return this.toRgbString();
|
||
}
|
||
if (format === "rgb") {
|
||
formattedString = this.toRgbString();
|
||
}
|
||
if (format === "prgb") {
|
||
formattedString = this.toPercentageRgbString();
|
||
}
|
||
if (format === "hex" || format === "hex6") {
|
||
formattedString = this.toHexString();
|
||
}
|
||
if (format === "hex3") {
|
||
formattedString = this.toHexString(true);
|
||
}
|
||
if (format === "hex4") {
|
||
formattedString = this.toHex8String(true);
|
||
}
|
||
if (format === "hex8") {
|
||
formattedString = this.toHex8String();
|
||
}
|
||
if (format === "name") {
|
||
formattedString = this.toName();
|
||
}
|
||
if (format === "hsl") {
|
||
formattedString = this.toHslString();
|
||
}
|
||
if (format === "hsv") {
|
||
formattedString = this.toHsvString();
|
||
}
|
||
return formattedString || this.toHexString();
|
||
},
|
||
clone: function clone() {
|
||
return tinycolor(this.toString());
|
||
},
|
||
_applyModification: function _applyModification(fn, args) {
|
||
var color = fn.apply(null, [this].concat([].slice.call(args)));
|
||
this._r = color._r;
|
||
this._g = color._g;
|
||
this._b = color._b;
|
||
this.setAlpha(color._a);
|
||
return this;
|
||
},
|
||
lighten: function lighten() {
|
||
return this._applyModification(_lighten, arguments);
|
||
},
|
||
brighten: function brighten() {
|
||
return this._applyModification(_brighten, arguments);
|
||
},
|
||
darken: function darken() {
|
||
return this._applyModification(_darken, arguments);
|
||
},
|
||
desaturate: function desaturate() {
|
||
return this._applyModification(_desaturate, arguments);
|
||
},
|
||
saturate: function saturate() {
|
||
return this._applyModification(_saturate, arguments);
|
||
},
|
||
greyscale: function greyscale() {
|
||
return this._applyModification(_greyscale, arguments);
|
||
},
|
||
spin: function spin() {
|
||
return this._applyModification(_spin, arguments);
|
||
},
|
||
_applyCombination: function _applyCombination(fn, args) {
|
||
return fn.apply(null, [this].concat([].slice.call(args)));
|
||
},
|
||
analogous: function analogous() {
|
||
return this._applyCombination(_analogous, arguments);
|
||
},
|
||
complement: function complement() {
|
||
return this._applyCombination(_complement, arguments);
|
||
},
|
||
monochromatic: function monochromatic() {
|
||
return this._applyCombination(_monochromatic, arguments);
|
||
},
|
||
splitcomplement: function splitcomplement() {
|
||
return this._applyCombination(_splitcomplement, arguments);
|
||
},
|
||
// Disabled until https://github.com/bgrins/TinyColor/issues/254
|
||
// polyad: function (number) {
|
||
// return this._applyCombination(polyad, [number]);
|
||
// },
|
||
triad: function triad() {
|
||
return this._applyCombination(polyad, [3]);
|
||
},
|
||
tetrad: function tetrad() {
|
||
return this._applyCombination(polyad, [4]);
|
||
}
|
||
};
|
||
|
||
// If input is an object, force 1 into "1.0" to handle ratios properly
|
||
// String input requires "1.0" as input, so 1 will be treated as 1
|
||
tinycolor.fromRatio = function (color, opts) {
|
||
if (_typeof(color) == "object") {
|
||
var newColor = {};
|
||
for (var i in color) {
|
||
if (color.hasOwnProperty(i)) {
|
||
if (i === "a") {
|
||
newColor[i] = color[i];
|
||
} else {
|
||
newColor[i] = convertToPercentage(color[i]);
|
||
}
|
||
}
|
||
}
|
||
color = newColor;
|
||
}
|
||
return tinycolor(color, opts);
|
||
};
|
||
|
||
// Given a string or object, convert that input to RGB
|
||
// Possible string inputs:
|
||
//
|
||
// "red"
|
||
// "#f00" or "f00"
|
||
// "#ff0000" or "ff0000"
|
||
// "#ff000000" or "ff000000"
|
||
// "rgb 255 0 0" or "rgb (255, 0, 0)"
|
||
// "rgb 1.0 0 0" or "rgb (1, 0, 0)"
|
||
// "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
|
||
// "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
|
||
// "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
|
||
// "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
|
||
// "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
|
||
//
|
||
function inputToRGB(color) {
|
||
var rgb = {
|
||
r: 0,
|
||
g: 0,
|
||
b: 0
|
||
};
|
||
var a = 1;
|
||
var s = null;
|
||
var v = null;
|
||
var l = null;
|
||
var ok = false;
|
||
var format = false;
|
||
if (typeof color == "string") {
|
||
color = stringInputToObject(color);
|
||
}
|
||
if (_typeof(color) == "object") {
|
||
if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
|
||
rgb = rgbToRgb(color.r, color.g, color.b);
|
||
ok = true;
|
||
format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
|
||
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
|
||
s = convertToPercentage(color.s);
|
||
v = convertToPercentage(color.v);
|
||
rgb = hsvToRgb(color.h, s, v);
|
||
ok = true;
|
||
format = "hsv";
|
||
} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
|
||
s = convertToPercentage(color.s);
|
||
l = convertToPercentage(color.l);
|
||
rgb = hslToRgb(color.h, s, l);
|
||
ok = true;
|
||
format = "hsl";
|
||
}
|
||
if (color.hasOwnProperty("a")) {
|
||
a = color.a;
|
||
}
|
||
}
|
||
a = boundAlpha(a);
|
||
return {
|
||
ok: ok,
|
||
format: color.format || format,
|
||
r: Math.min(255, Math.max(rgb.r, 0)),
|
||
g: Math.min(255, Math.max(rgb.g, 0)),
|
||
b: Math.min(255, Math.max(rgb.b, 0)),
|
||
a: a
|
||
};
|
||
}
|
||
|
||
// Conversion Functions
|
||
// --------------------
|
||
|
||
// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
|
||
// <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
|
||
|
||
// `rgbToRgb`
|
||
// Handle bounds / percentage checking to conform to CSS color spec
|
||
// <http://www.w3.org/TR/css3-color/>
|
||
// *Assumes:* r, g, b in [0, 255] or [0, 1]
|
||
// *Returns:* { r, g, b } in [0, 255]
|
||
function rgbToRgb(r, g, b) {
|
||
return {
|
||
r: bound01(r, 255) * 255,
|
||
g: bound01(g, 255) * 255,
|
||
b: bound01(b, 255) * 255
|
||
};
|
||
}
|
||
|
||
// `rgbToHsl`
|
||
// Converts an RGB color value to HSL.
|
||
// *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
|
||
// *Returns:* { h, s, l } in [0,1]
|
||
function rgbToHsl(r, g, b) {
|
||
r = bound01(r, 255);
|
||
g = bound01(g, 255);
|
||
b = bound01(b, 255);
|
||
var max = Math.max(r, g, b),
|
||
min = Math.min(r, g, b);
|
||
var h,
|
||
s,
|
||
l = (max + min) / 2;
|
||
if (max == min) {
|
||
h = s = 0; // achromatic
|
||
} else {
|
||
var d = max - min;
|
||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||
switch (max) {
|
||
case r:
|
||
h = (g - b) / d + (g < b ? 6 : 0);
|
||
break;
|
||
case g:
|
||
h = (b - r) / d + 2;
|
||
break;
|
||
case b:
|
||
h = (r - g) / d + 4;
|
||
break;
|
||
}
|
||
h /= 6;
|
||
}
|
||
return {
|
||
h: h,
|
||
s: s,
|
||
l: l
|
||
};
|
||
}
|
||
|
||
// `hslToRgb`
|
||
// Converts an HSL color value to RGB.
|
||
// *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
|
||
// *Returns:* { r, g, b } in the set [0, 255]
|
||
function hslToRgb(h, s, l) {
|
||
var r, g, b;
|
||
h = bound01(h, 360);
|
||
s = bound01(s, 100);
|
||
l = bound01(l, 100);
|
||
function hue2rgb(p, q, t) {
|
||
if (t < 0) t += 1;
|
||
if (t > 1) t -= 1;
|
||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||
if (t < 1 / 2) return q;
|
||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||
return p;
|
||
}
|
||
if (s === 0) {
|
||
r = g = b = l; // achromatic
|
||
} else {
|
||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||
var p = 2 * l - q;
|
||
r = hue2rgb(p, q, h + 1 / 3);
|
||
g = hue2rgb(p, q, h);
|
||
b = hue2rgb(p, q, h - 1 / 3);
|
||
}
|
||
return {
|
||
r: r * 255,
|
||
g: g * 255,
|
||
b: b * 255
|
||
};
|
||
}
|
||
|
||
// `rgbToHsv`
|
||
// Converts an RGB color value to HSV
|
||
// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
|
||
// *Returns:* { h, s, v } in [0,1]
|
||
function rgbToHsv(r, g, b) {
|
||
r = bound01(r, 255);
|
||
g = bound01(g, 255);
|
||
b = bound01(b, 255);
|
||
var max = Math.max(r, g, b),
|
||
min = Math.min(r, g, b);
|
||
var h,
|
||
s,
|
||
v = max;
|
||
var d = max - min;
|
||
s = max === 0 ? 0 : d / max;
|
||
if (max == min) {
|
||
h = 0; // achromatic
|
||
} else {
|
||
switch (max) {
|
||
case r:
|
||
h = (g - b) / d + (g < b ? 6 : 0);
|
||
break;
|
||
case g:
|
||
h = (b - r) / d + 2;
|
||
break;
|
||
case b:
|
||
h = (r - g) / d + 4;
|
||
break;
|
||
}
|
||
h /= 6;
|
||
}
|
||
return {
|
||
h: h,
|
||
s: s,
|
||
v: v
|
||
};
|
||
}
|
||
|
||
// `hsvToRgb`
|
||
// Converts an HSV color value to RGB.
|
||
// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
|
||
// *Returns:* { r, g, b } in the set [0, 255]
|
||
function hsvToRgb(h, s, v) {
|
||
h = bound01(h, 360) * 6;
|
||
s = bound01(s, 100);
|
||
v = bound01(v, 100);
|
||
var i = Math.floor(h),
|
||
f = h - i,
|
||
p = v * (1 - s),
|
||
q = v * (1 - f * s),
|
||
t = v * (1 - (1 - f) * s),
|
||
mod = i % 6,
|
||
r = [v, q, p, p, t, v][mod],
|
||
g = [t, v, v, q, p, p][mod],
|
||
b = [p, p, t, v, v, q][mod];
|
||
return {
|
||
r: r * 255,
|
||
g: g * 255,
|
||
b: b * 255
|
||
};
|
||
}
|
||
|
||
// `rgbToHex`
|
||
// Converts an RGB color to hex
|
||
// Assumes r, g, and b are contained in the set [0, 255]
|
||
// Returns a 3 or 6 character hex
|
||
function rgbToHex(r, g, b, allow3Char) {
|
||
var hex = [pad2(Math.round(r).toString(16)), pad2(Math.round(g).toString(16)), pad2(Math.round(b).toString(16))];
|
||
|
||
// Return a 3 character hex if possible
|
||
if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
|
||
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
|
||
}
|
||
return hex.join("");
|
||
}
|
||
|
||
// `rgbaToHex`
|
||
// Converts an RGBA color plus alpha transparency to hex
|
||
// Assumes r, g, b are contained in the set [0, 255] and
|
||
// a in [0, 1]. Returns a 4 or 8 character rgba hex
|
||
function rgbaToHex(r, g, b, a, allow4Char) {
|
||
var hex = [pad2(Math.round(r).toString(16)), pad2(Math.round(g).toString(16)), pad2(Math.round(b).toString(16)), pad2(convertDecimalToHex(a))];
|
||
|
||
// Return a 4 character hex if possible
|
||
if (allow4Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1) && hex[3].charAt(0) == hex[3].charAt(1)) {
|
||
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
|
||
}
|
||
return hex.join("");
|
||
}
|
||
|
||
// `rgbaToArgbHex`
|
||
// Converts an RGBA color to an ARGB Hex8 string
|
||
// Rarely used, but required for "toFilter()"
|
||
function rgbaToArgbHex(r, g, b, a) {
|
||
var hex = [pad2(convertDecimalToHex(a)), pad2(Math.round(r).toString(16)), pad2(Math.round(g).toString(16)), pad2(Math.round(b).toString(16))];
|
||
return hex.join("");
|
||
}
|
||
|
||
// `equals`
|
||
// Can be called with any tinycolor input
|
||
tinycolor.equals = function (color1, color2) {
|
||
if (!color1 || !color2) return false;
|
||
return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
|
||
};
|
||
tinycolor.random = function () {
|
||
return tinycolor.fromRatio({
|
||
r: Math.random(),
|
||
g: Math.random(),
|
||
b: Math.random()
|
||
});
|
||
};
|
||
|
||
// Modification Functions
|
||
// ----------------------
|
||
// Thanks to less.js for some of the basics here
|
||
// <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>
|
||
|
||
function _desaturate(color, amount) {
|
||
amount = amount === 0 ? 0 : amount || 10;
|
||
var hsl = tinycolor(color).toHsl();
|
||
hsl.s -= amount / 100;
|
||
hsl.s = clamp01(hsl.s);
|
||
return tinycolor(hsl);
|
||
}
|
||
function _saturate(color, amount) {
|
||
amount = amount === 0 ? 0 : amount || 10;
|
||
var hsl = tinycolor(color).toHsl();
|
||
hsl.s += amount / 100;
|
||
hsl.s = clamp01(hsl.s);
|
||
return tinycolor(hsl);
|
||
}
|
||
function _greyscale(color) {
|
||
return tinycolor(color).desaturate(100);
|
||
}
|
||
function _lighten(color, amount) {
|
||
amount = amount === 0 ? 0 : amount || 10;
|
||
var hsl = tinycolor(color).toHsl();
|
||
hsl.l += amount / 100;
|
||
hsl.l = clamp01(hsl.l);
|
||
return tinycolor(hsl);
|
||
}
|
||
function _brighten(color, amount) {
|
||
amount = amount === 0 ? 0 : amount || 10;
|
||
var rgb = tinycolor(color).toRgb();
|
||
rgb.r = Math.max(0, Math.min(255, rgb.r - Math.round(255 * -(amount / 100))));
|
||
rgb.g = Math.max(0, Math.min(255, rgb.g - Math.round(255 * -(amount / 100))));
|
||
rgb.b = Math.max(0, Math.min(255, rgb.b - Math.round(255 * -(amount / 100))));
|
||
return tinycolor(rgb);
|
||
}
|
||
function _darken(color, amount) {
|
||
amount = amount === 0 ? 0 : amount || 10;
|
||
var hsl = tinycolor(color).toHsl();
|
||
hsl.l -= amount / 100;
|
||
hsl.l = clamp01(hsl.l);
|
||
return tinycolor(hsl);
|
||
}
|
||
|
||
// Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
|
||
// Values outside of this range will be wrapped into this range.
|
||
function _spin(color, amount) {
|
||
var hsl = tinycolor(color).toHsl();
|
||
var hue = (hsl.h + amount) % 360;
|
||
hsl.h = hue < 0 ? 360 + hue : hue;
|
||
return tinycolor(hsl);
|
||
}
|
||
|
||
// Combination Functions
|
||
// ---------------------
|
||
// Thanks to jQuery xColor for some of the ideas behind these
|
||
// <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>
|
||
|
||
function _complement(color) {
|
||
var hsl = tinycolor(color).toHsl();
|
||
hsl.h = (hsl.h + 180) % 360;
|
||
return tinycolor(hsl);
|
||
}
|
||
function polyad(color, number) {
|
||
if (isNaN(number) || number <= 0) {
|
||
throw new Error("Argument to polyad must be a positive number");
|
||
}
|
||
var hsl = tinycolor(color).toHsl();
|
||
var result = [tinycolor(color)];
|
||
var step = 360 / number;
|
||
for (var i = 1; i < number; i++) {
|
||
result.push(tinycolor({
|
||
h: (hsl.h + i * step) % 360,
|
||
s: hsl.s,
|
||
l: hsl.l
|
||
}));
|
||
}
|
||
return result;
|
||
}
|
||
function _splitcomplement(color) {
|
||
var hsl = tinycolor(color).toHsl();
|
||
var h = hsl.h;
|
||
return [tinycolor(color), tinycolor({
|
||
h: (h + 72) % 360,
|
||
s: hsl.s,
|
||
l: hsl.l
|
||
}), tinycolor({
|
||
h: (h + 216) % 360,
|
||
s: hsl.s,
|
||
l: hsl.l
|
||
})];
|
||
}
|
||
function _analogous(color, results, slices) {
|
||
results = results || 6;
|
||
slices = slices || 30;
|
||
var hsl = tinycolor(color).toHsl();
|
||
var part = 360 / slices;
|
||
var ret = [tinycolor(color)];
|
||
for (hsl.h = (hsl.h - (part * results >> 1) + 720) % 360; --results;) {
|
||
hsl.h = (hsl.h + part) % 360;
|
||
ret.push(tinycolor(hsl));
|
||
}
|
||
return ret;
|
||
}
|
||
function _monochromatic(color, results) {
|
||
results = results || 6;
|
||
var hsv = tinycolor(color).toHsv();
|
||
var h = hsv.h,
|
||
s = hsv.s,
|
||
v = hsv.v;
|
||
var ret = [];
|
||
var modification = 1 / results;
|
||
while (results--) {
|
||
ret.push(tinycolor({
|
||
h: h,
|
||
s: s,
|
||
v: v
|
||
}));
|
||
v = (v + modification) % 1;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
// Utility Functions
|
||
// ---------------------
|
||
|
||
tinycolor.mix = function (color1, color2, amount) {
|
||
amount = amount === 0 ? 0 : amount || 50;
|
||
var rgb1 = tinycolor(color1).toRgb();
|
||
var rgb2 = tinycolor(color2).toRgb();
|
||
var p = amount / 100;
|
||
var rgba = {
|
||
r: (rgb2.r - rgb1.r) * p + rgb1.r,
|
||
g: (rgb2.g - rgb1.g) * p + rgb1.g,
|
||
b: (rgb2.b - rgb1.b) * p + rgb1.b,
|
||
a: (rgb2.a - rgb1.a) * p + rgb1.a
|
||
};
|
||
return tinycolor(rgba);
|
||
};
|
||
|
||
// Readability Functions
|
||
// ---------------------
|
||
// <http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef (WCAG Version 2)
|
||
|
||
// `contrast`
|
||
// Analyze the 2 colors and returns the color contrast defined by (WCAG Version 2)
|
||
tinycolor.readability = function (color1, color2) {
|
||
var c1 = tinycolor(color1);
|
||
var c2 = tinycolor(color2);
|
||
return (Math.max(c1.getLuminance(), c2.getLuminance()) + 0.05) / (Math.min(c1.getLuminance(), c2.getLuminance()) + 0.05);
|
||
};
|
||
|
||
// `isReadable`
|
||
// Ensure that foreground and background color combinations meet WCAG2 guidelines.
|
||
// The third argument is an optional Object.
|
||
// the 'level' property states 'AA' or 'AAA' - if missing or invalid, it defaults to 'AA';
|
||
// the 'size' property states 'large' or 'small' - if missing or invalid, it defaults to 'small'.
|
||
// If the entire object is absent, isReadable defaults to {level:"AA",size:"small"}.
|
||
|
||
// *Example*
|
||
// tinycolor.isReadable("#000", "#111") => false
|
||
// tinycolor.isReadable("#000", "#111",{level:"AA",size:"large"}) => false
|
||
tinycolor.isReadable = function (color1, color2, wcag2) {
|
||
var readability = tinycolor.readability(color1, color2);
|
||
var wcag2Parms, out;
|
||
out = false;
|
||
wcag2Parms = validateWCAG2Parms(wcag2);
|
||
switch (wcag2Parms.level + wcag2Parms.size) {
|
||
case "AAsmall":
|
||
case "AAAlarge":
|
||
out = readability >= 4.5;
|
||
break;
|
||
case "AAlarge":
|
||
out = readability >= 3;
|
||
break;
|
||
case "AAAsmall":
|
||
out = readability >= 7;
|
||
break;
|
||
}
|
||
return out;
|
||
};
|
||
|
||
// `mostReadable`
|
||
// Given a base color and a list of possible foreground or background
|
||
// colors for that base, returns the most readable color.
|
||
// Optionally returns Black or White if the most readable color is unreadable.
|
||
// *Example*
|
||
// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:false}).toHexString(); // "#112255"
|
||
// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:true}).toHexString(); // "#ffffff"
|
||
// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"large"}).toHexString(); // "#faf3f3"
|
||
// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"small"}).toHexString(); // "#ffffff"
|
||
tinycolor.mostReadable = function (baseColor, colorList, args) {
|
||
var bestColor = null;
|
||
var bestScore = 0;
|
||
var readability;
|
||
var includeFallbackColors, level, size;
|
||
args = args || {};
|
||
includeFallbackColors = args.includeFallbackColors;
|
||
level = args.level;
|
||
size = args.size;
|
||
for (var i = 0; i < colorList.length; i++) {
|
||
readability = tinycolor.readability(baseColor, colorList[i]);
|
||
if (readability > bestScore) {
|
||
bestScore = readability;
|
||
bestColor = tinycolor(colorList[i]);
|
||
}
|
||
}
|
||
if (tinycolor.isReadable(baseColor, bestColor, {
|
||
level: level,
|
||
size: size
|
||
}) || !includeFallbackColors) {
|
||
return bestColor;
|
||
} else {
|
||
args.includeFallbackColors = false;
|
||
return tinycolor.mostReadable(baseColor, ["#fff", "#000"], args);
|
||
}
|
||
};
|
||
|
||
// Big List of Colors
|
||
// ------------------
|
||
// <https://www.w3.org/TR/css-color-4/#named-colors>
|
||
var names = tinycolor.names = {
|
||
aliceblue: "f0f8ff",
|
||
antiquewhite: "faebd7",
|
||
aqua: "0ff",
|
||
aquamarine: "7fffd4",
|
||
azure: "f0ffff",
|
||
beige: "f5f5dc",
|
||
bisque: "ffe4c4",
|
||
black: "000",
|
||
blanchedalmond: "ffebcd",
|
||
blue: "00f",
|
||
blueviolet: "8a2be2",
|
||
brown: "a52a2a",
|
||
burlywood: "deb887",
|
||
burntsienna: "ea7e5d",
|
||
cadetblue: "5f9ea0",
|
||
chartreuse: "7fff00",
|
||
chocolate: "d2691e",
|
||
coral: "ff7f50",
|
||
cornflowerblue: "6495ed",
|
||
cornsilk: "fff8dc",
|
||
crimson: "dc143c",
|
||
cyan: "0ff",
|
||
darkblue: "00008b",
|
||
darkcyan: "008b8b",
|
||
darkgoldenrod: "b8860b",
|
||
darkgray: "a9a9a9",
|
||
darkgreen: "006400",
|
||
darkgrey: "a9a9a9",
|
||
darkkhaki: "bdb76b",
|
||
darkmagenta: "8b008b",
|
||
darkolivegreen: "556b2f",
|
||
darkorange: "ff8c00",
|
||
darkorchid: "9932cc",
|
||
darkred: "8b0000",
|
||
darksalmon: "e9967a",
|
||
darkseagreen: "8fbc8f",
|
||
darkslateblue: "483d8b",
|
||
darkslategray: "2f4f4f",
|
||
darkslategrey: "2f4f4f",
|
||
darkturquoise: "00ced1",
|
||
darkviolet: "9400d3",
|
||
deeppink: "ff1493",
|
||
deepskyblue: "00bfff",
|
||
dimgray: "696969",
|
||
dimgrey: "696969",
|
||
dodgerblue: "1e90ff",
|
||
firebrick: "b22222",
|
||
floralwhite: "fffaf0",
|
||
forestgreen: "228b22",
|
||
fuchsia: "f0f",
|
||
gainsboro: "dcdcdc",
|
||
ghostwhite: "f8f8ff",
|
||
gold: "ffd700",
|
||
goldenrod: "daa520",
|
||
gray: "808080",
|
||
green: "008000",
|
||
greenyellow: "adff2f",
|
||
grey: "808080",
|
||
honeydew: "f0fff0",
|
||
hotpink: "ff69b4",
|
||
indianred: "cd5c5c",
|
||
indigo: "4b0082",
|
||
ivory: "fffff0",
|
||
khaki: "f0e68c",
|
||
lavender: "e6e6fa",
|
||
lavenderblush: "fff0f5",
|
||
lawngreen: "7cfc00",
|
||
lemonchiffon: "fffacd",
|
||
lightblue: "add8e6",
|
||
lightcoral: "f08080",
|
||
lightcyan: "e0ffff",
|
||
lightgoldenrodyellow: "fafad2",
|
||
lightgray: "d3d3d3",
|
||
lightgreen: "90ee90",
|
||
lightgrey: "d3d3d3",
|
||
lightpink: "ffb6c1",
|
||
lightsalmon: "ffa07a",
|
||
lightseagreen: "20b2aa",
|
||
lightskyblue: "87cefa",
|
||
lightslategray: "789",
|
||
lightslategrey: "789",
|
||
lightsteelblue: "b0c4de",
|
||
lightyellow: "ffffe0",
|
||
lime: "0f0",
|
||
limegreen: "32cd32",
|
||
linen: "faf0e6",
|
||
magenta: "f0f",
|
||
maroon: "800000",
|
||
mediumaquamarine: "66cdaa",
|
||
mediumblue: "0000cd",
|
||
mediumorchid: "ba55d3",
|
||
mediumpurple: "9370db",
|
||
mediumseagreen: "3cb371",
|
||
mediumslateblue: "7b68ee",
|
||
mediumspringgreen: "00fa9a",
|
||
mediumturquoise: "48d1cc",
|
||
mediumvioletred: "c71585",
|
||
midnightblue: "191970",
|
||
mintcream: "f5fffa",
|
||
mistyrose: "ffe4e1",
|
||
moccasin: "ffe4b5",
|
||
navajowhite: "ffdead",
|
||
navy: "000080",
|
||
oldlace: "fdf5e6",
|
||
olive: "808000",
|
||
olivedrab: "6b8e23",
|
||
orange: "ffa500",
|
||
orangered: "ff4500",
|
||
orchid: "da70d6",
|
||
palegoldenrod: "eee8aa",
|
||
palegreen: "98fb98",
|
||
paleturquoise: "afeeee",
|
||
palevioletred: "db7093",
|
||
papayawhip: "ffefd5",
|
||
peachpuff: "ffdab9",
|
||
peru: "cd853f",
|
||
pink: "ffc0cb",
|
||
plum: "dda0dd",
|
||
powderblue: "b0e0e6",
|
||
purple: "800080",
|
||
rebeccapurple: "663399",
|
||
red: "f00",
|
||
rosybrown: "bc8f8f",
|
||
royalblue: "4169e1",
|
||
saddlebrown: "8b4513",
|
||
salmon: "fa8072",
|
||
sandybrown: "f4a460",
|
||
seagreen: "2e8b57",
|
||
seashell: "fff5ee",
|
||
sienna: "a0522d",
|
||
silver: "c0c0c0",
|
||
skyblue: "87ceeb",
|
||
slateblue: "6a5acd",
|
||
slategray: "708090",
|
||
slategrey: "708090",
|
||
snow: "fffafa",
|
||
springgreen: "00ff7f",
|
||
steelblue: "4682b4",
|
||
tan: "d2b48c",
|
||
teal: "008080",
|
||
thistle: "d8bfd8",
|
||
tomato: "ff6347",
|
||
turquoise: "40e0d0",
|
||
violet: "ee82ee",
|
||
wheat: "f5deb3",
|
||
white: "fff",
|
||
whitesmoke: "f5f5f5",
|
||
yellow: "ff0",
|
||
yellowgreen: "9acd32"
|
||
};
|
||
|
||
// Make it easy to access colors via `hexNames[hex]`
|
||
var hexNames = tinycolor.hexNames = flip(names);
|
||
|
||
// Utilities
|
||
// ---------
|
||
|
||
// `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
|
||
function flip(o) {
|
||
var flipped = {};
|
||
for (var i in o) {
|
||
if (o.hasOwnProperty(i)) {
|
||
flipped[o[i]] = i;
|
||
}
|
||
}
|
||
return flipped;
|
||
}
|
||
|
||
// Return a valid alpha value [0,1] with all invalid values being set to 1
|
||
function boundAlpha(a) {
|
||
a = parseFloat(a);
|
||
if (isNaN(a) || a < 0 || a > 1) {
|
||
a = 1;
|
||
}
|
||
return a;
|
||
}
|
||
|
||
// Take input from [0, n] and return it as [0, 1]
|
||
function bound01(n, max) {
|
||
if (isOnePointZero(n)) n = "100%";
|
||
var processPercent = isPercentage(n);
|
||
n = Math.min(max, Math.max(0, parseFloat(n)));
|
||
|
||
// Automatically convert percentage into number
|
||
if (processPercent) {
|
||
n = parseInt(n * max, 10) / 100;
|
||
}
|
||
|
||
// Handle floating point rounding errors
|
||
if (Math.abs(n - max) < 0.000001) {
|
||
return 1;
|
||
}
|
||
|
||
// Convert into [0, 1] range if it isn't already
|
||
return n % max / parseFloat(max);
|
||
}
|
||
|
||
// Force a number between 0 and 1
|
||
function clamp01(val) {
|
||
return Math.min(1, Math.max(0, val));
|
||
}
|
||
|
||
// Parse a base-16 hex value into a base-10 integer
|
||
function parseIntFromHex(val) {
|
||
return parseInt(val, 16);
|
||
}
|
||
|
||
// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
||
// <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
||
function isOnePointZero(n) {
|
||
return typeof n == "string" && n.indexOf(".") != -1 && parseFloat(n) === 1;
|
||
}
|
||
|
||
// Check to see if string passed in is a percentage
|
||
function isPercentage(n) {
|
||
return typeof n === "string" && n.indexOf("%") != -1;
|
||
}
|
||
|
||
// Force a hex value to have 2 characters
|
||
function pad2(c) {
|
||
return c.length == 1 ? "0" + c : "" + c;
|
||
}
|
||
|
||
// Replace a decimal with it's percentage value
|
||
function convertToPercentage(n) {
|
||
if (n <= 1) {
|
||
n = n * 100 + "%";
|
||
}
|
||
return n;
|
||
}
|
||
|
||
// Converts a decimal to a hex value
|
||
function convertDecimalToHex(d) {
|
||
return Math.round(parseFloat(d) * 255).toString(16);
|
||
}
|
||
// Converts a hex value to a decimal
|
||
function convertHexToDecimal(h) {
|
||
return parseIntFromHex(h) / 255;
|
||
}
|
||
var matchers = function () {
|
||
// <http://www.w3.org/TR/css3-values/#integers>
|
||
var CSS_INTEGER = "[-\\+]?\\d+%?";
|
||
|
||
// <http://www.w3.org/TR/css3-values/#number-value>
|
||
var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
|
||
|
||
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
|
||
var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
|
||
|
||
// Actual matching.
|
||
// Parentheses and commas are optional, but not required.
|
||
// Whitespace can take the place of commas or opening paren
|
||
var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
|
||
var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
|
||
return {
|
||
CSS_UNIT: new RegExp(CSS_UNIT),
|
||
rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
|
||
rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
|
||
hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
|
||
hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
|
||
hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
|
||
hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
|
||
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
||
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
|
||
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
||
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
|
||
};
|
||
}();
|
||
|
||
// `isValidCSSUnit`
|
||
// Take in a single string / number and check to see if it looks like a CSS unit
|
||
// (see `matchers` above for definition).
|
||
function isValidCSSUnit(color) {
|
||
return !!matchers.CSS_UNIT.exec(color);
|
||
}
|
||
|
||
// `stringInputToObject`
|
||
// Permissive string parsing. Take in a number of formats, and output an object
|
||
// based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
|
||
function stringInputToObject(color) {
|
||
color = color.replace(trimLeft, "").replace(trimRight, "").toLowerCase();
|
||
var named = false;
|
||
if (names[color]) {
|
||
color = names[color];
|
||
named = true;
|
||
} else if (color == "transparent") {
|
||
return {
|
||
r: 0,
|
||
g: 0,
|
||
b: 0,
|
||
a: 0,
|
||
format: "name"
|
||
};
|
||
}
|
||
|
||
// Try to match string input using regular expressions.
|
||
// Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
|
||
// Just return an object and let the conversion functions handle that.
|
||
// This way the result will be the same whether the tinycolor is initialized with string or object.
|
||
var match;
|
||
if (match = matchers.rgb.exec(color)) {
|
||
return {
|
||
r: match[1],
|
||
g: match[2],
|
||
b: match[3]
|
||
};
|
||
}
|
||
if (match = matchers.rgba.exec(color)) {
|
||
return {
|
||
r: match[1],
|
||
g: match[2],
|
||
b: match[3],
|
||
a: match[4]
|
||
};
|
||
}
|
||
if (match = matchers.hsl.exec(color)) {
|
||
return {
|
||
h: match[1],
|
||
s: match[2],
|
||
l: match[3]
|
||
};
|
||
}
|
||
if (match = matchers.hsla.exec(color)) {
|
||
return {
|
||
h: match[1],
|
||
s: match[2],
|
||
l: match[3],
|
||
a: match[4]
|
||
};
|
||
}
|
||
if (match = matchers.hsv.exec(color)) {
|
||
return {
|
||
h: match[1],
|
||
s: match[2],
|
||
v: match[3]
|
||
};
|
||
}
|
||
if (match = matchers.hsva.exec(color)) {
|
||
return {
|
||
h: match[1],
|
||
s: match[2],
|
||
v: match[3],
|
||
a: match[4]
|
||
};
|
||
}
|
||
if (match = matchers.hex8.exec(color)) {
|
||
return {
|
||
r: parseIntFromHex(match[1]),
|
||
g: parseIntFromHex(match[2]),
|
||
b: parseIntFromHex(match[3]),
|
||
a: convertHexToDecimal(match[4]),
|
||
format: named ? "name" : "hex8"
|
||
};
|
||
}
|
||
if (match = matchers.hex6.exec(color)) {
|
||
return {
|
||
r: parseIntFromHex(match[1]),
|
||
g: parseIntFromHex(match[2]),
|
||
b: parseIntFromHex(match[3]),
|
||
format: named ? "name" : "hex"
|
||
};
|
||
}
|
||
if (match = matchers.hex4.exec(color)) {
|
||
return {
|
||
r: parseIntFromHex(match[1] + "" + match[1]),
|
||
g: parseIntFromHex(match[2] + "" + match[2]),
|
||
b: parseIntFromHex(match[3] + "" + match[3]),
|
||
a: convertHexToDecimal(match[4] + "" + match[4]),
|
||
format: named ? "name" : "hex8"
|
||
};
|
||
}
|
||
if (match = matchers.hex3.exec(color)) {
|
||
return {
|
||
r: parseIntFromHex(match[1] + "" + match[1]),
|
||
g: parseIntFromHex(match[2] + "" + match[2]),
|
||
b: parseIntFromHex(match[3] + "" + match[3]),
|
||
format: named ? "name" : "hex"
|
||
};
|
||
}
|
||
return false;
|
||
}
|
||
function validateWCAG2Parms(parms) {
|
||
// return valid WCAG2 parms for isReadable.
|
||
// If input parms are invalid, return {"level":"AA", "size":"small"}
|
||
var level, size;
|
||
parms = parms || {
|
||
level: "AA",
|
||
size: "small"
|
||
};
|
||
level = (parms.level || "AA").toUpperCase();
|
||
size = (parms.size || "small").toLowerCase();
|
||
if (level !== "AA" && level !== "AAA") {
|
||
level = "AA";
|
||
}
|
||
if (size !== "small" && size !== "large") {
|
||
size = "small";
|
||
}
|
||
return {
|
||
level: level,
|
||
size: size
|
||
};
|
||
}
|
||
|
||
var colorStr2Hex = function colorStr2Hex(str) {
|
||
return isNaN(str) ? parseInt(tinycolor(str).toHex(), 16) : str;
|
||
};
|
||
var colorAlpha = function colorAlpha(str) {
|
||
return isNaN(str) ? tinycolor(str).getAlpha() : 1;
|
||
};
|
||
var autoColorScale = ordinal(schemePaired);
|
||
|
||
// Autoset attribute colorField by colorByAccessor property
|
||
// If an object has already a color, don't set it
|
||
// Objects can be nodes or links
|
||
function autoColorObjects(objects, colorByAccessor, colorField) {
|
||
if (!colorByAccessor || typeof colorField !== 'string') return;
|
||
objects.filter(function (obj) {
|
||
return !obj[colorField];
|
||
}).forEach(function (obj) {
|
||
obj[colorField] = autoColorScale(colorByAccessor(obj));
|
||
});
|
||
}
|
||
|
||
function getDagDepths (_ref, idAccessor) {
|
||
var nodes = _ref.nodes,
|
||
links = _ref.links;
|
||
var _ref2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
|
||
_ref2$nodeFilter = _ref2.nodeFilter,
|
||
nodeFilter = _ref2$nodeFilter === void 0 ? function () {
|
||
return true;
|
||
} : _ref2$nodeFilter,
|
||
_ref2$onLoopError = _ref2.onLoopError,
|
||
onLoopError = _ref2$onLoopError === void 0 ? function (loopIds) {
|
||
throw "Invalid DAG structure! Found cycle in node path: ".concat(loopIds.join(' -> '), ".");
|
||
} : _ref2$onLoopError;
|
||
// linked graph
|
||
var graph = {};
|
||
nodes.forEach(function (node) {
|
||
return graph[idAccessor(node)] = {
|
||
data: node,
|
||
out: [],
|
||
depth: -1,
|
||
skip: !nodeFilter(node)
|
||
};
|
||
});
|
||
links.forEach(function (_ref3) {
|
||
var source = _ref3.source,
|
||
target = _ref3.target;
|
||
var sourceId = getNodeId(source);
|
||
var targetId = getNodeId(target);
|
||
if (!graph.hasOwnProperty(sourceId)) throw "Missing source node with id: ".concat(sourceId);
|
||
if (!graph.hasOwnProperty(targetId)) throw "Missing target node with id: ".concat(targetId);
|
||
var sourceNode = graph[sourceId];
|
||
var targetNode = graph[targetId];
|
||
sourceNode.out.push(targetNode);
|
||
function getNodeId(node) {
|
||
return _typeof$1(node) === 'object' ? idAccessor(node) : node;
|
||
}
|
||
});
|
||
var foundLoops = [];
|
||
traverse(Object.values(graph));
|
||
var nodeDepths = Object.assign.apply(Object, [{}].concat(_toConsumableArray$2(Object.entries(graph).filter(function (_ref4) {
|
||
var _ref5 = _slicedToArray$3(_ref4, 2),
|
||
node = _ref5[1];
|
||
return !node.skip;
|
||
}).map(function (_ref6) {
|
||
var _ref7 = _slicedToArray$3(_ref6, 2),
|
||
id = _ref7[0],
|
||
node = _ref7[1];
|
||
return _defineProperty$1({}, id, node.depth);
|
||
}))));
|
||
return nodeDepths;
|
||
function traverse(nodes) {
|
||
var nodeStack = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
||
var currentDepth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
||
var _loop = function _loop() {
|
||
var node = nodes[i];
|
||
if (nodeStack.indexOf(node) !== -1) {
|
||
var loop = [].concat(_toConsumableArray$2(nodeStack.slice(nodeStack.indexOf(node))), [node]).map(function (d) {
|
||
return idAccessor(d.data);
|
||
});
|
||
if (!foundLoops.some(function (foundLoop) {
|
||
return foundLoop.length === loop.length && foundLoop.every(function (id, idx) {
|
||
return id === loop[idx];
|
||
});
|
||
})) {
|
||
foundLoops.push(loop);
|
||
onLoopError(loop);
|
||
}
|
||
return 1; // continue
|
||
}
|
||
if (currentDepth > node.depth) {
|
||
// Don't unnecessarily revisit chunks of the graph
|
||
node.depth = currentDepth;
|
||
traverse(node.out, [].concat(_toConsumableArray$2(nodeStack), [node]), currentDepth + (node.skip ? 0 : 1));
|
||
}
|
||
};
|
||
for (var i = 0, l = nodes.length; i < l; i++) {
|
||
if (_loop()) continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
var three$1 = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
|
||
: {
|
||
Group: three$2.Group,
|
||
Mesh: three$2.Mesh,
|
||
MeshLambertMaterial: three$2.MeshLambertMaterial,
|
||
Color: three$2.Color,
|
||
BufferGeometry: three$2.BufferGeometry,
|
||
BufferAttribute: three$2.BufferAttribute,
|
||
Matrix4: three$2.Matrix4,
|
||
Vector3: three$2.Vector3,
|
||
SphereGeometry: three$2.SphereGeometry,
|
||
CylinderGeometry: three$2.CylinderGeometry,
|
||
TubeGeometry: three$2.TubeGeometry,
|
||
ConeGeometry: three$2.ConeGeometry,
|
||
Line: three$2.Line,
|
||
LineBasicMaterial: three$2.LineBasicMaterial,
|
||
QuadraticBezierCurve3: three$2.QuadraticBezierCurve3,
|
||
CubicBezierCurve3: three$2.CubicBezierCurve3,
|
||
Box3: three$2.Box3
|
||
};
|
||
var ngraph = {
|
||
graph: graph,
|
||
forcelayout: forcelayout
|
||
};
|
||
|
||
//
|
||
|
||
var DAG_LEVEL_NODE_RATIO = 2;
|
||
|
||
// support multiple method names for backwards threejs compatibility
|
||
var setAttributeFn = new three$1.BufferGeometry().setAttribute ? 'setAttribute' : 'addAttribute';
|
||
var applyMatrix4Fn = new three$1.BufferGeometry().applyMatrix4 ? 'applyMatrix4' : 'applyMatrix';
|
||
var ForceGraph = index$2({
|
||
props: {
|
||
jsonUrl: {
|
||
onChange: function onChange(jsonUrl, state) {
|
||
var _this = this;
|
||
if (jsonUrl && !state.fetchingJson) {
|
||
// Load data asynchronously
|
||
state.fetchingJson = true;
|
||
state.onLoading();
|
||
fetch(jsonUrl).then(function (r) {
|
||
return r.json();
|
||
}).then(function (json) {
|
||
state.fetchingJson = false;
|
||
state.onFinishLoading(json);
|
||
_this.graphData(json);
|
||
});
|
||
}
|
||
},
|
||
triggerUpdate: false
|
||
},
|
||
graphData: {
|
||
"default": {
|
||
nodes: [],
|
||
links: []
|
||
},
|
||
onChange: function onChange(graphData, state) {
|
||
state.engineRunning = false; // Pause simulation immediately
|
||
}
|
||
},
|
||
numDimensions: {
|
||
"default": 3,
|
||
onChange: function onChange(numDim, state) {
|
||
var chargeForce = state.d3ForceLayout.force('charge');
|
||
// Increase repulsion on 3D mode for improved spatial separation
|
||
if (chargeForce) {
|
||
chargeForce.strength(numDim > 2 ? -60 : -30);
|
||
}
|
||
if (numDim < 3) {
|
||
eraseDimension(state.graphData.nodes, 'z');
|
||
}
|
||
if (numDim < 2) {
|
||
eraseDimension(state.graphData.nodes, 'y');
|
||
}
|
||
function eraseDimension(nodes, dim) {
|
||
nodes.forEach(function (d) {
|
||
delete d[dim]; // position
|
||
delete d["v".concat(dim)]; // velocity
|
||
});
|
||
}
|
||
}
|
||
},
|
||
dagMode: {
|
||
onChange: function onChange(dagMode, state) {
|
||
// td, bu, lr, rl, zin, zout, radialin, radialout
|
||
!dagMode && state.forceEngine === 'd3' && (state.graphData.nodes || []).forEach(function (n) {
|
||
return n.fx = n.fy = n.fz = undefined;
|
||
}); // unfix nodes when disabling dag mode
|
||
}
|
||
},
|
||
dagLevelDistance: {},
|
||
dagNodeFilter: {
|
||
"default": function _default(node) {
|
||
return true;
|
||
}
|
||
},
|
||
onDagError: {
|
||
triggerUpdate: false
|
||
},
|
||
nodeRelSize: {
|
||
"default": 4
|
||
},
|
||
// volume per val unit
|
||
nodeId: {
|
||
"default": 'id'
|
||
},
|
||
nodeVal: {
|
||
"default": 'val'
|
||
},
|
||
nodeResolution: {
|
||
"default": 8
|
||
},
|
||
// how many slice segments in the sphere's circumference
|
||
nodeColor: {
|
||
"default": 'color'
|
||
},
|
||
nodeAutoColorBy: {},
|
||
nodeOpacity: {
|
||
"default": 0.75
|
||
},
|
||
nodeVisibility: {
|
||
"default": true
|
||
},
|
||
nodeThreeObject: {},
|
||
nodeThreeObjectExtend: {
|
||
"default": false
|
||
},
|
||
nodePositionUpdate: {
|
||
triggerUpdate: false
|
||
},
|
||
// custom function to call for updating the node's position. Signature: (threeObj, { x, y, z}, node). If the function returns a truthy value, the regular node position update will not run.
|
||
linkSource: {
|
||
"default": 'source'
|
||
},
|
||
linkTarget: {
|
||
"default": 'target'
|
||
},
|
||
linkVisibility: {
|
||
"default": true
|
||
},
|
||
linkColor: {
|
||
"default": 'color'
|
||
},
|
||
linkAutoColorBy: {},
|
||
linkOpacity: {
|
||
"default": 0.2
|
||
},
|
||
linkWidth: {},
|
||
// Rounded to nearest decimal. For falsy values use dimensionless line with 1px regardless of distance.
|
||
linkResolution: {
|
||
"default": 6
|
||
},
|
||
// how many radial segments in each line tube's geometry
|
||
linkCurvature: {
|
||
"default": 0,
|
||
triggerUpdate: false
|
||
},
|
||
// line curvature radius (0: straight, 1: semi-circle)
|
||
linkCurveRotation: {
|
||
"default": 0,
|
||
triggerUpdate: false
|
||
},
|
||
// line curve rotation along the line axis (0: interection with XY plane, PI: upside down)
|
||
linkMaterial: {},
|
||
linkThreeObject: {},
|
||
linkThreeObjectExtend: {
|
||
"default": false
|
||
},
|
||
linkPositionUpdate: {
|
||
triggerUpdate: false
|
||
},
|
||
// custom function to call for updating the link's position. Signature: (threeObj, { start: { x, y, z}, end: { x, y, z }}, link). If the function returns a truthy value, the regular link position update will not run.
|
||
linkDirectionalArrowLength: {
|
||
"default": 0
|
||
},
|
||
linkDirectionalArrowColor: {},
|
||
linkDirectionalArrowRelPos: {
|
||
"default": 0.5,
|
||
triggerUpdate: false
|
||
},
|
||
// value between 0<>1 indicating the relative pos along the (exposed) line
|
||
linkDirectionalArrowResolution: {
|
||
"default": 8
|
||
},
|
||
// how many slice segments in the arrow's conic circumference
|
||
linkDirectionalParticles: {
|
||
"default": 0
|
||
},
|
||
// animate photons travelling in the link direction
|
||
linkDirectionalParticleSpeed: {
|
||
"default": 0.01,
|
||
triggerUpdate: false
|
||
},
|
||
// in link length ratio per frame
|
||
linkDirectionalParticleWidth: {
|
||
"default": 0.5
|
||
},
|
||
linkDirectionalParticleColor: {},
|
||
linkDirectionalParticleResolution: {
|
||
"default": 4
|
||
},
|
||
// how many slice segments in the particle sphere's circumference
|
||
forceEngine: {
|
||
"default": 'd3'
|
||
},
|
||
// d3 or ngraph
|
||
d3AlphaMin: {
|
||
"default": 0
|
||
},
|
||
d3AlphaDecay: {
|
||
"default": 0.0228,
|
||
triggerUpdate: false,
|
||
onChange: function onChange(alphaDecay, state) {
|
||
state.d3ForceLayout.alphaDecay(alphaDecay);
|
||
}
|
||
},
|
||
d3AlphaTarget: {
|
||
"default": 0,
|
||
triggerUpdate: false,
|
||
onChange: function onChange(alphaTarget, state) {
|
||
state.d3ForceLayout.alphaTarget(alphaTarget);
|
||
}
|
||
},
|
||
d3VelocityDecay: {
|
||
"default": 0.4,
|
||
triggerUpdate: false,
|
||
onChange: function onChange(velocityDecay, state) {
|
||
state.d3ForceLayout.velocityDecay(velocityDecay);
|
||
}
|
||
},
|
||
ngraphPhysics: {
|
||
"default": {
|
||
// defaults from https://github.com/anvaka/ngraph.physics.simulator/blob/master/index.js
|
||
timeStep: 20,
|
||
gravity: -1.2,
|
||
theta: 0.8,
|
||
springLength: 30,
|
||
springCoefficient: 0.0008,
|
||
dragCoefficient: 0.02
|
||
}
|
||
},
|
||
warmupTicks: {
|
||
"default": 0,
|
||
triggerUpdate: false
|
||
},
|
||
// how many times to tick the force engine at init before starting to render
|
||
cooldownTicks: {
|
||
"default": Infinity,
|
||
triggerUpdate: false
|
||
},
|
||
cooldownTime: {
|
||
"default": 15000,
|
||
triggerUpdate: false
|
||
},
|
||
// ms
|
||
onLoading: {
|
||
"default": function _default() {},
|
||
triggerUpdate: false
|
||
},
|
||
onFinishLoading: {
|
||
"default": function _default() {},
|
||
triggerUpdate: false
|
||
},
|
||
onUpdate: {
|
||
"default": function _default() {},
|
||
triggerUpdate: false
|
||
},
|
||
onFinishUpdate: {
|
||
"default": function _default() {},
|
||
triggerUpdate: false
|
||
},
|
||
onEngineTick: {
|
||
"default": function _default() {},
|
||
triggerUpdate: false
|
||
},
|
||
onEngineStop: {
|
||
"default": function _default() {},
|
||
triggerUpdate: false
|
||
}
|
||
},
|
||
methods: {
|
||
refresh: function refresh(state) {
|
||
state._flushObjects = true;
|
||
state._rerender();
|
||
return this;
|
||
},
|
||
// Expose d3 forces for external manipulation
|
||
d3Force: function d3Force(state, forceName, forceFn) {
|
||
if (forceFn === undefined) {
|
||
return state.d3ForceLayout.force(forceName); // Force getter
|
||
}
|
||
state.d3ForceLayout.force(forceName, forceFn); // Force setter
|
||
return this;
|
||
},
|
||
d3ReheatSimulation: function d3ReheatSimulation(state) {
|
||
state.d3ForceLayout.alpha(1);
|
||
this.resetCountdown();
|
||
return this;
|
||
},
|
||
// reset cooldown state
|
||
resetCountdown: function resetCountdown(state) {
|
||
state.cntTicks = 0;
|
||
state.startTickTime = new Date();
|
||
state.engineRunning = true;
|
||
return this;
|
||
},
|
||
tickFrame: function tickFrame(state) {
|
||
var isD3Sim = state.forceEngine !== 'ngraph';
|
||
if (state.engineRunning) {
|
||
layoutTick();
|
||
}
|
||
updateArrows();
|
||
updatePhotons();
|
||
return this;
|
||
|
||
//
|
||
|
||
function layoutTick() {
|
||
if (++state.cntTicks > state.cooldownTicks || new Date() - state.startTickTime > state.cooldownTime || isD3Sim && state.d3AlphaMin > 0 && state.d3ForceLayout.alpha() < state.d3AlphaMin) {
|
||
state.engineRunning = false; // Stop ticking graph
|
||
state.onEngineStop();
|
||
} else {
|
||
state.layout[isD3Sim ? 'tick' : 'step'](); // Tick it
|
||
state.onEngineTick();
|
||
}
|
||
var nodeThreeObjectExtendAccessor = index$1(state.nodeThreeObjectExtend);
|
||
|
||
// Update nodes position
|
||
state.graphData.nodes.forEach(function (node) {
|
||
var obj = node.__threeObj;
|
||
if (!obj) return;
|
||
var pos = isD3Sim ? node : state.layout.getNodePosition(node[state.nodeId]);
|
||
var extendedObj = nodeThreeObjectExtendAccessor(node);
|
||
if (!state.nodePositionUpdate || !state.nodePositionUpdate(extendedObj ? obj.children[0] : obj, {
|
||
x: pos.x,
|
||
y: pos.y,
|
||
z: pos.z
|
||
}, node) // pass child custom object if extending the default
|
||
|| extendedObj) {
|
||
obj.position.x = pos.x;
|
||
obj.position.y = pos.y || 0;
|
||
obj.position.z = pos.z || 0;
|
||
}
|
||
});
|
||
|
||
// Update links position
|
||
var linkWidthAccessor = index$1(state.linkWidth);
|
||
var linkCurvatureAccessor = index$1(state.linkCurvature);
|
||
var linkCurveRotationAccessor = index$1(state.linkCurveRotation);
|
||
var linkThreeObjectExtendAccessor = index$1(state.linkThreeObjectExtend);
|
||
state.graphData.links.forEach(function (link) {
|
||
var lineObj = link.__lineObj;
|
||
if (!lineObj) return;
|
||
var pos = isD3Sim ? link : state.layout.getLinkPosition(state.layout.graph.getLink(link.source, link.target).id);
|
||
var start = pos[isD3Sim ? 'source' : 'from'];
|
||
var end = pos[isD3Sim ? 'target' : 'to'];
|
||
if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
|
||
|
||
calcLinkCurve(link); // calculate link curve for all links, including custom replaced, so it can be used in directional functionality
|
||
|
||
var extendedObj = linkThreeObjectExtendAccessor(link);
|
||
if (state.linkPositionUpdate && state.linkPositionUpdate(extendedObj ? lineObj.children[1] : lineObj,
|
||
// pass child custom object if extending the default
|
||
{
|
||
start: {
|
||
x: start.x,
|
||
y: start.y,
|
||
z: start.z
|
||
},
|
||
end: {
|
||
x: end.x,
|
||
y: end.y,
|
||
z: end.z
|
||
}
|
||
}, link) && !extendedObj) {
|
||
// exit if successfully custom updated position of non-extended obj
|
||
return;
|
||
}
|
||
var curveResolution = 30; // # line segments
|
||
var curve = link.__curve;
|
||
|
||
// select default line obj if it's an extended group
|
||
var line = lineObj.children.length ? lineObj.children[0] : lineObj;
|
||
if (line.type === 'Line') {
|
||
// Update line geometry
|
||
if (!curve) {
|
||
// straight line
|
||
var linePos = line.geometry.getAttribute('position');
|
||
if (!linePos || !linePos.array || linePos.array.length !== 6) {
|
||
line.geometry[setAttributeFn]('position', linePos = new three$1.BufferAttribute(new Float32Array(2 * 3), 3));
|
||
}
|
||
linePos.array[0] = start.x;
|
||
linePos.array[1] = start.y || 0;
|
||
linePos.array[2] = start.z || 0;
|
||
linePos.array[3] = end.x;
|
||
linePos.array[4] = end.y || 0;
|
||
linePos.array[5] = end.z || 0;
|
||
linePos.needsUpdate = true;
|
||
} else {
|
||
// bezier curve line
|
||
line.geometry.setFromPoints(curve.getPoints(curveResolution));
|
||
}
|
||
line.geometry.computeBoundingSphere();
|
||
} else if (line.type === 'Mesh') {
|
||
// Update cylinder geometry
|
||
|
||
if (!curve) {
|
||
// straight tube
|
||
if (!line.geometry.type.match(/^Cylinder(Buffer)?Geometry$/)) {
|
||
var linkWidth = Math.ceil(linkWidthAccessor(link) * 10) / 10;
|
||
var r = linkWidth / 2;
|
||
var geometry = new three$1.CylinderGeometry(r, r, 1, state.linkResolution, 1, false);
|
||
geometry[applyMatrix4Fn](new three$1.Matrix4().makeTranslation(0, 1 / 2, 0));
|
||
geometry[applyMatrix4Fn](new three$1.Matrix4().makeRotationX(Math.PI / 2));
|
||
line.geometry.dispose();
|
||
line.geometry = geometry;
|
||
}
|
||
var vStart = new three$1.Vector3(start.x, start.y || 0, start.z || 0);
|
||
var vEnd = new three$1.Vector3(end.x, end.y || 0, end.z || 0);
|
||
var distance = vStart.distanceTo(vEnd);
|
||
line.position.x = vStart.x;
|
||
line.position.y = vStart.y;
|
||
line.position.z = vStart.z;
|
||
line.scale.z = distance;
|
||
line.parent.localToWorld(vEnd); // lookAt requires world coords
|
||
line.lookAt(vEnd);
|
||
} else {
|
||
// curved tube
|
||
if (!line.geometry.type.match(/^Tube(Buffer)?Geometry$/)) {
|
||
// reset object positioning
|
||
line.position.set(0, 0, 0);
|
||
line.rotation.set(0, 0, 0);
|
||
line.scale.set(1, 1, 1);
|
||
}
|
||
var _linkWidth = Math.ceil(linkWidthAccessor(link) * 10) / 10;
|
||
var _r = _linkWidth / 2;
|
||
var _geometry = new three$1.TubeGeometry(curve, curveResolution, _r, state.linkResolution, false);
|
||
line.geometry.dispose();
|
||
line.geometry = _geometry;
|
||
}
|
||
}
|
||
});
|
||
|
||
//
|
||
|
||
function calcLinkCurve(link) {
|
||
var pos = isD3Sim ? link : state.layout.getLinkPosition(state.layout.graph.getLink(link.source, link.target).id);
|
||
var start = pos[isD3Sim ? 'source' : 'from'];
|
||
var end = pos[isD3Sim ? 'target' : 'to'];
|
||
if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
|
||
|
||
var curvature = linkCurvatureAccessor(link);
|
||
if (!curvature) {
|
||
link.__curve = null; // Straight line
|
||
} else {
|
||
// bezier curve line (only for line types)
|
||
var vStart = new three$1.Vector3(start.x, start.y || 0, start.z || 0);
|
||
var vEnd = new three$1.Vector3(end.x, end.y || 0, end.z || 0);
|
||
var l = vStart.distanceTo(vEnd); // line length
|
||
|
||
var curve;
|
||
var curveRotation = linkCurveRotationAccessor(link);
|
||
if (l > 0) {
|
||
var dx = end.x - start.x;
|
||
var dy = end.y - start.y || 0;
|
||
var vLine = new three$1.Vector3().subVectors(vEnd, vStart);
|
||
var cp = vLine.clone().multiplyScalar(curvature).cross(dx !== 0 || dy !== 0 ? new three$1.Vector3(0, 0, 1) : new three$1.Vector3(0, 1, 0)) // avoid cross-product of parallel vectors (prefer Z, fallback to Y)
|
||
.applyAxisAngle(vLine.normalize(), curveRotation) // rotate along line axis according to linkCurveRotation
|
||
.add(new three$1.Vector3().addVectors(vStart, vEnd).divideScalar(2));
|
||
curve = new three$1.QuadraticBezierCurve3(vStart, cp, vEnd);
|
||
} else {
|
||
// Same point, draw a loop
|
||
var d = curvature * 70;
|
||
var endAngle = -curveRotation; // Rotate clockwise (from Z angle perspective)
|
||
var startAngle = endAngle + Math.PI / 2;
|
||
curve = new three$1.CubicBezierCurve3(vStart, new three$1.Vector3(d * Math.cos(startAngle), d * Math.sin(startAngle), 0).add(vStart), new three$1.Vector3(d * Math.cos(endAngle), d * Math.sin(endAngle), 0).add(vStart), vEnd);
|
||
}
|
||
link.__curve = curve;
|
||
}
|
||
}
|
||
}
|
||
function updateArrows() {
|
||
// update link arrow position
|
||
var arrowRelPosAccessor = index$1(state.linkDirectionalArrowRelPos);
|
||
var arrowLengthAccessor = index$1(state.linkDirectionalArrowLength);
|
||
var nodeValAccessor = index$1(state.nodeVal);
|
||
state.graphData.links.forEach(function (link) {
|
||
var arrowObj = link.__arrowObj;
|
||
if (!arrowObj) return;
|
||
var pos = isD3Sim ? link : state.layout.getLinkPosition(state.layout.graph.getLink(link.source, link.target).id);
|
||
var start = pos[isD3Sim ? 'source' : 'from'];
|
||
var end = pos[isD3Sim ? 'target' : 'to'];
|
||
if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
|
||
|
||
var startR = Math.cbrt(Math.max(0, nodeValAccessor(start) || 1)) * state.nodeRelSize;
|
||
var endR = Math.cbrt(Math.max(0, nodeValAccessor(end) || 1)) * state.nodeRelSize;
|
||
var arrowLength = arrowLengthAccessor(link);
|
||
var arrowRelPos = arrowRelPosAccessor(link);
|
||
var getPosAlongLine = link.__curve ? function (t) {
|
||
return link.__curve.getPoint(t);
|
||
} // interpolate along bezier curve
|
||
: function (t) {
|
||
// straight line: interpolate linearly
|
||
var iplt = function iplt(dim, start, end, t) {
|
||
return start[dim] + (end[dim] - start[dim]) * t || 0;
|
||
};
|
||
return {
|
||
x: iplt('x', start, end, t),
|
||
y: iplt('y', start, end, t),
|
||
z: iplt('z', start, end, t)
|
||
};
|
||
};
|
||
var lineLen = link.__curve ? link.__curve.getLength() : Math.sqrt(['x', 'y', 'z'].map(function (dim) {
|
||
return Math.pow((end[dim] || 0) - (start[dim] || 0), 2);
|
||
}).reduce(function (acc, v) {
|
||
return acc + v;
|
||
}, 0));
|
||
var posAlongLine = startR + arrowLength + (lineLen - startR - endR - arrowLength) * arrowRelPos;
|
||
var arrowHead = getPosAlongLine(posAlongLine / lineLen);
|
||
var arrowTail = getPosAlongLine((posAlongLine - arrowLength) / lineLen);
|
||
['x', 'y', 'z'].forEach(function (dim) {
|
||
return arrowObj.position[dim] = arrowTail[dim];
|
||
});
|
||
var headVec = _construct(three$1.Vector3, _toConsumableArray$2(['x', 'y', 'z'].map(function (c) {
|
||
return arrowHead[c];
|
||
})));
|
||
arrowObj.parent.localToWorld(headVec); // lookAt requires world coords
|
||
arrowObj.lookAt(headVec);
|
||
});
|
||
}
|
||
function updatePhotons() {
|
||
// update link particle positions
|
||
var particleSpeedAccessor = index$1(state.linkDirectionalParticleSpeed);
|
||
state.graphData.links.forEach(function (link) {
|
||
var cyclePhotons = link.__photonsObj && link.__photonsObj.children;
|
||
var singleHopPhotons = link.__singleHopPhotonsObj && link.__singleHopPhotonsObj.children;
|
||
if ((!singleHopPhotons || !singleHopPhotons.length) && (!cyclePhotons || !cyclePhotons.length)) return;
|
||
var pos = isD3Sim ? link : state.layout.getLinkPosition(state.layout.graph.getLink(link.source, link.target).id);
|
||
var start = pos[isD3Sim ? 'source' : 'from'];
|
||
var end = pos[isD3Sim ? 'target' : 'to'];
|
||
if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
|
||
|
||
var particleSpeed = particleSpeedAccessor(link);
|
||
var getPhotonPos = link.__curve ? function (t) {
|
||
return link.__curve.getPoint(t);
|
||
} // interpolate along bezier curve
|
||
: function (t) {
|
||
// straight line: interpolate linearly
|
||
var iplt = function iplt(dim, start, end, t) {
|
||
return start[dim] + (end[dim] - start[dim]) * t || 0;
|
||
};
|
||
return {
|
||
x: iplt('x', start, end, t),
|
||
y: iplt('y', start, end, t),
|
||
z: iplt('z', start, end, t)
|
||
};
|
||
};
|
||
var photons = [].concat(_toConsumableArray$2(cyclePhotons || []), _toConsumableArray$2(singleHopPhotons || []));
|
||
photons.forEach(function (photon, idx) {
|
||
var singleHop = photon.parent.__linkThreeObjType === 'singleHopPhotons';
|
||
if (!photon.hasOwnProperty('__progressRatio')) {
|
||
photon.__progressRatio = singleHop ? 0 : idx / cyclePhotons.length;
|
||
}
|
||
photon.__progressRatio += particleSpeed;
|
||
if (photon.__progressRatio >= 1) {
|
||
if (!singleHop) {
|
||
photon.__progressRatio = photon.__progressRatio % 1;
|
||
} else {
|
||
// remove particle
|
||
photon.parent.remove(photon);
|
||
emptyObject(photon);
|
||
return;
|
||
}
|
||
}
|
||
var photonPosRatio = photon.__progressRatio;
|
||
var pos = getPhotonPos(photonPosRatio);
|
||
['x', 'y', 'z'].forEach(function (dim) {
|
||
return photon.position[dim] = pos[dim];
|
||
});
|
||
});
|
||
});
|
||
}
|
||
},
|
||
emitParticle: function emitParticle(state, link) {
|
||
if (link && state.graphData.links.includes(link)) {
|
||
if (!link.__singleHopPhotonsObj) {
|
||
var obj = new three$1.Group();
|
||
obj.__linkThreeObjType = 'singleHopPhotons';
|
||
link.__singleHopPhotonsObj = obj;
|
||
state.graphScene.add(obj);
|
||
}
|
||
var particleWidthAccessor = index$1(state.linkDirectionalParticleWidth);
|
||
var photonR = Math.ceil(particleWidthAccessor(link) * 10) / 10 / 2;
|
||
var numSegments = state.linkDirectionalParticleResolution;
|
||
var particleGeometry = new three$1.SphereGeometry(photonR, numSegments, numSegments);
|
||
var linkColorAccessor = index$1(state.linkColor);
|
||
var particleColorAccessor = index$1(state.linkDirectionalParticleColor);
|
||
var photonColor = particleColorAccessor(link) || linkColorAccessor(link) || '#f0f0f0';
|
||
var materialColor = new three$1.Color(colorStr2Hex(photonColor));
|
||
var opacity = state.linkOpacity * 3;
|
||
var particleMaterial = new three$1.MeshLambertMaterial({
|
||
color: materialColor,
|
||
transparent: true,
|
||
opacity: opacity
|
||
});
|
||
|
||
// add a single hop particle
|
||
link.__singleHopPhotonsObj.add(new three$1.Mesh(particleGeometry, particleMaterial));
|
||
}
|
||
return this;
|
||
},
|
||
getGraphBbox: function getGraphBbox(state) {
|
||
var nodeFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
|
||
return true;
|
||
};
|
||
if (!state.initialised) return null;
|
||
|
||
// recursively collect all nested geometries bboxes
|
||
var bboxes = function getBboxes(obj) {
|
||
var bboxes = [];
|
||
if (obj.geometry) {
|
||
obj.geometry.computeBoundingBox();
|
||
var box = new three$1.Box3();
|
||
box.copy(obj.geometry.boundingBox).applyMatrix4(obj.matrixWorld);
|
||
bboxes.push(box);
|
||
}
|
||
return bboxes.concat.apply(bboxes, _toConsumableArray$2((obj.children || []).filter(function (obj) {
|
||
return !obj.hasOwnProperty('__graphObjType') || obj.__graphObjType === 'node' && nodeFilter(obj.__data);
|
||
} // exclude filtered out nodes
|
||
).map(getBboxes)));
|
||
}(state.graphScene);
|
||
if (!bboxes.length) return null;
|
||
|
||
// extract global x,y,z min/max
|
||
return Object.assign.apply(Object, _toConsumableArray$2(['x', 'y', 'z'].map(function (c) {
|
||
return _defineProperty$1({}, c, [min(bboxes, function (bb) {
|
||
return bb.min[c];
|
||
}), max(bboxes, function (bb) {
|
||
return bb.max[c];
|
||
})]);
|
||
})));
|
||
}
|
||
},
|
||
stateInit: function stateInit() {
|
||
return {
|
||
d3ForceLayout: d3ForceSimulation().force('link', d3ForceLink()).force('charge', d3ForceManyBody()).force('center', d3ForceCenter()).force('dagRadial', null).stop(),
|
||
engineRunning: false
|
||
};
|
||
},
|
||
init: function init(threeObj, state) {
|
||
// Main three object to manipulate
|
||
state.graphScene = threeObj;
|
||
},
|
||
update: function update(state, changedProps) {
|
||
var hasAnyPropChanged = function hasAnyPropChanged(propList) {
|
||
return propList.some(function (p) {
|
||
return changedProps.hasOwnProperty(p);
|
||
});
|
||
};
|
||
state.engineRunning = false; // pause simulation
|
||
state.onUpdate();
|
||
if (state.nodeAutoColorBy !== null && hasAnyPropChanged(['nodeAutoColorBy', 'graphData', 'nodeColor'])) {
|
||
// Auto add color to uncolored nodes
|
||
autoColorObjects(state.graphData.nodes, index$1(state.nodeAutoColorBy), state.nodeColor);
|
||
}
|
||
if (state.linkAutoColorBy !== null && hasAnyPropChanged(['linkAutoColorBy', 'graphData', 'linkColor'])) {
|
||
// Auto add color to uncolored links
|
||
autoColorObjects(state.graphData.links, index$1(state.linkAutoColorBy), state.linkColor);
|
||
}
|
||
|
||
// Digest nodes WebGL objects
|
||
if (state._flushObjects || hasAnyPropChanged(['graphData', 'nodeThreeObject', 'nodeThreeObjectExtend', 'nodeVal', 'nodeColor', 'nodeVisibility', 'nodeRelSize', 'nodeResolution', 'nodeOpacity'])) {
|
||
var customObjectAccessor = index$1(state.nodeThreeObject);
|
||
var customObjectExtendAccessor = index$1(state.nodeThreeObjectExtend);
|
||
var valAccessor = index$1(state.nodeVal);
|
||
var colorAccessor = index$1(state.nodeColor);
|
||
var visibilityAccessor = index$1(state.nodeVisibility);
|
||
var sphereGeometries = {}; // indexed by node value
|
||
var sphereMaterials = {}; // indexed by color
|
||
|
||
threeDigest(state.graphData.nodes.filter(visibilityAccessor), state.graphScene, {
|
||
purge: state._flushObjects || hasAnyPropChanged([
|
||
// recreate objects if any of these props have changed
|
||
'nodeThreeObject', 'nodeThreeObjectExtend']),
|
||
objFilter: function objFilter(obj) {
|
||
return obj.__graphObjType === 'node';
|
||
},
|
||
createObj: function createObj(node) {
|
||
var customObj = customObjectAccessor(node);
|
||
var extendObj = customObjectExtendAccessor(node);
|
||
if (customObj && state.nodeThreeObject === customObj) {
|
||
// clone object if it's a shared object among all nodes
|
||
customObj = customObj.clone();
|
||
}
|
||
var obj;
|
||
if (customObj && !extendObj) {
|
||
obj = customObj;
|
||
} else {
|
||
// Add default object (sphere mesh)
|
||
obj = new three$1.Mesh();
|
||
obj.__graphDefaultObj = true;
|
||
if (customObj && extendObj) {
|
||
obj.add(customObj); // extend default with custom
|
||
}
|
||
}
|
||
obj.__graphObjType = 'node'; // Add object type
|
||
|
||
return obj;
|
||
},
|
||
updateObj: function updateObj(obj, node) {
|
||
if (obj.__graphDefaultObj) {
|
||
// bypass internal updates for custom node objects
|
||
var val = valAccessor(node) || 1;
|
||
var radius = Math.cbrt(val) * state.nodeRelSize;
|
||
var numSegments = state.nodeResolution;
|
||
if (!obj.geometry.type.match(/^Sphere(Buffer)?Geometry$/) || obj.geometry.parameters.radius !== radius || obj.geometry.parameters.widthSegments !== numSegments) {
|
||
if (!sphereGeometries.hasOwnProperty(val)) {
|
||
sphereGeometries[val] = new three$1.SphereGeometry(radius, numSegments, numSegments);
|
||
}
|
||
obj.geometry.dispose();
|
||
obj.geometry = sphereGeometries[val];
|
||
}
|
||
var color = colorAccessor(node);
|
||
var materialColor = new three$1.Color(colorStr2Hex(color || '#ffffaa'));
|
||
var opacity = state.nodeOpacity * colorAlpha(color);
|
||
if (obj.material.type !== 'MeshLambertMaterial' || !obj.material.color.equals(materialColor) || obj.material.opacity !== opacity) {
|
||
if (!sphereMaterials.hasOwnProperty(color)) {
|
||
sphereMaterials[color] = new three$1.MeshLambertMaterial({
|
||
color: materialColor,
|
||
transparent: true,
|
||
opacity: opacity
|
||
});
|
||
}
|
||
obj.material.dispose();
|
||
obj.material = sphereMaterials[color];
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Digest links WebGL objects
|
||
if (state._flushObjects || hasAnyPropChanged(['graphData', 'linkThreeObject', 'linkThreeObjectExtend', 'linkMaterial', 'linkColor', 'linkWidth', 'linkVisibility', 'linkResolution', 'linkOpacity', 'linkDirectionalArrowLength', 'linkDirectionalArrowColor', 'linkDirectionalArrowResolution', 'linkDirectionalParticles', 'linkDirectionalParticleWidth', 'linkDirectionalParticleColor', 'linkDirectionalParticleResolution'])) {
|
||
var _customObjectAccessor = index$1(state.linkThreeObject);
|
||
var _customObjectExtendAccessor = index$1(state.linkThreeObjectExtend);
|
||
var customMaterialAccessor = index$1(state.linkMaterial);
|
||
var _visibilityAccessor = index$1(state.linkVisibility);
|
||
var _colorAccessor = index$1(state.linkColor);
|
||
var widthAccessor = index$1(state.linkWidth);
|
||
var cylinderGeometries = {}; // indexed by link width
|
||
var lambertLineMaterials = {}; // for cylinder objects, indexed by link color
|
||
var basicLineMaterials = {}; // for line objects, indexed by link color
|
||
|
||
var visibleLinks = state.graphData.links.filter(_visibilityAccessor);
|
||
|
||
// lines digest cycle
|
||
threeDigest(visibleLinks, state.graphScene, {
|
||
objBindAttr: '__lineObj',
|
||
purge: state._flushObjects || hasAnyPropChanged([
|
||
// recreate objects if any of these props have changed
|
||
'linkThreeObject', 'linkThreeObjectExtend', 'linkWidth']),
|
||
objFilter: function objFilter(obj) {
|
||
return obj.__graphObjType === 'link';
|
||
},
|
||
exitObj: function exitObj(obj) {
|
||
// remove trailing single photons
|
||
var singlePhotonsObj = obj.__data && obj.__data.__singleHopPhotonsObj;
|
||
if (singlePhotonsObj) {
|
||
singlePhotonsObj.parent.remove(singlePhotonsObj);
|
||
emptyObject(singlePhotonsObj);
|
||
delete obj.__data.__singleHopPhotonsObj;
|
||
}
|
||
},
|
||
createObj: function createObj(link) {
|
||
var customObj = _customObjectAccessor(link);
|
||
var extendObj = _customObjectExtendAccessor(link);
|
||
if (customObj && state.linkThreeObject === customObj) {
|
||
// clone object if it's a shared object among all links
|
||
customObj = customObj.clone();
|
||
}
|
||
var defaultObj;
|
||
if (!customObj || extendObj) {
|
||
// construct default line obj
|
||
var useCylinder = !!widthAccessor(link);
|
||
if (useCylinder) {
|
||
defaultObj = new three$1.Mesh();
|
||
} else {
|
||
// Use plain line (constant width)
|
||
var lineGeometry = new three$1.BufferGeometry();
|
||
lineGeometry[setAttributeFn]('position', new three$1.BufferAttribute(new Float32Array(2 * 3), 3));
|
||
defaultObj = new three$1.Line(lineGeometry);
|
||
}
|
||
}
|
||
var obj;
|
||
if (!customObj) {
|
||
obj = defaultObj;
|
||
obj.__graphDefaultObj = true;
|
||
} else {
|
||
if (!extendObj) {
|
||
// use custom object
|
||
obj = customObj;
|
||
} else {
|
||
// extend default with custom in a group
|
||
obj = new three$1.Group();
|
||
obj.__graphDefaultObj = true;
|
||
obj.add(defaultObj);
|
||
obj.add(customObj);
|
||
}
|
||
}
|
||
obj.renderOrder = 10; // Prevent visual glitches of dark lines on top of nodes by rendering them last
|
||
|
||
obj.__graphObjType = 'link'; // Add object type
|
||
|
||
return obj;
|
||
},
|
||
updateObj: function updateObj(updObj, link) {
|
||
if (updObj.__graphDefaultObj) {
|
||
// bypass internal updates for custom link objects
|
||
// select default object if it's an extended group
|
||
var obj = updObj.children.length ? updObj.children[0] : updObj;
|
||
var linkWidth = Math.ceil(widthAccessor(link) * 10) / 10;
|
||
var useCylinder = !!linkWidth;
|
||
if (useCylinder) {
|
||
var r = linkWidth / 2;
|
||
var numSegments = state.linkResolution;
|
||
if (!obj.geometry.type.match(/^Cylinder(Buffer)?Geometry$/) || obj.geometry.parameters.radiusTop !== r || obj.geometry.parameters.radialSegments !== numSegments) {
|
||
if (!cylinderGeometries.hasOwnProperty(linkWidth)) {
|
||
var geometry = new three$1.CylinderGeometry(r, r, 1, numSegments, 1, false);
|
||
geometry[applyMatrix4Fn](new three$1.Matrix4().makeTranslation(0, 1 / 2, 0));
|
||
geometry[applyMatrix4Fn](new three$1.Matrix4().makeRotationX(Math.PI / 2));
|
||
cylinderGeometries[linkWidth] = geometry;
|
||
}
|
||
obj.geometry.dispose();
|
||
obj.geometry = cylinderGeometries[linkWidth];
|
||
}
|
||
}
|
||
var customMaterial = customMaterialAccessor(link);
|
||
if (customMaterial) {
|
||
obj.material = customMaterial;
|
||
} else {
|
||
var color = _colorAccessor(link);
|
||
var materialColor = new three$1.Color(colorStr2Hex(color || '#f0f0f0'));
|
||
var opacity = state.linkOpacity * colorAlpha(color);
|
||
var materialType = useCylinder ? 'MeshLambertMaterial' : 'LineBasicMaterial';
|
||
if (obj.material.type !== materialType || !obj.material.color.equals(materialColor) || obj.material.opacity !== opacity) {
|
||
var lineMaterials = useCylinder ? lambertLineMaterials : basicLineMaterials;
|
||
if (!lineMaterials.hasOwnProperty(color)) {
|
||
lineMaterials[color] = new three$1[materialType]({
|
||
color: materialColor,
|
||
transparent: opacity < 1,
|
||
opacity: opacity,
|
||
depthWrite: opacity >= 1 // Prevent transparency issues
|
||
});
|
||
}
|
||
obj.material.dispose();
|
||
obj.material = lineMaterials[color];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// Arrows digest cycle
|
||
if (state.linkDirectionalArrowLength || changedProps.hasOwnProperty('linkDirectionalArrowLength')) {
|
||
var arrowLengthAccessor = index$1(state.linkDirectionalArrowLength);
|
||
var arrowColorAccessor = index$1(state.linkDirectionalArrowColor);
|
||
threeDigest(visibleLinks.filter(arrowLengthAccessor), state.graphScene, {
|
||
objBindAttr: '__arrowObj',
|
||
objFilter: function objFilter(obj) {
|
||
return obj.__linkThreeObjType === 'arrow';
|
||
},
|
||
createObj: function createObj() {
|
||
var obj = new three$1.Mesh(undefined, new three$1.MeshLambertMaterial({
|
||
transparent: true
|
||
}));
|
||
obj.__linkThreeObjType = 'arrow'; // Add object type
|
||
|
||
return obj;
|
||
},
|
||
updateObj: function updateObj(obj, link) {
|
||
var arrowLength = arrowLengthAccessor(link);
|
||
var numSegments = state.linkDirectionalArrowResolution;
|
||
if (!obj.geometry.type.match(/^Cone(Buffer)?Geometry$/) || obj.geometry.parameters.height !== arrowLength || obj.geometry.parameters.radialSegments !== numSegments) {
|
||
var coneGeometry = new three$1.ConeGeometry(arrowLength * 0.25, arrowLength, numSegments);
|
||
// Correct orientation
|
||
coneGeometry.translate(0, arrowLength / 2, 0);
|
||
coneGeometry.rotateX(Math.PI / 2);
|
||
obj.geometry.dispose();
|
||
obj.geometry = coneGeometry;
|
||
}
|
||
var arrowColor = arrowColorAccessor(link) || _colorAccessor(link) || '#f0f0f0';
|
||
obj.material.color = new three$1.Color(colorStr2Hex(arrowColor));
|
||
obj.material.opacity = state.linkOpacity * 3 * colorAlpha(arrowColor);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Photon particles digest cycle
|
||
if (state.linkDirectionalParticles || changedProps.hasOwnProperty('linkDirectionalParticles')) {
|
||
var particlesAccessor = index$1(state.linkDirectionalParticles);
|
||
var particleWidthAccessor = index$1(state.linkDirectionalParticleWidth);
|
||
var particleColorAccessor = index$1(state.linkDirectionalParticleColor);
|
||
var particleMaterials = {}; // indexed by link color
|
||
var particleGeometries = {}; // indexed by particle width
|
||
|
||
threeDigest(visibleLinks.filter(particlesAccessor), state.graphScene, {
|
||
objBindAttr: '__photonsObj',
|
||
objFilter: function objFilter(obj) {
|
||
return obj.__linkThreeObjType === 'photons';
|
||
},
|
||
createObj: function createObj() {
|
||
var obj = new three$1.Group();
|
||
obj.__linkThreeObjType = 'photons'; // Add object type
|
||
|
||
return obj;
|
||
},
|
||
updateObj: function updateObj(obj, link) {
|
||
var numPhotons = Math.round(Math.abs(particlesAccessor(link)));
|
||
var curPhoton = !!obj.children.length && obj.children[0];
|
||
var photonR = Math.ceil(particleWidthAccessor(link) * 10) / 10 / 2;
|
||
var numSegments = state.linkDirectionalParticleResolution;
|
||
var particleGeometry;
|
||
if (curPhoton && curPhoton.geometry.parameters.radius === photonR && curPhoton.geometry.parameters.widthSegments === numSegments) {
|
||
particleGeometry = curPhoton.geometry;
|
||
} else {
|
||
if (!particleGeometries.hasOwnProperty(photonR)) {
|
||
particleGeometries[photonR] = new three$1.SphereGeometry(photonR, numSegments, numSegments);
|
||
}
|
||
particleGeometry = particleGeometries[photonR];
|
||
curPhoton && curPhoton.geometry.dispose();
|
||
}
|
||
var photonColor = particleColorAccessor(link) || _colorAccessor(link) || '#f0f0f0';
|
||
var materialColor = new three$1.Color(colorStr2Hex(photonColor));
|
||
var opacity = state.linkOpacity * 3;
|
||
var particleMaterial;
|
||
if (curPhoton && curPhoton.material.color.equals(materialColor) && curPhoton.material.opacity === opacity) {
|
||
particleMaterial = curPhoton.material;
|
||
} else {
|
||
if (!particleMaterials.hasOwnProperty(photonColor)) {
|
||
particleMaterials[photonColor] = new three$1.MeshLambertMaterial({
|
||
color: materialColor,
|
||
transparent: true,
|
||
opacity: opacity
|
||
});
|
||
}
|
||
particleMaterial = particleMaterials[photonColor];
|
||
curPhoton && curPhoton.material.dispose();
|
||
}
|
||
|
||
// digest cycle for each photon
|
||
threeDigest(_toConsumableArray$2(new Array(numPhotons)).map(function (_, idx) {
|
||
return {
|
||
idx: idx
|
||
};
|
||
}), obj, {
|
||
idAccessor: function idAccessor(d) {
|
||
return d.idx;
|
||
},
|
||
createObj: function createObj() {
|
||
return new three$1.Mesh(particleGeometry, particleMaterial);
|
||
},
|
||
updateObj: function updateObj(obj) {
|
||
obj.geometry = particleGeometry;
|
||
obj.material = particleMaterial;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
}
|
||
state._flushObjects = false; // reset objects refresh flag
|
||
|
||
// simulation engine
|
||
if (hasAnyPropChanged(['graphData', 'nodeId', 'linkSource', 'linkTarget', 'numDimensions', 'forceEngine', 'dagMode', 'dagNodeFilter', 'dagLevelDistance'])) {
|
||
state.engineRunning = false; // Pause simulation
|
||
|
||
// parse links
|
||
state.graphData.links.forEach(function (link) {
|
||
link.source = link[state.linkSource];
|
||
link.target = link[state.linkTarget];
|
||
});
|
||
|
||
// Feed data to force-directed layout
|
||
var isD3Sim = state.forceEngine !== 'ngraph';
|
||
var layout;
|
||
if (isD3Sim) {
|
||
// D3-force
|
||
(layout = state.d3ForceLayout).stop().alpha(1) // re-heat the simulation
|
||
.numDimensions(state.numDimensions).nodes(state.graphData.nodes);
|
||
|
||
// add links (if link force is still active)
|
||
var linkForce = state.d3ForceLayout.force('link');
|
||
if (linkForce) {
|
||
linkForce.id(function (d) {
|
||
return d[state.nodeId];
|
||
}).links(state.graphData.links);
|
||
}
|
||
|
||
// setup dag force constraints
|
||
var nodeDepths = state.dagMode && getDagDepths(state.graphData, function (node) {
|
||
return node[state.nodeId];
|
||
}, {
|
||
nodeFilter: state.dagNodeFilter,
|
||
onLoopError: state.onDagError || undefined
|
||
});
|
||
var maxDepth = Math.max.apply(Math, _toConsumableArray$2(Object.values(nodeDepths || [])));
|
||
var dagLevelDistance = state.dagLevelDistance || state.graphData.nodes.length / (maxDepth || 1) * DAG_LEVEL_NODE_RATIO * (['radialin', 'radialout'].indexOf(state.dagMode) !== -1 ? 0.7 : 1);
|
||
|
||
// Fix nodes to x,y,z for dag mode
|
||
if (state.dagMode) {
|
||
var getFFn = function getFFn(fix, invert) {
|
||
return function (node) {
|
||
return !fix ? undefined : (nodeDepths[node[state.nodeId]] - maxDepth / 2) * dagLevelDistance * (invert ? -1 : 1);
|
||
};
|
||
};
|
||
var fxFn = getFFn(['lr', 'rl'].indexOf(state.dagMode) !== -1, state.dagMode === 'rl');
|
||
var fyFn = getFFn(['td', 'bu'].indexOf(state.dagMode) !== -1, state.dagMode === 'td');
|
||
var fzFn = getFFn(['zin', 'zout'].indexOf(state.dagMode) !== -1, state.dagMode === 'zout');
|
||
state.graphData.nodes.filter(state.dagNodeFilter).forEach(function (node) {
|
||
node.fx = fxFn(node);
|
||
node.fy = fyFn(node);
|
||
node.fz = fzFn(node);
|
||
});
|
||
}
|
||
|
||
// Use radial force for radial dags
|
||
state.d3ForceLayout.force('dagRadial', ['radialin', 'radialout'].indexOf(state.dagMode) !== -1 ? d3ForceRadial(function (node) {
|
||
var nodeDepth = nodeDepths[node[state.nodeId]] || -1;
|
||
return (state.dagMode === 'radialin' ? maxDepth - nodeDepth : nodeDepth) * dagLevelDistance;
|
||
}).strength(function (node) {
|
||
return state.dagNodeFilter(node) ? 1 : 0;
|
||
}) : null);
|
||
} else {
|
||
// ngraph
|
||
var _graph = ngraph.graph();
|
||
state.graphData.nodes.forEach(function (node) {
|
||
_graph.addNode(node[state.nodeId]);
|
||
});
|
||
state.graphData.links.forEach(function (link) {
|
||
_graph.addLink(link.source, link.target);
|
||
});
|
||
layout = ngraph.forcelayout(_graph, _objectSpread2$1({
|
||
dimensions: state.numDimensions
|
||
}, state.ngraphPhysics));
|
||
layout.graph = _graph; // Attach graph reference to layout
|
||
}
|
||
for (var i = 0; i < state.warmupTicks && !(isD3Sim && state.d3AlphaMin > 0 && state.d3ForceLayout.alpha() < state.d3AlphaMin); i++) {
|
||
layout[isD3Sim ? "tick" : "step"]();
|
||
} // Initial ticks before starting to render
|
||
|
||
state.layout = layout;
|
||
this.resetCountdown();
|
||
}
|
||
state.engineRunning = true; // resume simulation
|
||
|
||
state.onFinishUpdate();
|
||
}
|
||
});
|
||
|
||
function fromKapsule (kapsule) {
|
||
var baseClass = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Object;
|
||
var initKapsuleWithSelf = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
||
var FromKapsule = /*#__PURE__*/function (_baseClass) {
|
||
_inherits(FromKapsule, _baseClass);
|
||
function FromKapsule() {
|
||
var _this;
|
||
_classCallCheck$1(this, FromKapsule);
|
||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
||
args[_key] = arguments[_key];
|
||
}
|
||
_this = _callSuper(this, FromKapsule, [].concat(args));
|
||
_this.__kapsuleInstance = kapsule().apply(void 0, [].concat(_toConsumableArray$2(initKapsuleWithSelf ? [_assertThisInitialized(_this)] : []), args));
|
||
return _this;
|
||
}
|
||
return _createClass$1(FromKapsule);
|
||
}(baseClass); // attach kapsule props/methods to class prototype
|
||
Object.keys(kapsule()).forEach(function (m) {
|
||
return FromKapsule.prototype[m] = function () {
|
||
var _this$__kapsuleInstan;
|
||
var returnVal = (_this$__kapsuleInstan = this.__kapsuleInstance)[m].apply(_this$__kapsuleInstan, arguments);
|
||
return returnVal === this.__kapsuleInstance ? this // chain based on this class, not the kapsule obj
|
||
: returnVal;
|
||
};
|
||
});
|
||
return FromKapsule;
|
||
}
|
||
|
||
var three = window.THREE ? window.THREE : {
|
||
Group: three$2.Group
|
||
}; // Prefer consumption from global THREE, if exists
|
||
var threeForcegraph = fromKapsule(ForceGraph, three.Group, true);
|
||
|
||
return threeForcegraph;
|
||
|
||
}));
|
||
//# sourceMappingURL=three-forcegraph.js.map
|
||
export default TableCsv
|