@graphty/layout 1.2.9 → 1.4.0
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/README.md +400 -409
- package/dist/layout.js +884 -817
- package/dist/layout.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +3 -2
- package/dist/src/algorithms/index.d.ts.map +1 -0
- package/dist/src/algorithms/index.js +2 -2
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/optimization/index.d.ts +5 -4
- package/dist/src/algorithms/optimization/index.d.ts.map +1 -0
- package/dist/src/algorithms/optimization/index.js +3 -3
- package/dist/src/algorithms/optimization/index.js.map +1 -1
- package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +3 -5
- package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts.map +1 -0
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js +6 -9
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -1
- package/dist/src/algorithms/optimization/lbfgs.d.ts +3 -3
- package/dist/src/algorithms/optimization/lbfgs.d.ts.map +1 -0
- package/dist/src/algorithms/optimization/lbfgs.js +5 -7
- package/dist/src/algorithms/optimization/lbfgs.js.map +1 -1
- package/dist/src/algorithms/optimization/line-search.d.ts +1 -1
- package/dist/src/algorithms/optimization/line-search.d.ts.map +1 -0
- package/dist/src/algorithms/optimization/line-search.js +0 -1
- package/dist/src/algorithms/optimization/line-search.js.map +1 -1
- package/dist/src/algorithms/optimization/types.d.ts +2 -1
- package/dist/src/algorithms/optimization/types.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/check.d.ts +5 -4
- package/dist/src/algorithms/planarity/check.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/check.js +6 -6
- package/dist/src/algorithms/planarity/check.js.map +1 -1
- package/dist/src/algorithms/planarity/embedding.d.ts +5 -6
- package/dist/src/algorithms/planarity/embedding.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/embedding.js +15 -14
- package/dist/src/algorithms/planarity/embedding.js.map +1 -1
- package/dist/src/algorithms/planarity/index.d.ts +5 -4
- package/dist/src/algorithms/planarity/index.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/index.js +4 -4
- package/dist/src/algorithms/planarity/index.js.map +1 -1
- package/dist/src/algorithms/planarity/lr-test.d.ts +3 -2
- package/dist/src/algorithms/planarity/lr-test.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/lr-test.js +2 -2
- package/dist/src/algorithms/planarity/lr-test.js.map +1 -1
- package/dist/src/algorithms/planarity/special-graphs.d.ts +2 -4
- package/dist/src/algorithms/planarity/special-graphs.d.ts.map +1 -0
- package/dist/src/algorithms/planarity/special-graphs.js +18 -15
- package/dist/src/algorithms/planarity/special-graphs.js.map +1 -1
- package/dist/src/generators/basic.d.ts +2 -1
- package/dist/src/generators/basic.d.ts.map +1 -0
- package/dist/src/generators/basic.js +4 -4
- package/dist/src/generators/basic.js.map +1 -1
- package/dist/src/generators/bipartite.d.ts +2 -1
- package/dist/src/generators/bipartite.d.ts.map +1 -0
- package/dist/src/generators/bipartite.js +2 -2
- package/dist/src/generators/bipartite.js.map +1 -1
- package/dist/src/generators/grid.d.ts +2 -1
- package/dist/src/generators/grid.d.ts.map +1 -0
- package/dist/src/generators/grid.js +1 -1
- package/dist/src/generators/grid.js.map +1 -1
- package/dist/src/generators/index.d.ts +6 -5
- package/dist/src/generators/index.d.ts.map +1 -0
- package/dist/src/generators/index.js +5 -5
- package/dist/src/generators/index.js.map +1 -1
- package/dist/src/generators/random.d.ts +2 -1
- package/dist/src/generators/random.d.ts.map +1 -0
- package/dist/src/generators/random.js +2 -2
- package/dist/src/generators/random.js.map +1 -1
- package/dist/src/generators/scale-free.d.ts +2 -1
- package/dist/src/generators/scale-free.d.ts.map +1 -0
- package/dist/src/generators/scale-free.js +4 -4
- package/dist/src/generators/scale-free.js.map +1 -1
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +4 -4
- package/dist/src/layouts/basic/index.d.ts +2 -1
- package/dist/src/layouts/basic/index.d.ts.map +1 -0
- package/dist/src/layouts/basic/index.js +1 -1
- package/dist/src/layouts/basic/random.d.ts +2 -2
- package/dist/src/layouts/basic/random.d.ts.map +1 -0
- package/dist/src/layouts/basic/random.js +4 -5
- package/dist/src/layouts/basic/random.js.map +1 -1
- package/dist/src/layouts/force-directed/arf.d.ts +2 -2
- package/dist/src/layouts/force-directed/arf.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/arf.js +13 -8
- package/dist/src/layouts/force-directed/arf.js.map +1 -1
- package/dist/src/layouts/force-directed/forceatlas2.d.ts +4 -4
- package/dist/src/layouts/force-directed/forceatlas2.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/forceatlas2.js +83 -51
- package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -1
- package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +12 -12
- package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/fruchterman-reingold.js +18 -18
- package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -1
- package/dist/src/layouts/force-directed/index.d.ts +6 -5
- package/dist/src/layouts/force-directed/index.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/index.js +5 -5
- package/dist/src/layouts/force-directed/index.js.map +1 -1
- package/dist/src/layouts/force-directed/kamada-kawai.d.ts +3 -3
- package/dist/src/layouts/force-directed/kamada-kawai.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/kamada-kawai.js +13 -11
- package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -1
- package/dist/src/layouts/force-directed/spring.d.ts +12 -12
- package/dist/src/layouts/force-directed/spring.d.ts.map +1 -0
- package/dist/src/layouts/force-directed/spring.js +11 -12
- package/dist/src/layouts/force-directed/spring.js.map +1 -1
- package/dist/src/layouts/geometric/circular.d.ts +2 -2
- package/dist/src/layouts/geometric/circular.d.ts.map +1 -0
- package/dist/src/layouts/geometric/circular.js +11 -10
- package/dist/src/layouts/geometric/circular.js.map +1 -1
- package/dist/src/layouts/geometric/index.d.ts +4 -3
- package/dist/src/layouts/geometric/index.d.ts.map +1 -0
- package/dist/src/layouts/geometric/index.js +3 -3
- package/dist/src/layouts/geometric/shell.d.ts +2 -2
- package/dist/src/layouts/geometric/shell.d.ts.map +1 -0
- package/dist/src/layouts/geometric/shell.js +6 -6
- package/dist/src/layouts/geometric/shell.js.map +1 -1
- package/dist/src/layouts/geometric/spiral.d.ts +2 -2
- package/dist/src/layouts/geometric/spiral.d.ts.map +1 -0
- package/dist/src/layouts/geometric/spiral.js +6 -10
- package/dist/src/layouts/geometric/spiral.js.map +1 -1
- package/dist/src/layouts/hierarchical/bfs.d.ts +3 -3
- package/dist/src/layouts/hierarchical/bfs.d.ts.map +1 -0
- package/dist/src/layouts/hierarchical/bfs.js +6 -7
- package/dist/src/layouts/hierarchical/bfs.js.map +1 -1
- package/dist/src/layouts/hierarchical/bipartite.d.ts +3 -3
- package/dist/src/layouts/hierarchical/bipartite.d.ts.map +1 -0
- package/dist/src/layouts/hierarchical/bipartite.js +9 -10
- package/dist/src/layouts/hierarchical/bipartite.js.map +1 -1
- package/dist/src/layouts/hierarchical/index.d.ts +4 -3
- package/dist/src/layouts/hierarchical/index.d.ts.map +1 -0
- package/dist/src/layouts/hierarchical/index.js +3 -3
- package/dist/src/layouts/hierarchical/index.js.map +1 -1
- package/dist/src/layouts/hierarchical/multipartite.d.ts +3 -3
- package/dist/src/layouts/hierarchical/multipartite.d.ts.map +1 -0
- package/dist/src/layouts/hierarchical/multipartite.js +9 -10
- package/dist/src/layouts/hierarchical/multipartite.js.map +1 -1
- package/dist/src/layouts/index.d.ts +6 -5
- package/dist/src/layouts/index.d.ts.map +1 -0
- package/dist/src/layouts/index.js +5 -5
- package/dist/src/layouts/index.js.map +1 -1
- package/dist/src/layouts/specialized/index.d.ts +3 -2
- package/dist/src/layouts/specialized/index.d.ts.map +1 -0
- package/dist/src/layouts/specialized/index.js +2 -2
- package/dist/src/layouts/specialized/index.js.map +1 -1
- package/dist/src/layouts/specialized/planar.d.ts +2 -2
- package/dist/src/layouts/specialized/planar.d.ts.map +1 -0
- package/dist/src/layouts/specialized/planar.js +6 -7
- package/dist/src/layouts/specialized/planar.js.map +1 -1
- package/dist/src/layouts/specialized/spectral.d.ts +2 -2
- package/dist/src/layouts/specialized/spectral.d.ts.map +1 -0
- package/dist/src/layouts/specialized/spectral.js +31 -21
- package/dist/src/layouts/specialized/spectral.js.map +1 -1
- package/dist/src/types/embedding.d.ts +2 -1
- package/dist/src/types/embedding.d.ts.map +1 -0
- package/dist/src/types/graph.d.ts +2 -1
- package/dist/src/types/graph.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +4 -3
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +3 -3
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/layout.d.ts +2 -1
- package/dist/src/types/layout.d.ts.map +1 -0
- package/dist/src/utils/graph.d.ts +2 -5
- package/dist/src/utils/graph.d.ts.map +1 -0
- package/dist/src/utils/graph.js +4 -6
- package/dist/src/utils/graph.js.map +1 -1
- package/dist/src/utils/index.d.ts +6 -5
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +5 -5
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/numpy.d.ts +5 -4
- package/dist/src/utils/numpy.d.ts.map +1 -0
- package/dist/src/utils/numpy.js +22 -16
- package/dist/src/utils/numpy.js.map +1 -1
- package/dist/src/utils/params.d.ts +2 -2
- package/dist/src/utils/params.d.ts.map +1 -0
- package/dist/src/utils/params.js +0 -1
- package/dist/src/utils/params.js.map +1 -1
- package/dist/src/utils/random.d.ts +18 -0
- package/dist/src/utils/random.d.ts.map +1 -0
- package/dist/src/utils/random.js +18 -1
- package/dist/src/utils/random.js.map +1 -1
- package/dist/src/utils/rescale.d.ts +2 -3
- package/dist/src/utils/rescale.d.ts.map +1 -0
- package/dist/src/utils/rescale.js +12 -10
- package/dist/src/utils/rescale.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +35 -34
- package/src/algorithms/index.ts +2 -2
- package/src/algorithms/optimization/index.ts +4 -8
- package/src/algorithms/optimization/kamada-kawai-solver.ts +170 -179
- package/src/algorithms/optimization/lbfgs.ts +42 -49
- package/src/algorithms/optimization/line-search.ts +27 -28
- package/src/algorithms/optimization/types.ts +2 -2
- package/src/algorithms/planarity/check.ts +23 -23
- package/src/algorithms/planarity/embedding.ts +160 -171
- package/src/algorithms/planarity/index.ts +4 -8
- package/src/algorithms/planarity/lr-test.ts +46 -46
- package/src/algorithms/planarity/special-graphs.ts +73 -83
- package/src/generators/basic.ts +57 -57
- package/src/generators/bipartite.ts +37 -31
- package/src/generators/grid.ts +28 -28
- package/src/generators/index.ts +5 -11
- package/src/generators/random.ts +26 -25
- package/src/generators/scale-free.ts +56 -55
- package/src/index.ts +5 -5
- package/src/layouts/basic/index.ts +1 -1
- package/src/layouts/basic/random.ts +20 -16
- package/src/layouts/force-directed/arf.ts +107 -105
- package/src/layouts/force-directed/forceatlas2.ts +374 -352
- package/src/layouts/force-directed/fruchterman-reingold.ts +133 -134
- package/src/layouts/force-directed/index.ts +5 -5
- package/src/layouts/force-directed/kamada-kawai.ts +77 -80
- package/src/layouts/force-directed/spring.ts +24 -25
- package/src/layouts/geometric/circular.ts +63 -57
- package/src/layouts/geometric/index.ts +3 -3
- package/src/layouts/geometric/shell.ts +58 -53
- package/src/layouts/geometric/spiral.ts +60 -64
- package/src/layouts/hierarchical/bfs.ts +58 -59
- package/src/layouts/hierarchical/bipartite.ts +62 -63
- package/src/layouts/hierarchical/index.ts +3 -3
- package/src/layouts/hierarchical/multipartite.ts +62 -63
- package/src/layouts/index.ts +5 -5
- package/src/layouts/specialized/index.ts +2 -2
- package/src/layouts/specialized/planar.ts +41 -42
- package/src/layouts/specialized/spectral.ts +110 -99
- package/src/types/embedding.ts +5 -5
- package/src/types/graph.ts +4 -4
- package/src/types/index.ts +3 -3
- package/src/types/layout.ts +2 -2
- package/src/utils/graph.ts +31 -35
- package/src/utils/index.ts +5 -5
- package/src/utils/numpy.ts +96 -92
- package/src/utils/params.ts +14 -11
- package/src/utils/random.ts +57 -40
- package/src/utils/rescale.ts +169 -174
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -30
- package/dist/vitest.config.js.map +0 -1
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type { Graph, Node,
|
|
2
|
-
import { getNodesFromGraph
|
|
3
|
-
import { _processParams } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import type { Edge, Graph, Node, PositionMap } from "../../types";
|
|
2
|
+
import { getNodesFromGraph } from "../../utils/graph";
|
|
3
|
+
import { _processParams } from "../../utils/params";
|
|
4
|
+
import { RandomNumberGenerator } from "../../utils/random";
|
|
5
|
+
import { rescaleLayout } from "../../utils/rescale";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Position nodes using the ForceAtlas2 force-directed algorithm.
|
|
9
|
-
*
|
|
10
9
|
* @param G - Graph
|
|
11
10
|
* @param pos - Initial positions for nodes
|
|
12
11
|
* @param maxIter - Maximum number of iterations
|
|
@@ -18,409 +17,432 @@ import { RandomNumberGenerator } from '../../utils/random';
|
|
|
18
17
|
* @param nodeMass - Dictionary mapping nodes to their masses
|
|
19
18
|
* @param nodeSize - Dictionary mapping nodes to their sizes
|
|
20
19
|
* @param weight - Edge attribute for weight
|
|
21
|
-
* @param
|
|
20
|
+
* @param _dissuadeHubs - Whether to prevent hub nodes from clustering (unused)
|
|
22
21
|
* @param linlog - Whether to use logarithmic attraction
|
|
23
22
|
* @param seed - Random seed for initial positions
|
|
24
23
|
* @param dim - Dimension of layout
|
|
25
24
|
* @returns Positions dictionary keyed by node
|
|
26
25
|
*/
|
|
27
26
|
export function forceatlas2Layout(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
G: Graph,
|
|
28
|
+
pos: PositionMap | null = null,
|
|
29
|
+
maxIter: number = 100,
|
|
30
|
+
jitterTolerance: number = 1.0,
|
|
31
|
+
scalingRatio: number = 2.0,
|
|
32
|
+
gravity: number = 1.0,
|
|
33
|
+
distributedAction: boolean = false,
|
|
34
|
+
strongGravity: boolean = false,
|
|
35
|
+
nodeMass: Record<Node, number> | null = null,
|
|
36
|
+
nodeSize: Record<Node, number> | null = null,
|
|
37
|
+
weight: string | null = null,
|
|
38
|
+
_dissuadeHubs: boolean = false,
|
|
39
|
+
linlog: boolean = false,
|
|
40
|
+
seed: number | null = null,
|
|
41
|
+
dim: number = 2,
|
|
43
42
|
): PositionMap {
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const processed = _processParams(G, null, dim);
|
|
44
|
+
const graph = processed.G;
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
const nodes = getNodesFromGraph(graph);
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Initialize random number generator
|
|
54
|
-
const rng = new RandomNumberGenerator(seed ?? undefined);
|
|
55
|
-
|
|
56
|
-
// Initialize positions if not provided
|
|
57
|
-
let posArray: number[][];
|
|
58
|
-
if (pos === null) {
|
|
59
|
-
pos = {};
|
|
60
|
-
posArray = new Array(nodes.length);
|
|
61
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
62
|
-
posArray[i] = Array(dim).fill(0).map(() => rng.rand() as number * 2 - 1);
|
|
63
|
-
pos[nodes[i]] = posArray[i];
|
|
64
|
-
}
|
|
65
|
-
} else if (Object.keys(pos).length === nodes.length) {
|
|
66
|
-
// Use provided positions
|
|
67
|
-
posArray = new Array(nodes.length);
|
|
68
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
69
|
-
const nodePos = pos[nodes[i]];
|
|
70
|
-
posArray[i] = new Array(dim);
|
|
71
|
-
for (let d = 0; d < dim; d++) {
|
|
72
|
-
posArray[i][d] = d < nodePos.length ? nodePos[d] : (rng.rand() as number * 2 - 1);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
// Some nodes don't have positions, initialize within the range of existing positions
|
|
77
|
-
let minPos = Array(dim).fill(Number.POSITIVE_INFINITY);
|
|
78
|
-
let maxPos = Array(dim).fill(Number.NEGATIVE_INFINITY);
|
|
79
|
-
|
|
80
|
-
// Find min and max of existing positions
|
|
81
|
-
for (const node in pos) {
|
|
82
|
-
const nodePos = pos[node];
|
|
83
|
-
for (let d = 0; d < dim; d++) {
|
|
84
|
-
if (d < nodePos.length) {
|
|
85
|
-
minPos[d] = Math.min(minPos[d], nodePos[d]);
|
|
86
|
-
maxPos[d] = Math.max(maxPos[d], nodePos[d]);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
48
|
+
if (nodes.length === 0) {
|
|
49
|
+
return {};
|
|
89
50
|
}
|
|
90
51
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
52
|
+
// Initialize random number generator
|
|
53
|
+
const rng = new RandomNumberGenerator(seed ?? undefined);
|
|
54
|
+
|
|
55
|
+
// Initialize positions if not provided
|
|
56
|
+
let posArray: number[][];
|
|
57
|
+
if (pos === null) {
|
|
58
|
+
pos = {};
|
|
59
|
+
posArray = new Array(nodes.length);
|
|
60
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
61
|
+
posArray[i] = Array(dim)
|
|
62
|
+
.fill(0)
|
|
63
|
+
.map(() => (rng.rand() as number) * 2 - 1);
|
|
64
|
+
pos[nodes[i]] = posArray[i];
|
|
65
|
+
}
|
|
66
|
+
} else if (Object.keys(pos).length === nodes.length) {
|
|
67
|
+
// Use provided positions
|
|
68
|
+
posArray = new Array(nodes.length);
|
|
69
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
70
|
+
const nodePos = pos[nodes[i]];
|
|
71
|
+
posArray[i] = new Array(dim);
|
|
72
|
+
for (let d = 0; d < dim; d++) {
|
|
73
|
+
posArray[i][d] = d < nodePos.length ? nodePos[d] : (rng.rand() as number) * 2 - 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// Some nodes don't have positions, initialize within the range of existing positions
|
|
78
|
+
const minPos: number[] = Array(dim).fill(Number.POSITIVE_INFINITY);
|
|
79
|
+
const maxPos: number[] = Array(dim).fill(Number.NEGATIVE_INFINITY);
|
|
80
|
+
|
|
81
|
+
// Find min and max of existing positions
|
|
82
|
+
for (const node in pos) {
|
|
83
|
+
const nodePos = pos[node];
|
|
84
|
+
for (let d = 0; d < dim; d++) {
|
|
85
|
+
if (d < nodePos.length) {
|
|
86
|
+
minPos[d] = Math.min(minPos[d], nodePos[d]);
|
|
87
|
+
maxPos[d] = Math.max(maxPos[d], nodePos[d]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
98
91
|
|
|
99
|
-
|
|
100
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
101
|
-
const node = nodes[i];
|
|
102
|
-
if (pos[node]) {
|
|
103
|
-
const nodePos = pos[node];
|
|
104
|
-
posArray[i] = new Array(dim);
|
|
92
|
+
// If min/max are still infinity for some dimensions, use default range
|
|
105
93
|
for (let d = 0; d < dim; d++) {
|
|
106
|
-
|
|
94
|
+
if (!isFinite(minPos[d]) || !isFinite(maxPos[d])) {
|
|
95
|
+
minPos[d] = -1;
|
|
96
|
+
maxPos[d] = 1;
|
|
97
|
+
}
|
|
107
98
|
}
|
|
108
|
-
} else {
|
|
109
|
-
posArray[i] = Array(dim).fill(0).map((_, d) =>
|
|
110
|
-
minPos[d] + (rng.rand() as number) * (maxPos[d] - minPos[d])
|
|
111
|
-
);
|
|
112
|
-
pos[node] = posArray[i];
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Initialize mass and size arrays
|
|
118
|
-
const mass = new Array(nodes.length).fill(0);
|
|
119
|
-
const size = new Array(nodes.length).fill(0);
|
|
120
|
-
|
|
121
|
-
// Flag to track whether to adjust for node sizes
|
|
122
|
-
const adjustSizes = nodeSize !== null;
|
|
123
|
-
|
|
124
|
-
// Set node masses and sizes
|
|
125
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
126
|
-
const node = nodes[i];
|
|
127
|
-
mass[i] = nodeMass && nodeMass[node] ?
|
|
128
|
-
nodeMass[node] :
|
|
129
|
-
(Array.isArray(graph) ? 1 : getNodeDegree(graph, node) + 1);
|
|
130
|
-
|
|
131
|
-
size[i] = nodeSize && nodeSize[node] ? nodeSize[node] : 1;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Create adjacency matrix
|
|
135
|
-
const n = nodes.length;
|
|
136
|
-
const A = Array(n).fill(0).map(() => Array(n).fill(0));
|
|
137
|
-
|
|
138
|
-
// Populate adjacency matrix with edge weights
|
|
139
|
-
const edges = Array.isArray(graph) ? [] as Edge[] : graph.edges();
|
|
140
|
-
const nodeIndices: Record<Node, number> = {};
|
|
141
|
-
nodes.forEach((node, i) => { nodeIndices[node] = i; });
|
|
142
|
-
|
|
143
|
-
for (const [source, target] of edges) {
|
|
144
|
-
const i = nodeIndices[source];
|
|
145
|
-
const j = nodeIndices[target];
|
|
146
|
-
|
|
147
|
-
// Use edge weight if provided, otherwise default to 1
|
|
148
|
-
let edgeWeight = 1;
|
|
149
|
-
if (weight && !Array.isArray(graph) && graph.getEdgeData) {
|
|
150
|
-
edgeWeight = graph.getEdgeData(source, target, weight) || 1;
|
|
151
|
-
}
|
|
152
99
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
function estimateFactor(
|
|
170
|
-
n: number,
|
|
171
|
-
swing: number,
|
|
172
|
-
traction: number,
|
|
173
|
-
speed: number,
|
|
174
|
-
speedEfficiency: number,
|
|
175
|
-
jitterTolerance: number
|
|
176
|
-
): [number, number] {
|
|
177
|
-
// Optimal jitter parameters
|
|
178
|
-
const optJitter = 0.05 * Math.sqrt(n);
|
|
179
|
-
const minJitter = Math.sqrt(optJitter);
|
|
180
|
-
const maxJitter = 10;
|
|
181
|
-
const minSpeedEfficiency = 0.05;
|
|
182
|
-
|
|
183
|
-
// Estimate jitter based on current state
|
|
184
|
-
const other = Math.min(maxJitter, optJitter * traction / (n * n));
|
|
185
|
-
let jitter = jitterTolerance * Math.max(minJitter, other);
|
|
186
|
-
|
|
187
|
-
// Adjust speed efficiency based on swing/traction ratio
|
|
188
|
-
if (swing / traction > 2.0) {
|
|
189
|
-
if (speedEfficiency > minSpeedEfficiency) {
|
|
190
|
-
speedEfficiency *= 0.5;
|
|
191
|
-
}
|
|
192
|
-
jitter = Math.max(jitter, jitterTolerance);
|
|
100
|
+
posArray = new Array(nodes.length);
|
|
101
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
102
|
+
const node = nodes[i];
|
|
103
|
+
if (pos[node]) {
|
|
104
|
+
const nodePos = pos[node];
|
|
105
|
+
posArray[i] = new Array(dim);
|
|
106
|
+
for (let d = 0; d < dim; d++) {
|
|
107
|
+
posArray[i][d] = d < nodePos.length ? nodePos[d] : (rng.rand() as number) * 2 - 1;
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
posArray[i] = Array(dim)
|
|
111
|
+
.fill(0)
|
|
112
|
+
.map((_, d) => minPos[d] + (rng.rand() as number) * (maxPos[d] - minPos[d]));
|
|
113
|
+
pos[node] = posArray[i];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
193
116
|
}
|
|
194
117
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
jitter * speedEfficiency * traction / swing;
|
|
199
|
-
|
|
200
|
-
// Further adjust speed efficiency
|
|
201
|
-
if (swing > jitter * traction) {
|
|
202
|
-
if (speedEfficiency > minSpeedEfficiency) {
|
|
203
|
-
speedEfficiency *= 0.7;
|
|
204
|
-
}
|
|
205
|
-
} else if (speed < 1000) {
|
|
206
|
-
speedEfficiency *= 1.3;
|
|
207
|
-
}
|
|
118
|
+
// Initialize mass and size arrays
|
|
119
|
+
const mass = new Array(nodes.length).fill(0);
|
|
120
|
+
const size = new Array(nodes.length).fill(0);
|
|
208
121
|
|
|
209
|
-
//
|
|
210
|
-
const
|
|
211
|
-
speed = speed + Math.min(targetSpeed - speed, maxRise * speed);
|
|
122
|
+
// Flag to track whether to adjust for node sizes
|
|
123
|
+
const adjustSizes = nodeSize !== null;
|
|
212
124
|
|
|
213
|
-
|
|
214
|
-
|
|
125
|
+
// Set node masses and sizes
|
|
126
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
127
|
+
const node = nodes[i];
|
|
128
|
+
if (nodeMass && nodeMass[node]) {
|
|
129
|
+
mass[i] = nodeMass[node];
|
|
130
|
+
} else if (Array.isArray(graph)) {
|
|
131
|
+
mass[i] = 1;
|
|
132
|
+
} else {
|
|
133
|
+
mass[i] = getNodeDegree(graph, node) + 1;
|
|
134
|
+
}
|
|
215
135
|
|
|
216
|
-
|
|
217
|
-
for (let iter = 0; iter < maxIter; iter++) {
|
|
218
|
-
// Reset forces for this iteration
|
|
219
|
-
for (let i = 0; i < n; i++) {
|
|
220
|
-
for (let d = 0; d < dim; d++) {
|
|
221
|
-
attraction[i][d] = 0;
|
|
222
|
-
repulsion[i][d] = 0;
|
|
223
|
-
gravities[i][d] = 0;
|
|
224
|
-
}
|
|
136
|
+
size[i] = nodeSize && nodeSize[node] ? nodeSize[node] : 1;
|
|
225
137
|
}
|
|
226
138
|
|
|
227
|
-
//
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
139
|
+
// Create adjacency matrix
|
|
140
|
+
const n = nodes.length;
|
|
141
|
+
const A: number[][] = Array(n)
|
|
142
|
+
.fill(0)
|
|
143
|
+
.map(() => Array(n).fill(0) as number[]);
|
|
144
|
+
|
|
145
|
+
// Populate adjacency matrix with edge weights
|
|
146
|
+
const edges = Array.isArray(graph) ? ([] as Edge[]) : graph.edges();
|
|
147
|
+
const nodeIndices: Record<Node, number> = {};
|
|
148
|
+
nodes.forEach((node, i) => {
|
|
149
|
+
nodeIndices[node] = i;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
for (const [source, target] of edges) {
|
|
153
|
+
const i = nodeIndices[source];
|
|
154
|
+
const j = nodeIndices[target];
|
|
155
|
+
|
|
156
|
+
// Use edge weight if provided, otherwise default to 1
|
|
157
|
+
let edgeWeight = 1;
|
|
158
|
+
if (weight && !Array.isArray(graph) && graph.getEdgeData) {
|
|
159
|
+
edgeWeight = graph.getEdgeData(source, target, weight) || 1;
|
|
240
160
|
}
|
|
241
161
|
|
|
242
|
-
|
|
243
|
-
//
|
|
244
|
-
if (distance[i][j] < 0.01) distance[i][j] = 0.01;
|
|
245
|
-
}
|
|
162
|
+
A[i][j] = edgeWeight;
|
|
163
|
+
A[j][i] = edgeWeight; // For undirected graphs
|
|
246
164
|
}
|
|
247
165
|
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
166
|
+
// Initialize force arrays
|
|
167
|
+
const gravities: number[][] = Array(n)
|
|
168
|
+
.fill(0)
|
|
169
|
+
.map(() => Array(dim).fill(0) as number[]);
|
|
170
|
+
const attraction: number[][] = Array(n)
|
|
171
|
+
.fill(0)
|
|
172
|
+
.map(() => Array(dim).fill(0) as number[]);
|
|
173
|
+
const repulsion: number[][] = Array(n)
|
|
174
|
+
.fill(0)
|
|
175
|
+
.map(() => Array(dim).fill(0) as number[]);
|
|
176
|
+
|
|
177
|
+
// Simulation parameters
|
|
178
|
+
let speed = 1;
|
|
179
|
+
let speedEfficiency = 1;
|
|
180
|
+
const _swing = 1; // Reserved for adaptive speed control
|
|
181
|
+
const _traction = 1; // Reserved for adaptive speed control
|
|
182
|
+
|
|
183
|
+
// Helper function to estimate factor for force scaling
|
|
184
|
+
function estimateFactor(
|
|
185
|
+
nodeCount: number,
|
|
186
|
+
swingValue: number,
|
|
187
|
+
tractionValue: number,
|
|
188
|
+
currentSpeed: number,
|
|
189
|
+
currentSpeedEfficiency: number,
|
|
190
|
+
jitterToleranceParam: number,
|
|
191
|
+
): [number, number] {
|
|
192
|
+
let resultSpeed = currentSpeed;
|
|
193
|
+
let resultSpeedEfficiency = currentSpeedEfficiency;
|
|
194
|
+
|
|
195
|
+
// Optimal jitter parameters
|
|
196
|
+
const optJitter = 0.05 * Math.sqrt(nodeCount);
|
|
197
|
+
const minJitter = Math.sqrt(optJitter);
|
|
198
|
+
const maxJitter = 10;
|
|
199
|
+
const minSpeedEfficiency = 0.05;
|
|
200
|
+
|
|
201
|
+
// Estimate jitter based on current state
|
|
202
|
+
const other = Math.min(maxJitter, (optJitter * tractionValue) / (nodeCount * nodeCount));
|
|
203
|
+
let jitter = jitterToleranceParam * Math.max(minJitter, other);
|
|
204
|
+
|
|
205
|
+
// Adjust speed efficiency based on swing/traction ratio
|
|
206
|
+
if (swingValue / tractionValue > 2.0) {
|
|
207
|
+
if (resultSpeedEfficiency > minSpeedEfficiency) {
|
|
208
|
+
resultSpeedEfficiency *= 0.5;
|
|
209
|
+
}
|
|
210
|
+
jitter = Math.max(jitter, jitterToleranceParam);
|
|
211
|
+
}
|
|
254
212
|
|
|
255
|
-
|
|
256
|
-
|
|
213
|
+
// Calculate target speed
|
|
214
|
+
const targetSpeed = swingValue === 0 ? Number.POSITIVE_INFINITY : (jitter * resultSpeedEfficiency * tractionValue) / swingValue;
|
|
257
215
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// Linear attraction model
|
|
266
|
-
for (let i = 0; i < n; i++) {
|
|
267
|
-
for (let j = 0; j < n; j++) {
|
|
268
|
-
if (i === j || A[i][j] === 0) continue;
|
|
269
|
-
|
|
270
|
-
for (let d = 0; d < dim; d++) {
|
|
271
|
-
const force = -diff[i][j][d] * A[i][j];
|
|
272
|
-
attraction[i][d] += force;
|
|
273
|
-
}
|
|
216
|
+
// Further adjust speed efficiency
|
|
217
|
+
if (swingValue > jitter * tractionValue) {
|
|
218
|
+
if (resultSpeedEfficiency > minSpeedEfficiency) {
|
|
219
|
+
resultSpeedEfficiency *= 0.7;
|
|
220
|
+
}
|
|
221
|
+
} else if (resultSpeed < 1000) {
|
|
222
|
+
resultSpeedEfficiency *= 1.3;
|
|
274
223
|
}
|
|
275
|
-
|
|
224
|
+
|
|
225
|
+
// Limit the speed increase
|
|
226
|
+
const maxRise = 0.5;
|
|
227
|
+
resultSpeed = resultSpeed + Math.min(targetSpeed - resultSpeed, maxRise * resultSpeed);
|
|
228
|
+
|
|
229
|
+
return [resultSpeed, resultSpeedEfficiency];
|
|
276
230
|
}
|
|
277
231
|
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
for (let
|
|
282
|
-
|
|
232
|
+
// Main simulation loop
|
|
233
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
234
|
+
// Reset forces for this iteration
|
|
235
|
+
for (let i = 0; i < n; i++) {
|
|
236
|
+
for (let d = 0; d < dim; d++) {
|
|
237
|
+
attraction[i][d] = 0;
|
|
238
|
+
repulsion[i][d] = 0;
|
|
239
|
+
gravities[i][d] = 0;
|
|
240
|
+
}
|
|
283
241
|
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
242
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
243
|
+
// Compute pairwise differences and distances
|
|
244
|
+
const diff: number[][][] = Array(n)
|
|
245
|
+
.fill(0)
|
|
246
|
+
.map(() =>
|
|
247
|
+
Array(n)
|
|
248
|
+
.fill(0)
|
|
249
|
+
.map(() => Array(dim).fill(0) as number[]),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const distance: number[][] = Array(n)
|
|
253
|
+
.fill(0)
|
|
254
|
+
.map(() => Array(n).fill(0) as number[]);
|
|
255
|
+
|
|
256
|
+
for (let i = 0; i < n; i++) {
|
|
257
|
+
for (let j = 0; j < n; j++) {
|
|
258
|
+
if (i === j) {continue;}
|
|
259
|
+
|
|
260
|
+
for (let d = 0; d < dim; d++) {
|
|
261
|
+
diff[i][j][d] = posArray[i][d] - posArray[j][d];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
distance[i][j] = Math.sqrt(diff[i][j].reduce((sum: number, d: number) => sum + d * d, 0));
|
|
265
|
+
// Prevent division by zero
|
|
266
|
+
if (distance[i][j] < 0.01) {distance[i][j] = 0.01;}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
291
269
|
|
|
292
|
-
|
|
270
|
+
// Calculate attraction forces
|
|
271
|
+
if (linlog) {
|
|
272
|
+
// Logarithmic attraction model
|
|
273
|
+
for (let i = 0; i < n; i++) {
|
|
274
|
+
for (let j = 0; j < n; j++) {
|
|
275
|
+
if (i === j || A[i][j] === 0) {continue;}
|
|
276
|
+
|
|
277
|
+
const dist = distance[i][j];
|
|
278
|
+
const factor = (-Math.log(1 + dist) / dist) * A[i][j];
|
|
279
|
+
|
|
280
|
+
for (let d = 0; d < dim; d++) {
|
|
281
|
+
const force = factor * diff[i][j][d];
|
|
282
|
+
attraction[i][d] += force;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
// Linear attraction model
|
|
288
|
+
for (let i = 0; i < n; i++) {
|
|
289
|
+
for (let j = 0; j < n; j++) {
|
|
290
|
+
if (i === j || A[i][j] === 0) {continue;}
|
|
291
|
+
|
|
292
|
+
for (let d = 0; d < dim; d++) {
|
|
293
|
+
const force = -diff[i][j][d] * A[i][j];
|
|
294
|
+
attraction[i][d] += force;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
293
299
|
|
|
294
|
-
//
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
// Apply distributed attraction if enabled
|
|
301
|
+
if (distributedAction) {
|
|
302
|
+
for (let i = 0; i < n; i++) {
|
|
303
|
+
for (let d = 0; d < dim; d++) {
|
|
304
|
+
attraction[i][d] /= mass[i];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
298
307
|
}
|
|
299
308
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
309
|
+
// Calculate repulsion forces
|
|
310
|
+
for (let i = 0; i < n; i++) {
|
|
311
|
+
for (let j = 0; j < n; j++) {
|
|
312
|
+
if (i === j) {continue;}
|
|
303
313
|
|
|
304
|
-
|
|
305
|
-
const direction = diff[i][j][d] / dist;
|
|
306
|
-
repulsion[i][d] += direction * factor;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
314
|
+
let dist = distance[i][j];
|
|
310
315
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
centerOfMass[d] += posArray[i][d] / n;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
316
|
+
// Adjust distance for node sizes if needed
|
|
317
|
+
if (adjustSizes) {
|
|
318
|
+
dist -= size[i] - size[j];
|
|
319
|
+
dist = Math.max(dist, 0.01); // Prevent negative or zero distances
|
|
320
|
+
}
|
|
319
321
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
posCentered[d] = posArray[i][d] - centerOfMass[d];
|
|
324
|
-
}
|
|
322
|
+
const distSquared = dist * dist;
|
|
323
|
+
const massProduct = mass[i] * mass[j];
|
|
324
|
+
const factor = (massProduct / distSquared) * scalingRatio;
|
|
325
325
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
326
|
+
for (let d = 0; d < dim; d++) {
|
|
327
|
+
const direction = diff[i][j][d] / dist;
|
|
328
|
+
repulsion[i][d] += direction * factor;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
330
331
|
}
|
|
331
|
-
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
332
|
+
|
|
333
|
+
// Calculate gravity forces
|
|
334
|
+
// First find the center of mass
|
|
335
|
+
const centerOfMass: number[] = Array(dim).fill(0);
|
|
336
|
+
for (let i = 0; i < n; i++) {
|
|
337
|
+
for (let d = 0; d < dim; d++) {
|
|
338
|
+
centerOfMass[d] += posArray[i][d] / n;
|
|
339
|
+
}
|
|
340
340
|
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
341
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
342
|
+
for (let i = 0; i < n; i++) {
|
|
343
|
+
const posCentered = Array(dim);
|
|
344
|
+
for (let d = 0; d < dim; d++) {
|
|
345
|
+
posCentered[d] = posArray[i][d] - centerOfMass[d];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (strongGravity) {
|
|
349
|
+
// Strong gravity model
|
|
350
|
+
for (let d = 0; d < dim; d++) {
|
|
351
|
+
gravities[i][d] = -gravity * mass[i] * posCentered[d];
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
// Regular gravity model
|
|
355
|
+
const dist = Math.sqrt(posCentered.reduce((sum: number, val: number) => sum + val * val, 0));
|
|
356
|
+
|
|
357
|
+
if (dist > 0.01) {
|
|
358
|
+
for (let d = 0; d < dim; d++) {
|
|
359
|
+
const direction = posCentered[d] / dist;
|
|
360
|
+
gravities[i][d] = -gravity * mass[i] * direction;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
348
365
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
366
|
+
// Calculate total forces and update positions
|
|
367
|
+
const update = Array(n)
|
|
368
|
+
.fill(0)
|
|
369
|
+
.map(() => Array(dim).fill(0));
|
|
370
|
+
let totalSwing = 0;
|
|
371
|
+
let totalTraction = 0;
|
|
353
372
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
373
|
+
for (let i = 0; i < n; i++) {
|
|
374
|
+
for (let d = 0; d < dim; d++) {
|
|
375
|
+
update[i][d] = attraction[i][d] + repulsion[i][d] + gravities[i][d];
|
|
376
|
+
}
|
|
357
377
|
|
|
358
|
-
|
|
359
|
-
|
|
378
|
+
// Calculate swing and traction for this node
|
|
379
|
+
const oldPos = [...posArray[i]];
|
|
380
|
+
const newPos = oldPos.map((p, d) => p + (update[i][d] as number));
|
|
360
381
|
|
|
361
|
-
|
|
362
|
-
|
|
382
|
+
const swingVector = oldPos.map((p, d) => p - newPos[d]);
|
|
383
|
+
const tractionVector = oldPos.map((p, d) => p + newPos[d]);
|
|
363
384
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
385
|
+
const swingMagnitude = Math.sqrt(swingVector.reduce((sum: number, val: number) => sum + val * val, 0));
|
|
386
|
+
const tractionMagnitude = Math.sqrt(tractionVector.reduce((sum: number, val: number) => sum + val * val, 0));
|
|
387
|
+
|
|
388
|
+
totalSwing += mass[i] * swingMagnitude;
|
|
389
|
+
totalTraction += 0.5 * mass[i] * tractionMagnitude;
|
|
390
|
+
}
|
|
367
391
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
392
|
+
// Update speed and efficiency
|
|
393
|
+
[speed, speedEfficiency] = estimateFactor(
|
|
394
|
+
n,
|
|
395
|
+
totalSwing,
|
|
396
|
+
totalTraction,
|
|
397
|
+
speed,
|
|
398
|
+
speedEfficiency,
|
|
399
|
+
jitterTolerance,
|
|
400
|
+
);
|
|
377
401
|
|
|
378
|
-
|
|
379
|
-
|
|
402
|
+
// Apply forces to update positions
|
|
403
|
+
let totalMovement = 0;
|
|
404
|
+
|
|
405
|
+
for (let i = 0; i < n; i++) {
|
|
406
|
+
let factor;
|
|
407
|
+
|
|
408
|
+
if (adjustSizes) {
|
|
409
|
+
// Calculate displacement magnitude
|
|
410
|
+
const df = Math.sqrt(update[i].reduce((sum: number, val: number) => sum + val * val, 0));
|
|
411
|
+
const swinging = mass[i] * df;
|
|
412
|
+
|
|
413
|
+
// Determine scaling factor with size adjustments
|
|
414
|
+
factor = (0.1 * speed) / (1 + Math.sqrt(speed * swinging));
|
|
415
|
+
factor = Math.min(factor * df, 10) / df;
|
|
416
|
+
} else {
|
|
417
|
+
// Standard scaling factor
|
|
418
|
+
const swinging = (mass[i] as number) * Math.sqrt(update[i].reduce((sum: number, val: number) => sum + val * val, 0));
|
|
419
|
+
factor = speed / (1 + Math.sqrt(speed * swinging));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Apply factor to update position
|
|
423
|
+
for (let d = 0; d < dim; d++) {
|
|
424
|
+
const movement = update[i][d] * factor;
|
|
425
|
+
posArray[i][d] += movement;
|
|
426
|
+
totalMovement += Math.abs(movement);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
380
429
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
// Calculate displacement magnitude
|
|
386
|
-
const df = Math.sqrt(update[i].reduce((sum, val) => sum + val * val, 0));
|
|
387
|
-
const swinging = mass[i] * df;
|
|
388
|
-
|
|
389
|
-
// Determine scaling factor with size adjustments
|
|
390
|
-
factor = 0.1 * speed / (1 + Math.sqrt(speed * swinging));
|
|
391
|
-
factor = Math.min(factor * df, 10) / df;
|
|
392
|
-
} else {
|
|
393
|
-
// Standard scaling factor
|
|
394
|
-
const swinging = mass[i] * Math.sqrt(update[i].reduce((sum, val) => sum + val * val, 0));
|
|
395
|
-
factor = speed / (1 + Math.sqrt(speed * swinging));
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Apply factor to update position
|
|
399
|
-
for (let d = 0; d < dim; d++) {
|
|
400
|
-
const movement = update[i][d] * factor;
|
|
401
|
-
posArray[i][d] += movement;
|
|
402
|
-
totalMovement += Math.abs(movement);
|
|
403
|
-
}
|
|
430
|
+
// Check for convergence
|
|
431
|
+
if (totalMovement < 1e-10) {
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
404
434
|
}
|
|
405
435
|
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
436
|
+
// Create position dictionary
|
|
437
|
+
const positions: PositionMap = {};
|
|
438
|
+
for (let i = 0; i < n; i++) {
|
|
439
|
+
positions[nodes[i]] = posArray[i];
|
|
409
440
|
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Create position dictionary
|
|
413
|
-
const positions: PositionMap = {};
|
|
414
|
-
for (let i = 0; i < n; i++) {
|
|
415
|
-
positions[nodes[i]] = posArray[i];
|
|
416
|
-
}
|
|
417
441
|
|
|
418
|
-
|
|
442
|
+
return rescaleLayout(positions) as PositionMap;
|
|
419
443
|
}
|
|
420
444
|
|
|
421
445
|
// Helper function to get node degree
|
|
422
446
|
function getNodeDegree(graph: Graph, node: Node): number {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
).length;
|
|
426
|
-
}
|
|
447
|
+
return graph.edges().filter((edge: Edge) => edge[0] === node || edge[1] === node).length;
|
|
448
|
+
}
|