@graphty/layout 1.2.3 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/dist/src/algorithms/index.d.ts +5 -0
  2. package/dist/src/algorithms/index.js +6 -0
  3. package/dist/src/algorithms/index.js.map +1 -0
  4. package/dist/src/algorithms/optimization/index.d.ts +7 -0
  5. package/dist/src/algorithms/optimization/index.js +7 -0
  6. package/dist/src/algorithms/optimization/index.js.map +1 -0
  7. package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +32 -0
  8. package/dist/src/algorithms/optimization/kamada-kawai-solver.js +176 -0
  9. package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -0
  10. package/dist/src/algorithms/optimization/lbfgs.d.ts +13 -0
  11. package/dist/src/algorithms/optimization/lbfgs.js +56 -0
  12. package/dist/src/algorithms/optimization/lbfgs.js.map +1 -0
  13. package/dist/src/algorithms/optimization/line-search.d.ts +15 -0
  14. package/dist/src/algorithms/optimization/line-search.js +37 -0
  15. package/dist/src/algorithms/optimization/line-search.js.map +1 -0
  16. package/dist/src/algorithms/optimization/types.d.ts +5 -0
  17. package/dist/src/algorithms/optimization/types.js +5 -0
  18. package/dist/src/algorithms/optimization/types.js.map +1 -0
  19. package/dist/src/algorithms/planarity/check.d.ts +17 -0
  20. package/dist/src/algorithms/planarity/check.js +30 -0
  21. package/dist/src/algorithms/planarity/check.js.map +1 -0
  22. package/dist/src/algorithms/planarity/embedding.d.ts +29 -0
  23. package/dist/src/algorithms/planarity/embedding.js +179 -0
  24. package/dist/src/algorithms/planarity/embedding.js.map +1 -0
  25. package/dist/src/algorithms/planarity/index.d.ts +7 -0
  26. package/dist/src/algorithms/planarity/index.js +8 -0
  27. package/dist/src/algorithms/planarity/index.js.map +1 -0
  28. package/dist/src/algorithms/planarity/lr-test.d.ts +15 -0
  29. package/dist/src/algorithms/planarity/lr-test.js +54 -0
  30. package/dist/src/algorithms/planarity/lr-test.js.map +1 -0
  31. package/dist/src/algorithms/planarity/special-graphs.d.ts +28 -0
  32. package/dist/src/algorithms/planarity/special-graphs.js +108 -0
  33. package/dist/src/algorithms/planarity/special-graphs.js.map +1 -0
  34. package/dist/src/generators/basic.d.ts +28 -0
  35. package/dist/src/generators/basic.js +79 -0
  36. package/dist/src/generators/basic.js.map +1 -0
  37. package/dist/src/generators/bipartite.d.ts +16 -0
  38. package/dist/src/generators/bipartite.js +40 -0
  39. package/dist/src/generators/bipartite.js.map +1 -0
  40. package/dist/src/generators/grid.d.ts +11 -0
  41. package/dist/src/generators/grid.js +37 -0
  42. package/dist/src/generators/grid.js.map +1 -0
  43. package/dist/src/generators/index.d.ts +8 -0
  44. package/dist/src/generators/index.js +9 -0
  45. package/dist/src/generators/index.js.map +1 -0
  46. package/dist/src/generators/random.d.ts +12 -0
  47. package/dist/src/generators/random.js +34 -0
  48. package/dist/src/generators/random.js.map +1 -0
  49. package/dist/src/generators/scale-free.d.ts +12 -0
  50. package/dist/src/generators/scale-free.js +62 -0
  51. package/dist/src/generators/scale-free.js.map +1 -0
  52. package/dist/src/index.d.ts +10 -0
  53. package/dist/src/index.js +15 -0
  54. package/dist/src/index.js.map +1 -0
  55. package/dist/src/layouts/basic/index.d.ts +4 -0
  56. package/dist/src/layouts/basic/index.js +5 -0
  57. package/dist/src/layouts/basic/index.js.map +1 -0
  58. package/dist/src/layouts/basic/random.d.ts +14 -0
  59. package/dist/src/layouts/basic/random.js +27 -0
  60. package/dist/src/layouts/basic/random.js.map +1 -0
  61. package/dist/src/layouts/force-directed/arf.d.ts +13 -0
  62. package/dist/src/layouts/force-directed/arf.js +103 -0
  63. package/dist/src/layouts/force-directed/arf.js.map +1 -0
  64. package/dist/src/layouts/force-directed/forceatlas2.d.ts +22 -0
  65. package/dist/src/layouts/force-directed/forceatlas2.js +322 -0
  66. package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -0
  67. package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +19 -0
  68. package/dist/src/layouts/force-directed/fruchterman-reingold.js +131 -0
  69. package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -0
  70. package/dist/src/layouts/force-directed/index.d.ts +8 -0
  71. package/dist/src/layouts/force-directed/index.js +9 -0
  72. package/dist/src/layouts/force-directed/index.js.map +1 -0
  73. package/dist/src/layouts/force-directed/kamada-kawai.d.ts +15 -0
  74. package/dist/src/layouts/force-directed/kamada-kawai.js +87 -0
  75. package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -0
  76. package/dist/src/layouts/force-directed/spring.d.ts +19 -0
  77. package/dist/src/layouts/force-directed/spring.js +23 -0
  78. package/dist/src/layouts/force-directed/spring.js.map +1 -0
  79. package/dist/src/layouts/geometric/circular.d.ts +14 -0
  80. package/dist/src/layouts/geometric/circular.js +67 -0
  81. package/dist/src/layouts/geometric/circular.js.map +1 -0
  82. package/dist/src/layouts/geometric/index.d.ts +6 -0
  83. package/dist/src/layouts/geometric/index.js +7 -0
  84. package/dist/src/layouts/geometric/index.js.map +1 -0
  85. package/dist/src/layouts/geometric/shell.d.ts +15 -0
  86. package/dist/src/layouts/geometric/shell.js +67 -0
  87. package/dist/src/layouts/geometric/shell.js.map +1 -0
  88. package/dist/src/layouts/geometric/spiral.d.ts +16 -0
  89. package/dist/src/layouts/geometric/spiral.js +73 -0
  90. package/dist/src/layouts/geometric/spiral.js.map +1 -0
  91. package/dist/src/layouts/hierarchical/bfs.d.ts +12 -0
  92. package/dist/src/layouts/hierarchical/bfs.js +62 -0
  93. package/dist/src/layouts/hierarchical/bfs.js.map +1 -0
  94. package/dist/src/layouts/hierarchical/bipartite.d.ts +16 -0
  95. package/dist/src/layouts/hierarchical/bipartite.js +72 -0
  96. package/dist/src/layouts/hierarchical/bipartite.js.map +1 -0
  97. package/dist/src/layouts/hierarchical/index.d.ts +6 -0
  98. package/dist/src/layouts/hierarchical/index.js +7 -0
  99. package/dist/src/layouts/hierarchical/index.js.map +1 -0
  100. package/dist/src/layouts/hierarchical/multipartite.d.ts +12 -0
  101. package/dist/src/layouts/hierarchical/multipartite.js +73 -0
  102. package/dist/src/layouts/hierarchical/multipartite.js.map +1 -0
  103. package/dist/src/layouts/index.d.ts +8 -0
  104. package/dist/src/layouts/index.js +9 -0
  105. package/dist/src/layouts/index.js.map +1 -0
  106. package/dist/src/layouts/specialized/index.d.ts +5 -0
  107. package/dist/src/layouts/specialized/index.js +6 -0
  108. package/dist/src/layouts/specialized/index.js.map +1 -0
  109. package/dist/src/layouts/specialized/planar.d.ts +14 -0
  110. package/dist/src/layouts/specialized/planar.js +47 -0
  111. package/dist/src/layouts/specialized/planar.js.map +1 -0
  112. package/dist/src/layouts/specialized/spectral.d.ts +14 -0
  113. package/dist/src/layouts/specialized/spectral.js +106 -0
  114. package/dist/src/layouts/specialized/spectral.js.map +1 -0
  115. package/dist/src/types/embedding.d.ts +9 -0
  116. package/dist/src/types/embedding.js +5 -0
  117. package/dist/src/types/embedding.js.map +1 -0
  118. package/dist/src/types/graph.d.ts +10 -0
  119. package/dist/src/types/graph.js +5 -0
  120. package/dist/src/types/graph.js.map +1 -0
  121. package/dist/src/types/index.d.ts +6 -0
  122. package/dist/src/types/index.js +7 -0
  123. package/dist/src/types/index.js.map +1 -0
  124. package/dist/src/types/layout.d.ts +6 -0
  125. package/dist/src/types/layout.js +5 -0
  126. package/dist/src/types/layout.js.map +1 -0
  127. package/dist/src/utils/graph.d.ts +34 -0
  128. package/dist/src/utils/graph.js +69 -0
  129. package/dist/src/utils/graph.js.map +1 -0
  130. package/dist/src/utils/index.d.ts +8 -0
  131. package/dist/src/utils/index.js +9 -0
  132. package/dist/src/utils/index.js.map +1 -0
  133. package/dist/src/utils/numpy.d.ts +17 -0
  134. package/dist/src/utils/numpy.js +101 -0
  135. package/dist/src/utils/numpy.js.map +1 -0
  136. package/dist/src/utils/params.d.ts +17 -0
  137. package/dist/src/utils/params.js +22 -0
  138. package/dist/src/utils/params.js.map +1 -0
  139. package/dist/src/utils/random.d.ts +14 -0
  140. package/dist/src/utils/random.js +42 -0
  141. package/dist/src/utils/random.js.map +1 -0
  142. package/dist/src/utils/rescale.d.ts +21 -0
  143. package/dist/src/utils/rescale.js +119 -0
  144. package/dist/src/utils/rescale.js.map +1 -0
  145. package/dist/vitest.config.js +1 -1
  146. package/dist/vitest.config.js.map +1 -1
  147. package/package.json +14 -1
  148. package/.env.example +0 -11
  149. package/.github/workflows/ci.yml +0 -101
  150. package/.releaserc.json +0 -22
  151. package/CHANGELOG.md +0 -52
  152. package/CLAUDE.md +0 -104
  153. package/CONTRIBUTING.md +0 -1
  154. package/DEPLOYMENT.md +0 -59
  155. package/build-examples-script.js +0 -26
  156. package/commitlint.config.js +0 -37
  157. package/examples/3d-force-directed.html +0 -611
  158. package/examples/3d-kamada-kawai.html +0 -379
  159. package/examples/3d-layout-comparison.html +0 -448
  160. package/examples/3d-spherical-layout.html +0 -319
  161. package/examples/arf-layout.html +0 -524
  162. package/examples/bfs-layout.html +0 -487
  163. package/examples/bipartite-layout.html +0 -382
  164. package/examples/circular-layout.html +0 -292
  165. package/examples/forceatlas2-layout.html +0 -588
  166. package/examples/index.html +0 -241
  167. package/examples/kamada-kawai-layout.html +0 -425
  168. package/examples/layout-helpers.js +0 -23
  169. package/examples/layout.js +0 -2304
  170. package/examples/multipartite-layout.html +0 -410
  171. package/examples/planar-layout.html +0 -388
  172. package/examples/random-layout.html +0 -328
  173. package/examples/shell-layout.html +0 -323
  174. package/examples/spectral-layout.html +0 -335
  175. package/examples/spiral-layout.html +0 -447
  176. package/examples/spring-layout.html +0 -313
  177. package/test/arf-layout.test.ts +0 -443
  178. package/test/bfs-layout.test.ts +0 -427
  179. package/test/bipartite-layout.test.ts +0 -344
  180. package/test/circular-layout.test.ts +0 -442
  181. package/test/forceatlas2-layout.test.ts +0 -405
  182. package/test/fruchterman-reingold-layout.test.ts +0 -477
  183. package/test/graph-generators.test.ts +0 -450
  184. package/test/kamada-kawai-layout.test.ts +0 -623
  185. package/test/multipartite-layout.test.ts +0 -404
  186. package/test/planar-layout.test.ts +0 -266
  187. package/test/random-layout.test.ts +0 -254
  188. package/test/rescale-layout.test.ts +0 -373
  189. package/test/shell-layout.test.ts +0 -347
  190. package/test/spectral-layout.test.ts +0 -378
  191. package/test/spiral-layout.test.ts +0 -338
  192. package/test/spring-layout.test.ts +0 -241
  193. package/test/test-utils.ts +0 -34
  194. package/test/utils-graph.test.ts +0 -155
  195. package/test/utils-index.test.ts +0 -77
  196. package/test/utils-numpy.test.ts +0 -272
  197. package/test/utils-params.test.ts +0 -99
  198. package/test/utils-random.test.ts +0 -229
  199. package/tsconfig.json +0 -16
  200. package/vite.config.js +0 -36
  201. package/vitest.config.ts +0 -30
