@antv/layout 2.0.0-alpha.2 → 2.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/worker.js CHANGED
@@ -663,701 +663,763 @@ var pick = (function (object, keys) {
663
663
  return result;
664
664
  });
665
665
 
666
- class GraphLib {
667
- constructor(data, options = {}) {
668
- this.edgeIdCounter = new Map();
669
- this.nodeMap = extractNodeData(data.nodes, options.node);
670
- this.edgeMap = extractEdgeData(data.edges || [], options.edge, this.getEdgeId.bind(this));
671
- }
672
- data() {
673
- return { nodes: this.nodeMap, edges: this.edgeMap };
674
- }
675
- replace(result) {
676
- this.nodeMap = result.nodes;
677
- this.edgeMap = result.edges;
678
- this.clearCache();
679
- }
680
- nodes() {
681
- return Array.from(this.nodeMap.values());
682
- }
683
- node(id) {
684
- return this.nodeMap.get(id);
685
- }
686
- nodeAt(index) {
687
- if (!this.indexNodeCache) {
688
- this.buildNodeIndexCache();
666
+ /**
667
+ * Return the layout result for a graph with zero or one node.
668
+ * @param graph original graph
669
+ * @param center the layout center
670
+ * @returns layout result
671
+ */
672
+ function applySingleNodeLayout(model, center, dimensions = 2) {
673
+ const n = model.nodeCount();
674
+ if (n === 1) {
675
+ const first = model.firstNode();
676
+ first.x = center[0];
677
+ first.y = center[1];
678
+ if (dimensions === 3) {
679
+ first.z = center[2] || 0;
689
680
  }
690
- const nodeId = this.indexNodeCache.get(index);
691
- return nodeId ? this.nodeMap.get(nodeId) : undefined;
692
681
  }
693
- nodeIndexOf(id) {
694
- var _a;
695
- if (!this.nodeIndexCache) {
696
- this.buildNodeIndexCache();
682
+ }
683
+
684
+ /**
685
+ * Get the adjacency list of the graph model.
686
+ */
687
+ const getAdjList = (model, directed) => {
688
+ const n = model.nodeCount();
689
+ const adjList = Array.from({ length: n }, () => []);
690
+ // map node with index
691
+ const nodeMap = {};
692
+ let idx = 0;
693
+ model.forEachNode((node) => {
694
+ nodeMap[node.id] = idx++;
695
+ });
696
+ model.forEachEdge((e) => {
697
+ const s = nodeMap[e.source];
698
+ const t = nodeMap[e.target];
699
+ if (s == null || t == null)
700
+ return;
701
+ adjList[s].push(t);
702
+ adjList[t].push(s);
703
+ });
704
+ return adjList;
705
+ };
706
+ /**
707
+ * scale matrix
708
+ * @param matrix [ [], [], [] ]
709
+ * @param ratio
710
+ */
711
+ const scaleMatrix = (matrix, ratio) => {
712
+ const n = matrix.length;
713
+ const result = new Array(n);
714
+ for (let i = 0; i < n; i++) {
715
+ const row = matrix[i];
716
+ const m = row.length;
717
+ const newRow = new Array(m);
718
+ for (let j = 0; j < m; j++) {
719
+ newRow[j] = row[j] * ratio;
697
720
  }
698
- return (_a = this.nodeIndexCache.get(id)) !== null && _a !== void 0 ? _a : -1;
699
- }
700
- firstNode() {
701
- return this.nodeMap.values().next().value;
702
- }
703
- forEachNode(callback) {
704
- let i = 0;
705
- this.nodeMap.forEach((node) => callback(node, i++));
706
- }
707
- originalNode(id) {
708
- const node = this.nodeMap.get(id);
709
- return node === null || node === void 0 ? void 0 : node._original;
710
- }
711
- nodeCount() {
712
- return this.nodeMap.size;
713
- }
714
- edges() {
715
- return Array.from(this.edgeMap.values());
721
+ result[i] = newRow;
716
722
  }
717
- edge(id) {
718
- return this.edgeMap.get(id);
723
+ return result;
724
+ };
725
+ /**
726
+ * Use Johnson + Dijkstra to compute APSP for sparse graph.
727
+ * Fully compatible with floydWarshall(adjMatrix).
728
+ */
729
+ function johnson(adjList) {
730
+ const n = adjList.length;
731
+ // Step 1: add a dummy node q connected to all nodes with weight 0
732
+ new Array(n).fill(0);
733
+ // Bellman-Ford to compute potentials h(v)
734
+ // 因为权重全是 1,无负边,可直接跳过 BF,h 全 0 即可
735
+ // Step 2: reweight edges
736
+ // 因为 h(u)=h(v)=0,reweight 后仍然是 1,省略 reweight 过程
737
+ // Step 3: run Dijkstra from each node
738
+ const distAll = Array.from({ length: n }, () => new Array(n).fill(Infinity));
739
+ for (let s = 0; s < n; s++) {
740
+ distAll[s] = dijkstra(adjList, s);
719
741
  }
720
- firstEdge() {
721
- return this.edgeMap.values().next().value;
742
+ return distAll;
743
+ }
744
+ /**
745
+ * Dijkstra algorithm to find shortest paths from source to all nodes.
746
+ */
747
+ function dijkstra(adjList, source) {
748
+ const n = adjList.length;
749
+ const dist = new Array(n).fill(Infinity);
750
+ dist[source] = 0;
751
+ // Minimal binary heap
752
+ const heap = new MinHeap();
753
+ heap.push([0, source]); // [distance, node]
754
+ while (!heap.empty()) {
755
+ const [d, u] = heap.pop();
756
+ if (d !== dist[u])
757
+ continue;
758
+ const neighbors = adjList[u];
759
+ for (let i = 0; i < neighbors.length; i++) {
760
+ const v = neighbors[i];
761
+ const nd = d + 1;
762
+ if (nd < dist[v]) {
763
+ dist[v] = nd;
764
+ heap.push([nd, v]);
765
+ }
766
+ }
722
767
  }
723
- forEachEdge(callback) {
724
- let i = 0;
725
- this.edgeMap.forEach((edge) => callback(edge, i++));
768
+ return dist;
769
+ }
770
+ class MinHeap {
771
+ constructor() {
772
+ this.data = [];
726
773
  }
727
- originalEdge(id) {
728
- const edge = this.edgeMap.get(id);
729
- return edge === null || edge === void 0 ? void 0 : edge._original;
774
+ push(item) {
775
+ this.data.push(item);
776
+ this.bubbleUp(this.data.length - 1);
730
777
  }
731
- edgeCount() {
732
- return this.edgeMap.size;
778
+ pop() {
779
+ const top = this.data[0];
780
+ const end = this.data.pop();
781
+ if (this.data.length > 0) {
782
+ this.data[0] = end;
783
+ this.bubbleDown(0);
784
+ }
785
+ return top;
733
786
  }
734
- getEdgeId(edge) {
735
- if (edge.id)
736
- return edge.id;
737
- const baseId = `${edge.source}-${edge.target}`;
738
- const count = this.edgeIdCounter.get(baseId) || 0;
739
- const id = count === 0 ? baseId : `${baseId}-${count}`;
740
- this.edgeIdCounter.set(baseId, count + 1);
741
- return id;
787
+ empty() {
788
+ return this.data.length === 0;
742
789
  }
743
- degree(nodeId, direction = 'both') {
744
- if (!this.degreeCache) {
745
- this.buildDegreeCache();
790
+ bubbleUp(pos) {
791
+ const data = this.data;
792
+ while (pos > 0) {
793
+ const parent = (pos - 1) >> 1;
794
+ if (data[parent][0] <= data[pos][0])
795
+ break;
796
+ [data[parent], data[pos]] = [data[pos], data[parent]];
797
+ pos = parent;
746
798
  }
747
- const degree = this.degreeCache.get(nodeId);
748
- if (!degree)
749
- return 0;
750
- return degree[direction];
751
799
  }
752
- neighbors(nodeId, direction = 'both') {
753
- if (!this.outAdjacencyCache || !this.inAdjacencyCache) {
754
- this.buildAdjacencyCache();
755
- }
756
- if (direction === 'out') {
757
- return Array.from(this.outAdjacencyCache.get(nodeId) || []);
758
- }
759
- if (direction === 'in') {
760
- return Array.from(this.inAdjacencyCache.get(nodeId) || []);
761
- }
762
- if (this.bothAdjacencyCache) {
763
- return Array.from(this.bothAdjacencyCache.get(nodeId) || []);
800
+ bubbleDown(pos) {
801
+ const data = this.data;
802
+ const length = data.length;
803
+ while (true) {
804
+ const left = pos * 2 + 1;
805
+ const right = pos * 2 + 2;
806
+ let min = pos;
807
+ if (left < length && data[left][0] < data[min][0])
808
+ min = left;
809
+ if (right < length && data[right][0] < data[min][0])
810
+ min = right;
811
+ if (min === pos)
812
+ break;
813
+ [data[pos], data[min]] = [data[min], data[pos]];
814
+ pos = min;
764
815
  }
765
- const inSet = this.inAdjacencyCache.get(nodeId);
766
- const outSet = this.outAdjacencyCache.get(nodeId);
767
- if (!inSet && !outSet)
768
- return [];
769
- if (!inSet)
770
- return Array.from(outSet);
771
- if (!outSet)
772
- return Array.from(inSet);
773
- const merged = new Set();
774
- inSet.forEach((id) => merged.add(id));
775
- outSet.forEach((id) => merged.add(id));
776
- return Array.from(merged);
777
- }
778
- successors(nodeId) {
779
- return this.neighbors(nodeId, 'out');
780
- }
781
- predecessors(nodeId) {
782
- return this.neighbors(nodeId, 'in');
783
- }
784
- setNodeOrder(nodes) {
785
- const next = new Map();
786
- for (const node of nodes)
787
- next.set(node.id, node);
788
- this.nodeMap = next;
789
- this.nodeIndexCache = undefined;
790
- this.indexNodeCache = undefined;
791
816
  }
792
- clearCache() {
793
- this.degreeCache = undefined;
794
- this.inAdjacencyCache = undefined;
795
- this.outAdjacencyCache = undefined;
796
- this.bothAdjacencyCache = undefined;
797
- this.nodeIndexCache = undefined;
798
- this.indexNodeCache = undefined;
799
- }
800
- buildDegreeCache() {
801
- this.degreeCache = new Map();
802
- for (const edge of this.edges()) {
803
- const { source, target } = edge;
804
- if (edge.source === edge.target)
805
- continue;
806
- if (!this.degreeCache.has(source)) {
807
- this.degreeCache.set(source, { in: 0, out: 0, both: 0 });
808
- }
809
- const sourceDeg = this.degreeCache.get(edge.source);
810
- if (sourceDeg) {
811
- sourceDeg.out++;
812
- sourceDeg.both++;
813
- }
814
- if (!this.degreeCache.has(target)) {
815
- this.degreeCache.set(target, { in: 0, out: 0, both: 0 });
816
- }
817
- const targetDeg = this.degreeCache.get(edge.target);
818
- if (targetDeg) {
819
- targetDeg.in++;
820
- targetDeg.both++;
821
- }
817
+ }
818
+
819
+ /**
820
+ * Get nested property value
821
+ * For example: getNestedValue(obj, 'a.b.c') will return obj.a.b.c
822
+ */
823
+ function getNestedValue(obj, path) {
824
+ const keys = String(path).split('.');
825
+ return get$1(obj, keys);
826
+ }
827
+ /**
828
+ * Merge objects, but undefined values in source objects will not override existing values
829
+ * @param target - The target object
830
+ * @param sources - Source objects to merge
831
+ * @returns A new merged object
832
+ *
833
+ * @example
834
+ * assignDefined({ a: 1, b: 2 }, { b: undefined, c: 3 })
835
+ * // Returns: { a: 1, b: 2, c: 3 }
836
+ */
837
+ function assignDefined(target, ...sources) {
838
+ sources.forEach((source) => {
839
+ if (source) {
840
+ Object.keys(source).forEach((key) => {
841
+ const value = source[key];
842
+ if (value !== undefined) {
843
+ target[key] = value;
844
+ }
845
+ });
822
846
  }
823
- }
824
- buildAdjacencyCache() {
825
- this.inAdjacencyCache = new Map();
826
- this.outAdjacencyCache = new Map();
827
- for (const edge of this.edges()) {
828
- if (!this.nodeMap.has(edge.source) || !this.nodeMap.has(edge.target))
829
- continue;
830
- if (!this.outAdjacencyCache.has(edge.source)) {
831
- this.outAdjacencyCache.set(edge.source, new Set());
847
+ });
848
+ return target;
849
+ }
850
+
851
+ /**
852
+ * 通用排序核心函数
853
+ */
854
+ function sort$1(model, compareFn) {
855
+ const nodes = model.nodes();
856
+ nodes.sort(compareFn);
857
+ model.setNodeOrder(nodes);
858
+ return model;
859
+ }
860
+ function orderByDegree(model) {
861
+ return sort$1(model, (nodeA, nodeB) => {
862
+ const degreeA = model.degree(nodeA.id);
863
+ const degreeB = model.degree(nodeB.id);
864
+ return degreeB - degreeA; // descending order
865
+ });
866
+ }
867
+ /**
868
+ * 按 ID 排序
869
+ */
870
+ function orderById(model) {
871
+ return sort$1(model, (nodeA, nodeB) => {
872
+ const idA = nodeA.id;
873
+ const idB = nodeB.id;
874
+ if (typeof idA === 'number' && typeof idB === 'number') {
875
+ return idA - idB;
876
+ }
877
+ return String(idA).localeCompare(String(idB));
878
+ });
879
+ }
880
+ /**
881
+ * 按自定义比较函数排序
882
+ */
883
+ function orderBySorter(model, sorter) {
884
+ return sort$1(model, (nodeA, nodeB) => {
885
+ const a = model.originalNode(nodeA.id);
886
+ const b = model.originalNode(nodeB.id);
887
+ return sorter(a, b);
888
+ });
889
+ }
890
+ /**
891
+ * Order nodes according to graph topology
892
+ */
893
+ function orderByTopology(model, directed = false) {
894
+ const n = model.nodeCount();
895
+ if (n === 0)
896
+ return model;
897
+ const nodes = model.nodes();
898
+ const orderedNodes = [nodes[0]];
899
+ const pickFlags = {};
900
+ pickFlags[nodes[0].id] = true;
901
+ let k = 0;
902
+ let i = 0;
903
+ model.forEachNode((node) => {
904
+ if (i !== 0) {
905
+ const currentDegree = model.degree(node.id, 'both');
906
+ const nextDegree = i < n - 1 ? model.degree(nodes[i + 1].id, 'both') : 0;
907
+ const currentNodeId = orderedNodes[k].id;
908
+ const isNeighbor = model
909
+ .neighbors(currentNodeId, 'both')
910
+ .includes(node.id);
911
+ if ((i === n - 1 || currentDegree !== nextDegree || isNeighbor) &&
912
+ !pickFlags[node.id]) {
913
+ orderedNodes.push(node);
914
+ pickFlags[node.id] = true;
915
+ k++;
832
916
  }
833
- this.outAdjacencyCache.get(edge.source).add(edge.target);
834
- if (!this.inAdjacencyCache.has(edge.target)) {
835
- this.inAdjacencyCache.set(edge.target, new Set());
917
+ else {
918
+ const children = directed
919
+ ? model.successors(currentNodeId)
920
+ : model.neighbors(currentNodeId);
921
+ let foundChild = false;
922
+ for (let j = 0; j < children.length; j++) {
923
+ const childId = children[j];
924
+ const child = model.node(childId);
925
+ if (child &&
926
+ model.degree(childId) === model.degree(node.id) &&
927
+ !pickFlags[childId]) {
928
+ orderedNodes.push(child);
929
+ pickFlags[childId] = true;
930
+ foundChild = true;
931
+ break;
932
+ }
933
+ }
934
+ let ii = 0;
935
+ while (!foundChild) {
936
+ if (!pickFlags[nodes[ii].id]) {
937
+ orderedNodes.push(nodes[ii]);
938
+ pickFlags[nodes[ii].id] = true;
939
+ foundChild = true;
940
+ }
941
+ ii++;
942
+ if (ii === n) {
943
+ break;
944
+ }
945
+ }
836
946
  }
837
- this.inAdjacencyCache.get(edge.target).add(edge.source);
838
947
  }
948
+ i++;
949
+ });
950
+ // Update model with ordered nodes
951
+ model.setNodeOrder(orderedNodes);
952
+ return model;
953
+ }
954
+
955
+ function parsePoint(point) {
956
+ var _a;
957
+ return [point.x, point.y, (_a = point.z) !== null && _a !== void 0 ? _a : 0];
958
+ }
959
+
960
+ function parseSize(size) {
961
+ if (!size)
962
+ return [0, 0, 0];
963
+ if (isNumber(size))
964
+ return [size, size, size];
965
+ else if (Array.isArray(size) && size.length === 0)
966
+ return [0, 0, 0];
967
+ const [x, y = x, z = x] = size;
968
+ return [x, y, z];
969
+ }
970
+
971
+ /**
972
+ * Viewport configuration such as width, height and center point.
973
+ */
974
+ const normalizeViewport = (options) => {
975
+ const { width, height, center } = options;
976
+ const normalizedWidth = width !== null && width !== void 0 ? width : (typeof window !== 'undefined' ? window.innerWidth : 0);
977
+ const normalizedHeight = height !== null && height !== void 0 ? height : (typeof window !== 'undefined' ? window.innerHeight : 0);
978
+ const centerPoint = center !== null && center !== void 0 ? center : [normalizedWidth / 2, normalizedHeight / 2];
979
+ return {
980
+ width: normalizedWidth,
981
+ height: normalizedHeight,
982
+ center: centerPoint,
983
+ };
984
+ };
985
+
986
+ /**
987
+ * Format value with multiple types into a function that returns a number
988
+ * @param value The value to be formatted
989
+ * @param defaultValue The default value when value is invalid
990
+ * @returns A function that returns a number
991
+ */
992
+ function formatNumberFn(value, defaultValue) {
993
+ // If value is a function, return it directly
994
+ if (isFunction(value)) {
995
+ return value;
839
996
  }
840
- buildNodeIndexCache() {
841
- this.nodeIndexCache = new Map();
842
- this.indexNodeCache = new Map();
843
- let index = 0;
844
- this.nodeMap.forEach((_node, nodeId) => {
845
- this.nodeIndexCache.set(nodeId, index);
846
- this.indexNodeCache.set(index, nodeId);
847
- index++;
848
- });
849
- }
850
- destroy() {
851
- this.clearCache();
852
- this.nodeMap.clear();
853
- this.edgeMap.clear();
854
- this.edgeIdCounter.clear();
997
+ // If value is a number, return a function that returns this number
998
+ if (isNumber(value)) {
999
+ return () => value;
855
1000
  }
1001
+ // For other cases (undefined or invalid values), return default value function
1002
+ return () => defaultValue;
856
1003
  }
857
- const nodeFields = [
858
- 'id',
859
- 'x',
860
- 'y',
861
- 'z',
862
- 'vx',
863
- 'vy',
864
- 'vz',
865
- 'fx',
866
- 'fy',
867
- 'fz',
868
- 'parentId',
869
- ];
870
- const edgeFields = ['id', 'source', 'target', 'points'];
871
- function extractNodeData(nodes, node) {
872
- if (!nodes) {
873
- throw new Error('Data.nodes is required');
1004
+ /**
1005
+ * Format size config with multiple types into a function that returns a size
1006
+ * @param value The value to be formatted
1007
+ * @param defaultValue The default value when value is invalid
1008
+ * @param resultIsNumber Whether to return a number (max of width/height) or size array
1009
+ * @returns A function that returns a size
1010
+ */
1011
+ function formatSizeFn(value, defaultValue = 10) {
1012
+ // If value is undefined, return default value function
1013
+ if (!value) {
1014
+ return () => defaultValue;
874
1015
  }
875
- const result = new Map();
876
- for (const datum of nodes) {
877
- const nodeData = { _original: datum };
878
- for (const field of nodeFields) {
879
- const value = datum[field];
880
- if (isNil(value))
881
- continue;
882
- nodeData[field] = value;
883
- }
884
- if (node) {
885
- const customFields = node(datum);
886
- for (const key in customFields) {
887
- const value = customFields[key];
888
- if (isNil(value))
889
- continue;
890
- nodeData[key] = value;
891
- }
892
- }
893
- if (isNil(nodeData.id)) {
894
- throw new Error(`Node is missing id field`);
895
- }
896
- result.set(nodeData.id, nodeData);
1016
+ // If value is a function, return it directly
1017
+ if (isFunction(value)) {
1018
+ return value;
897
1019
  }
898
- return result;
899
- }
900
- function extractEdgeData(edges, edge, getEdgeId) {
901
- const result = new Map();
902
- for (const datum of edges) {
903
- const edgeData = { _original: datum };
904
- for (const field of edgeFields) {
905
- const value = datum[field];
906
- if (isNil(value))
907
- continue;
908
- edgeData[field] = value;
909
- }
910
- if (edge) {
911
- const customFields = edge(datum);
912
- for (const key in customFields) {
913
- const value = customFields[key];
914
- if (isNil(value))
915
- continue;
916
- edgeData[key] = value;
917
- }
918
- }
919
- if (isNil(edgeData.source) || isNil(edgeData.target)) {
920
- throw new Error(`Edge is missing source or target field`);
921
- }
922
- if (isNil(edgeData.id)) {
923
- edgeData.id = getEdgeId === null || getEdgeId === void 0 ? void 0 : getEdgeId(datum);
924
- }
925
- result.set(edgeData.id, edgeData);
1020
+ // If value is a number, return a function that returns this number
1021
+ if (isNumber(value)) {
1022
+ return () => value;
926
1023
  }
927
- return result;
928
- }
929
- function initNodePosition(model, width, height, dimensions = 2) {
930
- model.forEachNode((node) => {
931
- if (isNil(node.x)) {
932
- node.x = Math.random() * width;
933
- }
934
- if (isNil(node.y)) {
935
- node.y = Math.random() * height;
936
- }
937
- if (dimensions === 3 && isNil(node.z)) {
938
- node.z = Math.random() * Math.min(width, height);
939
- }
940
- });
1024
+ // If value is an array, return max or the array itself
1025
+ if (Array.isArray(value)) {
1026
+ return () => value;
1027
+ }
1028
+ // If value is an object with width and height
1029
+ if (isObject(value) && value.width && value.height) {
1030
+ return () => [value.width, value.height];
1031
+ }
1032
+ return () => defaultValue;
941
1033
  }
1034
+ /**
1035
+ * Format nodeSize and nodeSpacing into a function that returns the total size
1036
+ * @param nodeSize The size of the node
1037
+ * @param nodeSpacing The spacing around the node
1038
+ * @param defaultNodeSize The default node size when value is invalid
1039
+ * @returns A function that returns the total size (node size + spacing)
1040
+ */
1041
+ const formatNodeSizeFn = (nodeSize, nodeSpacing, defaultNodeSize = 10) => {
1042
+ const nodeSpacingFunc = formatNumberFn(nodeSpacing, 0);
1043
+ const nodeSizeFunc = formatSizeFn(nodeSize, defaultNodeSize);
1044
+ return (node) => {
1045
+ const size = nodeSizeFunc(node);
1046
+ const spacing = nodeSpacingFunc(node);
1047
+ return Math.max(...parseSize(size)) + spacing;
1048
+ };
1049
+ };
942
1050
 
943
- class RuntimeContext {
1051
+ class GraphLib {
944
1052
  constructor(data, options = {}) {
945
- this.graph = new GraphLib(data, options);
1053
+ this.edgeIdCounter = new Map();
1054
+ this.nodeMap = extractNodeData(data.nodes, options.node);
1055
+ this.edgeMap = extractEdgeData(data.edges || [], options.edge, this.getEdgeId.bind(this));
1056
+ }
1057
+ data() {
1058
+ return { nodes: this.nodeMap, edges: this.edgeMap };
1059
+ }
1060
+ replace(result) {
1061
+ this.nodeMap = result.nodes;
1062
+ this.edgeMap = result.edges;
1063
+ this.clearCache();
1064
+ }
1065
+ nodes() {
1066
+ return Array.from(this.nodeMap.values());
1067
+ }
1068
+ node(id) {
1069
+ return this.nodeMap.get(id);
1070
+ }
1071
+ nodeAt(index) {
1072
+ if (!this.indexNodeCache) {
1073
+ this.buildNodeIndexCache();
1074
+ }
1075
+ const nodeId = this.indexNodeCache.get(index);
1076
+ return nodeId ? this.nodeMap.get(nodeId) : undefined;
1077
+ }
1078
+ nodeIndexOf(id) {
1079
+ var _a;
1080
+ if (!this.nodeIndexCache) {
1081
+ this.buildNodeIndexCache();
1082
+ }
1083
+ return (_a = this.nodeIndexCache.get(id)) !== null && _a !== void 0 ? _a : -1;
1084
+ }
1085
+ firstNode() {
1086
+ return this.nodeMap.values().next().value;
1087
+ }
1088
+ forEachNode(callback) {
1089
+ let i = 0;
1090
+ this.nodeMap.forEach((node) => callback(node, i++));
1091
+ }
1092
+ originalNode(id) {
1093
+ const node = this.nodeMap.get(id);
1094
+ return node === null || node === void 0 ? void 0 : node._original;
1095
+ }
1096
+ nodeCount() {
1097
+ return this.nodeMap.size;
946
1098
  }
947
- export() {
948
- return this.graph.data();
1099
+ edges() {
1100
+ return Array.from(this.edgeMap.values());
949
1101
  }
950
- replace(result) {
951
- this.graph.replace(result);
1102
+ edge(id) {
1103
+ return this.edgeMap.get(id);
952
1104
  }
953
- forEachNode(callback) {
954
- this.graph.forEachNode(callback);
1105
+ firstEdge() {
1106
+ return this.edgeMap.values().next().value;
955
1107
  }
956
1108
  forEachEdge(callback) {
957
- this.graph.forEachEdge((edge, i) => {
958
- edge.sourceNode = this.graph.node(edge.source);
959
- edge.targetNode = this.graph.node(edge.target);
960
- callback(edge, i);
961
- });
1109
+ let i = 0;
1110
+ this.edgeMap.forEach((edge) => callback(edge, i++));
962
1111
  }
963
- destroy() {
964
- this.graph.destroy();
1112
+ originalEdge(id) {
1113
+ const edge = this.edgeMap.get(id);
1114
+ return edge === null || edge === void 0 ? void 0 : edge._original;
965
1115
  }
966
- }
967
-
968
- class Supervisor {
969
- constructor() {
970
- this.worker = null;
971
- this.workerApi = null;
1116
+ edgeCount() {
1117
+ return this.edgeMap.size;
972
1118
  }
973
- /**
974
- * Execute layout in worker
975
- */
976
- execute(layoutId, data, options) {
977
- return __awaiter(this, void 0, void 0, function* () {
978
- if (!this.worker) {
979
- yield this.initWorker();
980
- }
981
- if (!this.workerApi) {
982
- throw new Error('Worker API not initialized');
983
- }
984
- return yield this.workerApi.execute(layoutId, data, options);
985
- });
1119
+ getEdgeId(edge) {
1120
+ if (edge.id)
1121
+ return edge.id;
1122
+ const baseId = `${edge.source}-${edge.target}`;
1123
+ const count = this.edgeIdCounter.get(baseId) || 0;
1124
+ const id = count === 0 ? baseId : `${baseId}-${count}`;
1125
+ this.edgeIdCounter.set(baseId, count + 1);
1126
+ return id;
986
1127
  }
987
- /**
988
- * Destroy worker
989
- */
990
- destroy() {
991
- if (this.workerApi) {
992
- this.workerApi.destroy();
993
- }
994
- if (this.worker) {
995
- this.worker.terminate();
996
- this.worker = null;
997
- this.workerApi = null;
1128
+ degree(nodeId, direction = 'both') {
1129
+ if (!this.degreeCache) {
1130
+ this.buildDegreeCache();
998
1131
  }
1132
+ const degree = this.degreeCache.get(nodeId);
1133
+ if (!degree)
1134
+ return 0;
1135
+ return degree[direction];
999
1136
  }
1000
- /**
1001
- * Initialize worker
1002
- */
1003
- initWorker() {
1004
- return __awaiter(this, void 0, void 0, function* () {
1005
- const workerPath = this.resolveWorkerPath();
1006
- const isESM = workerPath.includes('/lib/') || workerPath.endsWith('.mjs');
1007
- const type = isESM ? 'module' : 'classic';
1008
- this.worker = new Worker(workerPath, { type });
1009
- this.workerApi = wrap(this.worker);
1010
- });
1011
- }
1012
- /**
1013
- * Resolve worker script path which works in both ESM and UMD environments
1014
- */
1015
- resolveWorkerPath() {
1016
- if (typeof import.meta !== 'undefined' && import.meta.url) {
1017
- const currentUrl = new URL(import.meta.url);
1018
- // e.g. `.../lib/runtime/supervisor.js` -> `.../lib/worker.js`
1019
- const asRoot = currentUrl.href.replace(/\/runtime\/[^/]+\.js$/, '/worker.js');
1020
- if (asRoot !== currentUrl.href)
1021
- return asRoot;
1022
- // Fallback: keep legacy behavior (same directory)
1023
- return currentUrl.href.replace(/\/[^/]+\.js$/, '/worker.js');
1137
+ neighbors(nodeId, direction = 'both') {
1138
+ if (!this.outAdjacencyCache || !this.inAdjacencyCache) {
1139
+ this.buildAdjacencyCache();
1024
1140
  }
1025
- if (typeof document !== 'undefined') {
1026
- const scripts = document.getElementsByTagName('script');
1027
- for (let i = scripts.length - 1; i >= 0; i--) {
1028
- const src = scripts[i].src;
1029
- if (src && (src.includes('index.js') || src.includes('index.min.js'))) {
1030
- return src.replace(/index(\.min)?\.js/, 'worker.js');
1031
- }
1032
- }
1141
+ if (direction === 'out') {
1142
+ return Array.from(this.outAdjacencyCache.get(nodeId) || []);
1033
1143
  }
1034
- return './worker.js';
1035
- }
1036
- }
1037
-
1038
- /**
1039
- * Return the layout result for a graph with zero or one node.
1040
- * @param graph original graph
1041
- * @param center the layout center
1042
- * @returns layout result
1043
- */
1044
- function applySingleNodeLayout(model, center, dimensions = 2) {
1045
- const n = model.nodeCount();
1046
- if (n === 1) {
1047
- const first = model.firstNode();
1048
- first.x = center[0];
1049
- first.y = center[1];
1050
- if (dimensions === 3) {
1051
- first.z = center[2] || 0;
1144
+ if (direction === 'in') {
1145
+ return Array.from(this.inAdjacencyCache.get(nodeId) || []);
1052
1146
  }
1053
- }
1054
- }
1055
-
1056
- /**
1057
- * Get the adjacency list of the graph model.
1058
- */
1059
- const getAdjList = (model, directed) => {
1060
- const n = model.nodeCount();
1061
- const adjList = Array.from({ length: n }, () => []);
1062
- // map node with index
1063
- const nodeMap = {};
1064
- let idx = 0;
1065
- model.forEachNode((node) => {
1066
- nodeMap[node.id] = idx++;
1067
- });
1068
- model.forEachEdge((e) => {
1069
- const s = nodeMap[e.source];
1070
- const t = nodeMap[e.target];
1071
- if (s == null || t == null)
1072
- return;
1073
- adjList[s].push(t);
1074
- adjList[t].push(s);
1075
- });
1076
- return adjList;
1077
- };
1078
- /**
1079
- * scale matrix
1080
- * @param matrix [ [], [], [] ]
1081
- * @param ratio
1082
- */
1083
- const scaleMatrix = (matrix, ratio) => {
1084
- const n = matrix.length;
1085
- const result = new Array(n);
1086
- for (let i = 0; i < n; i++) {
1087
- const row = matrix[i];
1088
- const m = row.length;
1089
- const newRow = new Array(m);
1090
- for (let j = 0; j < m; j++) {
1091
- newRow[j] = row[j] * ratio;
1147
+ if (this.bothAdjacencyCache) {
1148
+ return Array.from(this.bothAdjacencyCache.get(nodeId) || []);
1092
1149
  }
1093
- result[i] = newRow;
1150
+ const inSet = this.inAdjacencyCache.get(nodeId);
1151
+ const outSet = this.outAdjacencyCache.get(nodeId);
1152
+ if (!inSet && !outSet)
1153
+ return [];
1154
+ if (!inSet)
1155
+ return Array.from(outSet);
1156
+ if (!outSet)
1157
+ return Array.from(inSet);
1158
+ const merged = new Set();
1159
+ inSet.forEach((id) => merged.add(id));
1160
+ outSet.forEach((id) => merged.add(id));
1161
+ return Array.from(merged);
1094
1162
  }
1095
- return result;
1096
- };
1097
- /**
1098
- * Use Johnson + Dijkstra to compute APSP for sparse graph.
1099
- * Fully compatible with floydWarshall(adjMatrix).
1100
- */
1101
- function johnson(adjList) {
1102
- const n = adjList.length;
1103
- // Step 1: add a dummy node q connected to all nodes with weight 0
1104
- new Array(n).fill(0);
1105
- // Bellman-Ford to compute potentials h(v)
1106
- // 因为权重全是 1,无负边,可直接跳过 BF,h 全 0 即可
1107
- // Step 2: reweight edges
1108
- // 因为 h(u)=h(v)=0,reweight 后仍然是 1,省略 reweight 过程
1109
- // Step 3: run Dijkstra from each node
1110
- const distAll = Array.from({ length: n }, () => new Array(n).fill(Infinity));
1111
- for (let s = 0; s < n; s++) {
1112
- distAll[s] = dijkstra(adjList, s);
1163
+ successors(nodeId) {
1164
+ return this.neighbors(nodeId, 'out');
1113
1165
  }
1114
- return distAll;
1115
- }
1116
- /**
1117
- * Dijkstra algorithm to find shortest paths from source to all nodes.
1118
- */
1119
- function dijkstra(adjList, source) {
1120
- const n = adjList.length;
1121
- const dist = new Array(n).fill(Infinity);
1122
- dist[source] = 0;
1123
- // Minimal binary heap
1124
- const heap = new MinHeap();
1125
- heap.push([0, source]); // [distance, node]
1126
- while (!heap.empty()) {
1127
- const [d, u] = heap.pop();
1128
- if (d !== dist[u])
1129
- continue;
1130
- const neighbors = adjList[u];
1131
- for (let i = 0; i < neighbors.length; i++) {
1132
- const v = neighbors[i];
1133
- const nd = d + 1;
1134
- if (nd < dist[v]) {
1135
- dist[v] = nd;
1136
- heap.push([nd, v]);
1166
+ predecessors(nodeId) {
1167
+ return this.neighbors(nodeId, 'in');
1168
+ }
1169
+ setNodeOrder(nodes) {
1170
+ const next = new Map();
1171
+ for (const node of nodes)
1172
+ next.set(node.id, node);
1173
+ this.nodeMap = next;
1174
+ this.nodeIndexCache = undefined;
1175
+ this.indexNodeCache = undefined;
1176
+ }
1177
+ clearCache() {
1178
+ this.degreeCache = undefined;
1179
+ this.inAdjacencyCache = undefined;
1180
+ this.outAdjacencyCache = undefined;
1181
+ this.bothAdjacencyCache = undefined;
1182
+ this.nodeIndexCache = undefined;
1183
+ this.indexNodeCache = undefined;
1184
+ }
1185
+ buildDegreeCache() {
1186
+ this.degreeCache = new Map();
1187
+ for (const edge of this.edges()) {
1188
+ const { source, target } = edge;
1189
+ if (edge.source === edge.target)
1190
+ continue;
1191
+ if (!this.degreeCache.has(source)) {
1192
+ this.degreeCache.set(source, { in: 0, out: 0, both: 0 });
1193
+ }
1194
+ const sourceDeg = this.degreeCache.get(edge.source);
1195
+ if (sourceDeg) {
1196
+ sourceDeg.out++;
1197
+ sourceDeg.both++;
1198
+ }
1199
+ if (!this.degreeCache.has(target)) {
1200
+ this.degreeCache.set(target, { in: 0, out: 0, both: 0 });
1201
+ }
1202
+ const targetDeg = this.degreeCache.get(edge.target);
1203
+ if (targetDeg) {
1204
+ targetDeg.in++;
1205
+ targetDeg.both++;
1137
1206
  }
1138
1207
  }
1139
1208
  }
1140
- return dist;
1141
- }
1142
- class MinHeap {
1143
- constructor() {
1144
- this.data = [];
1209
+ buildAdjacencyCache() {
1210
+ this.inAdjacencyCache = new Map();
1211
+ this.outAdjacencyCache = new Map();
1212
+ for (const edge of this.edges()) {
1213
+ if (!this.nodeMap.has(edge.source) || !this.nodeMap.has(edge.target))
1214
+ continue;
1215
+ if (!this.outAdjacencyCache.has(edge.source)) {
1216
+ this.outAdjacencyCache.set(edge.source, new Set());
1217
+ }
1218
+ this.outAdjacencyCache.get(edge.source).add(edge.target);
1219
+ if (!this.inAdjacencyCache.has(edge.target)) {
1220
+ this.inAdjacencyCache.set(edge.target, new Set());
1221
+ }
1222
+ this.inAdjacencyCache.get(edge.target).add(edge.source);
1223
+ }
1145
1224
  }
1146
- push(item) {
1147
- this.data.push(item);
1148
- this.bubbleUp(this.data.length - 1);
1225
+ buildNodeIndexCache() {
1226
+ this.nodeIndexCache = new Map();
1227
+ this.indexNodeCache = new Map();
1228
+ let index = 0;
1229
+ this.nodeMap.forEach((_node, nodeId) => {
1230
+ this.nodeIndexCache.set(nodeId, index);
1231
+ this.indexNodeCache.set(index, nodeId);
1232
+ index++;
1233
+ });
1149
1234
  }
1150
- pop() {
1151
- const top = this.data[0];
1152
- const end = this.data.pop();
1153
- if (this.data.length > 0) {
1154
- this.data[0] = end;
1155
- this.bubbleDown(0);
1156
- }
1157
- return top;
1235
+ destroy() {
1236
+ this.clearCache();
1237
+ this.nodeMap.clear();
1238
+ this.edgeMap.clear();
1239
+ this.edgeIdCounter.clear();
1158
1240
  }
1159
- empty() {
1160
- return this.data.length === 0;
1241
+ }
1242
+ const nodeFields = [
1243
+ 'id',
1244
+ 'x',
1245
+ 'y',
1246
+ 'z',
1247
+ 'vx',
1248
+ 'vy',
1249
+ 'vz',
1250
+ 'fx',
1251
+ 'fy',
1252
+ 'fz',
1253
+ 'parentId',
1254
+ ];
1255
+ const edgeFields = ['id', 'source', 'target', 'points'];
1256
+ function extractNodeData(nodes, node) {
1257
+ if (!nodes) {
1258
+ throw new Error('Data.nodes is required');
1161
1259
  }
1162
- bubbleUp(pos) {
1163
- const data = this.data;
1164
- while (pos > 0) {
1165
- const parent = (pos - 1) >> 1;
1166
- if (data[parent][0] <= data[pos][0])
1167
- break;
1168
- [data[parent], data[pos]] = [data[pos], data[parent]];
1169
- pos = parent;
1260
+ const result = new Map();
1261
+ for (const datum of nodes) {
1262
+ const nodeData = { _original: datum };
1263
+ for (const field of nodeFields) {
1264
+ const value = datum[field];
1265
+ if (isNil(value))
1266
+ continue;
1267
+ nodeData[field] = value;
1170
1268
  }
1171
- }
1172
- bubbleDown(pos) {
1173
- const data = this.data;
1174
- const length = data.length;
1175
- while (true) {
1176
- const left = pos * 2 + 1;
1177
- const right = pos * 2 + 2;
1178
- let min = pos;
1179
- if (left < length && data[left][0] < data[min][0])
1180
- min = left;
1181
- if (right < length && data[right][0] < data[min][0])
1182
- min = right;
1183
- if (min === pos)
1184
- break;
1185
- [data[pos], data[min]] = [data[min], data[pos]];
1186
- pos = min;
1269
+ if (node) {
1270
+ const customFields = node(datum);
1271
+ for (const key in customFields) {
1272
+ const value = customFields[key];
1273
+ if (isNil(value))
1274
+ continue;
1275
+ nodeData[key] = value;
1276
+ }
1187
1277
  }
1188
- }
1189
- }
1190
-
1191
- /**
1192
- * Get nested property value
1193
- * For example: getNestedValue(obj, 'a.b.c') will return obj.a.b.c
1194
- */
1195
- function getNestedValue(obj, path) {
1196
- const keys = String(path).split('.');
1197
- return get$1(obj, keys);
1198
- }
1199
- /**
1200
- * Merge objects, but undefined values in source objects will not override existing values
1201
- * @param target - The target object
1202
- * @param sources - Source objects to merge
1203
- * @returns A new merged object
1204
- *
1205
- * @example
1206
- * assignDefined({ a: 1, b: 2 }, { b: undefined, c: 3 })
1207
- * // Returns: { a: 1, b: 2, c: 3 }
1208
- */
1209
- function assignDefined(target, ...sources) {
1210
- sources.forEach((source) => {
1211
- if (source) {
1212
- Object.keys(source).forEach((key) => {
1213
- const value = source[key];
1214
- if (value !== undefined) {
1215
- target[key] = value;
1216
- }
1217
- });
1278
+ if (isNil(nodeData.id)) {
1279
+ throw new Error(`Node is missing id field`);
1218
1280
  }
1219
- });
1220
- return target;
1221
- }
1222
- function mergeOptions(base, patch) {
1223
- return Object.assign({}, base, patch || {});
1224
- }
1225
-
1226
- /**
1227
- * 通用排序核心函数
1228
- */
1229
- function sort$1(model, compareFn) {
1230
- const nodes = model.nodes();
1231
- nodes.sort(compareFn);
1232
- model.setNodeOrder(nodes);
1233
- return model;
1234
- }
1235
- function orderByDegree(model) {
1236
- return sort$1(model, (nodeA, nodeB) => {
1237
- const degreeA = model.degree(nodeA.id);
1238
- const degreeB = model.degree(nodeB.id);
1239
- return degreeB - degreeA; // descending order
1240
- });
1281
+ result.set(nodeData.id, nodeData);
1282
+ }
1283
+ return result;
1241
1284
  }
1242
- /**
1243
- * ID 排序
1244
- */
1245
- function orderById(model) {
1246
- return sort$1(model, (nodeA, nodeB) => {
1247
- const idA = nodeA.id;
1248
- const idB = nodeB.id;
1249
- if (typeof idA === 'number' && typeof idB === 'number') {
1250
- return idA - idB;
1285
+ function extractEdgeData(edges, edge, getEdgeId) {
1286
+ const result = new Map();
1287
+ for (const datum of edges) {
1288
+ const edgeData = { _original: datum };
1289
+ for (const field of edgeFields) {
1290
+ const value = datum[field];
1291
+ if (isNil(value))
1292
+ continue;
1293
+ edgeData[field] = value;
1251
1294
  }
1252
- return String(idA).localeCompare(String(idB));
1253
- });
1254
- }
1255
- /**
1256
- * 按自定义比较函数排序
1257
- */
1258
- function orderBySorter(model, sorter) {
1259
- return sort$1(model, (nodeA, nodeB) => {
1260
- const a = model.originalNode(nodeA.id);
1261
- const b = model.originalNode(nodeB.id);
1262
- return sorter(a, b);
1263
- });
1264
- }
1265
- /**
1266
- * Order nodes according to graph topology
1267
- */
1268
- function orderByTopology(model, directed = false) {
1269
- const n = model.nodeCount();
1270
- if (n === 0)
1271
- return model;
1272
- const nodes = model.nodes();
1273
- const orderedNodes = [nodes[0]];
1274
- const pickFlags = {};
1275
- pickFlags[nodes[0].id] = true;
1276
- let k = 0;
1277
- let i = 0;
1278
- model.forEachNode((node) => {
1279
- if (i !== 0) {
1280
- const currentDegree = model.degree(node.id, 'both');
1281
- const nextDegree = i < n - 1 ? model.degree(nodes[i + 1].id, 'both') : 0;
1282
- const currentNodeId = orderedNodes[k].id;
1283
- const isNeighbor = model
1284
- .neighbors(currentNodeId, 'both')
1285
- .includes(node.id);
1286
- if ((i === n - 1 || currentDegree !== nextDegree || isNeighbor) &&
1287
- !pickFlags[node.id]) {
1288
- orderedNodes.push(node);
1289
- pickFlags[node.id] = true;
1290
- k++;
1291
- }
1292
- else {
1293
- const children = directed
1294
- ? model.successors(currentNodeId)
1295
- : model.neighbors(currentNodeId);
1296
- let foundChild = false;
1297
- for (let j = 0; j < children.length; j++) {
1298
- const childId = children[j];
1299
- const child = model.node(childId);
1300
- if (child &&
1301
- model.degree(childId) === model.degree(node.id) &&
1302
- !pickFlags[childId]) {
1303
- orderedNodes.push(child);
1304
- pickFlags[childId] = true;
1305
- foundChild = true;
1306
- break;
1307
- }
1308
- }
1309
- let ii = 0;
1310
- while (!foundChild) {
1311
- if (!pickFlags[nodes[ii].id]) {
1312
- orderedNodes.push(nodes[ii]);
1313
- pickFlags[nodes[ii].id] = true;
1314
- foundChild = true;
1315
- }
1316
- ii++;
1317
- if (ii === n) {
1318
- break;
1319
- }
1320
- }
1295
+ if (edge) {
1296
+ const customFields = edge(datum);
1297
+ for (const key in customFields) {
1298
+ const value = customFields[key];
1299
+ if (isNil(value))
1300
+ continue;
1301
+ edgeData[key] = value;
1321
1302
  }
1322
1303
  }
1323
- i++;
1304
+ if (isNil(edgeData.source) || isNil(edgeData.target)) {
1305
+ throw new Error(`Edge is missing source or target field`);
1306
+ }
1307
+ if (isNil(edgeData.id)) {
1308
+ edgeData.id = getEdgeId === null || getEdgeId === void 0 ? void 0 : getEdgeId(datum);
1309
+ }
1310
+ result.set(edgeData.id, edgeData);
1311
+ }
1312
+ return result;
1313
+ }
1314
+ function initNodePosition(model, width, height, dimensions = 2) {
1315
+ model.forEachNode((node) => {
1316
+ if (isNil(node.x)) {
1317
+ node.x = Math.random() * width;
1318
+ }
1319
+ if (isNil(node.y)) {
1320
+ node.y = Math.random() * height;
1321
+ }
1322
+ if (dimensions === 3 && isNil(node.z)) {
1323
+ node.z = Math.random() * Math.min(width, height);
1324
+ }
1324
1325
  });
1325
- // Update model with ordered nodes
1326
- model.setNodeOrder(orderedNodes);
1327
- return model;
1328
1326
  }
1329
1327
 
1330
- function parsePoint(point) {
1331
- var _a;
1332
- return [point.x, point.y, (_a = point.z) !== null && _a !== void 0 ? _a : 0];
1328
+ class RuntimeContext {
1329
+ constructor(data, options = {}) {
1330
+ this.graph = new GraphLib(data, options);
1331
+ }
1332
+ export() {
1333
+ return this.graph.data();
1334
+ }
1335
+ replace(result) {
1336
+ this.graph.replace(result);
1337
+ }
1338
+ forEachNode(callback) {
1339
+ this.graph.forEachNode(callback);
1340
+ }
1341
+ forEachEdge(callback) {
1342
+ this.graph.forEachEdge((edge, i) => {
1343
+ edge.sourceNode = this.graph.node(edge.source);
1344
+ edge.targetNode = this.graph.node(edge.target);
1345
+ callback(edge, i);
1346
+ });
1347
+ }
1348
+ destroy() {
1349
+ this.graph.destroy();
1350
+ }
1333
1351
  }
1334
1352
 
1335
- function parseSize(size) {
1336
- if (!size)
1337
- return [0, 0, 0];
1338
- if (isNumber(size))
1339
- return [size, size, size];
1340
- else if (Array.isArray(size) && size.length === 0)
1341
- return [0, 0, 0];
1342
- const [x, y = x, z = x] = size;
1343
- return [x, y, z];
1353
+ class Supervisor {
1354
+ constructor() {
1355
+ this.worker = null;
1356
+ this.workerApi = null;
1357
+ }
1358
+ /**
1359
+ * Execute layout in worker
1360
+ */
1361
+ execute(layoutId, data, options) {
1362
+ return __awaiter(this, void 0, void 0, function* () {
1363
+ if (!this.worker) {
1364
+ yield this.initWorker();
1365
+ }
1366
+ if (!this.workerApi) {
1367
+ throw new Error('Worker API not initialized');
1368
+ }
1369
+ return yield this.workerApi.execute(layoutId, data, options);
1370
+ });
1371
+ }
1372
+ /**
1373
+ * Destroy worker
1374
+ */
1375
+ destroy() {
1376
+ if (this.workerApi) {
1377
+ this.workerApi.destroy();
1378
+ }
1379
+ if (this.worker) {
1380
+ this.worker.terminate();
1381
+ this.worker = null;
1382
+ this.workerApi = null;
1383
+ }
1384
+ }
1385
+ /**
1386
+ * Initialize worker
1387
+ */
1388
+ initWorker() {
1389
+ return __awaiter(this, void 0, void 0, function* () {
1390
+ const workerPath = this.resolveWorkerPath();
1391
+ const isESM = workerPath.includes('/lib/') || workerPath.endsWith('.mjs');
1392
+ const type = isESM ? 'module' : 'classic';
1393
+ this.worker = new Worker(workerPath, { type });
1394
+ this.workerApi = wrap(this.worker);
1395
+ });
1396
+ }
1397
+ /**
1398
+ * Resolve worker script path which works in both ESM and UMD environments
1399
+ */
1400
+ resolveWorkerPath() {
1401
+ if (typeof import.meta !== 'undefined' && import.meta.url) {
1402
+ const currentUrl = new URL(import.meta.url);
1403
+ // e.g. `.../lib/runtime/supervisor.js` -> `.../lib/worker.js`
1404
+ const asRoot = currentUrl.href.replace(/\/runtime\/[^/]+\.js$/, '/worker.js');
1405
+ if (asRoot !== currentUrl.href)
1406
+ return asRoot;
1407
+ // Fallback: keep legacy behavior (same directory)
1408
+ return currentUrl.href.replace(/\/[^/]+\.js$/, '/worker.js');
1409
+ }
1410
+ if (typeof document !== 'undefined') {
1411
+ const scripts = document.getElementsByTagName('script');
1412
+ for (let i = scripts.length - 1; i >= 0; i--) {
1413
+ const src = scripts[i].src;
1414
+ if (src && (src.includes('index.js') || src.includes('index.min.js'))) {
1415
+ return src.replace(/index(\.min)?\.js/, 'worker.js');
1416
+ }
1417
+ }
1418
+ }
1419
+ return './worker.js';
1420
+ }
1344
1421
  }
1345
1422
 
1346
- /**
1347
- * Viewport configuration such as width, height and center point.
1348
- */
1349
- const normalizeViewport = (options) => {
1350
- const { width, height, center } = options;
1351
- const normalizedWidth = width !== null && width !== void 0 ? width : (typeof window !== 'undefined' ? window.innerWidth : 0);
1352
- const normalizedHeight = height !== null && height !== void 0 ? height : (typeof window !== 'undefined' ? window.innerHeight : 0);
1353
- const centerPoint = center !== null && center !== void 0 ? center : [normalizedWidth / 2, normalizedHeight / 2];
1354
- return {
1355
- width: normalizedWidth,
1356
- height: normalizedHeight,
1357
- center: centerPoint,
1358
- };
1359
- };
1360
-
1361
1423
  /**
1362
1424
  * <zh/> 布局基类
1363
1425
  *
@@ -1366,14 +1428,17 @@ const normalizeViewport = (options) => {
1366
1428
  class BaseLayout {
1367
1429
  constructor(options) {
1368
1430
  this.supervisor = null;
1369
- this.initialOptions = mergeOptions(this.getDefaultOptions(), options);
1431
+ this.initialOptions = this.mergeOptions(this.getDefaultOptions(), options);
1370
1432
  }
1371
1433
  get options() {
1372
1434
  return this.runtimeOptions || this.initialOptions;
1373
1435
  }
1436
+ mergeOptions(base, patch) {
1437
+ return Object.assign({}, base, patch || {});
1438
+ }
1374
1439
  execute(data, userOptions) {
1375
1440
  return __awaiter(this, void 0, void 0, function* () {
1376
- this.runtimeOptions = mergeOptions(this.initialOptions, userOptions);
1441
+ this.runtimeOptions = this.mergeOptions(this.initialOptions, userOptions);
1377
1442
  const { node, edge, enableWorker } = this.runtimeOptions;
1378
1443
  this.context = new RuntimeContext(data, { node, edge });
1379
1444
  this.model = this.context.graph;
@@ -1430,71 +1495,6 @@ class BaseLayout {
1430
1495
  class BaseLayoutWithIterations extends BaseLayout {
1431
1496
  }
1432
1497
 
1433
- /**
1434
- * Format value with multiple types into a function that returns a number
1435
- * @param value The value to be formatted
1436
- * @param defaultValue The default value when value is invalid
1437
- * @returns A function that returns a number
1438
- */
1439
- function formatNumberFn(value, defaultValue) {
1440
- // If value is a function, return it directly
1441
- if (isFunction(value)) {
1442
- return value;
1443
- }
1444
- // If value is a number, return a function that returns this number
1445
- if (isNumber(value)) {
1446
- return () => value;
1447
- }
1448
- // For other cases (undefined or invalid values), return default value function
1449
- return () => defaultValue;
1450
- }
1451
- /**
1452
- * Format size config with multiple types into a function that returns a size
1453
- * @param value The value to be formatted
1454
- * @param defaultValue The default value when value is invalid
1455
- * @param resultIsNumber Whether to return a number (max of width/height) or size array
1456
- * @returns A function that returns a size
1457
- */
1458
- function formatSizeFn(value, defaultValue = 10) {
1459
- // If value is undefined, return default value function
1460
- if (!value) {
1461
- return () => defaultValue;
1462
- }
1463
- // If value is a function, return it directly
1464
- if (isFunction(value)) {
1465
- return value;
1466
- }
1467
- // If value is a number, return a function that returns this number
1468
- if (isNumber(value)) {
1469
- return () => value;
1470
- }
1471
- // If value is an array, return max or the array itself
1472
- if (Array.isArray(value)) {
1473
- return () => value;
1474
- }
1475
- // If value is an object with width and height
1476
- if (isObject(value) && value.width && value.height) {
1477
- return () => [value.width, value.height];
1478
- }
1479
- return () => defaultValue;
1480
- }
1481
- /**
1482
- * Format nodeSize and nodeSpacing into a function that returns the total size
1483
- * @param nodeSize The size of the node
1484
- * @param nodeSpacing The spacing around the node
1485
- * @param defaultNodeSize The default node size when value is invalid
1486
- * @returns A function that returns the total size (node size + spacing)
1487
- */
1488
- const formatNodeSizeFn = (nodeSize, nodeSpacing, defaultNodeSize = 10) => {
1489
- const nodeSpacingFunc = formatNumberFn(nodeSpacing, 0);
1490
- const nodeSizeFunc = formatSizeFn(nodeSize, defaultNodeSize);
1491
- return (node) => {
1492
- const size = nodeSizeFunc(node);
1493
- const spacing = nodeSpacingFunc(node);
1494
- return Math.max(...parseSize(size)) + spacing;
1495
- };
1496
- };
1497
-
1498
1498
  /**
1499
1499
  * <zh/> 内部图数据结构,用于 antv-dagre 布局算法
1500
1500
  *
@@ -4934,9 +4934,9 @@ class AntVDagreLayout extends BaseLayout {
4934
4934
  return __awaiter(this, void 0, void 0, function* () {
4935
4935
  const { nodeSize, align, rankdir = 'TB', ranksep, nodesep, edgeLabelSpace, ranker = 'tight-tree', nodeOrder, begin, controlPoints, radial, sortByCombo,
4936
4936
  // focusNode,
4937
- preset, } = options;
4938
- const ranksepfunc = formatNumberFn(ranksep, DEFAULTS_LAYOUT_OPTIONS$8.ranksep);
4939
- const nodesepfunc = formatNumberFn(nodesep, DEFAULTS_LAYOUT_OPTIONS$8.nodesep);
4937
+ preset, ranksepFunc, nodesepFunc, } = options;
4938
+ const ranksepfunc = formatNumberFn(ranksepFunc, ranksep !== null && ranksep !== void 0 ? ranksep : 50);
4939
+ const nodesepfunc = formatNumberFn(nodesepFunc, nodesep !== null && nodesep !== void 0 ? nodesep : 50);
4940
4940
  let horisep = nodesepfunc;
4941
4941
  let vertisep = ranksepfunc;
4942
4942
  if (rankdir === 'LR' || rankdir === 'RL') {
@@ -4951,9 +4951,10 @@ class AntVDagreLayout extends BaseLayout {
4951
4951
  const edges = this.model.edges();
4952
4952
  nodes.forEach((node) => {
4953
4953
  var _a;
4954
- const size = parseSize(nodeSizeFunc(node));
4955
- const verti = vertisep(node);
4956
- const hori = horisep(node);
4954
+ const raw = node._original;
4955
+ const size = parseSize(nodeSizeFunc(raw));
4956
+ const verti = vertisep(raw);
4957
+ const hori = horisep(raw);
4957
4958
  const width = size[0] + 2 * hori;
4958
4959
  const height = size[1] + 2 * verti;
4959
4960
  const layer = (_a = node.data) === null || _a === void 0 ? void 0 : _a.layer;
@@ -5019,6 +5020,7 @@ class AntVDagreLayout extends BaseLayout {
5019
5020
  acyclicer: 'greedy',
5020
5021
  ranker,
5021
5022
  rankdir,
5023
+ nodesep,
5022
5024
  align,
5023
5025
  });
5024
5026
  const layoutTopLeft = [0, 0];
@@ -7123,25 +7125,17 @@ function forceInABox() {
7123
7125
  }
7124
7126
 
7125
7127
  const DEFAULTS_LAYOUT_OPTIONS$6 = {
7126
- centerStrength: 1,
7127
- edgeId: (d) => String(d.id),
7128
- linkDistance: 30,
7129
- edgeStrength: undefined,
7130
- edgeIterations: 1,
7128
+ link: {
7129
+ id: (d) => String(d.id),
7130
+ },
7131
+ manyBody: {
7132
+ strength: -30,
7133
+ },
7131
7134
  preventOverlap: false,
7132
7135
  nodeSize: 10,
7133
7136
  nodeSpacing: 0,
7134
- collideStrength: 1,
7135
- collideIterations: 1,
7136
- nodeStrength: -30,
7137
- distanceMin: undefined,
7138
- distanceMax: undefined,
7139
- theta: undefined,
7140
- alpha: 1,
7141
- alphaMin: 0.001,
7142
- alphaDecay: 1 - Math.pow(0.001, 1 / 300),
7143
- alphaTarget: 0,
7144
- velocityDecay: 0.4,
7137
+ x: false,
7138
+ y: false,
7145
7139
  clustering: false,
7146
7140
  clusterNodeStrength: -1,
7147
7141
  clusterEdgeStrength: 0.1,
@@ -7358,20 +7352,17 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7358
7352
  this.setupClusterForce(simulation, options);
7359
7353
  }
7360
7354
  getCenterOptions(options) {
7361
- if (!options.width ||
7362
- !options.height ||
7363
- options.centerStrength !== undefined) {
7364
- const viewport = normalizeViewport({
7365
- width: options.width,
7366
- height: options.height,
7367
- });
7368
- return assignDefined({}, options.center || {}, {
7369
- x: viewport.width / 2,
7370
- y: viewport.height / 2,
7371
- strength: options.centerStrength,
7372
- });
7373
- }
7374
- return undefined;
7355
+ if (options.center === false)
7356
+ return undefined;
7357
+ const viewport = normalizeViewport({
7358
+ width: options.width,
7359
+ height: options.height,
7360
+ });
7361
+ return assignDefined({}, options.center || {}, {
7362
+ x: viewport.width / 2,
7363
+ y: viewport.height / 2,
7364
+ strength: options.centerStrength,
7365
+ });
7375
7366
  }
7376
7367
  setupCenterForce(simulation, options) {
7377
7368
  const center = this.getCenterOptions(options);
@@ -7395,19 +7386,14 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7395
7386
  }
7396
7387
  }
7397
7388
  getManyBodyOptions(options) {
7398
- if (options.manyBody !== undefined ||
7399
- options.nodeStrength !== undefined ||
7400
- options.distanceMin !== undefined ||
7401
- options.distanceMax !== undefined ||
7402
- options.theta !== undefined) {
7403
- return assignDefined({}, options.manyBody || {}, {
7404
- strength: options.nodeStrength,
7405
- distanceMin: options.distanceMin,
7406
- distanceMax: options.distanceMax,
7407
- theta: options.theta,
7408
- });
7409
- }
7410
- return undefined;
7389
+ if (options.manyBody === false)
7390
+ return undefined;
7391
+ return assignDefined({}, options.manyBody || {}, {
7392
+ strength: options.nodeStrength,
7393
+ distanceMin: options.distanceMin,
7394
+ distanceMax: options.distanceMax,
7395
+ theta: options.theta,
7396
+ });
7411
7397
  }
7412
7398
  setupManyBodyForce(simulation, options) {
7413
7399
  const manyBody = this.getManyBodyOptions(options);
@@ -7433,19 +7419,14 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7433
7419
  }
7434
7420
  }
7435
7421
  getLinkOptions(options) {
7436
- if (options.link ||
7437
- options.edgeId !== undefined ||
7438
- options.linkDistance !== undefined ||
7439
- options.edgeStrength !== undefined ||
7440
- options.edgeIterations !== undefined) {
7441
- return assignDefined({}, options.link || {}, {
7442
- id: options.edgeId,
7443
- distance: options.linkDistance,
7444
- strength: options.edgeStrength,
7445
- iterations: options.edgeIterations,
7446
- });
7447
- }
7448
- return undefined;
7422
+ if (options.link === false)
7423
+ return undefined;
7424
+ return assignDefined({}, options.link || {}, {
7425
+ id: options.edgeId,
7426
+ distance: options.linkDistance,
7427
+ strength: options.edgeStrength,
7428
+ iterations: options.edgeIterations,
7429
+ });
7449
7430
  }
7450
7431
  setupLinkForce(simulation, options) {
7451
7432
  const edges = this.model.edges();
@@ -7472,23 +7453,16 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7472
7453
  }
7473
7454
  }
7474
7455
  getCollisionOptions(options) {
7475
- if (!options.preventOverlap)
7456
+ if (options.preventOverlap === false || options.collide === false)
7476
7457
  return undefined;
7477
- if (options.collide !== undefined ||
7478
- options.nodeSize !== undefined ||
7479
- options.nodeSpacing !== undefined ||
7480
- options.collideStrength !== undefined ||
7481
- options.collideIterations !== undefined) {
7482
- const radius = options.nodeSize || options.nodeSpacing
7483
- ? (d) => formatNodeSizeFn(options.nodeSize, options.nodeSpacing)(d._original) / 2
7484
- : undefined;
7485
- return assignDefined({}, options.collide || {}, {
7486
- radius,
7487
- strength: options.collideStrength,
7488
- iterations: options.collideIterations,
7489
- });
7490
- }
7491
- return undefined;
7458
+ const radius = options.nodeSize || options.nodeSpacing
7459
+ ? (d) => formatNodeSizeFn(options.nodeSize, options.nodeSpacing)(d._original) / 2
7460
+ : undefined;
7461
+ return assignDefined({}, options.collide || {}, {
7462
+ radius: options.collide || radius,
7463
+ strength: options.collideStrength,
7464
+ iterations: options.collideIterations,
7465
+ });
7492
7466
  }
7493
7467
  setupCollisionForce(simulation, options) {
7494
7468
  const collide = this.getCollisionOptions(options);
@@ -7513,18 +7487,13 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7513
7487
  }
7514
7488
  getXForceOptions(options) {
7515
7489
  var _a;
7516
- if (options.x !== undefined ||
7517
- options.forceXPosition !== undefined ||
7518
- options.forceXStrength !== undefined ||
7519
- options.width !== undefined ||
7520
- options.height !== undefined) {
7521
- const center = this.getCenterOptions(options);
7522
- return assignDefined({}, options.x || {}, {
7523
- x: (_a = options.forceXPosition) !== null && _a !== void 0 ? _a : (center && center.x),
7524
- strength: options.forceXStrength,
7525
- });
7526
- }
7527
- return undefined;
7490
+ if (options.x === false)
7491
+ return undefined;
7492
+ const center = this.getCenterOptions(options);
7493
+ return assignDefined({}, options.x || {}, {
7494
+ x: (_a = options.forceXPosition) !== null && _a !== void 0 ? _a : (center && center.x),
7495
+ strength: options.forceXStrength,
7496
+ });
7528
7497
  }
7529
7498
  setupXForce(simulation, options) {
7530
7499
  const x = this.getXForceOptions(options);
@@ -7547,18 +7516,13 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7547
7516
  }
7548
7517
  getYForceOptions(options) {
7549
7518
  var _a;
7550
- if (options.y !== undefined ||
7551
- options.forceYPosition !== undefined ||
7552
- options.forceYStrength !== undefined ||
7553
- options.width !== undefined ||
7554
- options.height !== undefined) {
7555
- const center = this.getCenterOptions(options);
7556
- return assignDefined({}, options.y || {}, {
7557
- y: (_a = options.forceYPosition) !== null && _a !== void 0 ? _a : (center && center.y),
7558
- strength: options.forceYStrength,
7559
- });
7560
- }
7561
- return undefined;
7519
+ if (options.y === false)
7520
+ return undefined;
7521
+ const center = this.getCenterOptions(options);
7522
+ return assignDefined({}, options.y || {}, {
7523
+ y: (_a = options.forceYPosition) !== null && _a !== void 0 ? _a : (center && center.y),
7524
+ strength: options.forceYStrength,
7525
+ });
7562
7526
  }
7563
7527
  setupYForce(simulation, options) {
7564
7528
  const y = this.getYForceOptions(options);