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

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.
Files changed (38) hide show
  1. package/dist/index.js +1207 -1247
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.min.js +2 -2
  4. package/dist/index.min.js.map +1 -1
  5. package/dist/worker.js +1 -1
  6. package/dist/worker.js.map +1 -1
  7. package/lib/algorithm/antv-dagre/index.js +10 -8
  8. package/lib/algorithm/antv-dagre/index.js.map +1 -1
  9. package/lib/algorithm/antv-dagre/types.d.ts +25 -3
  10. package/lib/algorithm/base-layout.d.ts +1 -0
  11. package/lib/algorithm/base-layout.js +5 -3
  12. package/lib/algorithm/base-layout.js.map +1 -1
  13. package/lib/algorithm/circular/index.js +2 -2
  14. package/lib/algorithm/circular/index.js.map +1 -1
  15. package/lib/algorithm/combo-combined/index.js +4 -4
  16. package/lib/algorithm/combo-combined/index.js.map +1 -1
  17. package/lib/algorithm/d3-force/index.d.ts +2 -2
  18. package/lib/algorithm/d3-force/index.js +60 -104
  19. package/lib/algorithm/d3-force/index.js.map +1 -1
  20. package/lib/index.d.ts +1 -1
  21. package/lib/index.js +1 -1
  22. package/lib/util/object.d.ts +1 -2
  23. package/lib/util/object.js +1 -4
  24. package/lib/util/object.js.map +1 -1
  25. package/lib/util/order.d.ts +1 -1
  26. package/lib/util/order.js +4 -1
  27. package/lib/util/order.js.map +1 -1
  28. package/lib/worker.js +768 -807
  29. package/lib/worker.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/algorithm/antv-dagre/index.ts +11 -12
  32. package/src/algorithm/antv-dagre/types.ts +25 -3
  33. package/src/algorithm/base-layout.ts +13 -8
  34. package/src/algorithm/circular/index.ts +2 -2
  35. package/src/algorithm/combo-combined/index.ts +5 -5
  36. package/src/algorithm/d3-force/index.ts +69 -122
  37. package/src/util/object.ts +0 -4
  38. package/src/util/order.ts +4 -0
