@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,347 +0,0 @@
1
- import { describe, it, assert } from 'vitest';
2
- import {
3
- shellLayout,
4
- completeGraph,
5
- cycleGraph,
6
- starGraph,
7
- wheelGraph,
8
- gridGraph,
9
- randomGraph
10
- } from '../src';
11
-
12
- describe('Shell Layout', () => {
13
- describe('Basic functionality', () => {
14
- it('should position all nodes', () => {
15
- const graph = completeGraph(8);
16
- const positions = shellLayout(graph);
17
-
18
- assert.equal(Object.keys(positions).length, 8);
19
- graph.nodes().forEach(node => {
20
- assert.isDefined(positions[node]);
21
- assert.equal(positions[node].length, 2);
22
- assert.isNumber(positions[node][0]);
23
- assert.isNumber(positions[node][1]);
24
- });
25
- });
26
-
27
- it('should handle empty graph', () => {
28
- const emptyGraph = { nodes: () => [], edges: () => [] };
29
- const positions = shellLayout(emptyGraph);
30
-
31
- assert.equal(Object.keys(positions).length, 0);
32
- });
33
-
34
- it('should handle single node', () => {
35
- const singleNode = { nodes: () => ['node1'], edges: () => [] };
36
- const positions = shellLayout(singleNode);
37
-
38
- assert.equal(Object.keys(positions).length, 1);
39
- assert.deepEqual(positions['node1'], [0, 0]);
40
- });
41
-
42
- it('should default to single shell when no shells specified', () => {
43
- const graph = cycleGraph(6);
44
- const positions = shellLayout(graph);
45
-
46
- // All nodes should be on a circle (single shell)
47
- const distances = graph.nodes().map(node => {
48
- const [x, y] = positions[node];
49
- return Math.sqrt(x * x + y * y);
50
- });
51
-
52
- const firstDist = distances[0];
53
- distances.forEach(d => {
54
- assert.approximately(d, firstDist, 1e-10);
55
- });
56
- });
57
- });
58
-
59
- describe('Multi-shell arrangements', () => {
60
- it('should arrange nodes in specified shells', () => {
61
- const graph = completeGraph(9);
62
- const shells = [
63
- [0], // Center node
64
- [1, 2, 3], // Inner shell
65
- [4, 5, 6, 7, 8] // Outer shell
66
- ];
67
-
68
- const positions = shellLayout(graph, shells);
69
-
70
- // Center node at origin
71
- assert.deepEqual(positions[0], [0, 0]);
72
-
73
- // Inner shell at same radius
74
- const innerRadii = [1, 2, 3].map(node => {
75
- const [x, y] = positions[node];
76
- return Math.sqrt(x * x + y * y);
77
- });
78
- const innerRadius = innerRadii[0];
79
- innerRadii.forEach(r => {
80
- assert.approximately(r, innerRadius, 1e-10);
81
- });
82
-
83
- // Outer shell at larger radius
84
- const outerRadii = [4, 5, 6, 7, 8].map(node => {
85
- const [x, y] = positions[node];
86
- return Math.sqrt(x * x + y * y);
87
- });
88
- const outerRadius = outerRadii[0];
89
- outerRadii.forEach(r => {
90
- assert.approximately(r, outerRadius, 1e-10);
91
- });
92
-
93
- // Outer radius should be larger than inner
94
- assert.isAbove(outerRadius, innerRadius);
95
- });
96
-
97
- it('should space shells evenly', () => {
98
- const graph = starGraph(13); // 1 center + 12 leaves
99
- const shells = [
100
- [0], // Center
101
- [1, 2, 3, 4], // Shell 1
102
- [5, 6, 7, 8], // Shell 2
103
- [9, 10, 11, 12] // Shell 3
104
- ];
105
-
106
- const positions = shellLayout(graph, shells);
107
-
108
- // Calculate radii for each shell
109
- const radii = shells.map((shell, idx) => {
110
- if (idx === 0) return 0; // Center
111
- const [x, y] = positions[shell[0]];
112
- return Math.sqrt(x * x + y * y);
113
- });
114
-
115
- // Check spacing between consecutive shells
116
- const spacing1 = radii[2] - radii[1];
117
- const spacing2 = radii[3] - radii[2];
118
-
119
- assert.approximately(spacing1, spacing2, 1e-5);
120
- });
121
-
122
- it('should arrange nodes evenly within each shell', () => {
123
- const graph = wheelGraph(9); // 1 center + 8 rim
124
- const shells = [
125
- [0],
126
- [1, 2, 3, 4, 5, 6, 7, 8]
127
- ];
128
-
129
- const positions = shellLayout(graph, shells);
130
-
131
- // Calculate angles for outer shell nodes
132
- const angles = shells[1].map(node => {
133
- const [x, y] = positions[node];
134
- return Math.atan2(y, x);
135
- }).sort((a, b) => a - b);
136
-
137
- // Check angle differences
138
- const expectedAngle = 2 * Math.PI / 8;
139
- for (let i = 0; i < angles.length; i++) {
140
- const diff = i < angles.length - 1
141
- ? angles[i + 1] - angles[i]
142
- : angles[0] + 2 * Math.PI - angles[i];
143
- assert.approximately(diff, expectedAngle, 1e-5);
144
- }
145
- });
146
- });
147
-
148
- describe('Parameter variations', () => {
149
- it('should respect scale parameter', () => {
150
- const graph = completeGraph(7);
151
- const shells = [[0, 1, 2], [3, 4, 5, 6]];
152
-
153
- const positions1 = shellLayout(graph, shells, 1);
154
- const positions2 = shellLayout(graph, shells, 2);
155
-
156
- // All distances should be scaled
157
- shells[1].forEach(node => {
158
- const r1 = Math.sqrt(positions1[node][0] ** 2 + positions1[node][1] ** 2);
159
- const r2 = Math.sqrt(positions2[node][0] ** 2 + positions2[node][1] ** 2);
160
- assert.approximately(r2 / r1, 2, 1e-5);
161
- });
162
- });
163
-
164
- it('should respect center parameter', () => {
165
- const graph = cycleGraph(5);
166
- const shells = [[0, 1, 2, 3, 4]];
167
- const center = [5, -3];
168
-
169
- const positions = shellLayout(graph, shells, 1, center);
170
-
171
- // Calculate center of mass
172
- const com = graph.nodes().reduce((acc, node) => {
173
- return [acc[0] + positions[node][0], acc[1] + positions[node][1]];
174
- }, [0, 0]).map(v => v / 5);
175
-
176
- assert.approximately(com[0], center[0], 1e-10);
177
- assert.approximately(com[1], center[1], 1e-10);
178
- });
179
-
180
- it('should only support 2D layouts', () => {
181
- const graph = completeGraph(6);
182
- const shells = [[0, 1], [2, 3, 4, 5]];
183
-
184
- // 2D layout works
185
- const positions2D = shellLayout(graph, shells, 1, [0, 0], 2);
186
- assert.equal(Object.keys(positions2D).length, 6);
187
-
188
- // 3D layout should throw error
189
- assert.throws(() => {
190
- shellLayout(graph, shells, 1, [0, 0, 0], 3);
191
- }, 'can only handle 2 dimensions');
192
- });
193
-
194
- it('should handle empty shells gracefully', () => {
195
- const graph = cycleGraph(5);
196
- const shells = [
197
- [0, 1],
198
- [], // Empty shell
199
- [2, 3, 4]
200
- ];
201
-
202
- const positions = shellLayout(graph, shells);
203
-
204
- // Should still position all nodes
205
- assert.equal(Object.keys(positions).length, 5);
206
-
207
- // First and last shell should have different radii
208
- const r1 = Math.sqrt(positions[0][0] ** 2 + positions[0][1] ** 2);
209
- const r3 = Math.sqrt(positions[2][0] ** 2 + positions[2][1] ** 2);
210
- assert.isAbove(r3, r1);
211
- });
212
- });
213
-
214
- describe('Special cases and edge cases', () => {
215
- it('should handle string node IDs', () => {
216
- const graph = {
217
- nodes: () => ['center', 'a', 'b', 'c', 'd'],
218
- edges: () => [['center', 'a'], ['center', 'b'], ['center', 'c'], ['center', 'd']]
219
- };
220
- const shells = [['center'], ['a', 'b', 'c', 'd']];
221
-
222
- const positions = shellLayout(graph, shells);
223
-
224
- assert.deepEqual(positions['center'], [0, 0]);
225
- ['a', 'b', 'c', 'd'].forEach(node => {
226
- const [x, y] = positions[node];
227
- const r = Math.sqrt(x * x + y * y);
228
- assert.isAbove(r, 0);
229
- assert.approximately(r, 0.5, 1e-10); // Second shell at radius 0.5 (scale=1, 2 shells)
230
- });
231
- });
232
-
233
- it('should handle nodes not in any shell', () => {
234
- const graph = completeGraph(6);
235
- const shells = [[0, 1], [2, 3]]; // Nodes 4, 5 not included
236
-
237
- const positions = shellLayout(graph, shells);
238
-
239
- // Only nodes in shells should be positioned
240
- assert.equal(Object.keys(positions).length, 4);
241
-
242
- // Nodes not in shells won't be positioned
243
- assert.isUndefined(positions[4]);
244
- assert.isUndefined(positions[5]);
245
- });
246
-
247
- it('should handle duplicate nodes in shells', () => {
248
- const graph = cycleGraph(4);
249
- const shells = [[0, 1], [1, 2, 3]]; // Node 1 in two shells
250
-
251
- const positions = shellLayout(graph, shells);
252
-
253
- // Should handle gracefully
254
- assert.equal(Object.keys(positions).length, 4);
255
-
256
- // Check node positions
257
- const r0 = Math.sqrt(positions[0][0] ** 2 + positions[0][1] ** 2);
258
- const r1 = Math.sqrt(positions[1][0] ** 2 + positions[1][1] ** 2);
259
- const r2 = Math.sqrt(positions[2][0] ** 2 + positions[2][1] ** 2);
260
- const r3 = Math.sqrt(positions[3][0] ** 2 + positions[3][1] ** 2);
261
-
262
- // Node 0 in first shell at radius 0.5
263
- assert.approximately(r0, 0.5, 1e-10);
264
-
265
- // Nodes 1, 2, 3 in second shell at radius 1.0
266
- // (Node 1 appears in second shell, not first)
267
- assert.approximately(r1, 1.0, 1e-10);
268
- assert.approximately(r2, 1.0, 1e-10);
269
- assert.approximately(r3, 1.0, 1e-10);
270
- });
271
-
272
- it('should produce consistent results', () => {
273
- const graph = randomGraph(10, 0.3, 42);
274
- const shells = [
275
- graph.nodes().slice(0, 3),
276
- graph.nodes().slice(3, 7),
277
- graph.nodes().slice(7)
278
- ];
279
-
280
- const positions1 = shellLayout(graph, shells);
281
- const positions2 = shellLayout(graph, shells);
282
-
283
- // Shell layout is deterministic
284
- graph.nodes().forEach(node => {
285
- assert.deepEqual(positions1[node], positions2[node]);
286
- });
287
- });
288
-
289
- it('should handle very large shells efficiently', () => {
290
- const graph = gridGraph(10, 10); // 100 nodes
291
- const shells = [
292
- graph.nodes().slice(0, 1), // 1 node
293
- graph.nodes().slice(1, 10), // 9 nodes
294
- graph.nodes().slice(10, 30), // 20 nodes
295
- graph.nodes().slice(30) // 70 nodes
296
- ];
297
-
298
- const startTime = performance.now();
299
- const positions = shellLayout(graph, shells);
300
- const endTime = performance.now();
301
-
302
- assert.equal(Object.keys(positions).length, 100);
303
- assert.isBelow(endTime - startTime, 100);
304
- });
305
- });
306
-
307
- describe('Layout patterns', () => {
308
- it('should create concentric circles for appropriate graphs', () => {
309
- // Create a graph with natural shell structure
310
- const nodes = Array.from({ length: 13 }, (_, i) => i);
311
- const edges: [number, number][] = [];
312
-
313
- // Connect center to inner ring
314
- for (let i = 1; i <= 4; i++) {
315
- edges.push([0, i]);
316
- }
317
-
318
- // Connect inner ring to outer ring
319
- for (let i = 1; i <= 4; i++) {
320
- edges.push([i, i + 4]);
321
- edges.push([i, i + 8]);
322
- }
323
-
324
- const graph = { nodes: () => nodes, edges: () => edges };
325
- const shells = [
326
- [0],
327
- [1, 2, 3, 4],
328
- [5, 6, 7, 8],
329
- [9, 10, 11, 12]
330
- ];
331
-
332
- const positions = shellLayout(graph, shells);
333
-
334
- // Verify concentric structure
335
- const radii = shells.map(shell => {
336
- if (shell.length === 0) return 0;
337
- const [x, y] = positions[shell[0]];
338
- return Math.sqrt(x * x + y * y);
339
- });
340
-
341
- assert.equal(radii[0], 0); // Center
342
- assert.isAbove(radii[1], 0);
343
- assert.isAbove(radii[2], radii[1]);
344
- assert.isAbove(radii[3], radii[2]);
345
- });
346
- });
347
- });