@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.
Files changed (237) hide show
  1. package/README.md +400 -409
  2. package/dist/layout.js +884 -817
  3. package/dist/layout.js.map +1 -1
  4. package/dist/src/algorithms/index.d.ts +3 -2
  5. package/dist/src/algorithms/index.d.ts.map +1 -0
  6. package/dist/src/algorithms/index.js +2 -2
  7. package/dist/src/algorithms/index.js.map +1 -1
  8. package/dist/src/algorithms/optimization/index.d.ts +5 -4
  9. package/dist/src/algorithms/optimization/index.d.ts.map +1 -0
  10. package/dist/src/algorithms/optimization/index.js +3 -3
  11. package/dist/src/algorithms/optimization/index.js.map +1 -1
  12. package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +3 -5
  13. package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts.map +1 -0
  14. package/dist/src/algorithms/optimization/kamada-kawai-solver.js +6 -9
  15. package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -1
  16. package/dist/src/algorithms/optimization/lbfgs.d.ts +3 -3
  17. package/dist/src/algorithms/optimization/lbfgs.d.ts.map +1 -0
  18. package/dist/src/algorithms/optimization/lbfgs.js +5 -7
  19. package/dist/src/algorithms/optimization/lbfgs.js.map +1 -1
  20. package/dist/src/algorithms/optimization/line-search.d.ts +1 -1
  21. package/dist/src/algorithms/optimization/line-search.d.ts.map +1 -0
  22. package/dist/src/algorithms/optimization/line-search.js +0 -1
  23. package/dist/src/algorithms/optimization/line-search.js.map +1 -1
  24. package/dist/src/algorithms/optimization/types.d.ts +2 -1
  25. package/dist/src/algorithms/optimization/types.d.ts.map +1 -0
  26. package/dist/src/algorithms/planarity/check.d.ts +5 -4
  27. package/dist/src/algorithms/planarity/check.d.ts.map +1 -0
  28. package/dist/src/algorithms/planarity/check.js +6 -6
  29. package/dist/src/algorithms/planarity/check.js.map +1 -1
  30. package/dist/src/algorithms/planarity/embedding.d.ts +5 -6
  31. package/dist/src/algorithms/planarity/embedding.d.ts.map +1 -0
  32. package/dist/src/algorithms/planarity/embedding.js +15 -14
  33. package/dist/src/algorithms/planarity/embedding.js.map +1 -1
  34. package/dist/src/algorithms/planarity/index.d.ts +5 -4
  35. package/dist/src/algorithms/planarity/index.d.ts.map +1 -0
  36. package/dist/src/algorithms/planarity/index.js +4 -4
  37. package/dist/src/algorithms/planarity/index.js.map +1 -1
  38. package/dist/src/algorithms/planarity/lr-test.d.ts +3 -2
  39. package/dist/src/algorithms/planarity/lr-test.d.ts.map +1 -0
  40. package/dist/src/algorithms/planarity/lr-test.js +2 -2
  41. package/dist/src/algorithms/planarity/lr-test.js.map +1 -1
  42. package/dist/src/algorithms/planarity/special-graphs.d.ts +2 -4
  43. package/dist/src/algorithms/planarity/special-graphs.d.ts.map +1 -0
  44. package/dist/src/algorithms/planarity/special-graphs.js +18 -15
  45. package/dist/src/algorithms/planarity/special-graphs.js.map +1 -1
  46. package/dist/src/generators/basic.d.ts +2 -1
  47. package/dist/src/generators/basic.d.ts.map +1 -0
  48. package/dist/src/generators/basic.js +4 -4
  49. package/dist/src/generators/basic.js.map +1 -1
  50. package/dist/src/generators/bipartite.d.ts +2 -1
  51. package/dist/src/generators/bipartite.d.ts.map +1 -0
  52. package/dist/src/generators/bipartite.js +2 -2
  53. package/dist/src/generators/bipartite.js.map +1 -1
  54. package/dist/src/generators/grid.d.ts +2 -1
  55. package/dist/src/generators/grid.d.ts.map +1 -0
  56. package/dist/src/generators/grid.js +1 -1
  57. package/dist/src/generators/grid.js.map +1 -1
  58. package/dist/src/generators/index.d.ts +6 -5
  59. package/dist/src/generators/index.d.ts.map +1 -0
  60. package/dist/src/generators/index.js +5 -5
  61. package/dist/src/generators/index.js.map +1 -1
  62. package/dist/src/generators/random.d.ts +2 -1
  63. package/dist/src/generators/random.d.ts.map +1 -0
  64. package/dist/src/generators/random.js +2 -2
  65. package/dist/src/generators/random.js.map +1 -1
  66. package/dist/src/generators/scale-free.d.ts +2 -1
  67. package/dist/src/generators/scale-free.d.ts.map +1 -0
  68. package/dist/src/generators/scale-free.js +4 -4
  69. package/dist/src/generators/scale-free.js.map +1 -1
  70. package/dist/src/index.d.ts +5 -4
  71. package/dist/src/index.d.ts.map +1 -0
  72. package/dist/src/index.js +4 -4
  73. package/dist/src/layouts/basic/index.d.ts +2 -1
  74. package/dist/src/layouts/basic/index.d.ts.map +1 -0
  75. package/dist/src/layouts/basic/index.js +1 -1
  76. package/dist/src/layouts/basic/random.d.ts +2 -2
  77. package/dist/src/layouts/basic/random.d.ts.map +1 -0
  78. package/dist/src/layouts/basic/random.js +4 -5
  79. package/dist/src/layouts/basic/random.js.map +1 -1
  80. package/dist/src/layouts/force-directed/arf.d.ts +2 -2
  81. package/dist/src/layouts/force-directed/arf.d.ts.map +1 -0
  82. package/dist/src/layouts/force-directed/arf.js +13 -8
  83. package/dist/src/layouts/force-directed/arf.js.map +1 -1
  84. package/dist/src/layouts/force-directed/forceatlas2.d.ts +4 -4
  85. package/dist/src/layouts/force-directed/forceatlas2.d.ts.map +1 -0
  86. package/dist/src/layouts/force-directed/forceatlas2.js +83 -51
  87. package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -1
  88. package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +12 -12
  89. package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts.map +1 -0
  90. package/dist/src/layouts/force-directed/fruchterman-reingold.js +18 -18
  91. package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -1
  92. package/dist/src/layouts/force-directed/index.d.ts +6 -5
  93. package/dist/src/layouts/force-directed/index.d.ts.map +1 -0
  94. package/dist/src/layouts/force-directed/index.js +5 -5
  95. package/dist/src/layouts/force-directed/index.js.map +1 -1
  96. package/dist/src/layouts/force-directed/kamada-kawai.d.ts +3 -3
  97. package/dist/src/layouts/force-directed/kamada-kawai.d.ts.map +1 -0
  98. package/dist/src/layouts/force-directed/kamada-kawai.js +13 -11
  99. package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -1
  100. package/dist/src/layouts/force-directed/spring.d.ts +12 -12
  101. package/dist/src/layouts/force-directed/spring.d.ts.map +1 -0
  102. package/dist/src/layouts/force-directed/spring.js +11 -12
  103. package/dist/src/layouts/force-directed/spring.js.map +1 -1
  104. package/dist/src/layouts/geometric/circular.d.ts +2 -2
  105. package/dist/src/layouts/geometric/circular.d.ts.map +1 -0
  106. package/dist/src/layouts/geometric/circular.js +11 -10
  107. package/dist/src/layouts/geometric/circular.js.map +1 -1
  108. package/dist/src/layouts/geometric/index.d.ts +4 -3
  109. package/dist/src/layouts/geometric/index.d.ts.map +1 -0
  110. package/dist/src/layouts/geometric/index.js +3 -3
  111. package/dist/src/layouts/geometric/shell.d.ts +2 -2
  112. package/dist/src/layouts/geometric/shell.d.ts.map +1 -0
  113. package/dist/src/layouts/geometric/shell.js +6 -6
  114. package/dist/src/layouts/geometric/shell.js.map +1 -1
  115. package/dist/src/layouts/geometric/spiral.d.ts +2 -2
  116. package/dist/src/layouts/geometric/spiral.d.ts.map +1 -0
  117. package/dist/src/layouts/geometric/spiral.js +6 -10
  118. package/dist/src/layouts/geometric/spiral.js.map +1 -1
  119. package/dist/src/layouts/hierarchical/bfs.d.ts +3 -3
  120. package/dist/src/layouts/hierarchical/bfs.d.ts.map +1 -0
  121. package/dist/src/layouts/hierarchical/bfs.js +6 -7
  122. package/dist/src/layouts/hierarchical/bfs.js.map +1 -1
  123. package/dist/src/layouts/hierarchical/bipartite.d.ts +3 -3
  124. package/dist/src/layouts/hierarchical/bipartite.d.ts.map +1 -0
  125. package/dist/src/layouts/hierarchical/bipartite.js +9 -10
  126. package/dist/src/layouts/hierarchical/bipartite.js.map +1 -1
  127. package/dist/src/layouts/hierarchical/index.d.ts +4 -3
  128. package/dist/src/layouts/hierarchical/index.d.ts.map +1 -0
  129. package/dist/src/layouts/hierarchical/index.js +3 -3
  130. package/dist/src/layouts/hierarchical/index.js.map +1 -1
  131. package/dist/src/layouts/hierarchical/multipartite.d.ts +3 -3
  132. package/dist/src/layouts/hierarchical/multipartite.d.ts.map +1 -0
  133. package/dist/src/layouts/hierarchical/multipartite.js +9 -10
  134. package/dist/src/layouts/hierarchical/multipartite.js.map +1 -1
  135. package/dist/src/layouts/index.d.ts +6 -5
  136. package/dist/src/layouts/index.d.ts.map +1 -0
  137. package/dist/src/layouts/index.js +5 -5
  138. package/dist/src/layouts/index.js.map +1 -1
  139. package/dist/src/layouts/specialized/index.d.ts +3 -2
  140. package/dist/src/layouts/specialized/index.d.ts.map +1 -0
  141. package/dist/src/layouts/specialized/index.js +2 -2
  142. package/dist/src/layouts/specialized/index.js.map +1 -1
  143. package/dist/src/layouts/specialized/planar.d.ts +2 -2
  144. package/dist/src/layouts/specialized/planar.d.ts.map +1 -0
  145. package/dist/src/layouts/specialized/planar.js +6 -7
  146. package/dist/src/layouts/specialized/planar.js.map +1 -1
  147. package/dist/src/layouts/specialized/spectral.d.ts +2 -2
  148. package/dist/src/layouts/specialized/spectral.d.ts.map +1 -0
  149. package/dist/src/layouts/specialized/spectral.js +31 -21
  150. package/dist/src/layouts/specialized/spectral.js.map +1 -1
  151. package/dist/src/types/embedding.d.ts +2 -1
  152. package/dist/src/types/embedding.d.ts.map +1 -0
  153. package/dist/src/types/graph.d.ts +2 -1
  154. package/dist/src/types/graph.d.ts.map +1 -0
  155. package/dist/src/types/index.d.ts +4 -3
  156. package/dist/src/types/index.d.ts.map +1 -0
  157. package/dist/src/types/index.js +3 -3
  158. package/dist/src/types/index.js.map +1 -1
  159. package/dist/src/types/layout.d.ts +2 -1
  160. package/dist/src/types/layout.d.ts.map +1 -0
  161. package/dist/src/utils/graph.d.ts +2 -5
  162. package/dist/src/utils/graph.d.ts.map +1 -0
  163. package/dist/src/utils/graph.js +4 -6
  164. package/dist/src/utils/graph.js.map +1 -1
  165. package/dist/src/utils/index.d.ts +6 -5
  166. package/dist/src/utils/index.d.ts.map +1 -0
  167. package/dist/src/utils/index.js +5 -5
  168. package/dist/src/utils/index.js.map +1 -1
  169. package/dist/src/utils/numpy.d.ts +5 -4
  170. package/dist/src/utils/numpy.d.ts.map +1 -0
  171. package/dist/src/utils/numpy.js +22 -16
  172. package/dist/src/utils/numpy.js.map +1 -1
  173. package/dist/src/utils/params.d.ts +2 -2
  174. package/dist/src/utils/params.d.ts.map +1 -0
  175. package/dist/src/utils/params.js +0 -1
  176. package/dist/src/utils/params.js.map +1 -1
  177. package/dist/src/utils/random.d.ts +18 -0
  178. package/dist/src/utils/random.d.ts.map +1 -0
  179. package/dist/src/utils/random.js +18 -1
  180. package/dist/src/utils/random.js.map +1 -1
  181. package/dist/src/utils/rescale.d.ts +2 -3
  182. package/dist/src/utils/rescale.d.ts.map +1 -0
  183. package/dist/src/utils/rescale.js +12 -10
  184. package/dist/src/utils/rescale.js.map +1 -1
  185. package/dist/tsconfig.tsbuildinfo +1 -0
  186. package/package.json +35 -34
  187. package/src/algorithms/index.ts +2 -2
  188. package/src/algorithms/optimization/index.ts +4 -8
  189. package/src/algorithms/optimization/kamada-kawai-solver.ts +170 -179
  190. package/src/algorithms/optimization/lbfgs.ts +42 -49
  191. package/src/algorithms/optimization/line-search.ts +27 -28
  192. package/src/algorithms/optimization/types.ts +2 -2
  193. package/src/algorithms/planarity/check.ts +23 -23
  194. package/src/algorithms/planarity/embedding.ts +160 -171
  195. package/src/algorithms/planarity/index.ts +4 -8
  196. package/src/algorithms/planarity/lr-test.ts +46 -46
  197. package/src/algorithms/planarity/special-graphs.ts +73 -83
  198. package/src/generators/basic.ts +57 -57
  199. package/src/generators/bipartite.ts +37 -31
  200. package/src/generators/grid.ts +28 -28
  201. package/src/generators/index.ts +5 -11
  202. package/src/generators/random.ts +26 -25
  203. package/src/generators/scale-free.ts +56 -55
  204. package/src/index.ts +5 -5
  205. package/src/layouts/basic/index.ts +1 -1
  206. package/src/layouts/basic/random.ts +20 -16
  207. package/src/layouts/force-directed/arf.ts +107 -105
  208. package/src/layouts/force-directed/forceatlas2.ts +374 -352
  209. package/src/layouts/force-directed/fruchterman-reingold.ts +133 -134
  210. package/src/layouts/force-directed/index.ts +5 -5
  211. package/src/layouts/force-directed/kamada-kawai.ts +77 -80
  212. package/src/layouts/force-directed/spring.ts +24 -25
  213. package/src/layouts/geometric/circular.ts +63 -57
  214. package/src/layouts/geometric/index.ts +3 -3
  215. package/src/layouts/geometric/shell.ts +58 -53
  216. package/src/layouts/geometric/spiral.ts +60 -64
  217. package/src/layouts/hierarchical/bfs.ts +58 -59
  218. package/src/layouts/hierarchical/bipartite.ts +62 -63
  219. package/src/layouts/hierarchical/index.ts +3 -3
  220. package/src/layouts/hierarchical/multipartite.ts +62 -63
  221. package/src/layouts/index.ts +5 -5
  222. package/src/layouts/specialized/index.ts +2 -2
  223. package/src/layouts/specialized/planar.ts +41 -42
  224. package/src/layouts/specialized/spectral.ts +110 -99
  225. package/src/types/embedding.ts +5 -5
  226. package/src/types/graph.ts +4 -4
  227. package/src/types/index.ts +3 -3
  228. package/src/types/layout.ts +2 -2
  229. package/src/utils/graph.ts +31 -35
  230. package/src/utils/index.ts +5 -5
  231. package/src/utils/numpy.ts +96 -92
  232. package/src/utils/params.ts +14 -11
  233. package/src/utils/random.ts +57 -40
  234. package/src/utils/rescale.ts +169 -174
  235. package/dist/vitest.config.d.ts +0 -2
  236. package/dist/vitest.config.js +0 -30
  237. package/dist/vitest.config.js.map +0 -1
