@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,486 +0,0 @@
1
- /**
2
- * Layout Helper Functions
3
- * =======================
4
- *
5
- * Essential utilities for graph layouts
6
- */
7
- /**
8
- * Universal Node Grouping
9
- * =======================
10
- * Groups nodes for shell, multipartite, or custom layouts
11
- */
12
- /**
13
- * Group nodes by various centrality measures.
14
- * Works for shell layout, multipartite layout, or any grouping need.
15
- *
16
- * @param graph - Input graph
17
- * @param method - Grouping method
18
- * @param numGroups - Number of groups to create
19
- * @returns Array of node arrays (groups)
20
- *
21
- * @example
22
- * // For shell layout
23
- * const shells = groupNodes(graph, 'degree', 3);
24
- * const positions = shellLayout(graph, shells);
25
- *
26
- * // For multipartite layout
27
- * const layers = groupNodes(graph, 'bfs', 0); // 0 = auto
28
- * const positions = multipartiteLayout(graph, layers);
29
- */
30
- export function groupNodes(graph, method = 'degree', numGroups = 3, options = {}) {
31
- const nodes = graph.nodes?.() || [];
32
- const edges = graph.edges?.() || [];
33
- switch (method) {
34
- case 'degree':
35
- return groupByDegree(nodes, edges, numGroups);
36
- case 'bfs':
37
- return groupByDistance(nodes, edges, options.root);
38
- case 'k-core':
39
- return groupByKCore(nodes, edges);
40
- case 'community':
41
- return groupByCommunity(nodes, edges, numGroups);
42
- default:
43
- return [nodes]; // Single group fallback
44
- }
45
- }
46
- /**
47
- * Bipartite Detection & Handling
48
- * ==============================
49
- */
50
- /**
51
- * Detect if graph is bipartite and find the two sets.
52
- *
53
- * @param graph - Input graph
54
- * @returns Bipartite sets or null if not bipartite
55
- *
56
- * @example
57
- * const result = detectBipartite(graph);
58
- * if (result) {
59
- * const positions = bipartiteLayout(graph, result.setA);
60
- * }
61
- */
62
- export function detectBipartite(graph) {
63
- const nodes = graph.nodes?.() || [];
64
- const edges = graph.edges?.() || [];
65
- if (nodes.length === 0)
66
- return { setA: [], setB: [] };
67
- // Build adjacency list
68
- const adj = new Map();
69
- nodes.forEach(node => adj.set(node, []));
70
- edges.forEach(([u, v]) => {
71
- adj.get(u).push(v);
72
- adj.get(v).push(u);
73
- });
74
- // Try to 2-color the graph
75
- const colors = new Map();
76
- for (const start of nodes) {
77
- if (colors.has(start))
78
- continue;
79
- const queue = [start];
80
- colors.set(start, 0);
81
- while (queue.length > 0) {
82
- const node = queue.shift();
83
- const nodeColor = colors.get(node);
84
- for (const neighbor of adj.get(node)) {
85
- if (!colors.has(neighbor)) {
86
- colors.set(neighbor, 1 - nodeColor);
87
- queue.push(neighbor);
88
- }
89
- else if (colors.get(neighbor) === nodeColor) {
90
- return null; // Not bipartite
91
- }
92
- }
93
- }
94
- }
95
- // Separate into sets
96
- const setA = [];
97
- const setB = [];
98
- nodes.forEach(node => {
99
- if (colors.get(node) === 0)
100
- setA.push(node);
101
- else
102
- setB.push(node);
103
- });
104
- return { setA, setB };
105
- }
106
- /**
107
- * Layout Analysis & Optimization
108
- * ==============================
109
- */
110
- /**
111
- * Find the best root node for tree-like layouts (BFS, hierarchical).
112
- *
113
- * @param graph - Input graph
114
- * @returns Best root node
115
- */
116
- export function findBestRoot(graph) {
117
- const nodes = graph.nodes?.() || [];
118
- const edges = graph.edges?.() || [];
119
- if (nodes.length === 0)
120
- return null;
121
- // Build adjacency list
122
- const adj = new Map();
123
- nodes.forEach(node => adj.set(node, new Set()));
124
- edges.forEach(([u, v]) => {
125
- adj.get(u).add(v);
126
- adj.get(v).add(u);
127
- });
128
- // Find node with minimum eccentricity (center of graph)
129
- let bestNode = nodes[0];
130
- let bestEccentricity = Infinity;
131
- for (const start of nodes) {
132
- // BFS to find max distance
133
- const distances = new Map();
134
- const queue = [start];
135
- distances.set(start, 0);
136
- let maxDist = 0;
137
- while (queue.length > 0) {
138
- const node = queue.shift();
139
- const dist = distances.get(node);
140
- adj.get(node).forEach(neighbor => {
141
- if (!distances.has(neighbor)) {
142
- distances.set(neighbor, dist + 1);
143
- maxDist = Math.max(maxDist, dist + 1);
144
- queue.push(neighbor);
145
- }
146
- });
147
- }
148
- if (maxDist < bestEccentricity) {
149
- bestEccentricity = maxDist;
150
- bestNode = start;
151
- }
152
- }
153
- return bestNode;
154
- }
155
- /**
156
- * Check if a graph is planar (can be drawn without edge crossings).
157
- *
158
- * @param graph - Input graph
159
- * @returns true if planar
160
- */
161
- export function isPlanar(graph) {
162
- const n = graph.nodes?.()?.length || 0;
163
- const m = graph.edges?.()?.length || 0;
164
- // Quick check: Euler's formula
165
- if (n >= 3 && m > 3 * n - 6)
166
- return false;
167
- // For accurate check, would need to try planarLayout
168
- // This is a simplified heuristic
169
- return true;
170
- }
171
- /**
172
- * Auto-configure force-directed layout parameters.
173
- *
174
- * @param graph - Input graph
175
- * @returns Recommended parameters
176
- */
177
- export function autoConfigureForce(graph) {
178
- const n = graph.nodes?.()?.length || 0;
179
- const m = graph.edges?.()?.length || 0;
180
- const density = n > 1 ? (2 * m) / (n * (n - 1)) : 0;
181
- return {
182
- k: Math.sqrt(1 / n), // Fruchterman-Reingold optimal distance
183
- iterations: n < 50 ? 500 : n < 200 ? 300 : 200,
184
- gravity: density < 0.1 ? 0.5 : density < 0.3 ? 1.0 : 2.0,
185
- scalingRatio: n < 50 ? 2.0 : n < 200 ? 5.0 : 10.0
186
- };
187
- }
188
- /**
189
- * Layout Quality Metrics
190
- * ======================
191
- */
192
- /**
193
- * Calculate simple layout quality metrics.
194
- *
195
- * @param graph - Input graph
196
- * @param positions - Layout positions
197
- * @returns Quality metrics
198
- */
199
- export function layoutQuality(graph, positions) {
200
- const nodes = graph.nodes?.() || [];
201
- const edges = graph.edges?.() || [];
202
- // Edge lengths
203
- const edgeLengths = edges.map(([u, v]) => {
204
- const p1 = positions[u];
205
- const p2 = positions[v];
206
- return Math.sqrt(p1.reduce((sum, val, i) => sum + (val - p2[i]) ** 2, 0));
207
- });
208
- const avgEdgeLength = edgeLengths.reduce((a, b) => a + b, 0) / edgeLengths.length || 0;
209
- const variance = edgeLengths.reduce((sum, len) => sum + (len - avgEdgeLength) ** 2, 0) / edgeLengths.length || 0;
210
- const edgeLengthStdDev = Math.sqrt(variance);
211
- // Minimum node distance
212
- let minNodeDistance = Infinity;
213
- for (let i = 0; i < nodes.length; i++) {
214
- for (let j = i + 1; j < nodes.length; j++) {
215
- const p1 = positions[nodes[i]];
216
- const p2 = positions[nodes[j]];
217
- const dist = Math.sqrt(p1.reduce((sum, val, k) => sum + (val - p2[k]) ** 2, 0));
218
- minNodeDistance = Math.min(minNodeDistance, dist);
219
- }
220
- }
221
- // Bounding box and aspect ratio
222
- let minX = Infinity, maxX = -Infinity;
223
- let minY = Infinity, maxY = -Infinity;
224
- nodes.forEach(node => {
225
- const [x, y] = positions[node];
226
- minX = Math.min(minX, x);
227
- maxX = Math.max(maxX, x);
228
- minY = Math.min(minY, y);
229
- maxY = Math.max(maxY, y);
230
- });
231
- const width = maxX - minX || 1;
232
- const height = maxY - minY || 1;
233
- const aspectRatio = Math.max(width, height) / Math.min(width, height);
234
- return {
235
- avgEdgeLength,
236
- edgeLengthStdDev,
237
- minNodeDistance: minNodeDistance === Infinity ? 0 : minNodeDistance,
238
- aspectRatio
239
- };
240
- }
241
- /**
242
- * Layout Utilities
243
- * ================
244
- */
245
- /**
246
- * Combine multiple layouts with weights.
247
- *
248
- * @param layouts - Array of position maps
249
- * @param weights - Weight for each layout (default: equal)
250
- * @returns Combined positions
251
- */
252
- export function combineLayouts(layouts, weights) {
253
- if (layouts.length === 0)
254
- return {};
255
- const w = weights || new Array(layouts.length).fill(1 / layouts.length);
256
- const result = {};
257
- const nodes = Object.keys(layouts[0]);
258
- nodes.forEach(node => {
259
- const dim = layouts[0][node].length;
260
- result[node] = new Array(dim).fill(0);
261
- layouts.forEach((layout, i) => {
262
- if (node in layout) {
263
- for (let d = 0; d < dim; d++) {
264
- result[node][d] += layout[node][d] * w[i];
265
- }
266
- }
267
- });
268
- });
269
- return result;
270
- }
271
- /**
272
- * Create smooth animation frames between layouts.
273
- *
274
- * @param from - Starting positions
275
- * @param to - Ending positions
276
- * @param steps - Number of frames
277
- * @returns Array of position maps
278
- */
279
- export function interpolateLayouts(from, to, steps = 10) {
280
- const frames = [];
281
- const nodes = Object.keys(from);
282
- for (let i = 0; i <= steps; i++) {
283
- const t = i / steps;
284
- const frame = {};
285
- nodes.forEach(node => {
286
- if (node in to) {
287
- frame[node] = from[node].map((val, d) => val + t * (to[node][d] - val));
288
- }
289
- });
290
- frames.push(frame);
291
- }
292
- return frames;
293
- }
294
- /**
295
- * Internal Helper Functions
296
- * =========================
297
- */
298
- function groupByDegree(nodes, edges, numGroups) {
299
- // Calculate degrees
300
- const degrees = new Map();
301
- nodes.forEach(node => degrees.set(node, 0));
302
- edges.forEach(([u, v]) => {
303
- degrees.set(u, degrees.get(u) + 1);
304
- degrees.set(v, degrees.get(v) + 1);
305
- });
306
- // Sort by degree
307
- const sorted = nodes.slice().sort((a, b) => degrees.get(b) - degrees.get(a));
308
- // Distribute into groups
309
- const groups = new Array(numGroups).fill(null).map(() => []);
310
- const nodesPerGroup = Math.ceil(nodes.length / numGroups);
311
- sorted.forEach((node, i) => {
312
- const groupIdx = Math.floor(i / nodesPerGroup);
313
- if (groupIdx < numGroups) {
314
- groups[groupIdx].push(node);
315
- }
316
- });
317
- return groups.filter(g => g.length > 0);
318
- }
319
- function groupByDistance(nodes, edges, root) {
320
- if (nodes.length === 0)
321
- return [];
322
- const start = root || nodes[0];
323
- const adj = new Map();
324
- nodes.forEach(node => adj.set(node, []));
325
- edges.forEach(([u, v]) => {
326
- adj.get(u).push(v);
327
- adj.get(v).push(u);
328
- });
329
- // BFS layers
330
- const visited = new Set();
331
- const layers = [];
332
- let currentLayer = [start];
333
- visited.add(start);
334
- while (currentLayer.length > 0) {
335
- layers.push(currentLayer.slice());
336
- const nextLayer = [];
337
- currentLayer.forEach(node => {
338
- adj.get(node).forEach(neighbor => {
339
- if (!visited.has(neighbor)) {
340
- visited.add(neighbor);
341
- nextLayer.push(neighbor);
342
- }
343
- });
344
- });
345
- currentLayer = nextLayer;
346
- }
347
- // Add disconnected nodes
348
- const unvisited = nodes.filter(n => !visited.has(n));
349
- if (unvisited.length > 0) {
350
- layers.push(unvisited);
351
- }
352
- return layers;
353
- }
354
- function groupByKCore(nodes, edges) {
355
- // Build adjacency
356
- const adj = new Map();
357
- const degree = new Map();
358
- nodes.forEach(node => {
359
- adj.set(node, new Set());
360
- degree.set(node, 0);
361
- });
362
- edges.forEach(([u, v]) => {
363
- adj.get(u).add(v);
364
- adj.get(v).add(u);
365
- degree.set(u, degree.get(u) + 1);
366
- degree.set(v, degree.get(v) + 1);
367
- });
368
- // k-core decomposition
369
- const coreNumber = new Map();
370
- const remaining = new Set(nodes);
371
- while (remaining.size > 0) {
372
- // Find minimum degree
373
- let minDegree = Infinity;
374
- remaining.forEach(node => {
375
- minDegree = Math.min(minDegree, degree.get(node));
376
- });
377
- // Remove nodes with degree <= minDegree
378
- const toRemove = [];
379
- remaining.forEach(node => {
380
- if (degree.get(node) <= minDegree) {
381
- toRemove.push(node);
382
- }
383
- });
384
- while (toRemove.length > 0) {
385
- const node = toRemove.shift();
386
- coreNumber.set(node, minDegree);
387
- remaining.delete(node);
388
- adj.get(node).forEach(neighbor => {
389
- if (remaining.has(neighbor)) {
390
- degree.set(neighbor, degree.get(neighbor) - 1);
391
- if (degree.get(neighbor) <= minDegree && !toRemove.includes(neighbor)) {
392
- toRemove.push(neighbor);
393
- }
394
- }
395
- });
396
- }
397
- }
398
- // Group by core number
399
- const groups = new Map();
400
- nodes.forEach(node => {
401
- const core = coreNumber.get(node);
402
- if (!groups.has(core))
403
- groups.set(core, []);
404
- groups.get(core).push(node);
405
- });
406
- // Return sorted by core number (highest first)
407
- return Array.from(groups.entries())
408
- .sort((a, b) => b[0] - a[0])
409
- .map(([, nodeList]) => nodeList);
410
- }
411
- function groupByCommunity(nodes, edges, targetGroups) {
412
- // Fast community detection using label propagation
413
- const communities = new Map();
414
- const adj = new Map();
415
-
416
- // Initialize each node in its own community
417
- nodes.forEach((node, i) => {
418
- communities.set(node, i);
419
- adj.set(node, new Set());
420
- });
421
-
422
- // Build adjacency
423
- edges.forEach(([u, v]) => {
424
- adj.get(u).add(v);
425
- adj.get(v).add(u);
426
- });
427
-
428
- // Label propagation algorithm
429
- const maxIterations = 10; // Prevent infinite loops
430
- for (let iter = 0; iter < maxIterations; iter++) {
431
- let changed = false;
432
-
433
- // Process nodes in random order
434
- const shuffledNodes = nodes.slice().sort(() => Math.random() - 0.5);
435
-
436
- for (const node of shuffledNodes) {
437
- const neighbors = adj.get(node);
438
- if (neighbors.size === 0) continue;
439
-
440
- // Count community frequencies among neighbors
441
- const commCounts = new Map();
442
- neighbors.forEach(neighbor => {
443
- const comm = communities.get(neighbor);
444
- commCounts.set(comm, (commCounts.get(comm) || 0) + 1);
445
- });
446
-
447
- // Find most frequent community
448
- let maxCount = 0;
449
- let bestComm = communities.get(node);
450
- commCounts.forEach((count, comm) => {
451
- if (count > maxCount || (count === maxCount && comm < bestComm)) {
452
- maxCount = count;
453
- bestComm = comm;
454
- }
455
- });
456
-
457
- if (bestComm !== communities.get(node)) {
458
- communities.set(node, bestComm);
459
- changed = true;
460
- }
461
- }
462
-
463
- if (!changed) break;
464
- }
465
-
466
- // Consolidate communities
467
- const groups = new Map();
468
- communities.forEach((comm, node) => {
469
- if (!groups.has(comm)) groups.set(comm, []);
470
- groups.get(comm).push(node);
471
- });
472
-
473
- let result = Array.from(groups.values());
474
-
475
- // If we have too many groups, merge the smallest ones
476
- while (result.length > targetGroups && result.length > 1) {
477
- // Sort by size
478
- result.sort((a, b) => a.length - b.length);
479
- // Merge two smallest groups
480
- const merged = result[0].concat(result[1]);
481
- result = [merged, ...result.slice(2)];
482
- }
483
-
484
- return result;
485
- }
486
- //# sourceMappingURL=layout-helpers.js.map