UNPKG

d3-force-straighten

Version:

A D3 force plugin that straightens chains of connected nodes into rigid lines.

287 lines (273 loc) 9.82 kB
// Version 1.0.0 d3-force-straighten - https://github.com/vasturiano/d3-force-straighten (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3 = global.d3 || {})); })(this, (function (exports) { 'use strict'; function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) { t && (r = t); var n = 0, F = function () {}; return { s: F, n: function () { return n >= r.length ? { done: true } : { done: false, value: r[n++] }; }, e: function (r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = true, u = false; return { s: function () { t = t.call(r); }, n: function () { var r = t.next(); return a = r.done, r; }, e: function (r) { u = true, o = r; }, f: function () { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } 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 _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function straighten () { var links = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; var nDim, nodes = [], id = function id(node) { return node.index; }, strength = 1, considerAlpha = false; var segments = []; function force(alpha) { var k = strength * (considerAlpha ? alpha : 1); segments.forEach(function (nodes) { var lr = linearRegression(nodes, nDim); nodes.forEach(function (node) { var target = closestPointOnLine(lr, node, nDim); node.vx += (target.x - node.x) * k; node.vy += (target.y - node.y) * k; }); }); } function initialize() { var nodesById = new Map(nodes.map(function (d, i) { return [id(d, i, nodes), d]; })); var linksByNode = new Map(); links.forEach(function (link) { if (_typeof(link.source) !== "object") link.source = nodesById.get(link.source) || link.source; if (_typeof(link.target) !== "object") link.target = nodesById.get(link.target) || link.target; [link.source, link.target].forEach(function (node) { !linksByNode.has(node) && linksByNode.set(node, []); linksByNode.get(node).push(link); }); }); segments.length = 0; // clear segments var visited = new Set(); var _traverseSingleLinks = function traverseSingleLinks(node, dir) { var accNodes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Set(); accNodes.add(node); var links = linksByNode.get(node); if (new Set(links.map(function (l) { return [l.source, l.target].flat(); })).size !== 2) return [node]; var nextNode = links.filter(function (l) { return !visited.has(l); }).map(function (link) { visited.add(link); return link[dir]; }).filter(function (node) { return !accNodes.has(node); })[0] || null; return [node].concat(_toConsumableArray(nextNode ? _traverseSingleLinks(nextNode, dir, accNodes) : [])); }; links.forEach(function (link) { if (visited.has(link)) return; visited.add(link); var segment = [].concat(_toConsumableArray(_traverseSingleLinks(link.source, 'source').reverse()), _toConsumableArray(_traverseSingleLinks(link.target, 'target'))); segment.length > 2 && new Set(segment).size === segment.length && segments.push(segment); }); } force.initialize = function (initNodes) { nodes = initNodes; for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } nDim = args.find(function (arg) { return [1, 2, 3].includes(arg); }) || 2; initialize(); }; force.links = function (_) { return arguments.length ? (links = _, initialize(), force) : links; }; force.id = function (_) { return arguments.length ? (id = _, force) : id; }; force.strength = function (_) { return arguments.length ? (strength = _, force) : strength; }; force.considerAlpha = function (_) { return arguments.length ? (considerAlpha = _, force) : considerAlpha; }; return force; } // function linearRegression(pnts) { var nDim = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2; if (pnts.length < 2) { throw new Error("At least two points are required for linear regression."); } var dims = ['x', nDim > 1 ? 'y' : null, nDim > 2 ? 'z' : null].filter(function (d) { return d; }); // Compute mean point var mean = Object.fromEntries(dims.map(function (dim) { return [dim, pnts.map(function (p) { return p[dim]; }).reduce(function (agg, n) { return agg + n; }, 0) / pnts.length]; })); // Compute covariance matrix var Sxx = 0, Sxy = 0, Sxz = 0; var Syy = 0, Syz = 0, Szz = 0; var _iterator = _createForOfIteratorHelper(pnts), _step; try { var _loop2 = function _loop2() { var p = _step.value; var d = Object.fromEntries(dims.map(function (dim) { return [dim, p[dim] - mean[dim]]; })); Sxx += d.x * d.x; if (nDim > 1) { Sxy += d.x * d.y; Syy += d.y * d.y; } if (nDim > 2) { Sxz += d.x * d.z; Syz += d.y * d.z; Szz += d.z * d.z; } }; for (_iterator.s(); !(_step = _iterator.n()).done;) { _loop2(); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } var covarianceMatrix = [[Sxx, Sxy, Sxz], [Sxy, Syy, Syz], [Sxz, Syz, Szz]]; function powerIteration(A) { var numIter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100; var b = [1, 1, 1]; var _loop = function _loop() { var Ab = [A[0][0] * b[0] + A[0][1] * b[1] + A[0][2] * b[2], A[1][0] * b[0] + A[1][1] * b[1] + A[1][2] * b[2], A[2][0] * b[0] + A[2][1] * b[1] + A[2][2] * b[2]]; var norm = Math.hypot.apply(Math, Ab); b = Ab.map(function (val) { return val / norm; }); }; for (var k = 0; k < numIter; k++) { _loop(); } return b; } var direction = powerIteration(covarianceMatrix); return { point: mean, // point on the line (center) direction: Object.fromEntries(dims.map(function (dim, idx) { return [dim, direction[idx]]; })) // direction vector (normalized) }; } function closestPointOnLine(line, point, nDim) { var dims = ['x', nDim > 1 ? 'y' : null, nDim > 2 ? 'z' : null].filter(function (d) { return d; }); var p = line.point; var d = line.direction; var qMinusP = Object.fromEntries(dims.map(function (dim) { return [dim, point[dim] - p[dim]]; })); // Dot product (q - p) · d var dotNumerator = dims.reduce(function (agg, dim) { return agg + qMinusP[dim] * d[dim]; }, 0); // Dot product d · d (magnitude squared) var dotDenominator = dims.reduce(function (agg, dim) { return agg + d[dim] * d[dim]; }, 0); // Scalar projection t var t = dotDenominator === 0 ? 0 : dotNumerator / dotDenominator; // Closest point = p + t * d return Object.fromEntries(dims.map(function (dim) { return [dim, p[dim] + t * d[dim]]; })); } exports.forceStraighten = straighten; })); //# sourceMappingURL=d3-force-straighten.js.map