@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
package/dist/layout.js CHANGED
@@ -1,8 +1,12 @@
1
1
  function rescaleLayout(pos, scale = 1, center = null) {
2
2
  if (Array.isArray(pos)) {
3
- if (pos.length === 0) return [];
3
+ if (pos.length === 0) {
4
+ return [];
5
+ }
4
6
  } else {
5
- if (Object.keys(pos).length === 0) return {};
7
+ if (Object.keys(pos).length === 0) {
8
+ return {};
9
+ }
6
10
  }
7
11
  const posValues = Array.isArray(pos) ? pos : Object.values(pos);
8
12
  const dim = posValues[0].length;
@@ -65,7 +69,9 @@ function rescaleLayout(pos, scale = 1, center = null) {
65
69
  if (i >= origPos.length) {
66
70
  return center[i];
67
71
  }
68
- if (isNaN(val)) return NaN;
72
+ if (isNaN(val)) {
73
+ return NaN;
74
+ }
69
75
  return val * scaleFactor + center[i];
70
76
  })
71
77
  );
@@ -76,7 +82,9 @@ function rescaleLayout(pos, scale = 1, center = null) {
76
82
  if (i >= origPos.length) {
77
83
  return center[i];
78
84
  }
79
- if (isNaN(val)) return NaN;
85
+ if (isNaN(val)) {
86
+ return NaN;
87
+ }
80
88
  return val * scaleFactor + center[i];
81
89
  });
82
90
  }
@@ -140,15 +148,6 @@ function rescaleLayoutDict(pos, scale = 1) {
140
148
  }
141
149
  return scaledPos;
142
150
  }
