@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.
- package/dist/index.js +1207 -1247
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/lib/algorithm/antv-dagre/index.js +10 -8
- package/lib/algorithm/antv-dagre/index.js.map +1 -1
- package/lib/algorithm/antv-dagre/types.d.ts +25 -3
- package/lib/algorithm/base-layout.d.ts +1 -0
- package/lib/algorithm/base-layout.js +5 -3
- package/lib/algorithm/base-layout.js.map +1 -1
- package/lib/algorithm/circular/index.js +2 -2
- package/lib/algorithm/circular/index.js.map +1 -1
- package/lib/algorithm/combo-combined/index.js +4 -4
- package/lib/algorithm/combo-combined/index.js.map +1 -1
- package/lib/algorithm/d3-force/index.d.ts +2 -2
- package/lib/algorithm/d3-force/index.js +60 -104
- package/lib/algorithm/d3-force/index.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/util/object.d.ts +1 -2
- package/lib/util/object.js +1 -4
- package/lib/util/object.js.map +1 -1
- package/lib/util/order.d.ts +1 -1
- package/lib/util/order.js +4 -1
- package/lib/util/order.js.map +1 -1
- package/lib/worker.js +768 -807
- package/lib/worker.js.map +1 -1
- package/package.json +1 -1
- package/src/algorithm/antv-dagre/index.ts +11 -12
- package/src/algorithm/antv-dagre/types.ts +25 -3
- package/src/algorithm/base-layout.ts +13 -8
- package/src/algorithm/circular/index.ts +2 -2
- package/src/algorithm/combo-combined/index.ts +5 -5
- package/src/algorithm/d3-force/index.ts +69 -122
- package/src/util/object.ts +0 -4
- 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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
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
|
-
|
|
718
|
-
|
|
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
|
-
|
|
721
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
768
|
+
return dist;
|
|
769
|
+
}
|
|
770
|
+
class MinHeap {
|
|
771
|
+
constructor() {
|
|
772
|
+
this.data = [];
|
|
726
773
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
774
|
+
push(item) {
|
|
775
|
+
this.data.push(item);
|
|
776
|
+
this.bubbleUp(this.data.length - 1);
|
|
730
777
|
}
|
|
731
|
-
|
|
732
|
-
|
|
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
|
-
|
|
735
|
-
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
|
899
|
-
|
|
900
|
-
|
|
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
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
|
1054
|
+
class GraphLib {
|
|
944
1055
|
constructor(data, options = {}) {
|
|
945
|
-
this.
|
|
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
|
-
|
|
948
|
-
return this.
|
|
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
|
-
|
|
951
|
-
this.
|
|
1105
|
+
edge(id) {
|
|
1106
|
+
return this.edgeMap.get(id);
|
|
952
1107
|
}
|
|
953
|
-
|
|
954
|
-
this.
|
|
1108
|
+
firstEdge() {
|
|
1109
|
+
return this.edgeMap.values().next().value;
|
|
955
1110
|
}
|
|
956
1111
|
forEachEdge(callback) {
|
|
957
|
-
|
|
958
|
-
|
|
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
|
-
|
|
964
|
-
this.
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
1026
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
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
|
-
|
|
1147
|
-
this.
|
|
1148
|
-
this.
|
|
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
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
-
|
|
1160
|
-
|
|
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
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
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
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
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
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
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(
|
|
4939
|
-
const nodesepfunc = formatNumberFn(
|
|
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
|
|
4955
|
-
const
|
|
4956
|
-
const
|
|
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
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
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
|
-
|
|
7135
|
-
|
|
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 (
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
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
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
options.
|
|
7402
|
-
options.
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
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
|
-
|
|
7438
|
-
|
|
7439
|
-
options.
|
|
7440
|
-
options.
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
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 (
|
|
7452
|
+
if (options.preventOverlap === false &&
|
|
7453
|
+
(options.collide === false || options.collide === undefined))
|
|
7476
7454
|
return undefined;
|
|
7477
|
-
|
|
7478
|
-
options.nodeSize
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
options.
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
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
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
options.
|
|
7521
|
-
|
|
7522
|
-
|
|
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
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
options.
|
|
7555
|
-
|
|
7556
|
-
|
|
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);
|