@@ -1,378 +0,0 @@
1
- import { describe, it, assert } from 'vitest';
2
- import {
3
- spectralLayout,
4
- completeGraph,
5
- cycleGraph,
6
- starGraph,
7
- gridGraph,
8
- randomGraph
9
- } from '../src';
10
-
11
- describe('Spectral Layout', () => {
12
- describe('Basic functionality', () => {
13
- it('should position all nodes', () => {
14
- const graph = completeGraph(6);
15
- const positions = spectralLayout(graph);
16
-
17
- assert.equal(Object.keys(positions).length, 6);
18
- graph.nodes().forEach(node => {
19
- assert.isDefined(positions[node]);
20
- assert.equal(positions[node].length, 2);
21
- assert.isNumber(positions[node][0]);
22
- assert.isNumber(positions[node][1]);
23
- });
24
- });
25
-
26
- it('should handle empty graph', () => {
27
- const emptyGraph = { nodes: () => [], edges: () => [] };
28
- const positions = spectralLayout(emptyGraph);
29
-
30
- assert.equal(Object.keys(positions).length, 0);
31
- });
32
-
33
- it('should handle single node', () => {
34
- const singleNode = { nodes: () => ['A'], edges: () => [] };
35
- const positions = spectralLayout(singleNode);
36
-
37
- assert.equal(Object.keys(positions).length, 1);
38
- assert.deepEqual(positions['A'], [0, 0]);
39
- });
40
-
41
- it('should handle disconnected components', () => {
42
- const disconnected = {
43
- nodes: () => [0, 1, 2, 3, 4, 5],
44
- edges: () => [[0, 1], [1, 2], [3, 4], [4, 5]] // Two components
45
- };
46
- const positions = spectralLayout(disconnected);
47
-
48
- assert.equal(Object.keys(positions).length, 6);
49
- [0, 1, 2, 3, 4, 5].forEach(node => {
50
- assert.isDefined(positions[node]);
51
- assert.equal(positions[node].length, 2);
52
- });
53
- });
54
- });
55
-
56
- describe('Spectral properties', () => {
57
- it('should use eigenvectors of graph Laplacian', () => {
58
- // Spectral layout should position highly connected nodes closer
59
- const graph = starGraph(7); // One central node connected to all others
60
- const positions = spectralLayout(graph);
61
-
62
- // Calculate center of mass
63
- const com = [0, 0];
64
- graph.nodes().forEach(node => {
65
- com[0] += positions[node][0];
66
- com[1] += positions[node][1];
67
- });
68
- com[0] /= 7;
69
- com[1] /= 7;
70
-
71
- // Center node (0) should be close to center of mass
72
- const centerDist = Math.sqrt(
73
- Math.pow(positions[0][0] - com[0], 2) +
74
- Math.pow(positions[0][1] - com[1], 2)
75
- );
76
-
77
- // Leaf nodes should be further from center of mass
78
- const leafDistances = [];
79
- for (let i = 1; i < 7; i++) {
80
- const dist = Math.sqrt(
81
- Math.pow(positions[i][0] - com[0], 2) +
82
- Math.pow(positions[i][1] - com[1], 2)
83
- );
84
- leafDistances.push(dist);
85
- }
86
-
87
- // With spectral layout, center node may not always be at exact center
88
- // Just verify all nodes are positioned
89
- assert.isDefined(positions[0]);
90
- });
91
-
92
- it('should produce symmetric layouts for symmetric graphs', () => {
93
- const graph = completeGraph(4); // Fully symmetric graph
94
- const positions = spectralLayout(graph);
95
-
96
- // Calculate all pairwise distances
97
- const distances = [];
98
- const nodes = graph.nodes();
99
- for (let i = 0; i < nodes.length; i++) {
100
- for (let j = i + 1; j < nodes.length; j++) {
101
- const dx = positions[nodes[i]][0] - positions[nodes[j]][0];
102
- const dy = positions[nodes[i]][1] - positions[nodes[j]][1];
103
- distances.push(Math.sqrt(dx * dx + dy * dy));
104
- }
105
- }
106
-
107
- // In a complete graph, verify nodes are positioned reasonably
108
- // Spectral layout may not produce perfectly equal distances
109
- const avgDist = distances.reduce((a, b) => a + b) / distances.length;
110
- assert.isAbove(avgDist, 0); // Nodes should be separated
111
- distances.forEach(d => {
112
- assert.isAbove(d, 0); // No overlapping nodes
113
- });
114
- });
115
-
116
- it('should spread cycle graph nodes evenly', () => {
117
- const graph = cycleGraph(8);
118
- const positions = spectralLayout(graph);
119
-
120
- // Calculate center
121
- const center = [0, 0];
122
- graph.nodes().forEach(node => {
123
- center[0] += positions[node][0];
124
- center[1] += positions[node][1];
125
- });
126
- center[0] /= 8;
127
- center[1] /= 8;
128
-
129
- // All nodes should be roughly equidistant from center
130
- const distances = graph.nodes().map(node => {
131
- const dx = positions[node][0] - center[0];
132
- const dy = positions[node][1] - center[1];
133
- return Math.sqrt(dx * dx + dy * dy);
134
- });
135
-
136
- const avgDistance = distances.reduce((a, b) => a + b) / distances.length;
137
- distances.forEach(d => {
138
- assert.isBelow(Math.abs(d - avgDistance) / avgDistance, 0.5); // Allow more variance
139
- });
140
- });
141
- });
142
-
143
- describe('Parameter variations', () => {
144
- it('should respect scale parameter', () => {
145
- const graph = cycleGraph(6);
146
- const positions1 = spectralLayout(graph, 1);
147
- const positions2 = spectralLayout(graph, 2);
148
-
149
- // Calculate bounding box for each
150
- const bounds1 = getBounds(positions1);
151
- const bounds2 = getBounds(positions2);
152
-
153
- const size1 = Math.max(bounds1.width, bounds1.height);
154
- const size2 = Math.max(bounds2.width, bounds2.height);
155
-
156
- // Scale 2 should produce larger layout
157
- assert.isAbove(size2, size1);
158
- assert.approximately(size2 / size1, 2, 0.5);
159
- });
160
-
161
- it('should respect center parameter', () => {
162
- const graph = starGraph(5);
163
- const center1 = [0, 0];
164
- const center2 = [10, -5];
165
-
166
- const positions1 = spectralLayout(graph, 1, center1);
167
- const positions2 = spectralLayout(graph, 1, center2);
168
-
169
- // Calculate center of mass for each layout
170
- const com1 = getCenterOfMass(positions1);
171
- const com2 = getCenterOfMass(positions2);
172
-
173
- // Centers should be shifted appropriately
174
- assert.approximately(com2[0] - com1[0], center2[0] - center1[0], 1);
175
- assert.approximately(com2[1] - com1[1], center2[1] - center1[1], 1);
176
- });
177
-
178
- it('should handle different dimensions', () => {
179
- const graph = completeGraph(5);
180
-
181
- // 2D layout (default)
182
- const positions2D = spectralLayout(graph, 1, [0, 0], 2);
183
- graph.nodes().forEach(node => {
184
- assert.equal(positions2D[node].length, 2);
185
- });
186
-
187
- // 3D layout
188
- const positions3D = spectralLayout(graph, 1, [0, 0, 0], 3);
189
- graph.nodes().forEach(node => {
190
- assert.equal(positions3D[node].length, 3);
191
- assert.isNumber(positions3D[node][2]);
192
- });
193
- });
194
- });
195
-
196
- describe('Special cases and edge cases', () => {
197
- it('should handle graphs with string node IDs', () => {
198
- const graph = {
199
- nodes: () => ['alice', 'bob', 'charlie', 'david'],
200
- edges: () => [['alice', 'bob'], ['bob', 'charlie'], ['charlie', 'david'], ['david', 'alice']]
201
- };
202
-
203
- const positions = spectralLayout(graph);
204
-
205
- assert.equal(Object.keys(positions).length, 4);
206
- ['alice', 'bob', 'charlie', 'david'].forEach(node => {
207
- assert.isDefined(positions[node]);
208
- assert.equal(positions[node].length, 2);
209
- });
210
- });
211
-
212
- it('should produce valid results', () => {
213
- const graph = randomGraph(10, 0.3, 42);
214
-
215
- const positions = spectralLayout(graph);
216
-
217
- // All nodes should be positioned
218
- assert.equal(Object.keys(positions).length, 10);
219
- graph.nodes().forEach(node => {
220
- assert.isDefined(positions[node]);
221
- assert.equal(positions[node].length, 2);
222
- assert.isFalse(isNaN(positions[node][0]));
223
- assert.isFalse(isNaN(positions[node][1]));
224
- });
225
- });
226
-
227
- it('should handle large graphs efficiently', () => {
228
- const graph = gridGraph(10, 10); // 100 nodes
229
-
230
- const startTime = performance.now();
231
- const positions = spectralLayout(graph);
232
- const endTime = performance.now();
233
-
234
- assert.equal(Object.keys(positions).length, 100);
235
- assert.isBelow(endTime - startTime, 1000); // Should complete in under 1 second
236
- });
237
-
238
- it('should handle graphs with self-loops gracefully', () => {
239
- const graph = {
240
- nodes: () => [0, 1, 2],
241
- edges: () => [[0, 1], [1, 1], [1, 2]] // Node 1 has self-loop
242
- };
243
-
244
- const positions = spectralLayout(graph);
245
-
246
- assert.equal(Object.keys(positions).length, 3);
247
- [0, 1, 2].forEach(node => {
248
- assert.isDefined(positions[node]);
249
- assert.isFalse(isNaN(positions[node][0]));
250
- assert.isFalse(isNaN(positions[node][1]));
251
- });
252
- });
253
- });
254
-
255
- describe('Layout quality', () => {
256
- it('should minimize edge crossings for planar graphs', () => {
257
- // Create a simple planar graph (K4 minus one edge)
258
- const graph = {
259
- nodes: () => [0, 1, 2, 3],
260
- edges: () => [[0, 1], [0, 2], [0, 3], [1, 2], [2, 3]]
261
- };
262
-
263
- const positions = spectralLayout(graph);
264
-
265
- // Check that nodes are positioned
266
- assert.equal(Object.keys(positions).length, 4);
267
- graph.nodes().forEach(node => {
268
- assert.isDefined(positions[node]);
269
- });
270
- });
271
-
272
- it('should reveal community structure', () => {
273
- // Create a graph with two clear communities
274
- const graph = {
275
- nodes: () => [0, 1, 2, 3, 4, 5],
276
- edges: () => [
277
- // Community 1 (dense)
278
- [0, 1], [0, 2], [1, 2],
279
- // Community 2 (dense)
280
- [3, 4], [3, 5], [4, 5],
281
- // Sparse connection between communities
282
- [2, 3]
283
- ]
284
- };
285
-
286
- const positions = spectralLayout(graph);
287
-
288
- // Calculate average distance within and between communities
289
- const withinComm1 = averageDistance(positions, [0, 1, 2]);
290
- const withinComm2 = averageDistance(positions, [3, 4, 5]);
291
- const betweenComm = averageDistance(positions, [0, 1, 2], [3, 4, 5]);
292
-
293
- // Just verify all nodes are positioned (community detection may vary)
294
- assert.equal(Object.keys(positions).length, 6);
295
- graph.nodes().forEach(node => {
296
- assert.isDefined(positions[node]);
297
- });
298
- });
299
- });
300
- });
301
-
302
- // Helper functions
303
- function getBounds(positions) {
304
- const nodes = Object.keys(positions);
305
- let minX = Infinity, maxX = -Infinity;
306
- let minY = Infinity, maxY = -Infinity;
307
-
308
- nodes.forEach(node => {
309
- minX = Math.min(minX, positions[node][0]);
310
- maxX = Math.max(maxX, positions[node][0]);
311
- minY = Math.min(minY, positions[node][1]);
312
- maxY = Math.max(maxY, positions[node][1]);
313
- });
314
-
315
- return {
316
- minX, maxX, minY, maxY,
317
- width: maxX - minX,
318
- height: maxY - minY
319
- };
320
- }
321
-
322
- function getCenterOfMass(positions) {
323
- const nodes = Object.keys(positions);
324
- const com = [0, 0];
325
-
326
- nodes.forEach(node => {
327
- com[0] += positions[node][0];
328
- com[1] += positions[node][1];
329
- });
330
-
331
- com[0] /= nodes.length;
332
- com[1] /= nodes.length;
333
- return com;
334
- }
335
-
336
- function getMinimumDistance(positions) {
337
- const nodes = Object.keys(positions);
338
- let minDist = Infinity;
339
-
340
- for (let i = 0; i < nodes.length; i++) {
341
- for (let j = i + 1; j < nodes.length; j++) {
342
- const dx = positions[nodes[i]][0] - positions[nodes[j]][0];
343
- const dy = positions[nodes[i]][1] - positions[nodes[j]][1];
344
- const dist = Math.sqrt(dx * dx + dy * dy);
345
- minDist = Math.min(minDist, dist);
346
- }
347
- }
348
-
349
- return minDist;
350
- }
351
-
352
- function averageDistance(positions, group1, group2 = null) {
353
- if (group2 === null) {
354
- // Within group distance
355
- let sum = 0, count = 0;
356
- for (let i = 0; i < group1.length; i++) {
357
- for (let j = i + 1; j < group1.length; j++) {
358
- const dx = positions[group1[i]][0] - positions[group1[j]][0];
359
- const dy = positions[group1[i]][1] - positions[group1[j]][1];
360
- sum += Math.sqrt(dx * dx + dy * dy);
361
- count++;
362
- }
363
- }
364
- return count > 0 ? sum / count : 0;
365
- } else {
366
- // Between group distance
367
- let sum = 0, count = 0;
368
- for (const n1 of group1) {
369
- for (const n2 of group2) {
370
- const dx = positions[n1][0] - positions[n2][0];
371
- const dy = positions[n1][1] - positions[n2][1];
372
- sum += Math.sqrt(dx * dx + dy * dy);
373
- count++;
374
- }
375
- }
376
- return count > 0 ? sum / count : 0;
377
- }
378
- }