@graphty/layout 1.2.2 → 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 (200) hide show
  1. package/README.md +317 -237
  2. package/dist/src/algorithms/index.d.ts +5 -0
  3. package/dist/src/algorithms/index.js +6 -0
  4. package/dist/src/algorithms/index.js.map +1 -0
  5. package/dist/src/algorithms/optimization/index.d.ts +7 -0
  6. package/dist/src/algorithms/optimization/index.js +7 -0
  7. package/dist/src/algorithms/optimization/index.js.map +1 -0
  8. package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +32 -0
  9. package/dist/src/algorithms/optimization/kamada-kawai-solver.js +176 -0
  10. package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -0
  11. package/dist/src/algorithms/optimization/lbfgs.d.ts +13 -0
  12. package/dist/src/algorithms/optimization/lbfgs.js +56 -0
  13. package/dist/src/algorithms/optimization/lbfgs.js.map +1 -0
  14. package/dist/src/algorithms/optimization/line-search.d.ts +15 -0
  15. package/dist/src/algorithms/optimization/line-search.js +37 -0
  16. package/dist/src/algorithms/optimization/line-search.js.map +1 -0
  17. package/dist/src/algorithms/optimization/types.d.ts +5 -0
  18. package/dist/src/algorithms/optimization/types.js +5 -0
  19. package/dist/src/algorithms/optimization/types.js.map +1 -0
  20. package/dist/src/algorithms/planarity/check.d.ts +17 -0
  21. package/dist/src/algorithms/planarity/check.js +30 -0
  22. package/dist/src/algorithms/planarity/check.js.map +1 -0
  23. package/dist/src/algorithms/planarity/embedding.d.ts +29 -0
  24. package/dist/src/algorithms/planarity/embedding.js +179 -0
  25. package/dist/src/algorithms/planarity/embedding.js.map +1 -0
  26. package/dist/src/algorithms/planarity/index.d.ts +7 -0
  27. package/dist/src/algorithms/planarity/index.js +8 -0
  28. package/dist/src/algorithms/planarity/index.js.map +1 -0
  29. package/dist/src/algorithms/planarity/lr-test.d.ts +15 -0
  30. package/dist/src/algorithms/planarity/lr-test.js +54 -0
  31. package/dist/src/algorithms/planarity/lr-test.js.map +1 -0
  32. package/dist/src/algorithms/planarity/special-graphs.d.ts +28 -0
  33. package/dist/src/algorithms/planarity/special-graphs.js +108 -0
  34. package/dist/src/algorithms/planarity/special-graphs.js.map +1 -0
  35. package/dist/src/generators/basic.d.ts +28 -0
  36. package/dist/src/generators/basic.js +79 -0
  37. package/dist/src/generators/basic.js.map +1 -0
  38. package/dist/src/generators/bipartite.d.ts +16 -0
  39. package/dist/src/generators/bipartite.js +40 -0
  40. package/dist/src/generators/bipartite.js.map +1 -0
  41. package/dist/src/generators/grid.d.ts +11 -0
  42. package/dist/src/generators/grid.js +37 -0
  43. package/dist/src/generators/grid.js.map +1 -0
  44. package/dist/src/generators/index.d.ts +8 -0
  45. package/dist/src/generators/index.js +9 -0
  46. package/dist/src/generators/index.js.map +1 -0
  47. package/dist/src/generators/random.d.ts +12 -0
  48. package/dist/src/generators/random.js +34 -0
  49. package/dist/src/generators/random.js.map +1 -0
  50. package/dist/src/generators/scale-free.d.ts +12 -0
  51. package/dist/src/generators/scale-free.js +62 -0
  52. package/dist/src/generators/scale-free.js.map +1 -0
  53. package/dist/src/index.d.ts +10 -0
  54. package/dist/src/index.js +15 -0
  55. package/dist/src/index.js.map +1 -0
  56. package/dist/src/layouts/basic/index.d.ts +4 -0
  57. package/dist/src/layouts/basic/index.js +5 -0
  58. package/dist/src/layouts/basic/index.js.map +1 -0
  59. package/dist/src/layouts/basic/random.d.ts +14 -0
  60. package/dist/src/layouts/basic/random.js +27 -0
  61. package/dist/src/layouts/basic/random.js.map +1 -0
  62. package/dist/src/layouts/force-directed/arf.d.ts +13 -0
  63. package/dist/src/layouts/force-directed/arf.js +103 -0
  64. package/dist/src/layouts/force-directed/arf.js.map +1 -0
  65. package/dist/src/layouts/force-directed/forceatlas2.d.ts +22 -0
  66. package/dist/src/layouts/force-directed/forceatlas2.js +322 -0
  67. package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -0
  68. package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +19 -0
  69. package/dist/src/layouts/force-directed/fruchterman-reingold.js +131 -0
  70. package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -0
  71. package/dist/src/layouts/force-directed/index.d.ts +8 -0
  72. package/dist/src/layouts/force-directed/index.js +9 -0
  73. package/dist/src/layouts/force-directed/index.js.map +1 -0
  74. package/dist/src/layouts/force-directed/kamada-kawai.d.ts +15 -0
  75. package/dist/src/layouts/force-directed/kamada-kawai.js +87 -0
  76. package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -0
  77. package/dist/src/layouts/force-directed/spring.d.ts +19 -0
  78. package/dist/src/layouts/force-directed/spring.js +23 -0
  79. package/dist/src/layouts/force-directed/spring.js.map +1 -0
  80. package/dist/src/layouts/geometric/circular.d.ts +14 -0
  81. package/dist/src/layouts/geometric/circular.js +67 -0
  82. package/dist/src/layouts/geometric/circular.js.map +1 -0
  83. package/dist/src/layouts/geometric/index.d.ts +6 -0
  84. package/dist/src/layouts/geometric/index.js +7 -0
  85. package/dist/src/layouts/geometric/index.js.map +1 -0
  86. package/dist/src/layouts/geometric/shell.d.ts +15 -0
  87. package/dist/src/layouts/geometric/shell.js +67 -0
  88. package/dist/src/layouts/geometric/shell.js.map +1 -0
  89. package/dist/src/layouts/geometric/spiral.d.ts +16 -0
  90. package/dist/src/layouts/geometric/spiral.js +73 -0
  91. package/dist/src/layouts/geometric/spiral.js.map +1 -0
  92. package/dist/src/layouts/hierarchical/bfs.d.ts +12 -0
  93. package/dist/src/layouts/hierarchical/bfs.js +62 -0
  94. package/dist/src/layouts/hierarchical/bfs.js.map +1 -0
  95. package/dist/src/layouts/hierarchical/bipartite.d.ts +16 -0
  96. package/dist/src/layouts/hierarchical/bipartite.js +72 -0
  97. package/dist/src/layouts/hierarchical/bipartite.js.map +1 -0
  98. package/dist/src/layouts/hierarchical/index.d.ts +6 -0
  99. package/dist/src/layouts/hierarchical/index.js +7 -0
  100. package/dist/src/layouts/hierarchical/index.js.map +1 -0
  101. package/dist/src/layouts/hierarchical/multipartite.d.ts +12 -0
  102. package/dist/src/layouts/hierarchical/multipartite.js +73 -0
  103. package/dist/src/layouts/hierarchical/multipartite.js.map +1 -0
  104. package/dist/src/layouts/index.d.ts +8 -0
  105. package/dist/src/layouts/index.js +9 -0
  106. package/dist/src/layouts/index.js.map +1 -0
  107. package/dist/src/layouts/specialized/index.d.ts +5 -0
  108. package/dist/src/layouts/specialized/index.js +6 -0
  109. package/dist/src/layouts/specialized/index.js.map +1 -0
  110. package/dist/src/layouts/specialized/planar.d.ts +14 -0
  111. package/dist/src/layouts/specialized/planar.js +47 -0
  112. package/dist/src/layouts/specialized/planar.js.map +1 -0
  113. package/dist/src/layouts/specialized/spectral.d.ts +14 -0
  114. package/dist/src/layouts/specialized/spectral.js +106 -0
  115. package/dist/src/layouts/specialized/spectral.js.map +1 -0
  116. package/dist/src/types/embedding.d.ts +9 -0
  117. package/dist/src/types/embedding.js +5 -0
  118. package/dist/src/types/embedding.js.map +1 -0
  119. package/dist/src/types/graph.d.ts +10 -0
  120. package/dist/src/types/graph.js +5 -0
  121. package/dist/src/types/graph.js.map +1 -0
  122. package/dist/src/types/index.d.ts +6 -0
  123. package/dist/src/types/index.js +7 -0
  124. package/dist/src/types/index.js.map +1 -0
  125. package/dist/src/types/layout.d.ts +6 -0
  126. package/dist/src/types/layout.js +5 -0
  127. package/dist/src/types/layout.js.map +1 -0
  128. package/dist/src/utils/graph.d.ts +34 -0
  129. package/dist/src/utils/graph.js +69 -0
  130. package/dist/src/utils/graph.js.map +1 -0
  131. package/dist/src/utils/index.d.ts +8 -0
  132. package/dist/src/utils/index.js +9 -0
  133. package/dist/src/utils/index.js.map +1 -0
  134. package/dist/src/utils/numpy.d.ts +17 -0
  135. package/dist/src/utils/numpy.js +101 -0
  136. package/dist/src/utils/numpy.js.map +1 -0
  137. package/dist/src/utils/params.d.ts +17 -0
  138. package/dist/src/utils/params.js +22 -0
  139. package/dist/src/utils/params.js.map +1 -0
  140. package/dist/src/utils/random.d.ts +14 -0
  141. package/dist/src/utils/random.js +42 -0
  142. package/dist/src/utils/random.js.map +1 -0
  143. package/dist/src/utils/rescale.d.ts +21 -0
  144. package/dist/src/utils/rescale.js +119 -0
  145. package/dist/src/utils/rescale.js.map +1 -0
  146. package/dist/vitest.config.js +1 -1
  147. package/dist/vitest.config.js.map +1 -1
  148. package/package.json +15 -1
  149. package/.env.example +0 -11
  150. package/.github/workflows/ci.yml +0 -101
  151. package/.releaserc.json +0 -22
  152. package/CHANGELOG.md +0 -45
  153. package/CLAUDE.md +0 -104
  154. package/CONTRIBUTING.md +0 -1
  155. package/DEPLOYMENT.md +0 -59
  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 -486
  169. package/examples/multipartite-layout.html +0 -410
  170. package/examples/planar-layout.html +0 -388
  171. package/examples/random-layout.html +0 -328
  172. package/examples/shell-layout.html +0 -323
  173. package/examples/spectral-layout.html +0 -335
  174. package/examples/spiral-layout.html +0 -447
  175. package/examples/spring-layout.html +0 -313
  176. package/test/arf-layout.test.ts +0 -443
  177. package/test/bfs-layout.test.ts +0 -427
  178. package/test/bipartite-layout.test.ts +0 -344
  179. package/test/circular-layout.test.ts +0 -442
  180. package/test/forceatlas2-layout.test.ts +0 -405
  181. package/test/fruchterman-reingold-layout.test.ts +0 -477
  182. package/test/graph-generators.test.ts +0 -450
  183. package/test/kamada-kawai-layout.test.ts +0 -623
  184. package/test/multipartite-layout.test.ts +0 -404
  185. package/test/planar-layout.test.ts +0 -266
  186. package/test/random-layout.test.ts +0 -254
  187. package/test/rescale-layout.test.ts +0 -373
  188. package/test/shell-layout.test.ts +0 -347
  189. package/test/spectral-layout.test.ts +0 -378
  190. package/test/spiral-layout.test.ts +0 -338
  191. package/test/spring-layout.test.ts +0 -241
  192. package/test/test-utils.ts +0 -34
  193. package/test/utils-graph.test.ts +0 -155
  194. package/test/utils-index.test.ts +0 -77
  195. package/test/utils-numpy.test.ts +0 -272
  196. package/test/utils-params.test.ts +0 -99
  197. package/test/utils-random.test.ts +0 -229
  198. package/tsconfig.json +0 -16
  199. package/vite.config.js +0 -36
  200. 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
- });