@graphty/layout 1.2.3 → 1.2.5

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 (203) 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 +340 -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 +194 -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 +19 -4
  148. package/src/layouts/force-directed/forceatlas2.ts +23 -4
  149. package/src/utils/rescale.ts +90 -15
  150. package/.env.example +0 -11
  151. package/.github/workflows/ci.yml +0 -101
  152. package/.releaserc.json +0 -22
  153. package/CHANGELOG.md +0 -52
  154. package/CLAUDE.md +0 -104
  155. package/CONTRIBUTING.md +0 -1
  156. package/DEPLOYMENT.md +0 -59
  157. package/build-examples-script.js +0 -26
  158. package/commitlint.config.js +0 -37
  159. package/examples/3d-force-directed.html +0 -611
  160. package/examples/3d-kamada-kawai.html +0 -379
  161. package/examples/3d-layout-comparison.html +0 -448
  162. package/examples/3d-spherical-layout.html +0 -319
  163. package/examples/arf-layout.html +0 -524
  164. package/examples/bfs-layout.html +0 -487
  165. package/examples/bipartite-layout.html +0 -382
  166. package/examples/circular-layout.html +0 -292
  167. package/examples/forceatlas2-layout.html +0 -588
  168. package/examples/index.html +0 -241
  169. package/examples/kamada-kawai-layout.html +0 -425
  170. package/examples/layout-helpers.js +0 -23
  171. package/examples/layout.js +0 -2304
  172. package/examples/multipartite-layout.html +0 -410
  173. package/examples/planar-layout.html +0 -388
  174. package/examples/random-layout.html +0 -328
  175. package/examples/shell-layout.html +0 -323
  176. package/examples/spectral-layout.html +0 -335
  177. package/examples/spiral-layout.html +0 -447
  178. package/examples/spring-layout.html +0 -313
  179. package/test/arf-layout.test.ts +0 -443
  180. package/test/bfs-layout.test.ts +0 -427
  181. package/test/bipartite-layout.test.ts +0 -344
  182. package/test/circular-layout.test.ts +0 -442
  183. package/test/forceatlas2-layout.test.ts +0 -405
  184. package/test/fruchterman-reingold-layout.test.ts +0 -477
  185. package/test/graph-generators.test.ts +0 -450
  186. package/test/kamada-kawai-layout.test.ts +0 -623
  187. package/test/multipartite-layout.test.ts +0 -404
  188. package/test/planar-layout.test.ts +0 -266
  189. package/test/random-layout.test.ts +0 -254
  190. package/test/rescale-layout.test.ts +0 -373
  191. package/test/shell-layout.test.ts +0 -347
  192. package/test/spectral-layout.test.ts +0 -378
  193. package/test/spiral-layout.test.ts +0 -338
  194. package/test/spring-layout.test.ts +0 -241
  195. package/test/test-utils.ts +0 -34
  196. package/test/utils-graph.test.ts +0 -155
  197. package/test/utils-index.test.ts +0 -77
  198. package/test/utils-numpy.test.ts +0 -272
  199. package/test/utils-params.test.ts +0 -99
  200. package/test/utils-random.test.ts +0 -229
  201. package/tsconfig.json +0 -16
  202. package/vite.config.js +0 -36
  203. 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
- }