@@ -1,12 +1,11 @@
1
- import type { Graph, Node, Edge, PositionMap } from '../../types';
2
- import { getNodesFromGraph, getEdgesFromGraph } from '../../utils/graph';
3
- import { _processParams } from '../../utils/params';
4
- import { rescaleLayout } from '../../utils/rescale';
5
- import { RandomNumberGenerator } from '../../utils/random';
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 dissuadeHubs - Whether to prevent hub nodes from clustering
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
- G: Graph,
29
- pos: PositionMap | null = null,
30
- maxIter: number = 100,
31
- jitterTolerance: number = 1.0,
32
- scalingRatio: number = 2.0,
33
- gravity: number = 1.0,
34
- distributedAction: boolean = false,
35
- strongGravity: boolean = false,
36
- nodeMass: Record<Node, number> | null = null,
37
- nodeSize: Record<Node, number> | null = null,
38
- weight: string | null = null,
39
- dissuadeHubs: boolean = false,
40
- linlog: boolean = false,
41
- seed: number | null = null,
42
- dim: number = 2
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
- const processed = _processParams(G, null, dim);
45
- const graph = processed.G;
43
+ const processed = _processParams(G, null, dim);
44
+ const graph = processed.G;
46
45
 
47
- const nodes = getNodesFromGraph(graph);
46
+ const nodes = getNodesFromGraph(graph);
48
47
 