143
- function _processParams(G, center, dim) {
144
- if (!center) {
145
- center = Array(dim).fill(0);
146
- }
147
- if (center.length !== dim) {
148
- throw new Error("length of center coordinates must match dimension of layout");
149
- }
150
- return { G, center };
151
- }
152
151
  function getNodesFromGraph(G) {
153
152
  if (Array.isArray(G)) {
154
153
  return G;
@@ -162,7 +161,9 @@ function getEdgesFromGraph(G) {
162
161
  return G.edges();
163
162
  }
164
163
  function getNeighbors(graph, node) {
165
- if (!graph.edges) return [];
164
+ if (!graph.edges) {
165
+ return [];
166
+ }
166
167
  const neighbors = /* @__PURE__ */ new Set();
167
168
  const edges = graph.edges();
168
169
  for (const [source, target] of edges) {
@@ -174,7 +175,20 @@ function getNeighbors(graph, node) {
174
175
  }
175
176
  return Array.from(neighbors);
176
177
  }
178
+ function _processParams(G, center, dim) {
179
+ if (!center) {
180
+ center = Array(dim).fill(0);
181
+ }
182
+ if (center.length !== dim) {
183
+ throw new Error("length of center coordinates must match dimension of layout");
184
+ }
185
+ return { G, center };
186
+ }
177
187
  class RandomNumberGenerator {
188
+ /**
189
+ * Creates a new random number generator instance.
190
+ * @param seed - Initial seed value for the generator. If not provided, uses a random seed.
191
+ */
178
192
  constructor(seed) {
179
193
  this.seed = seed || Math.floor(Math.random() * 1e6);
180
194
  this.m = 2 ** 35 - 31;
@@ -182,10 +196,19 @@ class RandomNumberGenerator {
182
196
  this.c = 1;
183
197
  this._state = this.seed % this.m;
184
198
  }
199
+ /**
200
+ * Generates the next random number in the sequence.
201
+ * @returns A random number between 0 and 1.
202
+ */
185
203
  _next() {
186
204
  this._state = (this.a * this._state + this.c) % this.m;
187
205
  return this._state / this.m;
188
206
  }
207
+ /**
208
+ * Generates random numbers according to the specified shape.
209
+ * @param shape - Determines the output format: null for single number, number for 1D array, array for multi-dimensional array.
210
+ * @returns A random number, 1D array, or multi-dimensional array depending on the shape parameter.
211
+ */
189
212
  rand(shape = null) {
190
213
  if (shape === null) {
191
214
  return this._next();
@@ -214,7 +237,7 @@ class RandomNumberGenerator {
214
237
  function randomLayout(G, center = null, dim = 2, seed = null) {
215
238
  const processed = _processParams(G, center, dim);
216
239
  const nodes = getNodesFromGraph(processed.G);
217
- center = processed.center;
240
+ ({ center } = processed);
218
241
  const rng = new RandomNumberGenerator(seed ?? void 0);
219
242
  const pos = {};
220
243
  nodes.forEach((node) => {
@@ -222,236 +245,363 @@ function randomLayout(G, center = null, dim = 2, seed = null) {
222
245
  });
223
246
  return pos;
224
247
  }
225
- const np = {
226
- zeros: function(shape) {
227
- if (typeof shape === "number") {
228
- return Array(shape).fill(0);
229
- }
230
- if (shape.length === 1) {
231
- return Array(shape[0]).fill(0);
232
- }
233
- return Array(shape[0]).fill(0).map(() => this.zeros(shape.slice(1)));
234
- },
235
- ones: function(shape) {
236
- if (typeof shape === "number") {
237
- return Array(shape).fill(1);
248
+ function arfLayout(G, pos = null, scaling = 1, a = 1.1, maxIter = 1e3, seed = null) {
249
+ if (a <= 1) {
250
+ throw new Error("The parameter a should be larger than 1");
251
+ }
252
+ const nodes = getNodesFromGraph(G);
253
+ const edges = getEdgesFromGraph(G);
254
+ if (nodes.length === 0) {
255
+ return {};
256
+ }
257
+ if (!pos) {
258
+ pos = randomLayout(G, null, 2, seed);
259
+ } else {
260
+ const rng = new RandomNumberGenerator(seed ?? void 0);
261
+ const defaultPos = {};
262
+ nodes.forEach((node) => {
263
+ if (!pos[node]) {
264
+ defaultPos[node] = [rng.rand(), rng.rand()];
265
+ }
266
+ });
267
+ pos = { ...pos, ...defaultPos };
268
+ }
269
+ const nodeIndex = {};
270
+ nodes.forEach((node, i) => {
271
+ nodeIndex[node] = i;
272
+ });
273
+ const positions = nodes.map((node) => [...pos[node]]);
274
+ const N = nodes.length;
275
+ const K = Array(N).fill(0).map(() => Array(N).fill(1));
276
+ for (let i = 0; i < N; i++) {
277
+ K[i][i] = 0;
278
+ }
279
+ for (const [source, target] of edges) {
280
+ if (source === target) {
281
+ continue;
238
282
  }
239
- if (shape.length === 1) {
240
- return Array(shape[0]).fill(1);
283
+ const i = nodeIndex[source];
284
+ const j = nodeIndex[target];
285
+ K[i][j] = a;
286
+ K[j][i] = a;
287
+ }
288
+ const rho = scaling * Math.sqrt(N);
289
+ const dt = 1e-3;
290
+ const etol = 1e-6;
291
+ let error = etol + 1;
292
+ let nIter = 0;
293
+ while (error > etol && nIter < maxIter) {
294
+ const change = Array(N).fill(0).map(() => [0, 0]);
295
+ for (let i = 0; i < N; i++) {
296
+ for (let j = 0; j < N; j++) {
297
+ if (i === j) {
298
+ continue;
299
+ }
300
+ const diff = positions[i].map((coord, dim) => coord - positions[j][dim]);
301
+ const dist = Math.sqrt(diff.reduce((sum, d) => sum + d * d, 0)) || 0.01;
302
+ for (let d = 0; d < diff.length; d++) {
303
+ change[i][d] += K[i][j] * diff[d] - rho / dist * diff[d];
304
+ }
305
+ }
241
306
  }
242
- return Array(shape[0]).fill(1).map(() => this.ones(shape.slice(1)));
243
- },
244
- linspace: function(start, stop, num) {
245
- if (num === 1) {
246
- return [start];
307
+ for (let i = 0; i < N; i++) {
308
+ for (let d = 0; d < positions[i].length; d++) {
309
+ positions[i][d] += change[i][d] * dt;
310
+ }
247
311
  }
248
- const step = (stop - start) / (num - 1);
249
- return Array.from({ length: num }, (_, i) => start + i * step);
250
- },
251
- array: function(arr) {
252
- return Array.isArray(arr) ? [...arr] : [arr];
253
- },
254
- repeat: function(a, repeats) {
255
- const result = [];
256
- for (let i = 0; i < repeats; i++) {
257
- result.push(...np.array(a));
312
+ error = change.reduce((sum, c) => sum + Math.sqrt(c.reduce((s, v) => s + v * v, 0)), 0);
313
+ nIter++;
314
+ }
315
+ const finalPos = {};
316
+ nodes.forEach((node, i) => {
317
+ finalPos[node] = positions[i];
318
+ });
319
+ return finalPos;
320
+ }
321
+ function forceatlas2Layout(G, pos = null, maxIter = 100, jitterTolerance = 1, scalingRatio = 2, gravity = 1, distributedAction = false, strongGravity = false, nodeMass = null, nodeSize = null, weight = null, _dissuadeHubs = false, linlog = false, seed = null, dim = 2) {
322
+ const processed = _processParams(G, null, dim);
323
+ const graph = processed.G;
324
+ const nodes = getNodesFromGraph(graph);
325
+ if (nodes.length === 0) {
326
+ return {};
327
+ }
328
+ const rng = new RandomNumberGenerator(seed ?? void 0);
329
+ let posArray;
330
+ if (pos === null) {
331
+ pos = {};
332
+ posArray = new Array(nodes.length);
333
+ for (let i = 0; i < nodes.length; i++) {
334
+ posArray[i] = Array(dim).fill(0).map(() => rng.rand() * 2 - 1);
335
+ pos[nodes[i]] = posArray[i];
258
336
  }
259
- return result;
260
- },
261
- mean: function(arr, axis = null) {
262
- if (axis === null) {
263
- const flatArr = Array.isArray(arr[0]) ? arr.flat(Infinity) : arr;
264
- const sum = flatArr.reduce((a, b) => a + b, 0);
265
- return sum / flatArr.length;
337
+ } else if (Object.keys(pos).length === nodes.length) {
338
+ posArray = new Array(nodes.length);
339
+ for (let i = 0; i < nodes.length; i++) {
340
+ const nodePos = pos[nodes[i]];
341
+ posArray[i] = new Array(dim);
342
+ for (let d = 0; d < dim; d++) {
343
+ posArray[i][d] = d < nodePos.length ? nodePos[d] : rng.rand() * 2 - 1;
344
+ }
266
345
  }
267
- if (axis === 0) {
268
- const result = [];
269
- const matrix = arr;
270
- for (let i = 0; i < matrix[0].length; i++) {
271
- let sum = 0;
272
- for (let j = 0; j < matrix.length; j++) {
273
- sum += matrix[j][i];
346
+ } else {
347
+ const minPos = Array(dim).fill(Number.POSITIVE_INFINITY);
348
+ const maxPos = Array(dim).fill(Number.NEGATIVE_INFINITY);
349
+ for (const node in pos) {
350
+ const nodePos = pos[node];
351
+ for (let d = 0; d < dim; d++) {
352
+ if (d < nodePos.length) {
353
+ minPos[d] = Math.min(minPos[d], nodePos[d]);
354
+ maxPos[d] = Math.max(maxPos[d], nodePos[d]);
274
355
  }
275
- result.push(sum / matrix.length);
276
356
  }
277
- return result;
278
357
  }
279
- return arr.map((row) => np.mean(row));
280
- },
281
- add: function(a, b) {
282
- if (!Array.isArray(a) && !Array.isArray(b)) {
283
- return a + b;
358
+ for (let d = 0; d < dim; d++) {
359
+ if (!isFinite(minPos[d]) || !isFinite(maxPos[d])) {
360
+ minPos[d] = -1;
361
+ maxPos[d] = 1;
362
+ }
284
363
  }
285
- if (!Array.isArray(a)) {
286
- return b.map((val) => a + val);
364
+ posArray = new Array(nodes.length);
365
+ for (let i = 0; i < nodes.length; i++) {
366
+ const node = nodes[i];
367
+ if (pos[node]) {
368
+ const nodePos = pos[node];
369
+ posArray[i] = new Array(dim);
370
+ for (let d = 0; d < dim; d++) {
371
+ posArray[i][d] = d < nodePos.length ? nodePos[d] : rng.rand() * 2 - 1;
372
+ }
373
+ } else {
374
+ posArray[i] = Array(dim).fill(0).map((_, d) => minPos[d] + rng.rand() * (maxPos[d] - minPos[d]));
375
+ pos[node] = posArray[i];
376
+ }
287
377
  }
288
- if (!Array.isArray(b)) {
289
- return a.map((val) => val + b);
378
+ }
379
+ const mass = new Array(nodes.length).fill(0);
380
+ const size = new Array(nodes.length).fill(0);
381
+ const adjustSizes = nodeSize !== null;
382
+ for (let i = 0; i < nodes.length; i++) {
383
+ const node = nodes[i];
384
+ if (nodeMass && nodeMass[node]) {
385
+ mass[i] = nodeMass[node];
386
+ } else if (Array.isArray(graph)) {
387
+ mass[i] = 1;
388
+ } else {
389
+ mass[i] = getNodeDegree(graph, node) + 1;
290
390
  }
291
- return a.map((val, i) => val + b[i]);
292
- },
293
- subtract: function(a, b) {
294
- if (!Array.isArray(a) && !Array.isArray(b)) {
295
- return a - b;
391
+ size[i] = nodeSize && nodeSize[node] ? nodeSize[node] : 1;
392
+ }
393
+ const n = nodes.length;
394
+ const A = Array(n).fill(0).map(() => Array(n).fill(0));
395
+ const edges = Array.isArray(graph) ? [] : graph.edges();
396
+ const nodeIndices = {};
397
+ nodes.forEach((node, i) => {
398
+ nodeIndices[node] = i;
399
+ });
400
+ for (const [source, target] of edges) {
401
+ const i = nodeIndices[source];
402
+ const j = nodeIndices[target];
403
+ let edgeWeight = 1;
404
+ if (weight && !Array.isArray(graph) && graph.getEdgeData) {
405
+ edgeWeight = graph.getEdgeData(source, target, weight) || 1;
296
406
  }
297
- if (!Array.isArray(a)) {
298
- return b.map((val) => a - val);
407
+ A[i][j] = edgeWeight;
408
+ A[j][i] = edgeWeight;
409
+ }
410
+ const gravities = Array(n).fill(0).map(() => Array(dim).fill(0));
411
+ const attraction = Array(n).fill(0).map(() => Array(dim).fill(0));
412
+ const repulsion = Array(n).fill(0).map(() => Array(dim).fill(0));
413
+ let speed = 1;
414
+ let speedEfficiency = 1;
415
+ function estimateFactor(nodeCount, swingValue, tractionValue, currentSpeed, currentSpeedEfficiency, jitterToleranceParam) {
416
+ let resultSpeed = currentSpeed;
417
+ let resultSpeedEfficiency = currentSpeedEfficiency;
418
+ const optJitter = 0.05 * Math.sqrt(nodeCount);
419
+ const minJitter = Math.sqrt(optJitter);
420
+ const maxJitter = 10;
421
+ const minSpeedEfficiency = 0.05;
422
+ const other = Math.min(maxJitter, optJitter * tractionValue / (nodeCount * nodeCount));
423
+ let jitter = jitterToleranceParam * Math.max(minJitter, other);
424
+ if (swingValue / tractionValue > 2) {
425
+ if (resultSpeedEfficiency > minSpeedEfficiency) {
426
+ resultSpeedEfficiency *= 0.5;
427
+ }
428
+ jitter = Math.max(jitter, jitterToleranceParam);
299
429
  }
300
- if (!Array.isArray(b)) {
301
- return a.map((val) => val - b);
430
+ const targetSpeed = swingValue === 0 ? Number.POSITIVE_INFINITY : jitter * resultSpeedEfficiency * tractionValue / swingValue;
431
+ if (swingValue > jitter * tractionValue) {
432
+ if (resultSpeedEfficiency > minSpeedEfficiency) {
433
+ resultSpeedEfficiency *= 0.7;
434
+ }
435
+ } else if (resultSpeed < 1e3) {
436
+ resultSpeedEfficiency *= 1.3;
302
437
  }
303
- return a.map((val, i) => val - b[i]);
304
- },
305
- max: function(arr) {
306
- if (!Array.isArray(arr)) return arr;
307
- return Math.max(...arr.flat(Infinity));
308
- },
309
- min: function(arr) {
310
- if (!Array.isArray(arr)) return arr;
311
- return Math.min(...arr.flat(Infinity));
312
- },
313
- norm: function(arr) {
314
- return Math.sqrt(arr.reduce((sum, val) => sum + val * val, 0));
315
- }
316
- };
317
- function circularLayout(G, scale = 1, center = null, dim = 2) {
318
- if (dim < 2) {
319
- throw new Error("cannot handle dimensions < 2");
438
+ const maxRise = 0.5;
439
+ resultSpeed = resultSpeed + Math.min(targetSpeed - resultSpeed, maxRise * resultSpeed);
440
+ return [resultSpeed, resultSpeedEfficiency];
320
441
  }
321
- const processed = _processParams(G, center, dim);
322
- const nodes = getNodesFromGraph(processed.G);
323
- center = processed.center;
324
- const pos = {};
325
- if (nodes.length === 0) {
326
- return pos;
327
- }
328
- if (nodes.length === 1) {
329
- pos[nodes[0]] = center;
330
- return pos;
331
- }
332
- if (dim === 2) {
333
- const theta = np.linspace(0, 2 * Math.PI, nodes.length + 1).slice(0, -1);
334
- nodes.forEach((node, i) => {
335
- const x = Math.cos(theta[i]) * scale + center[0];
336
- const y = Math.sin(theta[i]) * scale + center[1];
337
- pos[node] = [x, y];
338
- });
339
- } else if (dim === 3) {
340
- const n = nodes.length;
341
- const goldenRatio = (1 + Math.sqrt(5)) / 2;
342
- nodes.forEach((node, i) => {
343
- const theta = 2 * Math.PI * i / goldenRatio;
344
- const phi = Math.acos(1 - 2 * (i + 0.5) / n);
345
- const x = Math.sin(phi) * Math.cos(theta) * scale + center[0];
346
- const y = Math.sin(phi) * Math.sin(theta) * scale + center[1];
347
- const z = Math.cos(phi) * scale + center[2];
348
- pos[node] = [x, y, z];
349
- });
350
- } else {
351
- const rng = new RandomNumberGenerator();
352
- nodes.forEach((node) => {
353
- const coords = Array(dim).fill(0).map(() => rng.rand() * 2 - 1);
354
- const norm = Math.sqrt(coords.reduce((sum, c) => sum + c * c, 0));
355
- pos[node] = coords.map((c, j) => c / norm * scale + center[j]);
356
- });
357
- }
358
- return pos;
359
- }
360
- function shellLayout(G, nlist = null, scale = 1, center = null, dim = 2) {
361
- if (dim !== 2) {
362
- throw new Error("can only handle 2 dimensions");
363
- }
364
- const processed = _processParams(G, center, dim);
365
- const nodes = getNodesFromGraph(processed.G);
366
- center = processed.center;
367
- const pos = {};
368
- if (nodes.length === 0) {
369
- return pos;
370
- }
371
- if (nodes.length === 1) {
372
- pos[nodes[0]] = center;
373
- return pos;
374
- }
375
- if (!nlist) {
376
- nlist = [nodes];
377
- }
378
- const radiusBump = scale / nlist.length;
379
- let radius;
380
- if (nlist[0].length === 1) {
381
- radius = 0;
382
- pos[nlist[0][0]] = [...center];
383
- radius += radiusBump;
384
- } else {
385
- radius = radiusBump;
386
- }
387
- for (let i = 0; i < nlist.length; i++) {
388
- const shell = nlist[i];
389
- if (shell.length === 0) continue;
390
- if (shell.length === 1 && i === 0) {
391
- continue;
442
+ for (let iter = 0; iter < maxIter; iter++) {
443
+ for (let i = 0; i < n; i++) {
444
+ for (let d = 0; d < dim; d++) {
445
+ attraction[i][d] = 0;
446
+ repulsion[i][d] = 0;
447
+ gravities[i][d] = 0;
448
+ }
392
449
  }
393
- const theta = np.linspace(0, 2 * Math.PI, shell.length + 1).slice(0, -1);
394
- shell.forEach((node, j) => {
395
- const x = Math.cos(theta[j]) * radius + center[0];
396
- const y = Math.sin(theta[j]) * radius + center[1];
397
- pos[node] = [x, y];
398
- });
399
- radius += radiusBump;
400
- }
401
- return pos;
402
- }
403
- function spiralLayout(G, scale = 1, center = null, dim = 2, resolution = 0.35, equidistant = false) {
404
- if (dim !== 2) {
405
- throw new Error("can only handle 2 dimensions");
406
- }
407
- const processed = _processParams(G, center || [0, 0], dim);
408
- const nodes = getNodesFromGraph(processed.G);
409
- center = processed.center;
410
- const pos = {};
411
- if (nodes.length === 0) {
412
- return pos;
413
- }
414
- if (nodes.length === 1) {
415
- pos[nodes[0]] = [...center];
416
- return pos;
417
- }
418
- let positions = [];
419
- if (equidistant) {
420
- const chord = 1;
421
- const step = 0.5;
422
- let theta = resolution;
423
- theta += chord / (step * theta);
424
- for (let i = 0; i < nodes.length; i++) {
425
- const r = step * theta;
426
- theta += chord / r;
427
- positions.push([Math.cos(theta) * r, Math.sin(theta) * r]);
450
+ const diff = Array(n).fill(0).map(
451
+ () => Array(n).fill(0).map(() => Array(dim).fill(0))
452
+ );
453
+ const distance = Array(n).fill(0).map(() => Array(n).fill(0));
454
+ for (let i = 0; i < n; i++) {
455
+ for (let j = 0; j < n; j++) {
456
+ if (i === j) {
457
+ continue;
458
+ }
459
+ for (let d = 0; d < dim; d++) {
460
+ diff[i][j][d] = posArray[i][d] - posArray[j][d];
461
+ }
462
+ distance[i][j] = Math.sqrt(diff[i][j].reduce((sum, d) => sum + d * d, 0));
463
+ if (distance[i][j] < 0.01) {
464
+ distance[i][j] = 0.01;
465
+ }
466
+ }
467
+ }
468
+ if (linlog) {
469
+ for (let i = 0; i < n; i++) {
470
+ for (let j = 0; j < n; j++) {
471
+ if (i === j || A[i][j] === 0) {
472
+ continue;
473
+ }
474
+ const dist = distance[i][j];
475
+ const factor = -Math.log(1 + dist) / dist * A[i][j];
476
+ for (let d = 0; d < dim; d++) {
477
+ const force = factor * diff[i][j][d];
478
+ attraction[i][d] += force;
479
+ }
480
+ }
481
+ }
482
+ } else {
483
+ for (let i = 0; i < n; i++) {
484
+ for (let j = 0; j < n; j++) {
485
+ if (i === j || A[i][j] === 0) {
486
+ continue;
487
+ }
488
+ for (let d = 0; d < dim; d++) {
489
+ const force = -diff[i][j][d] * A[i][j];
490
+ attraction[i][d] += force;
491
+ }
492
+ }
493
+ }
494
+ }
495
+ if (distributedAction) {
496
+ for (let i = 0; i < n; i++) {
497
+ for (let d = 0; d < dim; d++) {
498
+ attraction[i][d] /= mass[i];
499
+ }
500
+ }
501
+ }
502
+ for (let i = 0; i < n; i++) {
503
+ for (let j = 0; j < n; j++) {
504
+ if (i === j) {
505
+ continue;
506
+ }
507
+ let dist = distance[i][j];
508
+ if (adjustSizes) {
509
+ dist -= size[i] - size[j];
510
+ dist = Math.max(dist, 0.01);
511
+ }
512
+ const distSquared = dist * dist;
513
+ const massProduct = mass[i] * mass[j];
514
+ const factor = massProduct / distSquared * scalingRatio;
515
+ for (let d = 0; d < dim; d++) {
516
+ const direction = diff[i][j][d] / dist;
517
+ repulsion[i][d] += direction * factor;
518
+ }
519
+ }
520
+ }
521
+ const centerOfMass = Array(dim).fill(0);
522
+ for (let i = 0; i < n; i++) {
523
+ for (let d = 0; d < dim; d++) {
524
+ centerOfMass[d] += posArray[i][d] / n;
525
+ }
526
+ }
527
+ for (let i = 0; i < n; i++) {
528
+ const posCentered = Array(dim);
529
+ for (let d = 0; d < dim; d++) {
530
+ posCentered[d] = posArray[i][d] - centerOfMass[d];
531
+ }
532
+ if (strongGravity) {
533
+ for (let d = 0; d < dim; d++) {
534
+ gravities[i][d] = -gravity * mass[i] * posCentered[d];
535
+ }
536
+ } else {
537
+ const dist = Math.sqrt(posCentered.reduce((sum, val) => sum + val * val, 0));
538
+ if (dist > 0.01) {
539
+ for (let d = 0; d < dim; d++) {
540
+ const direction = posCentered[d] / dist;
541
+ gravities[i][d] = -gravity * mass[i] * direction;
542
+ }
543
+ }
544
+ }
545
+ }
546
+ const update = Array(n).fill(0).map(() => Array(dim).fill(0));
547
+ let totalSwing = 0;
548
+ let totalTraction = 0;
549
+ for (let i = 0; i < n; i++) {
550
+ for (let d = 0; d < dim; d++) {
551
+ update[i][d] = attraction[i][d] + repulsion[i][d] + gravities[i][d];
552
+ }
553
+ const oldPos = [...posArray[i]];
554
+ const newPos = oldPos.map((p, d) => p + update[i][d]);
555
+ const swingVector = oldPos.map((p, d) => p - newPos[d]);
556
+ const tractionVector = oldPos.map((p, d) => p + newPos[d]);
557
+ const swingMagnitude = Math.sqrt(swingVector.reduce((sum, val) => sum + val * val, 0));
558
+ const tractionMagnitude = Math.sqrt(tractionVector.reduce((sum, val) => sum + val * val, 0));
559
+ totalSwing += mass[i] * swingMagnitude;
560
+ totalTraction += 0.5 * mass[i] * tractionMagnitude;
561
+ }
562
+ [speed, speedEfficiency] = estimateFactor(
563
+ n,
564
+ totalSwing,
565
+ totalTraction,
566
+ speed,
567
+ speedEfficiency,
568
+ jitterTolerance
569
+ );
570
+ let totalMovement = 0;
571
+ for (let i = 0; i < n; i++) {
572
+ let factor;
573
+ if (adjustSizes) {
574
+ const df = Math.sqrt(update[i].reduce((sum, val) => sum + val * val, 0));
575
+ const swinging = mass[i] * df;
576
+ factor = 0.1 * speed / (1 + Math.sqrt(speed * swinging));
577
+ factor = Math.min(factor * df, 10) / df;
578
+ } else {
579
+ const swinging = mass[i] * Math.sqrt(update[i].reduce((sum, val) => sum + val * val, 0));
580
+ factor = speed / (1 + Math.sqrt(speed * swinging));
581
+ }
582
+ for (let d = 0; d < dim; d++) {
583
+ const movement = update[i][d] * factor;
584
+ posArray[i][d] += movement;
585
+ totalMovement += Math.abs(movement);
586
+ }
587
+ }
588
+ if (totalMovement < 1e-10) {
589
+ break;
428
590
  }
429
- } else {
430
- const dist = Array.from({ length: nodes.length }, (_, i) => parseFloat(String(i)));
431
- const angle = dist.map((d) => resolution * d);
432
- positions = dist.map((d, i) => [
433
- Math.cos(angle[i]) * d,
434
- Math.sin(angle[i]) * d
435
- ]);
436
- }
437
- const posArray = [];
438
- for (let i = 0; i < positions.length; i++) {
439
- posArray.push(positions[i]);
440
- }
441
- const scaledPositions = rescaleLayout(posArray, scale);
442
- for (let i = 0; i < scaledPositions.length; i++) {
443
- scaledPositions[i][0] += center[0];
444
- scaledPositions[i][1] += center[1];
445
591
  }
446
- for (let i = 0; i < nodes.length; i++) {
447
- pos[nodes[i]] = scaledPositions[i];
592
+ const positions = {};
593
+ for (let i = 0; i < n; i++) {
594
+ positions[nodes[i]] = posArray[i];
448
595
  }
449
- return pos;
596
+ return rescaleLayout(positions);
597
+ }
598
+ function getNodeDegree(graph, node) {
599
+ return graph.edges().filter((edge) => edge[0] === node || edge[1] === node).length;
450
600
  }
451
601
  function fruchtermanReingoldLayout(G, k = null, pos = null, fixed = null, iterations = 50, scale = 1, center = null, dim = 2, seed = null) {
452
602
  const processed = _processParams(G, center, dim);
453
- let graph = processed.G;
454
- center = processed.center;
603
+ const graph = processed.G;
604
+ ({ center } = processed);
455
605
  const nodes = getNodesFromGraph(graph);
456
606
  const edges = getEdgesFromGraph(graph);
457
607
  if (nodes.length === 0) {
@@ -514,7 +664,9 @@ function fruchtermanReingoldLayout(G, k = null, pos = null, fixed = null, iterat
514
664
  }
515
665
  }
516
666
  for (const node of nodes) {
517
- if (fixedNodes.has(node)) continue;
667
+ if (fixedNodes.has(node)) {
668
+ continue;
669
+ }
518
670
  const magnitude = Math.sqrt(displacement[node].reduce((sum, d) => sum + d * d, 0));
519
671
  const limitedMagnitude = Math.min(magnitude, t);
520
672
  for (let j = 0; j < dim; j++) {
@@ -529,10 +681,7 @@ function fruchtermanReingoldLayout(G, k = null, pos = null, fixed = null, iterat
529
681
  }
530
682
  return positions;
531
683
  }
532
- function springLayout(G, k = null, pos = null, fixed = null, iterations = 50, scale = 1, center = null, dim = 2, seed = null) {
533
- return fruchtermanReingoldLayout(G, k, pos, fixed, iterations, scale, center, dim, seed);
534
- }
535
- function _lbfgsDirection(grad, sList, yList, m) {
684
+ function _lbfgsDirection(grad, sList, yList, _m) {
536
685
  if (sList.length === 0) {
537
686
  return grad.map((g) => -g);
538
687
  }
@@ -622,10 +771,8 @@ function _computeShortestPathDistances(G, weight) {
622
771
  function _kamadaKawaiSolve(distMatrix, positions, dim) {
623
772
  const nNodes = positions.length;
624
773
  const meanWeight = 1e-3;
625
- const invDistMatrix = distMatrix.map(
626
- (row) => row.map((d) => d === 0 ? 0 : 1 / (d + 1e-3))
627
- );
628
- let posVec = positions.flat();
774
+ const invDistMatrix = distMatrix.map((row) => row.map((d) => d === 0 ? 0 : 1 / (d + 1e-3)));
775
+ const posVec = positions.flat();
629
776
  const maxIter = 500;
630
777
  const gtol = 1e-5;
631
778
  const m = 10;
@@ -710,443 +857,296 @@ function _kamadaKawaiCostfn(posVec, invDist, meanWeight, dim) {
710
857
  }
711
858
  return [cost, grad];
712
859
  }
713
- function kamadaKawaiLayout(G, dist = null, pos = null, weight = "weight", scale = 1, center = null, dim = 2) {
714
- const processed = _processParams(G, center, dim);
715
- const graph = processed.G;
716
- center = processed.center;
717
- const nodes = getNodesFromGraph(graph);
718
- if (nodes.length === 0) {
719
- return {};
720
- }
721
- if (nodes.length === 1) {
722
- return { [nodes[0]]: center };
723
- }
724
- if (!dist) {
725
- if (Array.isArray(graph)) {
726
- throw new Error("Kamada-Kawai layout requires a Graph with edges, not just a list of nodes");
727
- }
728
- dist = _computeShortestPathDistances(graph, weight);
729
- }
730
- const nodesArray = Array.from(nodes);
731
- const nNodes = nodesArray.length;
732
- const distMatrix = Array(nNodes).fill(0).map(() => Array(nNodes).fill(1e6));
733
- for (let i = 0; i < nNodes; i++) {
734
- const nodeI = nodesArray[i];
735
- distMatrix[i][i] = 0;
736
- if (!dist[nodeI]) continue;
737
- for (let j = 0; j < nNodes; j++) {
738
- const nodeJ = nodesArray[j];
739
- if (dist[nodeI][nodeJ] !== void 0) {
740
- distMatrix[i][j] = dist[nodeI][nodeJ];
741
- }
742
- }
743
- }
744
- if (!pos) {
745
- if (dim >= 2) {
746
- pos = circularLayout(G, 1, center, dim);
747
- } else {
748
- const posArray2 = {};
749
- nodesArray.forEach((node, i) => {
750
- posArray2[node] = [i / (nNodes - 1 || 1)];
751
- });
752
- pos = posArray2;
753
- }
754
- }
755
- const posArray = new Array(nNodes);
756
- for (let i = 0; i < nNodes; i++) {
757
- const node = nodesArray[i];
758
- posArray[i] = pos[node] ? [...pos[node]] : Array(dim).fill(0);
759
- while (posArray[i].length < dim) {
760
- posArray[i].push(0);
761
- }
762
- }
763
- const newPositions = _kamadaKawaiSolve(distMatrix, posArray, dim);
764
- const finalPos = {};
765
- for (let i = 0; i < nNodes; i++) {
766
- finalPos[nodesArray[i]] = newPositions[i];
767
- }
768
- return rescaleLayout(finalPos, scale, center);
769
- }
770
- function forceatlas2Layout(G, pos = null, maxIter = 100, jitterTolerance = 1, scalingRatio = 2, gravity = 1, distributedAction = false, strongGravity = false, nodeMass = null, nodeSize = null, weight = null, dissuadeHubs = false, linlog = false, seed = null, dim = 2) {
771
- const processed = _processParams(G, null, dim);
772
- const graph = processed.G;
773
- const nodes = getNodesFromGraph(graph);
774
- if (nodes.length === 0) {
775
- return {};
776
- }
777
- const rng = new RandomNumberGenerator(seed ?? void 0);
778
- let posArray;
779
- if (pos === null) {
780
- pos = {};
781
- posArray = new Array(nodes.length);
782
- for (let i = 0; i < nodes.length; i++) {
783
- posArray[i] = Array(dim).fill(0).map(() => rng.rand() * 2 - 1);
784
- pos[nodes[i]] = posArray[i];
785
- }
786
- } else if (Object.keys(pos).length === nodes.length) {
787
- posArray = new Array(nodes.length);
788
- for (let i = 0; i < nodes.length; i++) {
789
- const nodePos = pos[nodes[i]];
790
- posArray[i] = new Array(dim);
791
- for (let d = 0; d < dim; d++) {
792
- posArray[i][d] = d < nodePos.length ? nodePos[d] : rng.rand() * 2 - 1;
793
- }
794
- }
795
- } else {
796
- let minPos = Array(dim).fill(Number.POSITIVE_INFINITY);
797
- let maxPos = Array(dim).fill(Number.NEGATIVE_INFINITY);
798
- for (const node in pos) {
799
- const nodePos = pos[node];
800
- for (let d = 0; d < dim; d++) {
801
- if (d < nodePos.length) {
802
- minPos[d] = Math.min(minPos[d], nodePos[d]);
803
- maxPos[d] = Math.max(maxPos[d], nodePos[d]);
804
- }
805
- }
806
- }
807
- for (let d = 0; d < dim; d++) {
808
- if (!isFinite(minPos[d]) || !isFinite(maxPos[d])) {
809
- minPos[d] = -1;
810
- maxPos[d] = 1;
811
- }
860
+ const np = {
861
+ zeros: function(shape) {
862
+ if (typeof shape === "number") {
863
+ return Array(shape).fill(0);
812
864
  }
813
- posArray = new Array(nodes.length);
814
- for (let i = 0; i < nodes.length; i++) {
815
- const node = nodes[i];
816
- if (pos[node]) {
817
- const nodePos = pos[node];
818
- posArray[i] = new Array(dim);
819
- for (let d = 0; d < dim; d++) {
820
- posArray[i][d] = d < nodePos.length ? nodePos[d] : rng.rand() * 2 - 1;
821
- }
822
- } else {
823
- posArray[i] = Array(dim).fill(0).map(
824
- (_, d) => minPos[d] + rng.rand() * (maxPos[d] - minPos[d])
825
- );
826
- pos[node] = posArray[i];
827
- }
865
+ if (shape.length === 1) {
866
+ return Array(shape[0]).fill(0);
828
867
  }
829
- }
830
- const mass = new Array(nodes.length).fill(0);
831
- const size = new Array(nodes.length).fill(0);
832
- const adjustSizes = nodeSize !== null;
833
- for (let i = 0; i < nodes.length; i++) {
834
- const node = nodes[i];
835
- mass[i] = nodeMass && nodeMass[node] ? nodeMass[node] : Array.isArray(graph) ? 1 : getNodeDegree(graph, node) + 1;
836
- size[i] = nodeSize && nodeSize[node] ? nodeSize[node] : 1;
837
- }
838
- const n = nodes.length;
839
- const A = Array(n).fill(0).map(() => Array(n).fill(0));
840
- const edges = Array.isArray(graph) ? [] : graph.edges();
841
- const nodeIndices = {};
842
- nodes.forEach((node, i) => {
843
- nodeIndices[node] = i;
844
- });
845
- for (const [source, target] of edges) {
846
- const i = nodeIndices[source];
847
- const j = nodeIndices[target];
848
- let edgeWeight = 1;
849
- if (weight && !Array.isArray(graph) && graph.getEdgeData) {
850
- edgeWeight = graph.getEdgeData(source, target, weight) || 1;
868
+ return Array(shape[0]).fill(0).map(() => this.zeros(shape.slice(1)));
869
+ },
870
+ ones: function(shape) {
871
+ if (typeof shape === "number") {
872
+ return Array(shape).fill(1);
851
873
  }
852
- A[i][j] = edgeWeight;
853
- A[j][i] = edgeWeight;
854
- }
855
- const gravities = Array(n).fill(0).map(() => Array(dim).fill(0));
856
- const attraction = Array(n).fill(0).map(() => Array(dim).fill(0));
857
- const repulsion = Array(n).fill(0).map(() => Array(dim).fill(0));
858
- let speed = 1;
859
- let speedEfficiency = 1;
860
- function estimateFactor(n2, swing2, traction2, speed2, speedEfficiency2, jitterTolerance2) {
861
- const optJitter = 0.05 * Math.sqrt(n2);
862
- const minJitter = Math.sqrt(optJitter);
863
- const maxJitter = 10;
864
- const minSpeedEfficiency = 0.05;
865
- const other = Math.min(maxJitter, optJitter * traction2 / (n2 * n2));
866
- let jitter = jitterTolerance2 * Math.max(minJitter, other);
867
- if (swing2 / traction2 > 2) {
868
- if (speedEfficiency2 > minSpeedEfficiency) {
869
- speedEfficiency2 *= 0.5;
870
- }
871
- jitter = Math.max(jitter, jitterTolerance2);
874
+ if (shape.length === 1) {
875
+ return Array(shape[0]).fill(1);
872
876
  }
873
- let targetSpeed = swing2 === 0 ? Number.POSITIVE_INFINITY : jitter * speedEfficiency2 * traction2 / swing2;
874
- if (swing2 > jitter * traction2) {
875
- if (speedEfficiency2 > minSpeedEfficiency) {
876
- speedEfficiency2 *= 0.7;
877
- }
878
- } else if (speed2 < 1e3) {
879
- speedEfficiency2 *= 1.3;
877
+ return Array(shape[0]).fill(1).map(() => this.ones(shape.slice(1)));
878
+ },
879
+ linspace: function(start, stop, num) {
880
+ if (num === 1) {
881
+ return [start];
880
882
  }
881
- const maxRise = 0.5;
882
- speed2 = speed2 + Math.min(targetSpeed - speed2, maxRise * speed2);
883
- return [speed2, speedEfficiency2];
884
- }
885
- for (let iter = 0; iter < maxIter; iter++) {
886
- for (let i = 0; i < n; i++) {
887
- for (let d = 0; d < dim; d++) {
888
- attraction[i][d] = 0;
889
- repulsion[i][d] = 0;
890
- gravities[i][d] = 0;
891
- }
883
+ const step = (stop - start) / (num - 1);
884
+ return Array.from({ length: num }, (_, i) => start + i * step);
885
+ },
886
+ array: function(arr) {
887
+ return Array.isArray(arr) ? [...arr] : [arr];
888
+ },
889
+ repeat: function(a, repeats) {
890
+ const result = [];
891
+ for (let i = 0; i < repeats; i++) {
892
+ result.push(...np.array(a));
892
893
  }
893
- const diff = Array(n).fill(0).map(
894
- () => Array(n).fill(0).map(() => Array(dim).fill(0))
895
- );
896
- const distance = Array(n).fill(0).map(() => Array(n).fill(0));
897
- for (let i = 0; i < n; i++) {
898
- for (let j = 0; j < n; j++) {
899
- if (i === j) continue;
900
- for (let d = 0; d < dim; d++) {
901
- diff[i][j][d] = posArray[i][d] - posArray[j][d];
902
- }
903
- distance[i][j] = Math.sqrt(diff[i][j].reduce((sum, d) => sum + d * d, 0));
904
- if (distance[i][j] < 0.01) distance[i][j] = 0.01;
905
- }
894
+ return result;
895
+ },
896
+ mean: function(arr, axis = null) {
897
+ if (axis === null) {
898
+ const flatArr = Array.isArray(arr[0]) ? arr.flat(Infinity) : arr;
899
+ const sum = flatArr.reduce((a, b) => a + b, 0);
900
+ return sum / flatArr.length;
906
901
  }
907
- if (linlog) {
908
- for (let i = 0; i < n; i++) {
909
- for (let j = 0; j < n; j++) {
910
- if (i === j || A[i][j] === 0) continue;
911
- const dist = distance[i][j];
912
- const factor = -Math.log(1 + dist) / dist * A[i][j];
913
- for (let d = 0; d < dim; d++) {
914
- const force = factor * diff[i][j][d];
915
- attraction[i][d] += force;
916
- }
917
- }
918
- }
919
- } else {
920
- for (let i = 0; i < n; i++) {
921
- for (let j = 0; j < n; j++) {
922
- if (i === j || A[i][j] === 0) continue;
923
- for (let d = 0; d < dim; d++) {
924
- const force = -diff[i][j][d] * A[i][j];
925
- attraction[i][d] += force;
926
- }
902
+ if (axis === 0) {
903
+ const result = [];
904
+ const matrix = arr;
905
+ for (let i = 0; i < matrix[0].length; i++) {
906
+ let sum = 0;
907
+ for (let j = 0; j < matrix.length; j++) {
908
+ sum += matrix[j][i];
927
909
  }
910
+ result.push(sum / matrix.length);
928
911
  }
912
+ return result;
929
913
  }
930
- if (distributedAction) {
931
- for (let i = 0; i < n; i++) {
932
- for (let d = 0; d < dim; d++) {
933
- attraction[i][d] /= mass[i];
934
- }
935
- }
914
+ return arr.map((row) => np.mean(row));
915
+ },
916
+ add: function(a, b) {
917
+ if (!Array.isArray(a) && !Array.isArray(b)) {
918
+ return a + b;
936
919
  }
937
- for (let i = 0; i < n; i++) {
938
- for (let j = 0; j < n; j++) {
939
- if (i === j) continue;
940
- let dist = distance[i][j];
941
- if (adjustSizes) {
942
- dist -= size[i] - size[j];
943
- dist = Math.max(dist, 0.01);
944
- }
945
- const distSquared = dist * dist;
946
- const massProduct = mass[i] * mass[j];
947
- const factor = massProduct / distSquared * scalingRatio;
948
- for (let d = 0; d < dim; d++) {
949
- const direction = diff[i][j][d] / dist;
950
- repulsion[i][d] += direction * factor;
951
- }
952
- }
920
+ if (!Array.isArray(a)) {
921
+ return b.map((val) => a + val);
953
922
  }
954
- const centerOfMass = Array(dim).fill(0);
955
- for (let i = 0; i < n; i++) {
956
- for (let d = 0; d < dim; d++) {
957
- centerOfMass[d] += posArray[i][d] / n;
958
- }
923
+ if (!Array.isArray(b)) {
924
+ return a.map((val) => val + b);
959
925
  }
960
- for (let i = 0; i < n; i++) {
961
- const posCentered = Array(dim);
962
- for (let d = 0; d < dim; d++) {
963
- posCentered[d] = posArray[i][d] - centerOfMass[d];
964
- }
965
- if (strongGravity) {
966
- for (let d = 0; d < dim; d++) {
967
- gravities[i][d] = -gravity * mass[i] * posCentered[d];
968
- }
969
- } else {
970
- const dist = Math.sqrt(posCentered.reduce((sum, val) => sum + val * val, 0));
971
- if (dist > 0.01) {
972
- for (let d = 0; d < dim; d++) {
973
- const direction = posCentered[d] / dist;
974
- gravities[i][d] = -gravity * mass[i] * direction;
975
- }
976
- }
977
- }
926
+ return a.map((val, i) => val + b[i]);
927
+ },
928
+ subtract: function(a, b) {
929
+ if (!Array.isArray(a) && !Array.isArray(b)) {
930
+ return a - b;
978
931
  }
979
- const update = Array(n).fill(0).map(() => Array(dim).fill(0));
980
- let totalSwing = 0;
981
- let totalTraction = 0;
982
- for (let i = 0; i < n; i++) {
983
- for (let d = 0; d < dim; d++) {
984
- update[i][d] = attraction[i][d] + repulsion[i][d] + gravities[i][d];
985
- }
986
- const oldPos = [...posArray[i]];
987
- const newPos = oldPos.map((p, d) => p + update[i][d]);
988
- const swingVector = oldPos.map((p, d) => p - newPos[d]);
989
- const tractionVector = oldPos.map((p, d) => p + newPos[d]);
990
- const swingMagnitude = Math.sqrt(swingVector.reduce((sum, val) => sum + val * val, 0));
991
- const tractionMagnitude = Math.sqrt(tractionVector.reduce((sum, val) => sum + val * val, 0));
992
- totalSwing += mass[i] * swingMagnitude;
993
- totalTraction += 0.5 * mass[i] * tractionMagnitude;
932
+ if (!Array.isArray(a)) {
933
+ return b.map((val) => a - val);
994
934
  }
995
- [speed, speedEfficiency] = estimateFactor(
996
- n,
997
- totalSwing,
998
- totalTraction,
999
- speed,
1000
- speedEfficiency,
1001
- jitterTolerance
1002
- );
1003
- let totalMovement = 0;
1004
- for (let i = 0; i < n; i++) {
1005
- let factor;
1006
- if (adjustSizes) {
1007
- const df = Math.sqrt(update[i].reduce((sum, val) => sum + val * val, 0));
1008
- const swinging = mass[i] * df;
1009
- factor = 0.1 * speed / (1 + Math.sqrt(speed * swinging));
1010
- factor = Math.min(factor * df, 10) / df;
1011
- } else {
1012
- const swinging = mass[i] * Math.sqrt(update[i].reduce((sum, val) => sum + val * val, 0));
1013
- factor = speed / (1 + Math.sqrt(speed * swinging));
1014
- }
1015
- for (let d = 0; d < dim; d++) {
1016
- const movement = update[i][d] * factor;
1017
- posArray[i][d] += movement;
1018
- totalMovement += Math.abs(movement);
1019
- }
935
+ if (!Array.isArray(b)) {
936
+ return a.map((val) => val - b);
1020
937
  }
1021
- if (totalMovement < 1e-10) {
1022
- break;
938
+ return a.map((val, i) => val - b[i]);
939
+ },
940
+ max: function(arr) {
941
+ if (!Array.isArray(arr)) {
942
+ return arr;
1023
943
  }
944
+ return Math.max(...arr.flat(Infinity));
945
+ },
946
+ min: function(arr) {
947
+ if (!Array.isArray(arr)) {
948
+ return arr;
949
+ }
950
+ return Math.min(...arr.flat(Infinity));
951
+ },
952
+ norm: function(arr) {
953
+ return Math.sqrt(arr.reduce((sum, val) => sum + val * val, 0));
1024
954
  }
1025
- const positions = {};
1026
- for (let i = 0; i < n; i++) {
1027
- positions[nodes[i]] = posArray[i];
1028
- }
1029
- return rescaleLayout(positions);
1030
- }
1031
- function getNodeDegree(graph, node) {
1032
- return graph.edges().filter(
1033
- (edge) => edge[0] === node || edge[1] === node
1034
- ).length;
1035
- }
1036
- function arfLayout(G, pos = null, scaling = 1, a = 1.1, maxIter = 1e3, seed = null) {
1037
- if (a <= 1) {
1038
- throw new Error("The parameter a should be larger than 1");
955
+ };
956
+ function circularLayout(G, scale = 1, center = null, dim = 2) {
957
+ if (dim < 2) {
958
+ throw new Error("cannot handle dimensions < 2");
1039
959
  }
1040
- const nodes = getNodesFromGraph(G);
1041
- const edges = getEdgesFromGraph(G);
960
+ const processed = _processParams(G, center, dim);
961
+ const nodes = getNodesFromGraph(processed.G);
962
+ ({ center } = processed);
963
+ const pos = {};
1042
964
  if (nodes.length === 0) {
1043
- return {};
965
+ return pos;
1044
966
  }
1045
- if (!pos) {
1046
- pos = randomLayout(G, null, 2, seed);
967
+ if (nodes.length === 1) {
968
+ pos[nodes[0]] = center;
969
+ return pos;
970
+ }
971
+ if (dim === 2) {
972
+ const theta = np.linspace(0, 2 * Math.PI, nodes.length + 1).slice(0, -1);
973
+ nodes.forEach((node, i) => {
974
+ const x = Math.cos(theta[i]) * scale + center[0];
975
+ const y = Math.sin(theta[i]) * scale + center[1];
976
+ pos[node] = [x, y];
977
+ });
978
+ } else if (dim === 3) {
979
+ const n = nodes.length;
980
+ const goldenRatio = (1 + Math.sqrt(5)) / 2;
981
+ nodes.forEach((node, i) => {
982
+ const theta = 2 * Math.PI * i / goldenRatio;
983
+ const phi = Math.acos(1 - 2 * (i + 0.5) / n);
984
+ const x = Math.sin(phi) * Math.cos(theta) * scale + center[0];
985
+ const y = Math.sin(phi) * Math.sin(theta) * scale + center[1];
986
+ const z = Math.cos(phi) * scale + center[2];
987
+ pos[node] = [x, y, z];
988
+ });
1047
989
  } else {
1048
- const rng = new RandomNumberGenerator(seed ?? void 0);
1049
- const defaultPos = {};
990
+ const rng = new RandomNumberGenerator();
1050
991
  nodes.forEach((node) => {
1051
- if (!pos[node]) {
1052
- defaultPos[node] = [rng.rand(), rng.rand()];
1053
- }
992
+ const coords = Array(dim).fill(0).map(() => rng.rand() * 2 - 1);
993
+ const norm = Math.sqrt(coords.reduce((sum, c) => sum + c * c, 0));
994
+ pos[node] = coords.map((c, j) => c / norm * scale + center[j]);
1054
995
  });
1055
- pos = { ...pos, ...defaultPos };
1056
996
  }
1057
- const nodeIndex = {};
1058
- nodes.forEach((node, i) => {
1059
- nodeIndex[node] = i;
1060
- });
1061
- const positions = nodes.map((node) => [...pos[node]]);
1062
- const N = nodes.length;
1063
- const K = Array(N).fill(0).map(() => Array(N).fill(1));
1064
- for (let i = 0; i < N; i++) {
1065
- K[i][i] = 0;
997
+ return pos;
998
+ }
999
+ function kamadaKawaiLayout(G, dist = null, pos = null, weight = "weight", scale = 1, center = null, dim = 2) {
1000
+ const processed = _processParams(G, center, dim);
1001
+ const graph = processed.G;
1002
+ ({ center } = processed);
1003
+ const nodes = getNodesFromGraph(graph);
1004
+ if (nodes.length === 0) {
1005
+ return {};
1066
1006
  }
1067
- for (const [source, target] of edges) {
1068
- if (source === target) continue;
1069
- const i = nodeIndex[source];
1070
- const j = nodeIndex[target];
1071
- K[i][j] = a;
1072
- K[j][i] = a;
1007
+ if (nodes.length === 1) {
1008
+ return { [nodes[0]]: center };
1073
1009
  }
1074
- const rho = scaling * Math.sqrt(N);
1075
- const dt = 1e-3;
1076
- const etol = 1e-6;
1077
- let error = etol + 1;
1078
- let nIter = 0;
1079
- while (error > etol && nIter < maxIter) {
1080
- const change = Array(N).fill(0).map(() => [0, 0]);
1081
- for (let i = 0; i < N; i++) {
1082
- for (let j = 0; j < N; j++) {
1083
- if (i === j) continue;
1084
- const diff = positions[i].map((coord, dim) => coord - positions[j][dim]);
1085
- const dist = Math.sqrt(diff.reduce((sum, d) => sum + d * d, 0)) || 0.01;
1086
- for (let d = 0; d < diff.length; d++) {
1087
- change[i][d] += K[i][j] * diff[d] - rho / dist * diff[d];
1088
- }
1089
- }
1010
+ if (!dist) {
1011
+ if (Array.isArray(graph)) {
1012
+ throw new Error("Kamada-Kawai layout requires a Graph with edges, not just a list of nodes");
1090
1013
  }
1091
- for (let i = 0; i < N; i++) {
1092
- for (let d = 0; d < positions[i].length; d++) {
1093
- positions[i][d] += change[i][d] * dt;
1014
+ dist = _computeShortestPathDistances(graph, weight);
1015
+ }
1016
+ const nodesArray = Array.from(nodes);
1017
+ const nNodes = nodesArray.length;
1018
+ const distMatrix = Array(nNodes).fill(0).map(() => Array(nNodes).fill(1e6));
1019
+ for (let i = 0; i < nNodes; i++) {
1020
+ const nodeI = nodesArray[i];
1021
+ distMatrix[i][i] = 0;
1022
+ if (!dist[nodeI]) {
1023
+ continue;
1024
+ }
1025
+ for (let j = 0; j < nNodes; j++) {
1026
+ const nodeJ = nodesArray[j];
1027
+ if (dist[nodeI][nodeJ] !== void 0) {
1028
+ distMatrix[i][j] = dist[nodeI][nodeJ];
1094
1029
  }
1095
1030
  }
1096
- error = change.reduce((sum, c) => sum + Math.sqrt(c.reduce((s, v) => s + v * v, 0)), 0);
1097
- nIter++;
1098
1031
  }
1032
+ if (!pos) {
1033
+ if (dim >= 2) {
1034
+ pos = circularLayout(G, 1, center, dim);
1035
+ } else {
1036
+ const posArray2 = {};
1037
+ nodesArray.forEach((node, i) => {
1038
+ posArray2[node] = [i / (nNodes - 1 || 1)];
1039
+ });
1040
+ pos = posArray2;
1041
+ }
1042
+ }
1043
+ const posArray = new Array(nNodes);
1044
+ for (let i = 0; i < nNodes; i++) {
1045
+ const node = nodesArray[i];
1046
+ posArray[i] = pos[node] ? [...pos[node]] : Array(dim).fill(0);
1047
+ while (posArray[i].length < dim) {
1048
+ posArray[i].push(0);
1049
+ }
1050
+ }
1051
+ const newPositions = _kamadaKawaiSolve(distMatrix, posArray, dim);
1099
1052
  const finalPos = {};
1100
- nodes.forEach((node, i) => {
1101
- finalPos[node] = positions[i];
1102
- });
1103
- return finalPos;
1053
+ for (let i = 0; i < nNodes; i++) {
1054
+ finalPos[nodesArray[i]] = newPositions[i];
1055
+ }
1056
+ return rescaleLayout(finalPos, scale, center);
1104
1057
  }
1105
- function bipartiteLayout(G, nodes = null, align = "vertical", scale = 1, center = null, aspectRatio = 4 / 3) {
1106
- if (align !== "vertical" && align !== "horizontal") {
1107
- throw new Error("align must be either vertical or horizontal");
1058
+ function springLayout(G, k = null, pos = null, fixed = null, iterations = 50, scale = 1, center = null, dim = 2, seed = null) {
1059
+ return fruchtermanReingoldLayout(G, k, pos, fixed, iterations, scale, center, dim, seed);
1060
+ }
1061
+ function shellLayout(G, nlist = null, scale = 1, center = null, dim = 2) {
1062
+ if (dim !== 2) {
1063
+ throw new Error("can only handle 2 dimensions");
1108
1064
  }
1109
- const processed = _processParams(G, center || [0, 0], 2);
1110
- const graph = processed.G;
1111
- center = processed.center;
1112
- const allNodes = getNodesFromGraph(graph);
1113
- if (allNodes.length === 0) {
1114
- return {};
1065
+ const processed = _processParams(G, center, dim);
1066
+ const nodes = getNodesFromGraph(processed.G);
1067
+ ({ center } = processed);
1068
+ const pos = {};
1069
+ if (nodes.length === 0) {
1070
+ return pos;
1115
1071
  }
1116
- if (!nodes) {
1117
- nodes = allNodes.filter((_, i) => i % 2 === 0);
1072
+ if (nodes.length === 1) {
1073
+ pos[nodes[0]] = center;
1074
+ return pos;
1118
1075
  }
1119
- const left = new Set(nodes);
1120
- const right = new Set(allNodes.filter((n) => !left.has(n)));
1121
- const height = 1;
1122
- const width = aspectRatio * height;
1123
- const offset = [width / 2, height / 2];
1076
+ if (!nlist) {
1077
+ nlist = [nodes];
1078
+ }
1079
+ const radiusBump = scale / nlist.length;
1080
+ let radius;
1081
+ if (nlist[0].length === 1) {
1082
+ radius = 0;
1083
+ pos[nlist[0][0]] = [...center];
1084
+ radius += radiusBump;
1085
+ } else {
1086
+ radius = radiusBump;
1087
+ }
1088
+ for (let i = 0; i < nlist.length; i++) {
1089
+ const shell = nlist[i];
1090
+ if (shell.length === 0) {
1091
+ continue;
1092
+ }
1093
+ if (shell.length === 1 && i === 0) {
1094
+ continue;
1095
+ }
1096
+ const theta = np.linspace(0, 2 * Math.PI, shell.length + 1).slice(0, -1);
1097
+ shell.forEach((node, j) => {
1098
+ const x = Math.cos(theta[j]) * radius + center[0];
1099
+ const y = Math.sin(theta[j]) * radius + center[1];
1100
+ pos[node] = [x, y];
1101
+ });
1102
+ radius += radiusBump;
1103
+ }
1104
+ return pos;
1105
+ }
1106
+ function spiralLayout(G, scale = 1, center = null, dim = 2, resolution = 0.35, equidistant = false) {
1107
+ if (dim !== 2) {
1108
+ throw new Error("can only handle 2 dimensions");
1109
+ }
1110
+ const processed = _processParams(G, center || [0, 0], dim);
1111
+ const nodes = getNodesFromGraph(processed.G);
1112
+ ({ center } = processed);
1124
1113
  const pos = {};
1125
- const leftNodes = [...left];
1126
- leftNodes.forEach((node, i) => {
1127
- const x = 0;
1128
- const y = i * height / (leftNodes.length || 1);
1129
- pos[node] = [x, y];
1130
- });
1131
- const rightNodes = [...right];
1132
- rightNodes.forEach((node, i) => {
1133
- const x = width;
1134
- const y = i * height / (rightNodes.length || 1);
1135
- pos[node] = [x, y];
1136
- });
1137
- for (const node in pos) {
1138
- pos[node][0] -= offset[0];
1139
- pos[node][1] -= offset[1];
1114
+ if (nodes.length === 0) {
1115
+ return pos;
1140
1116
  }
1141
- const scaledPos = rescaleLayout(pos, scale, center);
1142
- if (align === "horizontal") {
1143
- for (const node in scaledPos) {
1144
- const temp = scaledPos[node][0];
1145
- scaledPos[node][0] = scaledPos[node][1];
1146
- scaledPos[node][1] = temp;
1117
+ if (nodes.length === 1) {
1118
+ pos[nodes[0]] = [...center];
1119
+ return pos;
1120
+ }
1121
+ let positions = [];
1122
+ if (equidistant) {
1123
+ const chord = 1;
1124
+ const step = 0.5;
1125
+ let theta = resolution;
1126
+ theta += chord / (step * theta);
1127
+ for (let i = 0; i < nodes.length; i++) {
1128
+ const r = step * theta;
1129
+ theta += chord / r;
1130
+ positions.push([Math.cos(theta) * r, Math.sin(theta) * r]);
1147
1131
  }
1132
+ } else {
1133
+ const dist = Array.from({ length: nodes.length }, (_, i) => parseFloat(String(i)));
1134
+ const angle = dist.map((d) => resolution * d);
1135
+ positions = dist.map((d, i) => [Math.cos(angle[i]) * d, Math.sin(angle[i]) * d]);
1148
1136
  }
1149
- return scaledPos;
1137
+ const posArray = [];
1138
+ for (let i = 0; i < positions.length; i++) {
1139
+ posArray.push(positions[i]);
1140
+ }
1141
+ const scaledPositions = rescaleLayout(posArray, scale);
1142
+ for (let i = 0; i < scaledPositions.length; i++) {
1143
+ scaledPositions[i][0] += center[0];
1144
+ scaledPositions[i][1] += center[1];
1145
+ }
1146
+ for (let i = 0; i < nodes.length; i++) {
1147
+ pos[nodes[i]] = scaledPositions[i];
1148
+ }
1149
+ return pos;
1150
1150
  }
1151
1151
  function multipartiteLayout(G, subsetKey = "subset", align = "vertical", scale = 1, center = null) {
1152
1152
  if (align !== "vertical" && align !== "horizontal") {
@@ -1154,7 +1154,7 @@ function multipartiteLayout(G, subsetKey = "subset", align = "vertical", scale =
1154
1154
  }
1155
1155
  const processed = _processParams(G, center || [0, 0], 2);
1156
1156
  const graph = processed.G;
1157
- center = processed.center;
1157
+ ({ center } = processed);
1158
1158
  const allNodes = getNodesFromGraph(graph);
1159
1159
  if (allNodes.length === 0) {
1160
1160
  return {};
@@ -1174,7 +1174,7 @@ function multipartiteLayout(G, subsetKey = "subset", align = "vertical", scale =
1174
1174
  }
1175
1175
  const layerCount = Object.keys(layers).length;
1176
1176
  let pos = {};
1177
- Object.entries(layers).forEach(([layer, nodes], layerIdx) => {
1177
+ Object.entries(layers).forEach(([_layer, nodes], layerIdx) => {
1178
1178
  const layerNodes = Array.isArray(nodes) ? nodes : [nodes];
1179
1179
  const layerSize = layerNodes.length;
1180
1180
  layerNodes.forEach((node, nodeIdx) => {
@@ -1199,7 +1199,7 @@ function bfsLayout(G, start, align = "vertical", scale = 1, center = null) {
1199
1199
  throw new Error("BFS layout requires a Graph with edges, not just a list of nodes");
1200
1200
  }
1201
1201
  const graph = processed.G;
1202
- center = processed.center;
1202
+ ({ center } = processed);
1203
1203
  const allNodes = getNodesFromGraph(graph);
1204
1204
  if (allNodes.length === 0) {
1205
1205
  return {};
@@ -1216,163 +1216,68 @@ function bfsLayout(G, start, align = "vertical", scale = 1, center = null) {
1216
1216
  const neighbors = getNeighbors(graph, node);
1217
1217
  for (const neighbor of neighbors) {
1218
1218
  if (!visited.has(neighbor)) {
1219
- nextLayer.push(neighbor);
1220
- visited.add(neighbor);
1221
- }
1222
- }
1223
- }
1224
- if (nextLayer.length === 0) {
1225
- const unvisited = allNodes.filter((node) => !visited.has(node));
1226
- if (unvisited.length > 0) {
1227
- throw new Error("bfs_layout didn't include all nodes. Graph may be disconnected.");
1228
- }
1229
- break;
1230
- }
1231
- currentLayer++;
1232
- layers[currentLayer] = nextLayer;
1233
- }
1234
- return multipartiteLayout(graph, layers, align, scale, center);
1235
- }
1236
- function spectralLayout(G, scale = 1, center = null, dim = 2) {
1237
- const processed = _processParams(G, center, dim);
1238
- const graph = processed.G;
1239
- center = processed.center;
1240
- const nodes = getNodesFromGraph(graph);
1241
- if (nodes.length <= 2) {
1242
- if (nodes.length === 0) {
1243
- return {};
1244
- } else if (nodes.length === 1) {
1245
- return { [nodes[0]]: center };
1246
- } else {
1247
- return {
1248
- [nodes[0]]: center.map((v) => v - scale),
1249
- [nodes[1]]: center.map((v) => v + scale)
1250
- };
1251
- }
1252
- }
1253
- const N = nodes.length;
1254
- const nodeIndices = {};
1255
- nodes.forEach((node, i) => {
1256
- nodeIndices[node] = i;
1257
- });
1258
- const A = Array(N).fill(0).map(() => Array(N).fill(0));
1259
- const edges = getEdgesFromGraph(graph);
1260
- for (const [source, target] of edges) {
1261
- const i = nodeIndices[source];
1262
- const j = nodeIndices[target];
1263
- A[i][j] = 1;
1264
- A[j][i] = 1;
1265
- }
1266
- const L = Array(N).fill(0).map(() => Array(N).fill(0));
1267
- for (let i = 0; i < N; i++) {
1268
- L[i][i] = A[i].reduce((sum, val) => sum + val, 0);
1269
- for (let j = 0; j < N; j++) {
1270
- L[i][j] -= A[i][j];
1271
- }
1272
- }
1273
- const eigenvectors = [];
1274
- for (let d = 0; d < dim; d++) {
1275
- let vector = Array(N).fill(0).map(() => Math.random() - 0.5);
1276
- for (const ev of eigenvectors) {
1277
- const dot = vector.reduce((acc, val, idx) => acc + val * ev[idx], 0);
1278
- vector = vector.map((val, idx) => val - dot * ev[idx]);
1279
- }
1280
- const norm = Math.sqrt(vector.reduce((acc, val) => acc + val * val, 0));
1281
- vector = vector.map((val) => val / norm);
1282
- for (let iter = 0; iter < 100; iter++) {
1283
- const newVec = Array(N).fill(0);
1284
- for (let i = 0; i < N; i++) {
1285
- for (let j = 0; j < N; j++) {
1286
- newVec[i] += L[i][j] * vector[j];
1287
- }
1288
- }
1289
- const mean = newVec.reduce((acc, val) => acc + val, 0) / N;
1290
- newVec.forEach((val, idx, arr) => {
1291
- arr[idx] = val - mean;
1292
- });
1293
- const newNorm = Math.sqrt(newVec.reduce((acc, val) => acc + val * val, 0));
1294
- if (newNorm < 1e-10) continue;
1295
- vector = newVec.map((val) => val / newNorm);
1296
- }
1297
- eigenvectors.push(vector);
1298
- }
1299
- const positions = Array(N).fill(0).map(() => Array(dim).fill(0));
1300
- for (let i = 0; i < N; i++) {
1301
- for (let d = 0; d < dim; d++) {
1302
- positions[i][d] = eigenvectors[d][i];
1303
- }
1304
- }
1305
- const scaledPositions = rescaleLayout(positions, scale);
1306
- const pos = {};
1307
- nodes.forEach((node, i) => {
1308
- pos[node] = scaledPositions[i].map((val, j) => val + center[j]);
1309
- });
1310
- return pos;
1311
- }
1312
- function isK5(nodes, edges) {
1313
- if (nodes.length !== 5) return false;
1314
- if (edges.length !== 10) return false;
1315
- for (let i = 0; i < nodes.length; i++) {
1316
- for (let j = i + 1; j < nodes.length; j++) {
1317
- const hasEdge = edges.some(
1318
- (e) => e[0] === nodes[i] && e[1] === nodes[j] || e[0] === nodes[j] && e[1] === nodes[i]
1319
- );
1320
- if (!hasEdge) return false;
1321
- }
1322
- }
1323
- return true;
1324
- }
1325
- function isK33(nodes, edges) {
1326
- if (nodes.length !== 6) return false;
1327
- if (edges.length !== 9) return false;
1328
- const nodePartitions = tryFindBipartitePartition(nodes, edges);
1329
- if (!nodePartitions) return false;
1330
- const [part1, part2] = nodePartitions;
1331
- if (part1.length !== 3 || part2.length !== 3) return false;
1332
- for (const n1 of part1) {
1333
- for (const n2 of part2) {
1334
- const hasEdge = edges.some(
1335
- (e) => e[0] === n1 && e[1] === n2 || e[0] === n2 && e[1] === n1
1336
- );
1337
- if (!hasEdge) return false;
1338
- }
1339
- }
1340
- return true;
1341
- }
1342
- function tryFindBipartitePartition(nodes, edges) {
1343
- const colorMap = {};
1344
- const adjList = {};
1345
- for (const node of nodes) {
1346
- adjList[node] = [];
1347
- }
1348
- for (const [u, v] of edges) {
1349
- adjList[u].push(v);
1350
- adjList[v].push(u);
1351
- }
1352
- const queue = [nodes[0]];
1353
- colorMap[nodes[0]] = 0;
1354
- while (queue.length > 0) {
1355
- const node = queue.shift();
1356
- const nodeColor = colorMap[node];
1357
- for (const neighbor of adjList[node]) {
1358
- if (colorMap[neighbor] === void 0) {
1359
- colorMap[neighbor] = 1 - nodeColor;
1360
- queue.push(neighbor);
1361
- } else if (colorMap[neighbor] === nodeColor) {
1362
- return null;
1219
+ nextLayer.push(neighbor);
1220
+ visited.add(neighbor);
1221
+ }
1222
+ }
1223
+ }
1224
+ if (nextLayer.length === 0) {
1225
+ const unvisited = allNodes.filter((node) => !visited.has(node));
1226
+ if (unvisited.length > 0) {
1227
+ throw new Error("bfs_layout didn't include all nodes. Graph may be disconnected.");
1363
1228
  }
1229
+ break;
1364
1230
  }
1231
+ currentLayer++;
1232
+ layers[currentLayer] = nextLayer;
1365
1233
  }
1366
- const part0 = [];
1367
- const part1 = [];
1368
- for (const node of nodes) {
1369
- if (colorMap[node] === 0) {
1370
- part0.push(node);
1371
- } else {
1372
- part1.push(node);
1234
+ return multipartiteLayout(graph, layers, align, scale, center);
1235
+ }
1236
+ function bipartiteLayout(G, nodes = null, align = "vertical", scale = 1, center = null, aspectRatio = 4 / 3) {
1237
+ if (align !== "vertical" && align !== "horizontal") {
1238
+ throw new Error("align must be either vertical or horizontal");
1239
+ }
1240
+ const processed = _processParams(G, center || [0, 0], 2);
1241
+ const graph = processed.G;
1242
+ ({ center } = processed);
1243
+ const allNodes = getNodesFromGraph(graph);
1244
+ if (allNodes.length === 0) {
1245
+ return {};
1246
+ }
1247
+ if (!nodes) {
1248
+ nodes = allNodes.filter((_, i) => i % 2 === 0);
1249
+ }
1250
+ const left = new Set(nodes);
1251
+ const right = new Set(allNodes.filter((n) => !left.has(n)));
1252
+ const height = 1;
1253
+ const width = aspectRatio * height;
1254
+ const offset = [width / 2, height / 2];
1255
+ const pos = {};
1256
+ const leftNodes = [...left];
1257
+ leftNodes.forEach((node, i) => {
1258
+ const x = 0;
1259
+ const y = i * height / (leftNodes.length || 1);
1260
+ pos[node] = [x, y];
1261
+ });
1262
+ const rightNodes = [...right];
1263
+ rightNodes.forEach((node, i) => {
1264
+ const x = width;
1265
+ const y = i * height / (rightNodes.length || 1);
1266
+ pos[node] = [x, y];
1267
+ });
1268
+ for (const node in pos) {
1269
+ pos[node][0] -= offset[0];
1270
+ pos[node][1] -= offset[1];
1271
+ }
1272
+ const scaledPos = rescaleLayout(pos, scale, center);
1273
+ if (align === "horizontal") {
1274
+ for (const node in scaledPos) {
1275
+ const temp = scaledPos[node][0];
1276
+ scaledPos[node][0] = scaledPos[node][1];
1277
+ scaledPos[node][1] = temp;
1373
1278
  }
1374
1279
  }
1375
- return [part0, part1];
1280
+ return scaledPos;
1376
1281
  }
1377
1282
  function createTriangulationEmbedding(nodes, edges, seed = null) {
1378
1283
  const rng = new RandomNumberGenerator(seed ?? void 0);
@@ -1425,9 +1330,13 @@ function createTriangulationEmbedding(nodes, edges, seed = null) {
1425
1330
  }
1426
1331
  return embedding;
1427
1332
  }
1428
- function findCycle(nodes, edges, adjMap) {
1429
- if (nodes.length === 0) return null;
1430
- if (nodes.length <= 2) return nodes;
1333
+ function findCycle(nodes, _edges, adjMap) {
1334
+ if (nodes.length === 0) {
1335
+ return null;
1336
+ }
1337
+ if (nodes.length <= 2) {
1338
+ return nodes;
1339
+ }
1431
1340
  if (nodes.length <= 8) {
1432
1341
  let hamiltonianCycleDFS = function(node) {
1433
1342
  path.push(node);
@@ -1463,7 +1372,9 @@ function findCycle(nodes, edges, adjMap) {
1463
1372
  function findCycleDFS(node, parentNode) {
1464
1373
  visited.add(node);
1465
1374
  for (const neighbor of adjMap[node]) {
1466
- if (neighbor === parentNode) continue;
1375
+ if (neighbor === parentNode) {
1376
+ continue;
1377
+ }
1467
1378
  if (visited.has(neighbor)) {
1468
1379
  cycleFound = constructCycle(node, neighbor, parent);
1469
1380
  return true;
@@ -1535,7 +1446,86 @@ function lrPlanarityTest(nodes, edges, seed = null) {
1535
1446
  const embedding = createTriangulationEmbedding(nodes, edges, seed);
1536
1447
  return { isPlanar: true, embedding };
1537
1448
  }
1538
- function checkPlanarity(G, nodes, edges, seed = null) {
1449
+ function isK5(nodes, edges) {
1450
+ if (nodes.length !== 5) {
1451
+ return false;
1452
+ }
1453
+ if (edges.length !== 10) {
1454
+ return false;
1455
+ }
1456
+ for (let i = 0; i < nodes.length; i++) {
1457
+ for (let j = i + 1; j < nodes.length; j++) {
1458
+ const hasEdge = edges.some(
1459
+ (e) => e[0] === nodes[i] && e[1] === nodes[j] || e[0] === nodes[j] && e[1] === nodes[i]
1460
+ );
1461
+ if (!hasEdge) {
1462
+ return false;
1463
+ }
1464
+ }
1465
+ }
1466
+ return true;
1467
+ }
1468
+ function isK33(nodes, edges) {
1469
+ if (nodes.length !== 6) {
1470
+ return false;
1471
+ }
1472
+ if (edges.length !== 9) {
1473
+ return false;
1474
+ }
1475
+ const nodePartitions = tryFindBipartitePartition(nodes, edges);
1476
+ if (!nodePartitions) {
1477
+ return false;
1478
+ }
1479
+ const [part1, part2] = nodePartitions;
1480
+ if (part1.length !== 3 || part2.length !== 3) {
1481
+ return false;
1482
+ }
1483
+ for (const n1 of part1) {
1484
+ for (const n2 of part2) {
1485
+ const hasEdge = edges.some((e) => e[0] === n1 && e[1] === n2 || e[0] === n2 && e[1] === n1);
1486
+ if (!hasEdge) {
1487
+ return false;
1488
+ }
1489
+ }
1490
+ }
1491
+ return true;
1492
+ }
1493
+ function tryFindBipartitePartition(nodes, edges) {
1494
+ const colorMap = {};
1495
+ const adjList = {};
1496
+ for (const node of nodes) {
1497
+ adjList[node] = [];
1498
+ }
1499
+ for (const [u, v] of edges) {
1500
+ adjList[u].push(v);
1501
+ adjList[v].push(u);
1502
+ }
1503
+ const queue = [nodes[0]];
1504
+ colorMap[nodes[0]] = 0;
1505
+ while (queue.length > 0) {
1506
+ const node = queue.shift();
1507
+ const nodeColor = colorMap[node];
1508
+ for (const neighbor of adjList[node]) {
1509
+ if (colorMap[neighbor] === void 0) {
1510
+ colorMap[neighbor] = 1 - nodeColor;
1511
+ queue.push(neighbor);
1512
+ } else if (colorMap[neighbor] === nodeColor) {
1513
+ return null;
1514
+ }
1515
+ }
1516
+ }
1517
+ const part0 = [];
1518
+ const part1 = [];
1519
+ for (const node of nodes) {
1520
+ if (colorMap[node] === 0) {
1521
+ part0.push(node);
1522
+ } else {
1523
+ part1.push(node);
1524
+ }
1525
+ }
1526
+ return [part0, part1];
1527
+ }
1528
+ function checkPlanarity(_G, nodes, edges, seed = null) {
1539
1529
  if (nodes.length <= 4) {
1540
1530
  return { isPlanar: true, embedding: createTriangulationEmbedding(nodes, edges, seed) };
1541
1531
  }
@@ -1554,7 +1544,7 @@ function planarLayout(G, scale = 1, center = null, dim = 2, seed = null) {
1554
1544
  throw new Error("Planar layout requires a Graph with edges, not just a list of nodes");
1555
1545
  }
1556
1546
  const graph = processed.G;
1557
- center = processed.center;
1547
+ ({ center } = processed);
1558
1548
  const nodes = getNodesFromGraph(graph);
1559
1549
  const edges = getEdgesFromGraph(graph);
1560
1550
  if (nodes.length === 0) {
@@ -1571,6 +1561,83 @@ function planarLayout(G, scale = 1, center = null, dim = 2, seed = null) {
1571
1561
  pos = rescaleLayout(pos, scale, center);
1572
1562
  return pos;
1573
1563
  }
1564
+ function spectralLayout(G, scale = 1, center = null, dim = 2) {
1565
+ const processed = _processParams(G, center, dim);
1566
+ const graph = processed.G;
1567
+ ({ center } = processed);
1568
+ const nodes = getNodesFromGraph(graph);
1569
+ if (nodes.length <= 2) {
1570
+ if (nodes.length === 0) {
1571
+ return {};
1572
+ } else if (nodes.length === 1) {
1573
+ return { [nodes[0]]: center };
1574
+ }
1575
+ return {
1576
+ [nodes[0]]: center.map((v) => v - scale),
1577
+ [nodes[1]]: center.map((v) => v + scale)
1578
+ };
1579
+ }
1580
+ const N = nodes.length;
1581
+ const nodeIndices = {};
1582
+ nodes.forEach((node, i) => {
1583
+ nodeIndices[node] = i;
1584
+ });
1585
+ const A = Array(N).fill(0).map(() => Array(N).fill(0));
1586
+ const edges = getEdgesFromGraph(graph);
1587
+ for (const [source, target] of edges) {
1588
+ const i = nodeIndices[source];
1589
+ const j = nodeIndices[target];
1590
+ A[i][j] = 1;
1591
+ A[j][i] = 1;
1592
+ }
1593
+ const L = Array(N).fill(0).map(() => Array(N).fill(0));
1594
+ for (let i = 0; i < N; i++) {
1595
+ L[i][i] = A[i].reduce((sum, val) => sum + val, 0);
1596
+ for (let j = 0; j < N; j++) {
1597
+ L[i][j] -= A[i][j];
1598
+ }
1599
+ }
1600
+ const eigenvectors = [];
1601
+ for (let d = 0; d < dim; d++) {
1602
+ let vector = Array(N).fill(0).map(() => Math.random() - 0.5);
1603
+ for (const ev of eigenvectors) {
1604
+ const dot = vector.reduce((acc, val, idx) => acc + val * ev[idx], 0);
1605
+ vector = vector.map((val, idx) => val - dot * ev[idx]);
1606
+ }
1607
+ const norm = Math.sqrt(vector.reduce((acc, val) => acc + val * val, 0));
1608
+ vector = vector.map((val) => val / norm);
1609
+ for (let iter = 0; iter < 100; iter++) {
1610
+ const newVec = Array(N).fill(0);
1611
+ for (let i = 0; i < N; i++) {
1612
+ for (let j = 0; j < N; j++) {
1613
+ newVec[i] += L[i][j] * vector[j];
1614
+ }
1615
+ }
1616
+ const mean = newVec.reduce((acc, val) => acc + val, 0) / N;
1617
+ newVec.forEach((val, idx, arr) => {
1618
+ arr[idx] = val - mean;
1619
+ });
1620
+ const newNorm = Math.sqrt(newVec.reduce((acc, val) => acc + val * val, 0));
1621
+ if (newNorm < 1e-10) {
1622
+ continue;
1623
+ }
1624
+ vector = newVec.map((val) => val / newNorm);
1625
+ }
1626
+ eigenvectors.push(vector);
1627
+ }
1628
+ const positions = Array(N).fill(0).map(() => Array(dim).fill(0));
1629
+ for (let i = 0; i < N; i++) {
1630
+ for (let d = 0; d < dim; d++) {
1631
+ positions[i][d] = eigenvectors[d][i];
1632
+ }
1633
+ }
1634
+ const scaledPositions = rescaleLayout(positions, scale);
1635
+ const pos = {};
1636
+ nodes.forEach((node, i) => {
1637
+ pos[node] = scaledPositions[i].map((val, j) => val + center[j]);
1638
+ });
1639
+ return pos;
1640
+ }
1574
1641
  function completeGraph(n) {
1575
1642
  const nodes = Array.from({ length: n }, (_, i) => i);
1576
1643
  const edges = [];
@@ -1623,6 +1690,30 @@ function wheelGraph(n) {
1623
1690
  edges: () => edges
1624
1691
  };
1625
1692
  }
1693
+ function bipartiteGraph(n1, n2, p, seed) {
1694
+ const setA = Array.from({ length: n1 }, (_, i) => `A${i}`);
1695
+ const setB = Array.from({ length: n2 }, (_, i) => `B${i}`);
1696
+ const nodes = [...setA, ...setB];
1697
+ const edges = [];
1698
+ let currentSeed = seed;
1699
+ const random = seed !== void 0 ? () => {
1700
+ currentSeed = (currentSeed * 9301 + 49297) % 233280;
1701
+ return currentSeed / 233280;
1702
+ } : Math.random;
1703
+ for (const a of setA) {
1704
+ for (const b of setB) {
1705
+ if (random() < p) {
1706
+ edges.push([a, b]);
1707
+ }
1708
+ }
1709
+ }
1710
+ return {
1711
+ nodes: () => nodes,
1712
+ edges: () => edges,
1713
+ setA,
1714
+ setB
1715
+ };
1716
+ }
1626
1717
  function gridGraph(rows, cols) {
1627
1718
  const nodes = [];
1628
1719
  const edges = [];
@@ -1650,7 +1741,7 @@ function randomGraph(n, p, seed) {
1650
1741
  const nodes = Array.from({ length: n }, (_, i) => i);
1651
1742
  const edges = [];
1652
1743
  let currentSeed = seed;
1653
- let random = seed !== void 0 ? () => {
1744
+ const random = seed !== void 0 ? () => {
1654
1745
  currentSeed = (currentSeed * 9301 + 49297) % 233280;
1655
1746
  return currentSeed / 233280;
1656
1747
  } : Math.random;
@@ -1666,30 +1757,6 @@ function randomGraph(n, p, seed) {
1666
1757
  edges: () => edges
1667
1758
  };
1668
1759
  }
1669
- function bipartiteGraph(n1, n2, p, seed) {
1670
- const setA = Array.from({ length: n1 }, (_, i) => `A${i}`);
1671
- const setB = Array.from({ length: n2 }, (_, i) => `B${i}`);
1672
- const nodes = [...setA, ...setB];
1673
- const edges = [];
1674
- let currentSeed = seed;
1675
- let random = seed !== void 0 ? () => {
1676
- currentSeed = (currentSeed * 9301 + 49297) % 233280;
1677
- return currentSeed / 233280;
1678
- } : Math.random;
1679
- for (const a of setA) {
1680
- for (const b of setB) {
1681
- if (random() < p) {
1682
- edges.push([a, b]);
1683
- }
1684
- }
1685
- }
1686
- return {
1687
- nodes: () => nodes,
1688
- edges: () => edges,
1689
- setA,
1690
- setB
1691
- };
1692
- }
1693
1760
  function scaleFreeGraph(n, m, seed) {
1694
1761
  if (m >= n) {
1695
1762
  throw new Error("m must be less than n");
@@ -1698,7 +1765,7 @@ function scaleFreeGraph(n, m, seed) {
1698
1765
  const edges = [];
1699
1766
  const degrees = new Array(n).fill(0);
1700
1767
  let currentSeed = seed;
1701
- let random = seed !== void 0 ? () => {
1768
+ const random = seed !== void 0 ? () => {
1702
1769
  currentSeed = (currentSeed * 9301 + 49297) % 233280;
1703
1770
  return currentSeed / 233280;
1704
1771
  } : Math.random;
@@ -1713,7 +1780,7 @@ function scaleFreeGraph(n, m, seed) {
1713
1780
  const targets = /* @__PURE__ */ new Set();
1714
1781
  const totalDegree = degrees.reduce((sum, d) => sum + d, 0);
1715
1782
  while (targets.size < m) {
1716
- let r = random() * totalDegree;
1783
+ const r = random() * totalDegree;
1717
1784
  let cumSum = 0;
1718
1785
  for (let j = 0; j < i; j++) {
1719
1786
  cumSum += degrees[j];