package/lib/worker.js CHANGED
@@ -663,701 +663,766 @@ 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, order = 'desc') {
861
+ return sort$1(model, (nodeA, nodeB) => {
862
+ const degreeA = model.degree(nodeA.id);
863
+ const degreeB = model.degree(nodeB.id);
864
+ if (order === 'asc') {
865
+ return degreeA - degreeB; // ascending order
866
+ }
867
+ return degreeB - degreeA; // descending order
868
+ });
869
+ }
870
+ /**
871
+ * 按 ID 排序
872
+ */
873
+ function orderById(model) {
874
+ return sort$1(model, (nodeA, nodeB) => {
875
+ const idA = nodeA.id;
876
+ const idB = nodeB.id;
877
+ if (typeof idA === 'number' && typeof idB === 'number') {
878
+ return idA - idB;
879
+ }
880
+ return String(idA).localeCompare(String(idB));
881
+ });
882
+ }
883
+ /**
884
+ * 按自定义比较函数排序
885
+ */
886
+ function orderBySorter(model, sorter) {
887
+ return sort$1(model, (nodeA, nodeB) => {
888
+ const a = model.originalNode(nodeA.id);
889
+ const b = model.originalNode(nodeB.id);
890
+ return sorter(a, b);
891
+ });
892
+ }
893
+ /**
894
+ * Order nodes according to graph topology
895
+ */
896
+ function orderByTopology(model, directed = false) {
897
+ const n = model.nodeCount();
898
+ if (n === 0)
899
+ return model;
900
+ const nodes = model.nodes();
901
+ const orderedNodes = [nodes[0]];
902
+ const pickFlags = {};
903
+ pickFlags[nodes[0].id] = true;
904
+ let k = 0;
905
+ let i = 0;
906
+ model.forEachNode((node) => {
907
+ if (i !== 0) {
908
+ const currentDegree = model.degree(node.id, 'both');
909
+ const nextDegree = i < n - 1 ? model.degree(nodes[i + 1].id, 'both') : 0;
910
+ const currentNodeId = orderedNodes[k].id;
911
+ const isNeighbor = model
912
+ .neighbors(currentNodeId, 'both')
913
+ .includes(node.id);
914
+ if ((i === n - 1 || currentDegree !== nextDegree || isNeighbor) &&
915
+ !pickFlags[node.id]) {
916
+ orderedNodes.push(node);
917
+ pickFlags[node.id] = true;
918
+ k++;
832
919
  }
833
- this.outAdjacencyCache.get(edge.source).add(edge.target);
834
- if (!this.inAdjacencyCache.has(edge.target)) {
835
- this.inAdjacencyCache.set(edge.target, new Set());
920
+ else {
921
+ const children = directed
922
+ ? model.successors(currentNodeId)
923
+ : model.neighbors(currentNodeId);
924
+ let foundChild = false;
925
+ for (let j = 0; j < children.length; j++) {
926
+ const childId = children[j];
927
+ const child = model.node(childId);
928
+ if (child &&
929
+ model.degree(childId) === model.degree(node.id) &&
930
+ !pickFlags[childId]) {
931
+ orderedNodes.push(child);
932
+ pickFlags[childId] = true;
933
+ foundChild = true;
934
+ break;
935
+ }
936
+ }
937
+ let ii = 0;
938
+ while (!foundChild) {
939
+ if (!pickFlags[nodes[ii].id]) {
940
+ orderedNodes.push(nodes[ii]);
941
+ pickFlags[nodes[ii].id] = true;
942
+ foundChild = true;
943
+ }
944
+ ii++;
945
+ if (ii === n) {
946
+ break;
947
+ }
948
+ }
836
949
  }
837
- this.inAdjacencyCache.get(edge.target).add(edge.source);
838
950
  }
951
+ i++;
952
+ });
953
+ // Update model with ordered nodes
954
+ model.setNodeOrder(orderedNodes);
955
+ return model;
956
+ }
957
+
958
+ function parsePoint(point) {
959
+ var _a;
960
+ return [point.x, point.y, (_a = point.z) !== null && _a !== void 0 ? _a : 0];
961
+ }
962
+
963
+ function parseSize(size) {
964
+ if (!size)
965
+ return [0, 0, 0];
966
+ if (isNumber(size))
967
+ return [size, size, size];
968
+ else if (Array.isArray(size) && size.length === 0)
969
+ return [0, 0, 0];
970
+ const [x, y = x, z = x] = size;
971
+ return [x, y, z];
972
+ }
973
+
974
+ /**
975
+ * Viewport configuration such as width, height and center point.
976
+ */
977
+ const normalizeViewport = (options) => {
978
+ const { width, height, center } = options;
979
+ const normalizedWidth = width !== null && width !== void 0 ? width : (typeof window !== 'undefined' ? window.innerWidth : 0);
980
+ const normalizedHeight = height !== null && height !== void 0 ? height : (typeof window !== 'undefined' ? window.innerHeight : 0);
981
+ const centerPoint = center !== null && center !== void 0 ? center : [normalizedWidth / 2, normalizedHeight / 2];
982
+ return {
983
+ width: normalizedWidth,
984
+ height: normalizedHeight,
985
+ center: centerPoint,
986
+ };
987
+ };
988
+
989
+ /**
990
+ * Format value with multiple types into a function that returns a number
991
+ * @param value The value to be formatted
992
+ * @param defaultValue The default value when value is invalid
993
+ * @returns A function that returns a number
994
+ */
995
+ function formatNumberFn(value, defaultValue) {
996
+ // If value is a function, return it directly
997
+ if (isFunction(value)) {
998
+ return value;
839
999
  }
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();
1000
+ // If value is a number, return a function that returns this number
1001
+ if (isNumber(value)) {
1002
+ return () => value;
855
1003
  }
1004
+ // For other cases (undefined or invalid values), return default value function
1005
+ return () => defaultValue;
856
1006
  }
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');
1007
+ /**
1008
+ * Format size config with multiple types into a function that returns a size
1009
+ * @param value The value to be formatted
1010
+ * @param defaultValue The default value when value is invalid
1011
+ * @param resultIsNumber Whether to return a number (max of width/height) or size array
1012
+ * @returns A function that returns a size
1013
+ */
1014
+ function formatSizeFn(value, defaultValue = 10) {
1015
+ // If value is undefined, return default value function
1016
+ if (!value) {
1017
+ return () => defaultValue;
874
1018
  }
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);
1019
+ // If value is a function, return it directly
1020
+ if (isFunction(value)) {
1021
+ return value;
897
1022
  }
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);
1023
+ // If value is a number, return a function that returns this number
1024
+ if (isNumber(value)) {
1025
+ return () => value;
926
1026
  }
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
- });
1027
+ // If value is an array, return max or the array itself
1028
+ if (Array.isArray(value)) {
1029
+ return () => value;
1030
+ }
1031
+ // If value is an object with width and height
1032
+ if (isObject(value) && value.width && value.height) {
1033
+ return () => [value.width, value.height];
1034
+ }
1035
+ return () => defaultValue;
941
1036
  }
