@antv/layout
Version:
graph layout algorithm
1,356 lines (1,320 loc) • 941 kB
JavaScript
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
const proxyMarker = Symbol("Comlink.proxy");
const createEndpoint = Symbol("Comlink.endpoint");
const releaseProxy = Symbol("Comlink.releaseProxy");
const finalizer = Symbol("Comlink.finalizer");
const throwMarker = Symbol("Comlink.thrown");
const isObject$1 = (val) => (typeof val === "object" && val !== null) || typeof val === "function";
/**
* Internal transfer handle to handle objects marked to proxy.
*/
const proxyTransferHandler = {
canHandle: (val) => isObject$1(val) && val[proxyMarker],
serialize(obj) {
const { port1, port2 } = new MessageChannel();
expose(obj, port1);
return [port2, [port2]];
},
deserialize(port) {
port.start();
return wrap(port);
},
};
/**
* Internal transfer handler to handle thrown exceptions.
*/
const throwTransferHandler = {
canHandle: (value) => isObject$1(value) && throwMarker in value,
serialize({ value }) {
let serialized;
if (value instanceof Error) {
serialized = {
isError: true,
value: {
message: value.message,
name: value.name,
stack: value.stack,
},
};
}
else {
serialized = { isError: false, value };
}
return [serialized, []];
},
deserialize(serialized) {
if (serialized.isError) {
throw Object.assign(new Error(serialized.value.message), serialized.value);
}
throw serialized.value;
},
};
/**
* Allows customizing the serialization of certain values.
*/
const transferHandlers = new Map([
["proxy", proxyTransferHandler],
["throw", throwTransferHandler],
]);
function isAllowedOrigin(allowedOrigins, origin) {
for (const allowedOrigin of allowedOrigins) {
if (origin === allowedOrigin || allowedOrigin === "*") {
return true;
}
if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {
return true;
}
}
return false;
}
function expose(obj, ep = globalThis, allowedOrigins = ["*"]) {
ep.addEventListener("message", function callback(ev) {
if (!ev || !ev.data) {
return;
}
if (!isAllowedOrigin(allowedOrigins, ev.origin)) {
console.warn(`Invalid origin '${ev.origin}' for comlink proxy`);
return;
}
const { id, type, path } = Object.assign({ path: [] }, ev.data);
const argumentList = (ev.data.argumentList || []).map(fromWireValue);
let returnValue;
try {
const parent = path.slice(0, -1).reduce((obj, prop) => obj[prop], obj);
const rawValue = path.reduce((obj, prop) => obj[prop], obj);
switch (type) {
case "GET" /* MessageType.GET */:
{
returnValue = rawValue;
}
break;
case "SET" /* MessageType.SET */:
{
parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
returnValue = true;
}
break;
case "APPLY" /* MessageType.APPLY */:
{
returnValue = rawValue.apply(parent, argumentList);
}
break;
case "CONSTRUCT" /* MessageType.CONSTRUCT */:
{
const value = new rawValue(...argumentList);
returnValue = proxy(value);
}
break;
case "ENDPOINT" /* MessageType.ENDPOINT */:
{
const { port1, port2 } = new MessageChannel();
expose(obj, port2);
returnValue = transfer(port1, [port1]);
}
break;
case "RELEASE" /* MessageType.RELEASE */:
{
returnValue = undefined;
}
break;
default:
return;
}
}
catch (value) {
returnValue = { value, [throwMarker]: 0 };
}
Promise.resolve(returnValue)
.catch((value) => {
return { value, [throwMarker]: 0 };
})
.then((returnValue) => {
const [wireValue, transferables] = toWireValue(returnValue);
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
if (type === "RELEASE" /* MessageType.RELEASE */) {
// detach and deactive after sending release response above.
ep.removeEventListener("message", callback);
closeEndPoint(ep);
if (finalizer in obj && typeof obj[finalizer] === "function") {
obj[finalizer]();
}
}
})
.catch((error) => {
// Send Serialization Error To Caller
const [wireValue, transferables] = toWireValue({
value: new TypeError("Unserializable return value"),
[throwMarker]: 0,
});
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
});
});
if (ep.start) {
ep.start();
}
}
function isMessagePort(endpoint) {
return endpoint.constructor.name === "MessagePort";
}
function closeEndPoint(endpoint) {
if (isMessagePort(endpoint))
endpoint.close();
}
function wrap(ep, target) {
const pendingListeners = new Map();
ep.addEventListener("message", function handleMessage(ev) {
const { data } = ev;
if (!data || !data.id) {
return;
}
const resolver = pendingListeners.get(data.id);
if (!resolver) {
return;
}
try {
resolver(data);
}
finally {
pendingListeners.delete(data.id);
}
});
return createProxy(ep, pendingListeners, [], target);
}
function throwIfProxyReleased(isReleased) {
if (isReleased) {
throw new Error("Proxy has been released and is not useable");
}
}
function releaseEndpoint(ep) {
return requestResponseMessage(ep, new Map(), {
type: "RELEASE" /* MessageType.RELEASE */,
}).then(() => {
closeEndPoint(ep);
});
}
const proxyCounter = new WeakMap();
const proxyFinalizers = "FinalizationRegistry" in globalThis &&
new FinalizationRegistry((ep) => {
const newCount = (proxyCounter.get(ep) || 0) - 1;
proxyCounter.set(ep, newCount);
if (newCount === 0) {
releaseEndpoint(ep);
}
});
function registerProxy(proxy, ep) {
const newCount = (proxyCounter.get(ep) || 0) + 1;
proxyCounter.set(ep, newCount);
if (proxyFinalizers) {
proxyFinalizers.register(proxy, ep, proxy);
}
}
function unregisterProxy(proxy) {
if (proxyFinalizers) {
proxyFinalizers.unregister(proxy);
}
}
function createProxy(ep, pendingListeners, path = [], target = function () { }) {
let isProxyReleased = false;
const proxy = new Proxy(target, {
get(_target, prop) {
throwIfProxyReleased(isProxyReleased);
if (prop === releaseProxy) {
return () => {
unregisterProxy(proxy);
releaseEndpoint(ep);
pendingListeners.clear();
isProxyReleased = true;
};
}
if (prop === "then") {
if (path.length === 0) {
return { then: () => proxy };
}
const r = requestResponseMessage(ep, pendingListeners, {
type: "GET" /* MessageType.GET */,
path: path.map((p) => p.toString()),
}).then(fromWireValue);
return r.then.bind(r);
}
return createProxy(ep, pendingListeners, [...path, prop]);
},
set(_target, prop, rawValue) {
throwIfProxyReleased(isProxyReleased);
// FIXME: ES6 Proxy Handler `set` methods are supposed to return a
// boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯
const [value, transferables] = toWireValue(rawValue);
return requestResponseMessage(ep, pendingListeners, {
type: "SET" /* MessageType.SET */,
path: [...path, prop].map((p) => p.toString()),
value,
}, transferables).then(fromWireValue);
},
apply(_target, _thisArg, rawArgumentList) {
throwIfProxyReleased(isProxyReleased);
const last = path[path.length - 1];
if (last === createEndpoint) {
return requestResponseMessage(ep, pendingListeners, {
type: "ENDPOINT" /* MessageType.ENDPOINT */,
}).then(fromWireValue);
}
// We just pretend that `bind()` didn’t happen.
if (last === "bind") {
return createProxy(ep, pendingListeners, path.slice(0, -1));
}
const [argumentList, transferables] = processArguments(rawArgumentList);
return requestResponseMessage(ep, pendingListeners, {
type: "APPLY" /* MessageType.APPLY */,
path: path.map((p) => p.toString()),
argumentList,
}, transferables).then(fromWireValue);
},
construct(_target, rawArgumentList) {
throwIfProxyReleased(isProxyReleased);
const [argumentList, transferables] = processArguments(rawArgumentList);
return requestResponseMessage(ep, pendingListeners, {
type: "CONSTRUCT" /* MessageType.CONSTRUCT */,
path: path.map((p) => p.toString()),
argumentList,
}, transferables).then(fromWireValue);
},
});
registerProxy(proxy, ep);
return proxy;
}
function myFlat(arr) {
return Array.prototype.concat.apply([], arr);
}
function processArguments(argumentList) {
const processed = argumentList.map(toWireValue);
return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
}
const transferCache = new WeakMap();
function transfer(obj, transfers) {
transferCache.set(obj, transfers);
return obj;
}
function proxy(obj) {
return Object.assign(obj, { [proxyMarker]: true });
}
function toWireValue(value) {
for (const [name, handler] of transferHandlers) {
if (handler.canHandle(value)) {
const [serializedValue, transferables] = handler.serialize(value);
return [
{
type: "HANDLER" /* WireValueType.HANDLER */,
name,
value: serializedValue,
},
transferables,
];
}
}
return [
{
type: "RAW" /* WireValueType.RAW */,
value,
},
transferCache.get(value) || [],
];
}
function fromWireValue(value) {
switch (value.type) {
case "HANDLER" /* WireValueType.HANDLER */:
return transferHandlers.get(value.name).deserialize(value.value);
case "RAW" /* WireValueType.RAW */:
return value.value;
}
}
function requestResponseMessage(ep, pendingListeners, msg, transfers) {
return new Promise((resolve) => {
const id = generateUUID();
pendingListeners.set(id, resolve);
if (ep.start) {
ep.start();
}
ep.postMessage(Object.assign({ id }, msg), transfers);
});
}
function generateUUID() {
return new Array(4)
.fill(0)
.map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16))
.join("-");
}
var isArrayLike = function (value) {
/**
* isArrayLike([1, 2, 3]) => true
* isArrayLike(document.body.children) => true
* isArrayLike('abc') => true
* isArrayLike(Function) => false
*/
return value !== null && typeof value !== 'function' && isFinite(value.length);
};
/**
* 判断值是否为函数
* @return 是否为函数
*/
function isFunction(value) {
return typeof value === 'function';
}
/**
* 判断值是否为 null 或 undefined
* @return 是否为 null 或 undefined
*/
function isNil(value) {
return value === null || value === undefined;
}
/**
* 判断值是否为数组
* @return 是否为数组
*/
function isArray(value) {
return Array.isArray(value);
}
var isObject = (function (value) {
/**
* isObject({}) => true
* isObject([1, 2, 3]) => true
* isObject(Function) => true
* isObject(null) => false
*/
var type = typeof value;
return (value !== null && type === 'object') || type === 'function';
});
function each$1(elements, func) {
if (!elements) {
return;
}
var rst;
if (isArray(elements)) {
for (var i = 0, len = elements.length; i < len; i++) {
rst = func(elements[i], i);
if (rst === false) {
break;
}
}
}
else if (isObject(elements)) {
for (var k in elements) {
if (elements.hasOwnProperty(k)) {
rst = func(elements[k], k);
if (rst === false) {
break;
}
}
}
}
}
var isObjectLike = function (value) {
/**
* isObjectLike({}) => true
* isObjectLike([1, 2, 3]) => true
* isObjectLike(Function) => false
* isObjectLike(null) => false
*/
return typeof value === 'object' && value !== null;
};
var toString$2 = {}.toString;
var isType = function (value, type) { return toString$2.call(value) === '[object ' + type + ']'; };
var isPlainObject = function (value) {
/**
* isObjectLike(new Foo) => false
* isObjectLike([1, 2, 3]) => false
* isObjectLike({ x: 0, y: 0 }) => true
* isObjectLike(Object.create(null)) => true
*/
if (!isObjectLike(value) || !isType(value, 'Object')) {
return false;
}
if (Object.getPrototypeOf(value) === null) {
return true;
}
var proto = value;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(value) === proto;
};
/**
* 判断值是否为字符串
* @return 是否为字符串
*/
function isString(value) {
return typeof value === 'string';
}
/**
* 判断值是否为数字
* @return 是否为数字
*/
function isNumber(value) {
return typeof value === 'number';
}
var toString$1 = {}.toString;
var getType = function (value) {
return toString$1
.call(value)
.replace(/^\[object /, '')
.replace(/]$/, '');
};
/**
* 是否是布尔类型
*
* @param {Object} value 测试的值
* @return {Boolean}
*/
var isBoolean = function (value) {
return isType(value, 'Boolean');
};
var objectProto = Object.prototype;
var isPrototype = function (value) {
var Ctor = value && value.constructor;
var proto = (typeof Ctor === 'function' && Ctor.prototype) || objectProto;
return value === proto;
};
var clone = function (obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
var rst;
if (isArray(obj)) {
rst = [];
for (var i = 0, l = obj.length; i < l; i++) {
if (typeof obj[i] === 'object' && obj[i] != null) {
rst[i] = clone(obj[i]);
}
else {
rst[i] = obj[i];
}
}
}
else {
rst = {};
for (var k in obj) {
if (typeof obj[k] === 'object' && obj[k] != null) {
rst[k] = clone(obj[k]);
}
else {
rst[k] = obj[k];
}
}
}
return rst;
};
var MAX_MIX_LEVEL = 5;
function hasOwn(object, property) {
if (Object.hasOwn) {
return Object.hasOwn(object, property);
}
if (object == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
return Object.prototype.hasOwnProperty.call(Object(object), property);
}
function _deepMix(dist, src, level, maxLevel) {
level = level || 0;
maxLevel = maxLevel || MAX_MIX_LEVEL;
for (var key in src) {
if (hasOwn(src, key)) {
var value = src[key];
if (value !== null && isPlainObject(value)) {
if (!isPlainObject(dist[key])) {
dist[key] = {};
}
if (level < maxLevel) {
_deepMix(dist[key], value, level + 1, maxLevel);
}
else {
dist[key] = src[key];
}
}
else if (isArray(value)) {
dist[key] = [];
dist[key] = dist[key].concat(value);
}
else if (value !== undefined) {
dist[key] = value;
}
}
}
}
// todo 重写
var deepMix = function (rst) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
for (var i = 0; i < args.length; i += 1) {
_deepMix(rst, args[i]);
}
return rst;
};
var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
function isEmpty(value) {
/**
* isEmpty(null) => true
* isEmpty() => true
* isEmpty(true) => true
* isEmpty(1) => true
* isEmpty([1, 2, 3]) => false
* isEmpty('abc') => false
* isEmpty({ a: 1 }) => false
*/
if (isNil(value)) {
return true;
}
if (isArrayLike(value)) {
return !value.length;
}
var type = getType(value);
if (type === 'Map' || type === 'Set') {
return !value.size;
}
if (isPrototype(value)) {
return !Object.keys(value).length;
}
for (var key in value) {
if (hasOwnProperty$1.call(value, key)) {
return false;
}
}
return true;
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
var pick = (function (object, keys) {
if (object === null || !isPlainObject(object)) {
return {};
}
var result = {};
each$1(keys, function (key) {
if (hasOwnProperty.call(object, key)) {
result[key] = object[key];
}
});
return result;
});
/**
* Return the layout result for a graph with zero or one node.
* @param graph original graph
* @param center the layout center
* @returns layout result
*/
function applySingleNodeLayout(model, center, dimensions = 2) {
const n = model.nodeCount();
if (n === 1) {
const first = model.firstNode();
first.x = center[0];
first.y = center[1];
if (dimensions === 3) {
first.z = center[2] || 0;
}
}
}
const e={abs:Math.abs,ceil:Math.ceil,floor:Math.floor,max:Math.max,min:Math.min,round:Math.round,sqrt:Math.sqrt,pow:Math.pow};class n extends Error{constructor(e,t,n){super(e),this.position=t,this.token=n,this.name="ExpressionError";}}var r;!function(e){e[e.STRING=0]="STRING",e[e.NUMBER=1]="NUMBER",e[e.BOOLEAN=2]="BOOLEAN",e[e.NULL=3]="NULL",e[e.IDENTIFIER=4]="IDENTIFIER",e[e.OPERATOR=5]="OPERATOR",e[e.FUNCTION=6]="FUNCTION",e[e.DOT=7]="DOT",e[e.BRACKET_LEFT=8]="BRACKET_LEFT",e[e.BRACKET_RIGHT=9]="BRACKET_RIGHT",e[e.PAREN_LEFT=10]="PAREN_LEFT",e[e.PAREN_RIGHT=11]="PAREN_RIGHT",e[e.COMMA=12]="COMMA",e[e.QUESTION=13]="QUESTION",e[e.COLON=14]="COLON",e[e.DOLLAR=15]="DOLLAR";}(r||(r={}));const o=new Set([32,9,10,13]),a$2=new Set([43,45,42,47,37,33,38,124,61,60,62]),s=new Map([["true",r.BOOLEAN],["false",r.BOOLEAN],["null",r.NULL]]),i=new Map([["===",true],["!==",true],["<=",true],[">=",true],["&&",true],["||",true],["+",true],["-",true],["*",true],["/",true],["%",true],["!",true],["<",true],[">",true]]),u=new Map([[46,r.DOT],[91,r.BRACKET_LEFT],[93,r.BRACKET_RIGHT],[40,r.PAREN_LEFT],[41,r.PAREN_RIGHT],[44,r.COMMA],[63,r.QUESTION],[58,r.COLON],[36,r.DOLLAR]]),c$2=new Map;for(const[e,t]of u.entries())c$2.set(e,{type:t,value:String.fromCharCode(e)});function p(e){return e>=48&&e<=57}function l(e){return e>=97&&e<=122||e>=65&&e<=90||95===e}function f(e){return l(e)||p(e)}function E(e){return a$2.has(e)}var h;!function(e){e[e.Program=0]="Program",e[e.Literal=1]="Literal",e[e.Identifier=2]="Identifier",e[e.MemberExpression=3]="MemberExpression",e[e.CallExpression=4]="CallExpression",e[e.BinaryExpression=5]="BinaryExpression",e[e.UnaryExpression=6]="UnaryExpression",e[e.ConditionalExpression=7]="ConditionalExpression";}(h||(h={}));const d=new Map([["||",2],["&&",3],["===",4],["!==",4],[">",5],[">=",5],["<",5],["<=",5],["+",6],["-",6],["*",7],["/",7],["%",7],["!",8]]),R={type:h.Literal,value:null},T={type:h.Literal,value:true},w={type:h.Literal,value:false},y$4=e=>{let t=0;const o=e.length,a=()=>t>=o?null:e[t],s=()=>e[t++],i=e=>{const t=a();return null!==t&&t.type===e},u=e=>e.type===r.OPERATOR?d.get(e.value)||-1:e.type===r.DOT||e.type===r.BRACKET_LEFT?9:e.type===r.QUESTION?1:-1,c=e=>{let o,u;if(s().type===r.DOT){if(!i(r.IDENTIFIER)){const e=a();throw new n("Expected property name",t,e?e.value:"<end of input>")}const e=s();o={type:h.Identifier,name:e.value},u=false;}else {if(o=l(0),!i(r.BRACKET_RIGHT)){const e=a();throw new n("Expected closing bracket",t,e?e.value:"<end of input>")}s(),u=true;}return {type:h.MemberExpression,object:e,property:o,computed:u}},p=()=>{const e=a();if(!e)throw new n("Unexpected end of input",t,"<end of input>");if(e.type===r.OPERATOR&&("!"===e.value||"-"===e.value)){s();const t=p();return {type:h.UnaryExpression,operator:e.value,argument:t,prefix:true}}switch(e.type){case r.NUMBER:return s(),{type:h.Literal,value:Number(e.value)};case r.STRING:return s(),{type:h.Literal,value:e.value};case r.BOOLEAN:return s(),"true"===e.value?T:w;case r.NULL:return s(),R;case r.IDENTIFIER:return s(),{type:h.Identifier,name:e.value};case r.FUNCTION:return (()=>{const e=s(),o=[];if(!i(r.PAREN_LEFT)){const e=a();throw new n("Expected opening parenthesis after function name",t,e?e.value:"<end of input>")}for(s();;){if(i(r.PAREN_RIGHT)){s();break}if(!a()){const e=a();throw new n("Expected closing parenthesis",t,e?e.value:"<end of input>")}if(o.length>0){if(!i(r.COMMA)){const e=a();throw new n("Expected comma between function arguments",t,e?e.value:"<end of input>")}s();}const e=l(0);o.push(e);}return {type:h.CallExpression,callee:{type:h.Identifier,name:e.value},arguments:o}})();case r.PAREN_LEFT:{s();const e=l(0);if(!i(r.PAREN_RIGHT)){const e=a();throw new n("Expected closing parenthesis",t,e?e.value:"<end of input>")}return s(),e}default:throw new n(`Unexpected token: ${e.type}`,t,e.value)}},l=(f=0)=>{let E=p();for(;t<o;){const o=e[t],p=u(o);if(p<=f)break;if(o.type!==r.QUESTION)if(o.type!==r.OPERATOR){if(o.type!==r.DOT&&o.type!==r.BRACKET_LEFT)break;E=c(E);}else {s();const e=l(p);E={type:h.BinaryExpression,operator:o.value,left:E,right:e};}else {s();const e=l(0);if(!i(r.COLON)){const e=a();throw new n("Expected : in conditional expression",t,e?e.value:"<end of input>")}s();const o=l(0);E={type:h.ConditionalExpression,test:E,consequent:e,alternate:o};}}return E},f=l();return {type:h.Program,body:f}},O=(e,t,r)=>{let o=t;r&&(o={...t,context:{...t.context,...r}});const a=e=>{switch(e.type){case h.Literal:return (e=>e.value)(e);case h.Identifier:return (e=>{if(!(e.name in o.context))throw new n(`Undefined variable: ${e.name}`);return o.context[e.name]})(e);case h.MemberExpression:return (e=>{const t=a(e.object);if(null==t)throw new n("Cannot access property of null or undefined");return t[e.computed?a(e.property):e.property.name]})(e);case h.CallExpression:return (e=>{const t=o.functions[e.callee.name];if(!t)throw new n(`Undefined function: ${e.callee.name}`);return t(...e.arguments.map((e=>a(e))))})(e);case h.BinaryExpression:return (e=>{if("&&"===e.operator){const t=a(e.left);return t?a(e.right):t}if("||"===e.operator){return a(e.left)||a(e.right)}const t=a(e.left),r=a(e.right);switch(e.operator){case "+":return t+r;case "-":return t-r;case "*":return t*r;case "/":return t/r;case "%":return t%r;case "===":return t===r;case "!==":return t!==r;case ">":return t>r;case ">=":return t>=r;case "<":return t<r;case "<=":return t<=r;default:throw new n(`Unknown operator: ${e.operator}`)}})(e);case h.UnaryExpression:return (e=>{const t=a(e.argument);if(e.prefix)switch(e.operator){case "!":return !t;case "-":if("number"!=typeof t)throw new n(`Cannot apply unary - to non-number: ${t}`);return -t;default:throw new n(`Unknown operator: ${e.operator}`)}throw new n(`Postfix operators are not supported: ${e.operator}`)})(e);case h.ConditionalExpression:return (e=>{const t=a(e.test);return a(t?e.consequent:e.alternate)})(e);default:throw new n(`Evaluation error: Unsupported node type: ${e.type}`)}};return a(e.body)};function A(t){const a=(e=>{const t=e,a=t.length,u=new Array(Math.ceil(a/3));let h=0,d=0;function R(e){const o=d+1;d++;let s="",i=false;for(;d<a;){const n=t.charCodeAt(d);if(n===e)return i||(s=t.substring(o,d)),d++,{type:r.STRING,value:s};92===n?(i||(s=t.substring(o,d),i=true),d++,s+=t[d]):i&&(s+=t[d]),d++;}throw new n(`Unterminated string starting with ${String.fromCharCode(e)}`,d,t.substring(Math.max(0,d-10),d))}function T(){const e=d;for(45===t.charCodeAt(d)&&d++;d<a&&p(t.charCodeAt(d));)d++;if(d<a&&46===t.charCodeAt(d))for(d++;d<a&&p(t.charCodeAt(d));)d++;const n=t.slice(e,d);return {type:r.NUMBER,value:n}}function w(){d++;const e=d;if(d<a&&l(t.charCodeAt(d)))for(d++;d<a&&f(t.charCodeAt(d));)d++;const n=t.slice(e,d);return {type:r.FUNCTION,value:n}}function y(){const e=d++;for(;d<a&&f(t.charCodeAt(d));)d++;const n=t.slice(e,d),o=s.get(n);return o?{type:o,value:n}:{type:r.IDENTIFIER,value:n}}function O(){if(d+2<a){const e=t.substring(d,d+3);if(i.has(e))return d+=3,{type:r.OPERATOR,value:e}}if(d+1<a){const e=t.substring(d,d+2);if(i.has(e))return d+=2,{type:r.OPERATOR,value:e}}const e=t[d];if(i.has(e))return d++,{type:r.OPERATOR,value:e};throw new n(`Unknown operator at position ${d}: ${t.substring(d,d+1)}`,d,t.substring(Math.max(0,d-10),d))}for(;d<a;){const e=t.charCodeAt(d);if(A=e,o.has(A)){d++;continue}const r=c$2.get(e);if(r)u[h++]=r,d++;else if(34!==e&&39!==e)if(p(e)||45===e&&d+1<a&&p(t.charCodeAt(d+1)))u[h++]=T();else if(64!==e)if(l(e))u[h++]=y();else {if(!E(e))throw new n(`Unexpected character: ${t[d]}`,d,t.substring(Math.max(0,d-10),d));u[h++]=O();}else u[h++]=w();else u[h++]=R(e);}var A;return h===u.length?u:u.slice(0,h)})(t),u=y$4(a),h=((e={},t={})=>({context:e,functions:t}))({},e);return (e={})=>O(u,h,e)}function N(e,t={}){return A(e)(t)}
/**
* Evaluate an expression if (and only if) it's a valid string expression.
* - Returns `undefined` when `expression` is not a string, empty, or invalid.
*
* @example
* evaluateExpression('x + y', { x: 10, y: 20 }) // 30
*/
function evaluateExpression(expression, context) {
if (typeof expression !== 'string')
return undefined;
const source = expression.trim();
if (!source)
return undefined;
try {
A(source);
return N(source, context);
}
catch (_a) {
return undefined;
}
}
function parseSize(size) {
if (!size)
return [0, 0, 0];
if (isNumber(size))
return [size, size, size];
else if (Array.isArray(size) && size.length === 0)
return [0, 0, 0];
const [x, y = x, z = x] = size;
return [x, y, z];
}
function isSize(value) {
if (isNumber(value))
return true;
if (Array.isArray(value)) {
return value.every((item) => isNumber(item));
}
return false;
}
/**
* Format a value into a callable function when it is a string expression.
* - `string` => `(context) => evaluateExpression(string, context)`
* - `function` => returned as-is
* - other => returned as-is
*/
function formatFn(value, argNames) {
if (typeof value === 'function')
return value;
if (typeof value === 'string') {
const expr = value;
return (...argv) => {
const ctx = {};
for (let i = 0; i < argNames.length; i++) {
ctx[argNames[i]] = argv[i];
}
return evaluateExpression(expr, ctx);
};
}
return () => value;
}
/**
* Format value with multiple types into a function that returns a number
* @param value The value to be formatted
* @param defaultValue The default value when value is invalid
* @returns A function that returns a number
*/
function formatNumberFn(value, defaultValue, type = 'node') {
// If value is undefined, return default value function
if (isNil(value)) {
return () => defaultValue;
}
// If value is an expression, return a function that evaluates the expression
if (isString(value)) {
const numberFn = formatFn(value, [type]);
return (d) => {
const evaluated = numberFn(d);
if (isNumber(evaluated))
return evaluated;
return defaultValue;
};
}
// If value is a function, return it directly
if (isFunction(value)) {
return value;
}
// If value is a number, return a function that returns this number
if (isNumber(value)) {
return () => value;
}
// For other cases (undefined or invalid values), return default value function
return () => defaultValue;
}
/**
* Format size config with multiple types into a function that returns a size
* @param value The value to be formatted
* @param defaultValue The default value when value is invalid
* @param resultIsNumber Whether to return a number (max of width/height) or size array
* @returns A function that returns a size
*/
function formatSizeFn(value, defaultValue = 10, type = 'node') {
// If value is undefined, return default value function
if (isNil(value)) {
return () => defaultValue;
}
// If value is an expression, return a function that evaluates the expression
if (isString(value)) {
const sizeFn = formatFn(value, [type]);
return (d) => {
const evaluated = sizeFn(d);
if (isSize(evaluated))
return evaluated;
return defaultValue;
};
}
// If value is a function, return it directly
if (isFunction(value)) {
return value;
}
// If value is a number, return a function that returns this number
if (isNumber(value)) {
return () => value;
}
// If value is an array, return max or the array itself
if (Array.isArray(value)) {
return () => value;
}
return () => defaultValue;
}
/**
* Format nodeSize and nodeSpacing into a function that returns the total size
* @param nodeSize The size of the node
* @param nodeSpacing The spacing around the node
* @param defaultNodeSize The default node size when value is invalid
* @param defaultNodeSpacing The default node spacing when value is invalid
* @returns A function that returns the total size (node size + spacing)
*/
const formatNodeSizeFn = (nodeSize, nodeSpacing, defaultNodeSize = 10, defaultNodeSpacing = 0) => {
const nodeSpacingFunc = formatSizeFn(nodeSpacing, defaultNodeSpacing);
const nodeSizeFunc = formatSizeFn(nodeSize, defaultNodeSize);
return (d) => {
const [sizeW, sizeH, sizeD] = parseSize(nodeSizeFunc(d));
const [spacingW, spacingH, spacingD] = parseSize(nodeSpacingFunc(d));
return [sizeW + spacingW, sizeH + spacingH, sizeD + spacingD];
};
};
/**
* Get the adjacency list of the graph model.
*/
const getAdjList = (model, directed) => {
const n = model.nodeCount();
const adjList = Array.from({ length: n }, () => []);
// map node with index
const nodeMap = {};
let idx = 0;
model.forEachNode((node) => {
nodeMap[node.id] = idx++;
});
model.forEachEdge((e) => {
const s = nodeMap[e.source];
const t = nodeMap[e.target];
if (s == null || t == null)
return;
adjList[s].push(t);
adjList[t].push(s);
});
return adjList;
};
/**
* scale matrix
* @param matrix [ [], [], [] ]
* @param ratio
*/
const scaleMatrix = (matrix, ratio) => {
const n = matrix.length;
const result = new Array(n);
for (let i = 0; i < n; i++) {
const row = matrix[i];
const m = row.length;
const newRow = new Array(m);
for (let j = 0; j < m; j++) {
newRow[j] = row[j] * ratio;
}
result[i] = newRow;
}
return result;
};
/**
* Use Johnson + Dijkstra to compute APSP for sparse graph.
* Fully compatible with floydWarshall(adjMatrix).
*/
function johnson(adjList) {
const n = adjList.length;
// Step 1: add a dummy node q connected to all nodes with weight 0
new Array(n).fill(0);
// Bellman-Ford to compute potentials h(v)
// 因为权重全是 1,无负边,可直接跳过 BF,h 全 0 即可
// Step 2: reweight edges
// 因为 h(u)=h(v)=0,reweight 后仍然是 1,省略 reweight 过程
// Step 3: run Dijkstra from each node
const distAll = Array.from({ length: n }, () => new Array(n).fill(Infinity));
for (let s = 0; s < n; s++) {
distAll[s] = dijkstra(adjList, s);
}
return distAll;
}
/**
* Dijkstra algorithm to find shortest paths from source to all nodes.
*/
function dijkstra(adjList, source) {
const n = adjList.length;
const dist = new Array(n).fill(Infinity);
dist[source] = 0;
// Minimal binary heap
const heap = new MinHeap();
heap.push([0, source]); // [distance, node]
while (!heap.empty()) {
const [d, u] = heap.pop();
if (d !== dist[u])
continue;
const neighbors = adjList[u];
for (let i = 0; i < neighbors.length; i++) {
const v = neighbors[i];
const nd = d + 1;
if (nd < dist[v]) {
dist[v] = nd;
heap.push([nd, v]);
}
}
}
return dist;
}
class MinHeap {
constructor() {
this.data = [];
}
push(item) {
this.data.push(item);
this.bubbleUp(this.data.length - 1);
}
pop() {
const top = this.data[0];
const end = this.data.pop();
if (this.data.length > 0) {
this.data[0] = end;
this.bubbleDown(0);
}
return top;
}
empty() {
return this.data.length === 0;
}
bubbleUp(pos) {
const data = this.data;
while (pos > 0) {
const parent = (pos - 1) >> 1;
if (data[parent][0] <= data[pos][0])
break;
[data[parent], data[pos]] = [data[pos], data[parent]];
pos = parent;
}
}
bubbleDown(pos) {
const data = this.data;
const length = data.length;
while (true) {
const left = pos * 2 + 1;
const right = pos * 2 + 2;
let min = pos;
if (left < length && data[left][0] < data[min][0])
min = left;
if (right < length && data[right][0] < data[min][0])
min = right;
if (min === pos)
break;
[data[pos], data[min]] = [data[min], data[pos]];
pos = min;
}
}
}
/**
* Merge objects, but undefined values in source objects will not override existing values
* @param target - The target object
* @param sources - Source objects to merge
* @returns A new merged object
*
* @example
* assignDefined({ a: 1, b: 2 }, { b: undefined, c: 3 })
* // Returns: { a: 1, b: 2, c: 3 }
*/
function assignDefined(target, ...sources) {
sources.forEach((source) => {
if (source) {
Object.keys(source).forEach((key) => {
const value = source[key];
if (value !== undefined) {
target[key] = value;
}
});
}
});
return target;
}
/**
* 通用排序核心函数
*/
function sort$1(model, compareFn) {
const nodes = model.nodes();
nodes.sort(compareFn);
model.setNodeOrder(nodes);
return model;
}
function orderByDegree(model, order = 'desc') {
return sort$1(model, (nodeA, nodeB) => {
const degreeA = model.degree(nodeA.id);
const degreeB = model.degree(nodeB.id);
if (order === 'asc') {
return degreeA - degreeB; // ascending order
}
return degreeB - degreeA; // descending order
});
}
/**
* 按 ID 排序
*/
function orderById(model) {
return sort$1(model, (nodeA, nodeB) => {
const idA = nodeA.id;
const idB = nodeB.id;
if (typeof idA === 'number' && typeof idB === 'number') {
return idA - idB;
}
return String(idA).localeCompare(String(idB));
});
}
/**
* 按自定义比较函数排序
*/
function orderBySorter(model, sorter) {
return sort$1(model, (nodeA, nodeB) => {
const a = model.originalNode(nodeA.id);
const b = model.originalNode(nodeB.id);
return sorter(a, b);
});
}
/**
* Order nodes according to graph topology
*/
function orderByTopology(model, directed = false) {
const n = model.nodeCount();
if (n === 0)
return model;
const nodes = model.nodes();
const orderedNodes = [nodes[0]];
const pickFlags = {};
pickFlags[nodes[0].id] = true;
let k = 0;
let i = 0;
model.forEachNode((node) => {
if (i !== 0) {
const currentDegree = model.degree(node.id, 'both');
const nextDegree = i < n - 1 ? model.degree(nodes[i + 1].id, 'both') : 0;
const currentNodeId = orderedNodes[k].id;
const isNeighbor = model
.neighbors(currentNodeId, 'both')
.includes(node.id);
if ((i === n - 1 || currentDegree !== nextDegree || isNeighbor) &&
!pickFlags[node.id]) {
orderedNodes.push(node);
pickFlags[node.id] = true;
k++;
}
else {
const children = directed
? model.successors(currentNodeId)
: model.neighbors(currentNodeId);
let foundChild = false;
for (let j = 0; j < children.length; j++) {
const childId = children[j];
const child = model.node(childId);
if (child &&
model.degree(childId) === model.degree(node.id) &&
!pickFlags[childId]) {
orderedNodes.push(child);
pickFlags[childId] = true;
foundChild = true;
break;
}
}
let ii = 0;
while (!foundChild) {
if (!pickFlags[nodes[ii].id]) {
orderedNodes.push(nodes[ii]);
pickFlags[nodes[ii].id] = true;
foundChild = true;
}
ii++;
if (ii === n) {
break;
}
}
}
}
i++;
});
// Update model with ordered nodes
model.setNodeOrder(orderedNodes);
return model;
}
function parsePoint(point) {
var _a;
return [point.x, point.y, (_a = point.z) !== null && _a !== void 0 ? _a : 0];
}
/**
* Viewport configuration such as width, height and center point.
*/
const normalizeViewport = (options) => {
const { width, height, center } = options;
const normalizedWidth = width !== null && width !== void 0 ? width : (typeof window !== 'undefined' ? window.innerWidth : 0);
const normalizedHeight = height !== null && height !== void 0 ? height : (typeof window !== 'undefined' ? window.innerHeight : 0);
const centerPoint = center !== null && center !== void 0 ? center : [normalizedWidth / 2, normalizedHeight / 2];
return {
width: normalizedWidth,
height: normalizedHeight,
center: centerPoint,
};
};
class GraphLib {
constructor(data, options = {}) {
this.edgeIdCounter = new Map();
this.nodeMap = extractNodeData(data.nodes, options.node);
this.edgeMap = extractEdgeData(data.edges || [], options.edge, this.getEdgeId.bind(this));
}
data() {
return { nodes: this.nodeMap, edges: this.edgeMap };
}
replace(result) {
this.nodeMap = result.nodes;
this.edgeMap = result.edges;
this.clearCache();
}
nodes() {
return Array.from(this.nodeMap.values());
}
node(id) {
return this.nodeMap.get(id);
}
nodeAt(index) {
if (!this.indexNodeCache) {
this.buildNodeIndexCache();
}
const nodeId = this.indexNodeCache.get(index);
return nodeId ? this.nodeMap.get(nodeId) : undefined;
}
nodeIndexOf(id) {
var _a;
if (!this.nodeIndexCache) {
this.buildNodeIndexCache();
}
return (_a = this.nodeIndexCache.get(id)) !== null && _a !== void 0 ? _a : -1;
}
firstNode() {
return this.nodeMap.values().next().value;
}
forEachNode(callback) {
let i = 0;
this.nodeMap.forEach((node) => callback(node, i++));
}
originalNode(id) {
const node = this.nodeMap.get(id);
return node === null || node === void 0 ? void 0 : node._original;
}
nodeCount() {
return this.nodeMap.size;
}
edges() {
return Array.from(this.edgeMap.values());
}
edge(id) {
return this.edgeMap.get(id);
}
firstEdge() {
return this.edgeMap.values().next().value;
}
forEachEdge(callback) {
let i = 0;
this.edgeMap.forEach((edge) => callback(edge, i++));
}
originalEdge(id) {
const edge = this.edgeMap.get(id);
return edge === null || edge === void 0 ? void 0 : edge._original;
}
edgeCount() {
return this.edgeMap.size;
}
getEdgeId(edge) {
if (edge.id)
return edge.id;
const baseId = `${edge.source}-${edge.target}`;
const count = this.edgeIdCounter.get(baseId) || 0;
const id = count === 0 ? baseId : `${baseId}-${count}`;
this.edgeIdCounter.set(baseId, count + 1);
return id;
}
degree(nodeId, direction = 'both') {
if (!this.degreeCache) {
this.buildDegreeCache();
}
const degree = this.degreeCache.get(nodeId);
if (!degree)
return 0;
return degree[direction];
}
neighbors(nodeId, direction = 'both') {
if (!this.outAdjacencyCache || !this.inAdjacencyCache) {
this.buildAdjacencyCache();
}
if (direction === 'out') {
return Array.from(this.outAdjacencyCache.get(nodeId) || []);
}
if (direction === 'in') {
return Array.from(this.inAdjacencyCache.get(nodeId) || []);
}
if (this.bothAdjacencyCache) {
return Array.from(this.bothAdjacencyCache.get(nodeId) || []);
}
const inSet = this.inAdjacencyCache.get(nodeId);
const outSet = this.outAdjacencyCache.get(nodeId);
if (!inSet && !outSet)
return [];
if (!inSet)
return Array.from(outSet);
if (!outSet)
return Array.from(inSet);
const merged = new Set();
inSet.forEach((id) => merged.add(id));
outSet.forEach((id) => merged.add(id));
return Array.from(merged);
}
successors(nodeId) {
return this.neighbors(nodeId, 'out');
}
predecessors(nodeId) {
return this.neighbors(nodeId, 'in');
}
setNodeOrder(nodes) {
const next = new Map();
for (const node of nodes)
next.set(node.id, node);
this.nodeMap = next;
this.nodeIndexCache = undefined;
this.indexNodeCache = undefined;
}
clearCache() {
this.degreeCache = undefined;
this.inAdjacencyCache = undefined;
this.outAdjacencyCache = undefined;
this.bothAdjacencyCache = undefined;
this.nodeIndexCache = undefined;
this.indexNodeCache = undefined;
}
buildDegreeCache() {
this.degreeCache = new Map();
for (const edge of this.edges()) {
const { source, target } = edge;
if (edge.source === edge.target)
continue;
if (!this.degreeCache.has(source)) {
this.degreeCache.set(source, { in: 0, out: 0, both: 0 });
}
const sourceDeg = this.degreeCache.get(edge.source);
if (sourceDeg) {
sourceDeg.out++;
sourceDeg.both++;
}
if (!this.degreeCache.has(target)) {
this.degreeCache.set(target, { in: 0, out: 0, both: 0 });
}
const targetDeg = this.degreeCache.get(edge.target);
if (targetDeg) {
targetDeg.in++;
targetDeg.both++;
}
}
}
buildAdjacencyCache() {
this.inAdjacencyCache = new Map();
this.outAdjacencyCache = new Map();
for (const edge of this.edges()) {
if (!this.nodeMap.has(edge.source) || !this.nodeMap.has(edge.target))
continue;
if (!this.outAdjacencyCache.has(edge.source)) {
this.outAdjacencyCache.set(edge.source, new Set());
}
this.outAdjacencyCache.get(edge.source).add(edge.target);
if (!this.inAdjacencyCache.has(edge.target)) {
this.inAdjacencyCache.set(edge.target, new Set());
}
this.inAdjacencyCache.get(edge.target).add(edge.source);
}
}
buildNodeIndexCache() {
this.nodeIndexCache = new Map();
this.indexNodeCache = new Map();
let index = 0;
this.nodeMap.forEach((_node, nodeId) => {
this.nodeIndexCache.set(nodeId, index);
this.indexNodeCache.set(index, nodeId);
index++;
});
}
destroy() {
this.clearCache();
this.nodeMap.clear();
this.edgeMap.clear();
this.edgeIdCounter.clear();
}
}
const nodeFields = [
'id',
'x',
'y',
'z',
'vx',
'vy',
'vz',
'fx',
'fy',
'fz',
'parentId',
];
const edgeFields = ['id', 'source', 'target', 'points'];
function extractNodeData(nodes, node) {
if (!nodes) {
throw new Error('Data.nodes is required');
}
const result = new Map();
for (const datum of nodes) {
const nodeData = { _original: datum };
for (const field of nodeFields) {
const value = datum[field];
if (isNil(value))
continue;
nodeData[field] = value;
}
if (node) {
const customFields = node(datum);
for (const key in customFields) {
const value = customFields[key];
if (isNil(value))
continue;
nodeData[key] = value;
}
}
if (isNil(nodeData.id)) {
throw new Error(`Node is missing id field`);
}
result.set(nodeData.id, nodeData);
}
return result;
}
function extractEdgeData(edges, edge, getEdgeId) {
const result = new Map();
for (const datum of edges) {
const edgeData = { _original: datum };
for (const field of edgeFields) {
const value = datum[field];
if (isNil(value))
continue;
edgeData[field] = value;
}
if (edge) {
const customFields = edge(datum);
for (const key in customFields) {
const value = customFields[key];
if (isNil(value))
continue;
edgeData[key] = value;