49
- if (nodes.length === 0) {
50
- return {};
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
- // If min/max are still infinity for some dimensions, use default range
92
- for (let d = 0; d < dim; d++) {
93
- if (!isFinite(minPos[d]) || !isFinite(maxPos[d])) {
94
- minPos[d] = -1;
95
- maxPos[d] = 1;
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
- posArray = new Array(nodes.length);
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
- posArray[i][d] = d < nodePos.length ? nodePos[d] : (rng.rand() as number * 2 - 1);
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
- A[i][j] = edgeWeight;
154
- A[j][i] = edgeWeight; // For undirected graphs
155
- }
156
-
157
- // Initialize force arrays
158
- const gravities = Array(n).fill(0).map(() => Array(dim).fill(0));
159
- const attraction = Array(n).fill(0).map(() => Array(dim).fill(0));
160
- const repulsion = Array(n).fill(0).map(() => Array(dim).fill(0));
161
-
162
- // Simulation parameters
163
- let speed = 1;
164
- let speedEfficiency = 1;
165
- let swing = 1;
166
- let traction = 1;
167
-
168
- // Helper function to estimate factor for force scaling
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
- // Calculate target speed
196
- let targetSpeed = swing === 0 ?
197
- Number.POSITIVE_INFINITY :
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
- // Limit the speed increase
210
- const maxRise = 0.5;
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
- return [speed, speedEfficiency];
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
- // Main simulation loop
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
- // Compute pairwise differences and distances
228
- const diff = Array(n).fill(0).map(() =>
229
- Array(n).fill(0).map(() => Array(dim).fill(0))
230
- );
231
-
232
- const distance = Array(n).fill(0).map(() => Array(n).fill(0));
233
-
234
- for (let i = 0; i < n; i++) {
235
- for (let j = 0; j < n; j++) {
236
- if (i === j) continue;
237
-
238
- for (let d = 0; d < dim; d++) {
239
- diff[i][j][d] = posArray[i][d] - posArray[j][d];
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
- distance[i][j] = Math.sqrt(diff[i][j].reduce((sum, d) => sum + d * d, 0));
243
- // Prevent division by zero
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
- // Calculate attraction forces
249
- if (linlog) {
250
- // Logarithmic attraction model
251
- for (let i = 0; i < n; i++) {
252
- for (let j = 0; j < n; j++) {
253
- if (i === j || A[i][j] === 0) continue;
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
- const dist = distance[i][j];
256
- const factor = -Math.log(1 + dist) / dist * A[i][j];
213
+ // Calculate target speed
214
+ const targetSpeed = swingValue === 0 ? Number.POSITIVE_INFINITY : (jitter * resultSpeedEfficiency * tractionValue) / swingValue;
257
215
 
258
- for (let d = 0; d < dim; d++) {
259
- const force = factor * diff[i][j][d];
260
- attraction[i][d] += force;
261
- }
262
- }
263
- }
264
- } else {
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
- // Apply distributed attraction if enabled
279
- if (distributedAction) {
280
- for (let i = 0; i < n; i++) {
281
- for (let d = 0; d < dim; d++) {
282
- attraction[i][d] /= mass[i];
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
- // Calculate repulsion forces
288
- for (let i = 0; i < n; i++) {
289
- for (let j = 0; j < n; j++) {
290
- if (i === j) continue;
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
- let dist = distance[i][j];
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
- // Adjust distance for node sizes if needed
295
- if (adjustSizes) {
296
- dist -= size[i] - size[j];
297
- dist = Math.max(dist, 0.01); // Prevent negative or zero distances
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
- const distSquared = dist * dist;
301
- const massProduct = mass[i] * mass[j];
302
- const factor = (massProduct / distSquared) * scalingRatio;
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
- for (let d = 0; d < dim; d++) {
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
- // Calculate gravity forces
312
- // First find the center of mass
313
- const centerOfMass = Array(dim).fill(0);
314
- for (let i = 0; i < n; i++) {
315
- for (let d = 0; d < dim; d++) {
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
- for (let i = 0; i < n; i++) {
321
- const posCentered = Array(dim);
322
- for (let d = 0; d < dim; d++) {
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
- if (strongGravity) {
327
- // Strong gravity model
328
- for (let d = 0; d < dim; d++) {
329
- gravities[i][d] = -gravity * mass[i] * posCentered[d];
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
- } else {
332
- // Regular gravity model
333
- const dist = Math.sqrt(posCentered.reduce((sum, val) => sum + val * val, 0));
334
-
335
- if (dist > 0.01) {
336
- for (let d = 0; d < dim; d++) {
337
- const direction = posCentered[d] / dist;
338
- gravities[i][d] = -gravity * mass[i] * direction;
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
- // Calculate total forces and update positions
345
- const update = Array(n).fill(0).map(() => Array(dim).fill(0));
346
- let totalSwing = 0;
347
- let totalTraction = 0;
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
- for (let i = 0; i < n; i++) {
350
- for (let d = 0; d < dim; d++) {
351
- update[i][d] = attraction[i][d] + repulsion[i][d] + gravities[i][d];
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
- // Calculate swing and traction for this node
355
- const oldPos = [...posArray[i]];
356
- const newPos = oldPos.map((p, d) => p + update[i][d]);
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
- const swingVector = oldPos.map((p, d) => p - newPos[d]);
359
- const tractionVector = oldPos.map((p, d) => p + newPos[d]);
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
- const swingMagnitude = Math.sqrt(swingVector.reduce((sum, val) => sum + val * val, 0));
362
- const tractionMagnitude = Math.sqrt(tractionVector.reduce((sum, val) => sum + val * val, 0));
382
+ const swingVector = oldPos.map((p, d) => p - newPos[d]);
383
+ const tractionVector = oldPos.map((p, d) => p + newPos[d]);
363
384
 
364
- totalSwing += mass[i] * swingMagnitude;
365
- totalTraction += 0.5 * mass[i] * tractionMagnitude;
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
- // Update speed and efficiency
369
- [speed, speedEfficiency] = estimateFactor(
370
- n,
371
- totalSwing,
372
- totalTraction,
373
- speed,
374
- speedEfficiency,
375
- jitterTolerance
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
- // Apply forces to update positions
379
- let totalMovement = 0;
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
- for (let i = 0; i < n; i++) {
382
- let factor;
383
-
384
- if (adjustSizes) {
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
- // Check for convergence
407
- if (totalMovement < 1e-10) {
408
- break;
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
- return rescaleLayout(positions) as PositionMap;
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
- return graph.edges().filter((edge: Edge) =>
424
- edge[0] === node || edge[1] === node
425
- ).length;
426
- }
447
+ return graph.edges().filter((edge: Edge) => edge[0] === node || edge[1] === node).length;
448
+ }