1037
+ /**
1038
+ * Format nodeSize and nodeSpacing into a function that returns the total size
1039
+ * @param nodeSize The size of the node
1040
+ * @param nodeSpacing The spacing around the node
1041
+ * @param defaultNodeSize The default node size when value is invalid
1042
+ * @returns A function that returns the total size (node size + spacing)
1043
+ */
1044
+ const formatNodeSizeFn = (nodeSize, nodeSpacing, defaultNodeSize = 10) => {
1045
+ const nodeSpacingFunc = formatNumberFn(nodeSpacing, 0);
1046
+ const nodeSizeFunc = formatSizeFn(nodeSize, defaultNodeSize);
1047
+ return (node) => {
1048
+ const size = nodeSizeFunc(node);
1049
+ const spacing = nodeSpacingFunc(node);
1050
+ return Math.max(...parseSize(size)) + spacing;
1051
+ };
1052
+ };
942
1053
 
943
- class RuntimeContext {
1054
+ class GraphLib {
944
1055
  constructor(data, options = {}) {
945
- this.graph = new GraphLib(data, options);
1056
+ this.edgeIdCounter = new Map();
1057
+ this.nodeMap = extractNodeData(data.nodes, options.node);
1058
+ this.edgeMap = extractEdgeData(data.edges || [], options.edge, this.getEdgeId.bind(this));
946
1059
  }
947
- export() {
948
- return this.graph.data();
1060
+ data() {
1061
+ return { nodes: this.nodeMap, edges: this.edgeMap };
1062
+ }
1063
+ replace(result) {
1064
+ this.nodeMap = result.nodes;
1065
+ this.edgeMap = result.edges;
1066
+ this.clearCache();
1067
+ }
1068
+ nodes() {
1069
+ return Array.from(this.nodeMap.values());
1070
+ }
1071
+ node(id) {
1072
+ return this.nodeMap.get(id);
1073
+ }
1074
+ nodeAt(index) {
1075
+ if (!this.indexNodeCache) {
1076
+ this.buildNodeIndexCache();
1077
+ }
1078
+ const nodeId = this.indexNodeCache.get(index);
1079
+ return nodeId ? this.nodeMap.get(nodeId) : undefined;
1080
+ }
1081
+ nodeIndexOf(id) {
1082
+ var _a;
1083
+ if (!this.nodeIndexCache) {
1084
+ this.buildNodeIndexCache();
1085
+ }
1086
+ return (_a = this.nodeIndexCache.get(id)) !== null && _a !== void 0 ? _a : -1;
1087
+ }
1088
+ firstNode() {
1089
+ return this.nodeMap.values().next().value;
1090
+ }
1091
+ forEachNode(callback) {
1092
+ let i = 0;
1093
+ this.nodeMap.forEach((node) => callback(node, i++));
1094
+ }
1095
+ originalNode(id) {
1096
+ const node = this.nodeMap.get(id);
1097
+ return node === null || node === void 0 ? void 0 : node._original;
1098
+ }
1099
+ nodeCount() {
1100
+ return this.nodeMap.size;
1101
+ }
1102
+ edges() {
1103
+ return Array.from(this.edgeMap.values());
949
1104
  }
950
- replace(result) {
951
- this.graph.replace(result);
1105
+ edge(id) {
1106
+ return this.edgeMap.get(id);
952
1107
  }
953
- forEachNode(callback) {
954
- this.graph.forEachNode(callback);
1108
+ firstEdge() {
1109
+ return this.edgeMap.values().next().value;
955
1110
  }
956
1111
  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
- });
1112
+ let i = 0;
1113
+ this.edgeMap.forEach((edge) => callback(edge, i++));
962
1114
  }
963
- destroy() {
964
- this.graph.destroy();
1115
+ originalEdge(id) {
1116
+ const edge = this.edgeMap.get(id);
1117
+ return edge === null || edge === void 0 ? void 0 : edge._original;
965
1118
  }
966
- }
967
-
968
- class Supervisor {
969
- constructor() {
970
- this.worker = null;
971
- this.workerApi = null;
1119
+ edgeCount() {
1120
+ return this.edgeMap.size;
972
1121
  }
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
- });
1122
+ getEdgeId(edge) {
1123
+ if (edge.id)
1124
+ return edge.id;
1125
+ const baseId = `${edge.source}-${edge.target}`;
1126
+ const count = this.edgeIdCounter.get(baseId) || 0;
1127
+ const id = count === 0 ? baseId : `${baseId}-${count}`;
1128
+ this.edgeIdCounter.set(baseId, count + 1);
1129
+ return id;
986
1130
  }
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;
1131
+ degree(nodeId, direction = 'both') {
1132
+ if (!this.degreeCache) {
1133
+ this.buildDegreeCache();
998
1134
  }
1135
+ const degree = this.degreeCache.get(nodeId);
1136
+ if (!degree)
1137
+ return 0;
1138
+ return degree[direction];
999
1139
  }
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');
1140
+ neighbors(nodeId, direction = 'both') {
1141
+ if (!this.outAdjacencyCache || !this.inAdjacencyCache) {
1142
+ this.buildAdjacencyCache();
1024
1143
  }
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
- }
1144
+ if (direction === 'out') {
1145
+ return Array.from(this.outAdjacencyCache.get(nodeId) || []);
1033
1146
  }
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;
1147
+ if (direction === 'in') {
1148
+ return Array.from(this.inAdjacencyCache.get(nodeId) || []);
1052
1149
  }
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;
1150
+ if (this.bothAdjacencyCache) {
1151
+ return Array.from(this.bothAdjacencyCache.get(nodeId) || []);
1092
1152
  }
1093
- result[i] = newRow;
1153
+ const inSet = this.inAdjacencyCache.get(nodeId);
1154
+ const outSet = this.outAdjacencyCache.get(nodeId);
1155
+ if (!inSet && !outSet)
1156
+ return [];
1157
+ if (!inSet)
1158
+ return Array.from(outSet);
1159
+ if (!outSet)
1160
+ return Array.from(inSet);
1161
+ const merged = new Set();
1162
+ inSet.forEach((id) => merged.add(id));
1163
+ outSet.forEach((id) => merged.add(id));
1164
+ return Array.from(merged);
1094
1165
  }
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);
1166
+ successors(nodeId) {
1167
+ return this.neighbors(nodeId, 'out');
1113
1168
  }
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]);
1169
+ predecessors(nodeId) {
1170
+ return this.neighbors(nodeId, 'in');
1171
+ }
1172
+ setNodeOrder(nodes) {
1173
+ const next = new Map();
1174
+ for (const node of nodes)
1175
+ next.set(node.id, node);
1176
+ this.nodeMap = next;
1177
+ this.nodeIndexCache = undefined;
1178
+ this.indexNodeCache = undefined;
1179
+ }
1180
+ clearCache() {
1181
+ this.degreeCache = undefined;
1182
+ this.inAdjacencyCache = undefined;
1183
+ this.outAdjacencyCache = undefined;
1184
+ this.bothAdjacencyCache = undefined;
1185
+ this.nodeIndexCache = undefined;
1186
+ this.indexNodeCache = undefined;
1187
+ }
1188
+ buildDegreeCache() {
1189
+ this.degreeCache = new Map();
1190
+ for (const edge of this.edges()) {
1191
+ const { source, target } = edge;
1192
+ if (edge.source === edge.target)
1193
+ continue;
1194
+ if (!this.degreeCache.has(source)) {
1195
+ this.degreeCache.set(source, { in: 0, out: 0, both: 0 });
1196
+ }
1197
+ const sourceDeg = this.degreeCache.get(edge.source);
1198
+ if (sourceDeg) {
1199
+ sourceDeg.out++;
1200
+ sourceDeg.both++;
1201
+ }
1202
+ if (!this.degreeCache.has(target)) {
1203
+ this.degreeCache.set(target, { in: 0, out: 0, both: 0 });
1204
+ }
1205
+ const targetDeg = this.degreeCache.get(edge.target);
1206
+ if (targetDeg) {
1207
+ targetDeg.in++;
1208
+ targetDeg.both++;
1137
1209
  }
1138
1210
  }
1139
1211
  }
1140
- return dist;
1141
- }
1142
- class MinHeap {
1143
- constructor() {
1144
- this.data = [];
1212
+ buildAdjacencyCache() {
1213
+ this.inAdjacencyCache = new Map();
1214
+ this.outAdjacencyCache = new Map();
1215
+ for (const edge of this.edges()) {
1216
+ if (!this.nodeMap.has(edge.source) || !this.nodeMap.has(edge.target))
1217
+ continue;
1218
+ if (!this.outAdjacencyCache.has(edge.source)) {
1219
+ this.outAdjacencyCache.set(edge.source, new Set());
1220
+ }
1221
+ this.outAdjacencyCache.get(edge.source).add(edge.target);
1222
+ if (!this.inAdjacencyCache.has(edge.target)) {
1223
+ this.inAdjacencyCache.set(edge.target, new Set());
1224
+ }
1225
+ this.inAdjacencyCache.get(edge.target).add(edge.source);
1226
+ }
1145
1227
  }
1146
- push(item) {
1147
- this.data.push(item);
1148
- this.bubbleUp(this.data.length - 1);
1228
+ buildNodeIndexCache() {
1229
+ this.nodeIndexCache = new Map();
1230
+ this.indexNodeCache = new Map();
1231
+ let index = 0;
1232
+ this.nodeMap.forEach((_node, nodeId) => {
1233
+ this.nodeIndexCache.set(nodeId, index);
1234
+ this.indexNodeCache.set(index, nodeId);
1235
+ index++;
1236
+ });
1149
1237
  }
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;
1238
+ destroy() {
1239
+ this.clearCache();
1240
+ this.nodeMap.clear();
1241
+ this.edgeMap.clear();
1242
+ this.edgeIdCounter.clear();
1158
1243
  }
1159
- empty() {
1160
- return this.data.length === 0;
1244
+ }
1245
+ const nodeFields = [
1246
+ 'id',
1247
+ 'x',
1248
+ 'y',
1249
+ 'z',
1250
+ 'vx',
1251
+ 'vy',
1252
+ 'vz',
1253
+ 'fx',
1254
+ 'fy',
1255
+ 'fz',
1256
+ 'parentId',
1257
+ ];
1258
+ const edgeFields = ['id', 'source', 'target', 'points'];
1259
+ function extractNodeData(nodes, node) {
1260
+ if (!nodes) {
1261
+ throw new Error('Data.nodes is required');
1161
1262
  }
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;
1263
+ const result = new Map();
1264
+ for (const datum of nodes) {
1265
+ const nodeData = { _original: datum };
1266
+ for (const field of nodeFields) {
1267
+ const value = datum[field];
1268
+ if (isNil(value))
1269
+ continue;
1270
+ nodeData[field] = value;
1170
1271
  }
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;
1272
+ if (node) {
1273
+ const customFields = node(datum);
1274
+ for (const key in customFields) {
1275
+ const value = customFields[key];
1276
+ if (isNil(value))
1277
+ continue;
1278
+ nodeData[key] = value;
1279
+ }
1187
1280
  }
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
- });
1281
+ if (isNil(nodeData.id)) {
1282
+ throw new Error(`Node is missing id field`);
1218
1283
  }
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
- });
1284
+ result.set(nodeData.id, nodeData);
1285
+ }
1286
+ return result;
1241
1287
  }
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;
1288
+ function extractEdgeData(edges, edge, getEdgeId) {
1289
+ const result = new Map();
1290
+ for (const datum of edges) {
1291
+ const edgeData = { _original: datum };
1292
+ for (const field of edgeFields) {
1293
+ const value = datum[field];
1294
+ if (isNil(value))
1295
+ continue;
1296
+ edgeData[field] = value;
1251
1297
  }
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
- }
1298
+ if (edge) {
1299
+ const customFields = edge(datum);
1300
+ for (const key in customFields) {
1301
+ const value = customFields[key];
1302
+ if (isNil(value))
1303
+ continue;
1304
+ edgeData[key] = value;
1321
1305
  }
1322
1306
  }
1323
- i++;
1307
+ if (isNil(edgeData.source) || isNil(edgeData.target)) {
1308
+ throw new Error(`Edge is missing source or target field`);
1309
+ }
1310
+ if (isNil(edgeData.id)) {
1311
+ edgeData.id = getEdgeId === null || getEdgeId === void 0 ? void 0 : getEdgeId(datum);
1312
+ }
1313
+ result.set(edgeData.id, edgeData);
1314
+ }
1315
+ return result;
1316
+ }
1317
+ function initNodePosition(model, width, height, dimensions = 2) {
1318
+ model.forEachNode((node) => {
1319
+ if (isNil(node.x)) {
1320
+ node.x = Math.random() * width;
1321
+ }
1322
+ if (isNil(node.y)) {
1323
+ node.y = Math.random() * height;
1324
+ }
1325
+ if (dimensions === 3 && isNil(node.z)) {
1326
+ node.z = Math.random() * Math.min(width, height);
1327
+ }
1324
1328
  });
1325
- // Update model with ordered nodes
1326
- model.setNodeOrder(orderedNodes);
1327
- return model;
1328
1329
  }
1329
1330
 
1330
- function parsePoint(point) {
1331
- var _a;
1332
- return [point.x, point.y, (_a = point.z) !== null && _a !== void 0 ? _a : 0];
1331
+ class RuntimeContext {
1332
+ constructor(data, options = {}) {
1333
+ this.graph = new GraphLib(data, options);
1334
+ }
1335
+ export() {
1336
+ return this.graph.data();
1337
+ }
1338
+ replace(result) {
1339
+ this.graph.replace(result);
1340
+ }
1341
+ forEachNode(callback) {
1342
+ this.graph.forEachNode(callback);
1343
+ }
1344
+ forEachEdge(callback) {
1345
+ this.graph.forEachEdge((edge, i) => {
1346
+ edge.sourceNode = this.graph.node(edge.source);
1347
+ edge.targetNode = this.graph.node(edge.target);
1348
+ callback(edge, i);
1349
+ });
1350
+ }
1351
+ destroy() {
1352
+ this.graph.destroy();
1353
+ }
1333
1354
  }
1334
1355
 
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];
1356
+ class Supervisor {
1357
+ constructor() {
1358
+ this.worker = null;
1359
+ this.workerApi = null;
1360
+ }
1361
+ /**
1362
+ * Execute layout in worker
1363
+ */
1364
+ execute(layoutId, data, options) {
1365
+ return __awaiter(this, void 0, void 0, function* () {
1366
+ if (!this.worker) {
1367
+ yield this.initWorker();
1368
+ }
1369
+ if (!this.workerApi) {
1370
+ throw new Error('Worker API not initialized');
1371
+ }
1372
+ return yield this.workerApi.execute(layoutId, data, options);
1373
+ });
1374
+ }
1375
+ /**
1376
+ * Destroy worker
1377
+ */
1378
+ destroy() {
1379
+ if (this.workerApi) {
1380
+ this.workerApi.destroy();
1381
+ }
1382
+ if (this.worker) {
1383
+ this.worker.terminate();
1384
+ this.worker = null;
1385
+ this.workerApi = null;
1386
+ }
1387
+ }
1388
+ /**
1389
+ * Initialize worker
1390
+ */
1391
+ initWorker() {
1392
+ return __awaiter(this, void 0, void 0, function* () {
1393
+ const workerPath = this.resolveWorkerPath();
1394
+ const isESM = workerPath.includes('/lib/') || workerPath.endsWith('.mjs');
1395
+ const type = isESM ? 'module' : 'classic';
1396
+ this.worker = new Worker(workerPath, { type });
1397
+ this.workerApi = wrap(this.worker);
1398
+ });
1399
+ }
1400
+ /**
1401
+ * Resolve worker script path which works in both ESM and UMD environments
1402
+ */
1403
+ resolveWorkerPath() {
1404
+ if (typeof import.meta !== 'undefined' && import.meta.url) {
1405
+ const currentUrl = new URL(import.meta.url);
1406
+ // e.g. `.../lib/runtime/supervisor.js` -> `.../lib/worker.js`
1407
+ const asRoot = currentUrl.href.replace(/\/runtime\/[^/]+\.js$/, '/worker.js');
1408
+ if (asRoot !== currentUrl.href)
1409
+ return asRoot;
1410
+ // Fallback: keep legacy behavior (same directory)
1411
+ return currentUrl.href.replace(/\/[^/]+\.js$/, '/worker.js');
1412
+ }
1413
+ if (typeof document !== 'undefined') {
1414
+ const scripts = document.getElementsByTagName('script');
1415
+ for (let i = scripts.length - 1; i >= 0; i--) {
1416
+ const src = scripts[i].src;
1417
+ if (src && (src.includes('index.js') || src.includes('index.min.js'))) {
1418
+ return src.replace(/index(\.min)?\.js/, 'worker.js');
1419
+ }
1420
+ }
1421
+ }
1422
+ return './worker.js';
1423
+ }
1344
1424
  }
1345
1425
 
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
1426
  /**
1362
1427
  * <zh/> 布局基类
1363
1428
  *
@@ -1366,14 +1431,17 @@ const normalizeViewport = (options) => {
1366
1431
  class BaseLayout {
1367
1432
  constructor(options) {
1368
1433
  this.supervisor = null;
1369
- this.initialOptions = mergeOptions(this.getDefaultOptions(), options);
1434
+ this.initialOptions = this.mergeOptions(this.getDefaultOptions(), options);
1370
1435
  }
1371
1436
  get options() {
1372
1437
  return this.runtimeOptions || this.initialOptions;
1373
1438
  }
1439
+ mergeOptions(base, patch) {
1440
+ return Object.assign({}, base, patch || {});
1441
+ }
1374
1442
  execute(data, userOptions) {
1375
1443
  return __awaiter(this, void 0, void 0, function* () {
1376
- this.runtimeOptions = mergeOptions(this.initialOptions, userOptions);
1444
+ this.runtimeOptions = this.mergeOptions(this.initialOptions, userOptions);
1377
1445
  const { node, edge, enableWorker } = this.runtimeOptions;
1378
1446
  this.context = new RuntimeContext(data, { node, edge });
1379
1447
  this.model = this.context.graph;
@@ -1430,71 +1498,6 @@ class BaseLayout {
1430
1498
  class BaseLayoutWithIterations extends BaseLayout {
1431
1499
  }
1432
1500
 
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
1501
  /**
1499
1502
  * <zh/> 内部图数据结构,用于 antv-dagre 布局算法
1500
1503
  *
@@ -4934,9 +4937,9 @@ class AntVDagreLayout extends BaseLayout {
4934
4937
  return __awaiter(this, void 0, void 0, function* () {
4935
4938
  const { nodeSize, align, rankdir = 'TB', ranksep, nodesep, edgeLabelSpace, ranker = 'tight-tree', nodeOrder, begin, controlPoints, radial, sortByCombo,
4936
4939
  // focusNode,
4937
- preset, } = options;
4938
- const ranksepfunc = formatNumberFn(ranksep, DEFAULTS_LAYOUT_OPTIONS$8.ranksep);
4939
- const nodesepfunc = formatNumberFn(nodesep, DEFAULTS_LAYOUT_OPTIONS$8.nodesep);
4940
+ preset, ranksepFunc, nodesepFunc, } = options;
4941
+ const ranksepfunc = formatNumberFn(ranksepFunc, ranksep !== null && ranksep !== void 0 ? ranksep : 50);
4942
+ const nodesepfunc = formatNumberFn(nodesepFunc, nodesep !== null && nodesep !== void 0 ? nodesep : 50);
4940
4943
  let horisep = nodesepfunc;
4941
4944
  let vertisep = ranksepfunc;
4942
4945
  if (rankdir === 'LR' || rankdir === 'RL') {
@@ -4951,9 +4954,10 @@ class AntVDagreLayout extends BaseLayout {
4951
4954
  const edges = this.model.edges();
4952
4955
  nodes.forEach((node) => {
4953
4956
  var _a;
4954
- const size = parseSize(nodeSizeFunc(node));
4955
- const verti = vertisep(node);
4956
- const hori = horisep(node);
4957
+ const raw = node._original;
4958
+ const size = parseSize(nodeSizeFunc(raw));
4959
+ const verti = vertisep(raw);
4960
+ const hori = horisep(raw);
4957
4961
  const width = size[0] + 2 * hori;
4958
4962
  const height = size[1] + 2 * verti;
4959
4963
  const layer = (_a = node.data) === null || _a === void 0 ? void 0 : _a.layer;
@@ -5019,6 +5023,7 @@ class AntVDagreLayout extends BaseLayout {
5019
5023
  acyclicer: 'greedy',
5020
5024
  ranker,
5021
5025
  rankdir,
5026
+ nodesep,
5022
5027
  align,
5023
5028
  });
5024
5029
  const layoutTopLeft = [0, 0];
@@ -5277,7 +5282,7 @@ class CircularLayout extends BaseLayout {
5277
5282
  }
5278
5283
  else if (ordering === 'degree') {
5279
5284
  // layout according to the descent order of degrees
5280
- orderByDegree(this.model);
5285
+ orderByDegree(this.model, 'asc');
5281
5286
  }
5282
5287
  let { radius, startRadius, endRadius } = this.options;
5283
5288
  const nodes = this.model.nodes();
@@ -7123,25 +7128,17 @@ function forceInABox() {
7123
7128
  }
7124
7129
 
7125
7130
  const DEFAULTS_LAYOUT_OPTIONS$6 = {
7126
- centerStrength: 1,
7127
- edgeId: (d) => String(d.id),
7128
- linkDistance: 30,
7129
- edgeStrength: undefined,
7130
- edgeIterations: 1,
7131
+ link: {
7132
+ id: (d) => String(d.id),
7133
+ },
7134
+ manyBody: {
7135
+ strength: -30,
7136
+ },
7131
7137
  preventOverlap: false,
7132
7138
  nodeSize: 10,
7133
7139
  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,
7140
+ x: false,
7141
+ y: false,
7145
7142
  clustering: false,
7146
7143
  clusterNodeStrength: -1,
7147
7144
  clusterEdgeStrength: 0.1,
@@ -7256,14 +7253,7 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7256
7253
  });
7257
7254
  }
7258
7255
  parseOptions(options) {
7259
- var _a, _b;
7260
7256
  const _ = options;
7261
- // process nodeSize
7262
- if (_.collide && ((_a = _.collide) === null || _a === void 0 ? void 0 : _a.radius) === undefined) {
7263
- _.collide = _.collide || {};
7264
- // @ts-ignore
7265
- _.collide.radius = (_b = _.nodeSize) !== null && _b !== void 0 ? _b : 10;
7266
- }
7267
7257
  // process iterations
7268
7258
  if (_.iterations === undefined) {
7269
7259
  if (_.link && _.link.iterations === undefined) {
@@ -7358,20 +7348,17 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7358
7348
  this.setupClusterForce(simulation, options);
7359
7349
  }
7360
7350
  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;
7351
+ if (options.center === false)
7352
+ return undefined;
7353
+ const viewport = normalizeViewport({
7354
+ width: options.width,
7355
+ height: options.height,
7356
+ });
7357
+ return assignDefined({}, options.center || {}, {
7358
+ x: viewport.width / 2,
7359
+ y: viewport.height / 2,
7360
+ strength: options.centerStrength,
7361
+ });
7375
7362
  }
7376
7363
  setupCenterForce(simulation, options) {
7377
7364
  const center = this.getCenterOptions(options);
@@ -7395,19 +7382,14 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7395
7382
  }
7396
7383
  }
7397
7384
  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;
7385
+ if (options.manyBody === false)
7386
+ return undefined;
7387
+ return assignDefined({}, options.manyBody || {}, {
7388
+ strength: options.nodeStrength,
7389
+ distanceMin: options.distanceMin,
7390
+ distanceMax: options.distanceMax,
7391
+ theta: options.theta,
7392
+ });
7411
7393
  }
7412
7394
  setupManyBodyForce(simulation, options) {
7413
7395
  const manyBody = this.getManyBodyOptions(options);
@@ -7433,19 +7415,14 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7433
7415
  }
7434
7416
  }
7435
7417
  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;
7418
+ if (options.link === false)
7419
+ return undefined;
7420
+ return assignDefined({}, options.link || {}, {
7421
+ id: options.edgeId,
7422
+ distance: options.linkDistance,
7423
+ strength: options.edgeStrength,
7424
+ iterations: options.edgeIterations,
7425
+ });
7449
7426
  }
7450
7427
  setupLinkForce(simulation, options) {
7451
7428
  const edges = this.model.edges();
@@ -7472,23 +7449,17 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7472
7449
  }
7473
7450
  }
7474
7451
  getCollisionOptions(options) {
7475
- if (!options.preventOverlap)
7452
+ if (options.preventOverlap === false &&
7453
+ (options.collide === false || options.collide === undefined))
7476
7454
  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;
7455
+ const radius = options.nodeSize || options.nodeSpacing
7456
+ ? (d) => formatNodeSizeFn(options.nodeSize, options.nodeSpacing)(d._original) / 2
7457
+ : undefined;
7458
+ return assignDefined({}, options.collide || {}, {
7459
+ radius: (options.collide && options.collide.radius) || radius,
7460
+ strength: options.collideStrength,
7461
+ iterations: options.collideIterations,
7462
+ });
7492
7463
  }
7493
7464
  setupCollisionForce(simulation, options) {
7494
7465
  const collide = this.getCollisionOptions(options);
@@ -7513,18 +7484,13 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7513
7484
  }
7514
7485
  getXForceOptions(options) {
7515
7486
  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;
7487
+ if (options.x === false)
7488
+ return undefined;
7489
+ const center = this.getCenterOptions(options);
7490
+ return assignDefined({}, options.x || {}, {
7491
+ x: (_a = options.forceXPosition) !== null && _a !== void 0 ? _a : (center && center.x),
7492
+ strength: options.forceXStrength,
7493
+ });
7528
7494
  }
7529
7495
  setupXForce(simulation, options) {
7530
7496
  const x = this.getXForceOptions(options);
@@ -7547,18 +7513,13 @@ class D3ForceLayout extends BaseLayoutWithIterations {
7547
7513
  }
7548
7514
  getYForceOptions(options) {
7549
7515
  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;
7516
+ if (options.y === false)
7517
+ return undefined;
7518
+ const center = this.getCenterOptions(options);
7519
+ return assignDefined({}, options.y || {}, {
7520
+ y: (_a = options.forceYPosition) !== null && _a !== void 0 ? _a : (center && center.y),
7521
+ strength: options.forceYStrength,
7522
+ });
7562
7523
  }
7563
7524
  setupYForce(simulation, options) {
7564
7525
  const y = this.getYForceOptions(options);