@graphty/layout 1.1.0 → 1.1.1

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 (43) hide show
  1. package/.github/workflows/ci.yml +89 -14
  2. package/.releaserc.json +22 -0
  3. package/CHANGELOG.md +24 -0
  4. package/CLAUDE.md +104 -0
  5. package/CONTRIBUTING.md +1 -0
  6. package/README.md +609 -93
  7. package/dist/layout-helpers.d.ts +123 -0
  8. package/dist/layout-helpers.js +457 -0
  9. package/dist/layout-helpers.js.map +1 -0
  10. package/dist/layout.d.ts +275 -0
  11. package/dist/layout.js +2280 -0
  12. package/dist/layout.js.map +1 -0
  13. package/dist/vitest.config.d.ts +2 -0
  14. package/dist/vitest.config.js +30 -0
  15. package/dist/vitest.config.js.map +1 -0
  16. package/examples/bfs-layout.html +37 -39
  17. package/examples/bipartite-layout.html +77 -69
  18. package/examples/circular-layout.html +13 -34
  19. package/examples/forceatlas2-layout.html +122 -28
  20. package/examples/multipartite-layout.html +64 -51
  21. package/examples/shell-layout.html +53 -34
  22. package/examples/spring-layout.html +11 -1
  23. package/layout-helpers.ts +559 -0
  24. package/layout.ts +277 -1
  25. package/package.json +17 -6
  26. package/test/arf-layout.test.ts +443 -0
  27. package/test/bfs-layout.test.ts +427 -0
  28. package/test/bipartite-layout.test.ts +344 -0
  29. package/test/circular-layout.test.ts +300 -0
  30. package/test/forceatlas2-layout.test.ts +405 -0
  31. package/test/fruchterman-reingold-layout.test.ts +477 -0
  32. package/test/graph-generators.test.ts +450 -0
  33. package/test/kamada-kawai-layout.test.ts +351 -0
  34. package/test/multipartite-layout.test.ts +404 -0
  35. package/test/planar-layout.test.ts +266 -0
  36. package/test/random-layout.test.ts +254 -0
  37. package/test/rescale-layout.test.ts +373 -0
  38. package/test/shell-layout.test.ts +347 -0
  39. package/test/spectral-layout.test.ts +378 -0
  40. package/test/spiral-layout.test.ts +338 -0
  41. package/test/spring-layout.test.ts +241 -0
  42. package/vitest.config.ts +30 -0
  43. package/.releaserc +0 -3
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Layout
2
2
 
3
+ [![CI](https://github.com/graphty-org/layout/actions/workflows/ci.yml/badge.svg)](https://github.com/graphty-org/layout/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/graphty-org/layout/branch/main/graph/badge.svg)](https://codecov.io/gh/graphty-org/layout)
5
+ [![npm version](https://badge.fury.io/js/%40graphty%2Flayout.svg)](https://badge.fury.io/js/%40graphty%2Flayout)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
3
8
  Layout is a TypeScript library for positioning nodes in graphs. It's a TypeScript port of the [layout algorithms](https://networkx.org/documentation/stable/reference/drawing.html) from the Python [NetworkX](https://networkx.org/documentation/stable/) library.
4
9
 
5
10
  ## Features
@@ -20,12 +25,34 @@ The library offers various graph layout algorithms, including:
20
25
  - **ForceAtlas2 Layout** - Advanced force-directed algorithm
21
26
  - **ARF Layout** - Layout with attractive and repulsive forces
22
27
 
28
+ Additionally, the library includes:
29
+
30
+ **Graph Generators** for creating common graph types:
31
+ - **Complete Graph** - All nodes connected to each other
32
+ - **Cycle Graph** - Nodes connected in a circular path
33
+ - **Star Graph** - Central hub connected to all other nodes
34
+ - **Wheel Graph** - Hub connected to nodes arranged in a rim cycle
35
+ - **Grid Graph** - 2D grid with nodes connected to neighbors
36
+ - **Random Graph** - Erdős–Rényi random graph model
37
+ - **Bipartite Graph** - Graph with two disjoint node sets
38
+ - **Scale-Free Graph** - Barabási–Albert preferential attachment model
39
+
40
+ **Layout Helpers** for intelligent graph analysis and layout optimization:
41
+ - **groupNodes** - Universal node grouping by degree, distance, k-core, or community
42
+ - **detectBipartite** - Automatic bipartite graph detection
43
+ - **findBestRoot** - Optimal root selection for tree layouts
44
+ - **autoConfigureForce** - Smart parameter configuration for force layouts
45
+ - **layoutQuality** - Layout quality measurement
46
+ - **combineLayouts** - Blend multiple layout algorithms
47
+ - **interpolateLayouts** - Smooth animation between layouts
48
+
23
49
  ## How to Use
24
50
 
25
51
  Import the library in your TypeScript/JavaScript project:
26
52
 
27
53
  ```typescript
28
54
  import {
55
+ // Layout algorithms
29
56
  randomLayout,
30
57
  circularLayout,
31
58
  springLayout,
@@ -39,10 +66,50 @@ import {
39
66
  kamadaKawaiLayout,
40
67
  forceatlas2Layout,
41
68
  arfLayout,
42
- rescaleLayout
69
+ rescaleLayout,
70
+
71
+ // Graph generators
72
+ completeGraph,
73
+ cycleGraph,
74
+ starGraph,
75
+ wheelGraph,
76
+ gridGraph,
77
+ randomGraph,
78
+ bipartiteGraph,
79
+ scaleFreeGraph,
80
+
81
+ // Layout helpers
82
+ groupNodes,
83
+ detectBipartite,
84
+ findBestRoot,
85
+ autoConfigureForce,
86
+ layoutQuality,
87
+ combineLayouts,
88
+ interpolateLayouts
43
89
  } from './layout.js'
44
90
  ```
45
91
 
92
+ ## Quick Start
93
+
94
+ ```typescript
95
+ // Generate a graph
96
+ const graph = scaleFreeGraph(30, 2, 42);
97
+
98
+ // Auto-configure and layout
99
+ const config = autoConfigureForce(graph);
100
+ const positions = springLayout(graph, config.k, null, null, config.iterations);
101
+
102
+ // Or use specialized layouts
103
+ const bipartite = detectBipartite(graph);
104
+ if (bipartite) {
105
+ const positions = bipartiteLayout(graph, bipartite.setA);
106
+ }
107
+
108
+ // Or use shell layout with automatic grouping
109
+ const shells = groupNodes(graph, 'degree', 3);
110
+ const positions = shellLayout(graph, shells);
111
+ ```
112
+
46
113
  ## Graph Structure
47
114
 
48
115
  The module accepts graphs in two formats:
@@ -61,57 +128,306 @@ const graph = {
61
128
  const nodes = [0, 1, 2, 3]
62
129
  ```
63
130
 
131
+ ## Graph Generation
132
+
133
+ The library includes utilities to generate common graph types for testing and demonstration:
134
+
135
+ ### Complete Graph
136
+ Creates a complete graph with all possible edges between nodes.
137
+ ```typescript
138
+ const graph = completeGraph(5)
139
+ // Creates a graph with 5 nodes (0-4) and 10 edges (all pairs connected)
140
+ ```
141
+
142
+ ### Cycle Graph
143
+ Creates a cycle graph where nodes form a closed loop.
144
+ ```typescript
145
+ const graph = cycleGraph(6)
146
+ // Creates a graph with 6 nodes (0-5) connected in a cycle: 0-1-2-3-4-5-0
147
+ ```
148
+
149
+ ### Star Graph
150
+ Creates a star graph with one central hub connected to all other nodes.
151
+ ```typescript
152
+ const graph = starGraph(7)
153
+ // Creates a graph with 7 nodes where node 0 is connected to all others (1-6)
154
+ ```
155
+
156
+ ### Wheel Graph
157
+ Creates a wheel graph - a hub connected to all nodes of a rim cycle.
158
+ ```typescript
159
+ const graph = wheelGraph(6)
160
+ // Creates a graph with 6 nodes: hub (0) connected to rim cycle (1-2-3-4-5-1)
161
+ ```
162
+
163
+ ### Grid Graph
164
+ Creates a 2D grid graph with specified rows and columns.
165
+ ```typescript
166
+ const graph = gridGraph(3, 4)
167
+ // Creates a 3x4 grid with nodes named "row,col" (e.g., "0,0", "0,1", etc.)
168
+ // Nodes are connected to their horizontal and vertical neighbors
169
+ ```
170
+
171
+ ### Random Graph
172
+ Creates a random graph with specified edge probability.
173
+ ```typescript
174
+ const graph = randomGraph(10, 0.3, 42)
175
+ // Creates a graph with 10 nodes (0-9)
176
+ // Each possible edge has 30% chance of existing
177
+ // Seed 42 ensures reproducible results
178
+ ```
179
+
180
+ ### Bipartite Graph
181
+ Creates a bipartite graph with two sets of nodes.
182
+ ```typescript
183
+ const graph = bipartiteGraph(3, 4, 0.5, 123)
184
+ // Creates two sets: A0,A1,A2 and B0,B1,B2,B3
185
+ // Each edge between sets has 50% chance of existing
186
+ // Returns graph with additional setA and setB properties
187
+ ```
188
+
189
+ ### Scale-Free Graph
190
+ Creates a scale-free graph using the Barabási-Albert preferential attachment model.
191
+ ```typescript
192
+ const graph = scaleFreeGraph(20, 2, 456)
193
+ // Creates a graph with 20 nodes
194
+ // Each new node connects to 2 existing nodes (preferential attachment)
195
+ // Results in a power-law degree distribution with some high-degree hubs
196
+ ```
197
+
198
+ ### Using Generated Graphs with Layouts
199
+
200
+ All generated graphs work seamlessly with the layout algorithms:
201
+
202
+ ```typescript
203
+ // Generate a complete graph and apply circular layout
204
+ const graph = completeGraph(8);
205
+ const positions = circularLayout(graph);
206
+
207
+ // Generate a grid with auto-configured spring layout
208
+ const grid = gridGraph(5, 5);
209
+ const config = autoConfigureForce(grid);
210
+ const gridPositions = springLayout(grid, config.k, null, null, config.iterations);
211
+
212
+ // Generate a scale-free network with optimized ForceAtlas2
213
+ const network = scaleFreeGraph(50, 3, 42);
214
+ const networkConfig = autoConfigureForce(network);
215
+ const networkPositions = forceatlas2Layout(
216
+ network,
217
+ null,
218
+ networkConfig.iterations,
219
+ 1.0,
220
+ networkConfig.scalingRatio,
221
+ networkConfig.gravity
222
+ );
223
+
224
+ // Use bipartite graph with automatic detection
225
+ const bipartite = bipartiteGraph(5, 7, 0.4, 123);
226
+ const bipartitePositions = bipartiteLayout(bipartite, bipartite.setA);
227
+ ```
228
+
229
+ ## Layout Helpers
230
+
231
+ The library includes helper functions to simplify working with complex layouts:
232
+
233
+ ### `groupNodes()` - Universal Node Grouping
234
+
235
+ Groups nodes for shell, multipartite, or custom layouts based on various metrics:
236
+
237
+ ```typescript
238
+ // Group by degree (connectivity) - great for shell layouts
239
+ const shells = groupNodes(graph, 'degree', 3);
240
+ const positions = shellLayout(graph, shells);
241
+
242
+ // Group by distance from root - perfect for hierarchical layouts
243
+ const layers = groupNodes(graph, 'bfs', 0, { root: 'A' });
244
+ const positions = multipartiteLayout(graph, layers);
245
+
246
+ // Group by k-core (dense subgraphs) - ideal for social networks
247
+ const cores = groupNodes(graph, 'k-core');
248
+ const positions = shellLayout(graph, cores);
249
+
250
+ // Group by community detection - useful for modular networks
251
+ const communities = groupNodes(graph, 'community', 5);
252
+ ```
253
+
254
+ ### `detectBipartite()` - Automatic Bipartite Detection
255
+
256
+ Automatically detects if a graph is bipartite and finds the two sets:
257
+
258
+ ```typescript
259
+ const result = detectBipartite(graph);
260
+ if (result) {
261
+ // Graph is bipartite! Use specialized layout
262
+ const positions = bipartiteLayout(graph, result.setA);
263
+ } else {
264
+ // Not bipartite, use general layout
265
+ const positions = springLayout(graph);
266
+ }
267
+ ```
268
+
269
+ ### `findBestRoot()` - Optimal Root Node Selection
270
+
271
+ Finds the best starting node for tree-like layouts (BFS, hierarchical):
272
+
273
+ ```typescript
274
+ const root = findBestRoot(graph);
275
+ const positions = bfsLayout(graph, root);
276
+ ```
277
+
278
+ ### `autoConfigureForce()` - Smart Force Layout Configuration
279
+
280
+ Automatically configures parameters based on graph properties:
281
+
282
+ ```typescript
283
+ const config = autoConfigureForce(graph);
284
+
285
+ // Use with Fruchterman-Reingold
286
+ const positions = springLayout(graph, config.k, null, null, config.iterations);
287
+
288
+ // Use with ForceAtlas2
289
+ const positions = forceatlas2Layout(graph, null, config.iterations, 1.0,
290
+ config.scalingRatio, config.gravity);
291
+ ```
292
+
293
+ ### `layoutQuality()` - Layout Quality Metrics
294
+
295
+ Measure and compare layout quality:
296
+
297
+ ```typescript
298
+ const circular = circularLayout(graph);
299
+ const spring = springLayout(graph);
300
+
301
+ const metricsC = layoutQuality(graph, circular);
302
+ const metricsS = layoutQuality(graph, spring);
303
+
304
+ console.log('Circular layout - avg edge length:', metricsC.avgEdgeLength);
305
+ console.log('Spring layout - avg edge length:', metricsS.avgEdgeLength);
306
+ console.log('Spring layout - min node distance:', metricsS.minNodeDistance);
307
+ ```
308
+
309
+ ### `combineLayouts()` - Blend Multiple Layouts
310
+
311
+ Create hybrid layouts by combining different algorithms:
312
+
313
+ ```typescript
314
+ const circular = circularLayout(graph);
315
+ const spring = springLayout(graph);
316
+
317
+ // 30% circular structure, 70% force-directed
318
+ const hybrid = combineLayouts([circular, spring], [0.3, 0.7]);
319
+ ```
320
+
321
+ ### `interpolateLayouts()` - Smooth Layout Transitions
322
+
323
+ Create animation frames between different layouts:
324
+
325
+ ```typescript
326
+ const startLayout = circularLayout(graph);
327
+ const endLayout = springLayout(graph);
328
+
329
+ // Generate 30 frames for smooth animation
330
+ const frames = interpolateLayouts(startLayout, endLayout, 30);
331
+ // Use frames[0] through frames[30] for animation
332
+ ```
333
+
334
+ ### Helper Usage Patterns
335
+
336
+ #### Smart Shell Layout
337
+ ```typescript
338
+ // Automatically choose best grouping method based on graph density
339
+ const n = graph.nodes().length;
340
+ const m = graph.edges().length;
341
+ const density = (2 * m) / (n * (n - 1));
342
+
343
+ const method = density < 0.1 ? 'bfs' : density > 0.5 ? 'k-core' : 'degree';
344
+ const shells = groupNodes(graph, method);
345
+ const positions = shellLayout(graph, shells);
346
+ ```
347
+
348
+ #### Adaptive Layout Selection
349
+ ```typescript
350
+ // Choose layout based on graph properties
351
+ let positions;
352
+
353
+ if (detectBipartite(graph)) {
354
+ const { setA } = detectBipartite(graph);
355
+ positions = bipartiteLayout(graph, setA);
356
+ } else if (graph.nodes().length > 100) {
357
+ // Large graph - use fast layout
358
+ positions = circularLayout(graph);
359
+ } else {
360
+ // Default to auto-configured force layout
361
+ const config = autoConfigureForce(graph);
362
+ positions = springLayout(graph, config.k, null, null, config.iterations);
363
+ }
364
+ ```
365
+
366
+ #### Progressive Layout Refinement
367
+ ```typescript
368
+ // Start with fast layout, progressively refine
369
+ const initial = circularLayout(graph);
370
+ const refined = springLayout(graph, null, initial, null, 50);
371
+ const final = kamadaKawaiLayout(graph, null, refined);
372
+ ```
373
+
374
+ ### Layout Helper Quick Reference
375
+
376
+ | Helper Function | Purpose | Best Use Case |
377
+ |----------------|---------|---------------|
378
+ | `groupNodes(graph, 'degree')` | Group by connectivity | Shell layouts for scale-free networks |
379
+ | `groupNodes(graph, 'bfs')` | Group by distance from root | Hierarchical/tree layouts |
380
+ | `groupNodes(graph, 'k-core')` | Group by subgraph density | Social network analysis |
381
+ | `groupNodes(graph, 'community')` | Group by detected communities | Modular network visualization |
382
+ | `detectBipartite(graph)` | Check if graph is bipartite | Matching problems, assignments |
383
+ | `findBestRoot(graph)` | Find optimal tree root | BFS layout, hierarchical layout |
384
+ | `autoConfigureForce(graph)` | Auto-configure force parameters | Any force-directed layout |
385
+ | `layoutQuality(graph, pos)` | Measure layout quality | Comparing different layouts |
386
+ | `combineLayouts([...], [...])` | Blend multiple layouts | Custom hybrid visualizations |
387
+ | `interpolateLayouts(from, to)` | Create animation frames | Interactive transitions |
388
+
64
389
  ## Usage Examples
65
390
 
66
391
  ### Circular Layout
67
392
  ```typescript
68
- const graph = {
69
- nodes: () => ['A', 'B', 'C', 'D'],
70
- edges: () => [['A', 'B'], ['B', 'C'], ['C', 'D'], ['D', 'A']]
71
- }
393
+ // Use our graph generator instead of manual construction
394
+ const graph = cycleGraph(8);
72
395
 
73
- const positions = circularLayout(graph, 1, [0, 0], 2)
74
- // Output: { A: [1, 0], B: [0, 1], C: [-1, 0], D: [0, -1] }
396
+ const positions = circularLayout(graph);
397
+ // Nodes arranged in a perfect circle
75
398
  ```
76
399
 
77
400
  ### Spring Layout (Fruchterman-Reingold)
78
401
  ```typescript
79
- // Force-directed layout with custom parameters
80
- const positions = springLayout(
81
- graph, // graph
82
- null, // k: optimal distance (auto)
83
- null, // pos: initial positions (auto)
84
- ['A'], // fixed: fixed nodes
85
- 100, // iterations: iterations
86
- 1, // scale: scale
87
- [0, 0], // center: center
88
- 2, // dim: dimensions
89
- 42 // seed: random seed
90
- )
402
+ // Generate a grid and apply force-directed layout with auto-configured parameters
403
+ const graph = gridGraph(5, 5);
404
+ const config = autoConfigureForce(graph);
91
405
 
92
- // or (same function)
93
- const positions = fruchtermanReingoldLayout(
406
+ const positions = springLayout(
94
407
  graph,
95
- null, // k: optimal distance
96
- null, // pos: initial positions
97
- null, // fixed: fixed nodes
98
- 50, // iterations
99
- 1, // scale
100
- [0, 0], // center
101
- 2, // dim
102
- 42 // seed
103
- )
408
+ config.k, // optimal distance
409
+ null, // initial positions
410
+ null, // fixed nodes
411
+ config.iterations // iterations
412
+ );
413
+
414
+ // Or use fruchtermanReingoldLayout (same function)
415
+ const positions2 = fruchtermanReingoldLayout(graph, config.k);
104
416
  ```
105
417
 
106
418
  ### Bipartite graph layout
107
419
  ```typescript
108
- const bipartiteGraph = {
109
- nodes: () => ['A1', 'A2', 'B1', 'B2', 'B3'],
110
- edges: () => [['A1', 'B1'], ['A1', 'B2'], ['A2', 'B2'], ['A2', 'B3']]
111
- }
420
+ // Generate a bipartite graph and detect sets automatically
421
+ const graph = bipartiteGraph(4, 6, 0.5, 42);
422
+
423
+ // Option 1: Use the built-in sets
424
+ const positions = bipartiteLayout(graph, graph.setA, 'vertical');
112
425
 
113
- const leftNodes = ['A1', 'A2']
114
- const positions = bipartiteLayout(bipartiteGraph, leftNodes, 'vertical')
426
+ // Option 2: Auto-detect bipartite structure
427
+ const detected = detectBipartite(graph);
428
+ if (detected) {
429
+ const positions2 = bipartiteLayout(graph, detected.setA, 'horizontal');
430
+ }
115
431
  ```
116
432
 
117
433
  ## Common Parameters
@@ -176,68 +492,78 @@ const scaledPositions = rescaleLayout(positions, 2.0, [10, 10])
176
492
 
177
493
  ### Random Layout
178
494
  ```typescript
179
- // Random placement in a unit square [0,1]
180
- const positions = randomLayout(graph, [0, 0], 2, 42)
181
- // center: [0, 0], dim: 2, seed: 42
495
+ // Generate any graph and apply random layout
496
+ const graph = completeGraph(10);
497
+ const positions = randomLayout(graph, [0, 0], 2, 42);
498
+ // Nodes randomly placed in unit square with seed 42
182
499
  ```
183
500
 
184
501
  ### Circular Layout
185
502
  ```typescript
186
- // Nodes arranged on a circle
187
- const positions = circularLayout(graph, 1, [0, 0], 2)
188
- // scale: 1, center: [0, 0], dim: 2
503
+ // Perfect for cyclic or complete graphs
504
+ const graph = cycleGraph(12);
505
+ const positions = circularLayout(graph);
506
+ // 12 nodes evenly spaced on a circle
189
507
  ```
190
508
 
191
509
  ### Shell Layout
192
510
  ```typescript
193
- // Nodes in concentric circles
194
- const shells = [['A'], ['B', 'C'], ['D', 'E', 'F']]
195
- const positions = shellLayout(graph, shells, 1, [0, 0], 2)
196
- // nlist: shells, scale: 1, center: [0, 0], dim: 2
511
+ // Use automatic node grouping for shell layout
512
+ const graph = scaleFreeGraph(30, 2, 42);
513
+
514
+ // Group nodes by degree (hubs in center)
515
+ const shells = groupNodes(graph, 'degree', 3);
516
+ const positions = shellLayout(graph, shells);
517
+
518
+ // Or group by k-core for social networks
519
+ const kCoreShells = groupNodes(graph, 'k-core');
520
+ const positions2 = shellLayout(graph, kCoreShells);
197
521
  ```
198
522
 
199
523
  ### Spring Layout (Fruchterman-Reingold)
200
524
  ```typescript
201
- // Force layout with custom parameters
525
+ // Auto-configure parameters based on graph size
526
+ const graph = randomGraph(20, 0.2, 42);
527
+ const config = autoConfigureForce(graph);
528
+
202
529
  const positions = springLayout(
203
- graph, // graph
204
- null, // k: optimal distance (auto)
205
- null, // pos: initial positions (auto)
206
- ['A'], // fixed: fixed nodes
207
- 100, // iterations: iterations
208
- 1, // scale: scale
209
- [0, 0], // center: center
210
- 2, // dim: dimensions
211
- 42 // seed: random seed
212
- )
530
+ graph,
531
+ config.k, // optimal distance
532
+ null, // initial positions
533
+ null, // fixed nodes
534
+ config.iterations // iterations
535
+ );
213
536
  ```
214
537
 
215
538
  ### Spectral Layout
216
539
  ```typescript
217
- // Layout based on eigenvectors of the Laplacian matrix
218
- const positions = spectralLayout(graph, 1, [0, 0], 2)
540
+ // Great for revealing graph structure
541
+ const graph = gridGraph(6, 6);
542
+ const positions = spectralLayout(graph);
543
+ // Grid structure preserved in spectral embedding
219
544
  ```
220
545
 
221
546
  ### Spiral Layout
222
547
  ```typescript
223
- // Spiral arrangement
548
+ // Perfect for sequential or time-based data
549
+ const graph = cycleGraph(50);
224
550
  const positions = spiralLayout(
225
551
  graph,
226
552
  1, // scale
227
553
  [0, 0], // center
228
554
  2, // dim
229
- 0.35, // resolution: spacing control
230
- false // equidistant: equidistant points
231
- )
555
+ 0.35, // resolution
556
+ true // equidistant points
557
+ );
232
558
  ```
233
559
 
234
560
  ### Bipartite Layout
235
561
  ```typescript
236
- // For bipartite graphs
237
- const leftNodes = ['A1', 'A2']
562
+ // Generate bipartite graph and layout automatically
563
+ const graph = bipartiteGraph(5, 7, 0.4, 42);
238
564
  const positions = bipartiteLayout(
239
565
  graph,
240
- leftNodes, // first group nodes
566
+ graph.setA, // first group nodes (auto-generated)
241
567
  'vertical', // align: 'vertical' or 'horizontal'
242
568
  1, // scale
243
569
  [0, 0], // center
@@ -247,15 +573,19 @@ const positions = bipartiteLayout(
247
573
 
248
574
  ### Multipartite Layout
249
575
  ```typescript
250
- // For multi-level graphs
251
- const layers = {
252
- 0: ['A1', 'A2'],
253
- 1: ['B1', 'B2', 'B3'],
254
- 2: ['C1']
255
- }
576
+ // Use automatic layer detection with groupNodes
577
+ const graph = scaleFreeGraph(20, 2, 42);
578
+ const layers = groupNodes(graph, 'bfs', 0, { root: findBestRoot(graph) });
579
+
580
+ // Convert to multipartite format
581
+ const layerMap = {};
582
+ layers.forEach((nodes, i) => {
583
+ layerMap[i] = nodes;
584
+ });
585
+
256
586
  const positions = multipartiteLayout(
257
587
  graph,
258
- layers, // subsetKey: layer mapping
588
+ layerMap, // subsetKey: layer mapping
259
589
  'vertical', // align
260
590
  1, // scale
261
591
  [0, 0] // center
@@ -264,10 +594,13 @@ const positions = multipartiteLayout(
264
594
 
265
595
  ### BFS Layout
266
596
  ```typescript
267
- // Layout based on breadth-first search
597
+ // Use automatic root detection for tree-like graphs
598
+ const graph = starGraph(10);
599
+ const root = findBestRoot(graph); // Automatically finds node 0 (hub)
600
+
268
601
  const positions = bfsLayout(
269
602
  graph,
270
- 'A', // start: starting node
603
+ root, // start: best root node
271
604
  'vertical', // align
272
605
  1, // scale
273
606
  [0, 0] // center
@@ -276,14 +609,24 @@ const positions = bfsLayout(
276
609
 
277
610
  ### Planar Layout
278
611
  ```typescript
279
- // Planar layout (for planar graphs)
280
- const positions = planarLayout(graph, 1, [0, 0], 2)
612
+ // Create a planar graph (grid is always planar)
613
+ const graph = gridGraph(4, 4);
614
+ const positions = planarLayout(graph, 1, [0, 0], 2);
281
615
  // Note: throws error if graph is not planar
616
+
617
+ // For unknown graphs, check planarity first
618
+ if (isPlanar(graph)) {
619
+ const positions = planarLayout(graph);
620
+ } else {
621
+ // Fall back to non-planar layout
622
+ const positions = springLayout(graph);
623
+ }
282
624
  ```
283
625
 
284
626
  ### Kamada-Kawai Layout
285
627
  ```typescript
286
- // Layout based on shortest path distances
628
+ // Great for small to medium graphs
629
+ const graph = wheelGraph(8);
287
630
  const positions = kamadaKawaiLayout(
288
631
  graph,
289
632
  null, // dist: distance matrix (auto)
@@ -297,29 +640,33 @@ const positions = kamadaKawaiLayout(
297
640
 
298
641
  ### ForceAtlas2 Layout
299
642
  ```typescript
300
- // Advanced force algorithm
643
+ // Auto-configure for your graph type
644
+ const graph = scaleFreeGraph(50, 3, 42);
645
+ const config = autoConfigureForce(graph);
646
+
301
647
  const positions = forceatlas2Layout(
302
648
  graph,
303
- null, // pos: initial positions
304
- 100, // maxIter: maximum iterations
305
- 1.0, // jitterTolerance
306
- 2.0, // scalingRatio
307
- 1.0, // gravity: attraction towards center
308
- false, // distributedAction
309
- false, // strongGravity
310
- null, // nodeMass: node masses
311
- null, // nodeSize: node sizes
312
- null, // weight: weight attribute
313
- false, // dissuadeHubs
314
- false, // linlog: logarithmic attraction
315
- 42, // seed
316
- 2 // dim
649
+ null, // pos: initial positions
650
+ config.iterations, // maxIter: auto-configured
651
+ 1.0, // jitterTolerance
652
+ config.scalingRatio, // scalingRatio: auto-configured
653
+ config.gravity, // gravity: auto-configured
654
+ false, // distributedAction
655
+ false, // strongGravity
656
+ null, // nodeMass: node masses
657
+ null, // nodeSize: node sizes
658
+ null, // weight: weight attribute
659
+ true, // dissuadeHubs: good for scale-free
660
+ false, // linlog: logarithmic attraction
661
+ 42, // seed
662
+ 2 // dim
317
663
  )
318
664
  ```
319
665
 
320
666
  ### ARF Layout
321
667
  ```typescript
322
668
  // Layout with attractive and repulsive forces
669
+ const graph = completeGraph(10);
323
670
  const positions = arfLayout(
324
671
  graph,
325
672
  null, // pos: initial positions
@@ -330,6 +677,135 @@ const positions = arfLayout(
330
677
  )
331
678
  ```
332
679
 
680
+ ## Advanced Examples: Combining Generators and Helpers
681
+
682
+ ### Example 1: Community-Based Visualization
683
+ ```typescript
684
+ // Generate a scale-free network (hubs and communities)
685
+ const graph = scaleFreeGraph(100, 3, 42);
686
+
687
+ // Detect communities and use them for shell layout
688
+ const communities = groupNodes(graph, 'community', 4);
689
+ const positions = shellLayout(graph, communities);
690
+
691
+ // Or use communities for coloring in your visualization
692
+ const communityMap = new Map();
693
+ communities.forEach((nodes, idx) => {
694
+ nodes.forEach(node => communityMap.set(node, idx));
695
+ });
696
+ ```
697
+
698
+ ### Example 2: Adaptive Layout Selection
699
+ ```typescript
700
+ function chooseOptimalLayout(graph) {
701
+ const n = graph.nodes().length;
702
+ const m = graph.edges().length;
703
+ const density = (2 * m) / (n * (n - 1));
704
+
705
+ // Check for special graph types
706
+ const bipartite = detectBipartite(graph);
707
+ if (bipartite) {
708
+ return bipartiteLayout(graph, bipartite.setA);
709
+ }
710
+
711
+ // Choose based on graph properties
712
+ if (n > 100) {
713
+ // Large graph - use fast circular layout
714
+ return circularLayout(graph);
715
+ } else if (density < 0.1) {
716
+ // Sparse graph - use spring layout
717
+ const config = autoConfigureForce(graph);
718
+ return springLayout(graph, config.k, null, null, config.iterations);
719
+ } else {
720
+ // Dense graph - use spectral or kamada-kawai
721
+ return n < 50 ? kamadaKawaiLayout(graph) : spectralLayout(graph);
722
+ }
723
+ }
724
+
725
+ // Usage
726
+ const graph = randomGraph(30, 0.3, 42);
727
+ const positions = chooseOptimalLayout(graph);
728
+ ```
729
+
730
+ ### Example 3: Animated Layout Transitions
731
+ ```typescript
732
+ // Start with circular layout
733
+ const graph = completeGraph(15);
734
+ const startLayout = circularLayout(graph);
735
+
736
+ // Optimize with force-directed
737
+ const config = autoConfigureForce(graph);
738
+ const endLayout = springLayout(graph, config.k, null, null, config.iterations);
739
+
740
+ // Create smooth animation frames
741
+ const frames = interpolateLayouts(startLayout, endLayout, 60);
742
+
743
+ // Use frames[0] through frames[60] for animation
744
+ function animate(frameIndex) {
745
+ const positions = frames[frameIndex];
746
+ // Update your visualization with these positions
747
+ }
748
+ ```
749
+
750
+ ### Example 4: Hierarchical Network Analysis
751
+ ```typescript
752
+ // Generate a preferential attachment network
753
+ const graph = scaleFreeGraph(50, 2, 42);
754
+
755
+ // Find natural hierarchy using k-core decomposition
756
+ const kCores = groupNodes(graph, 'k-core');
757
+
758
+ // Layout with most connected nodes in center
759
+ const positions = shellLayout(graph, kCores);
760
+
761
+ // Or create a tree-like view
762
+ const root = findBestRoot(graph);
763
+ const bfsPositions = bfsLayout(graph, root);
764
+ ```
765
+
766
+ ### Example 5: Quality-Driven Layout
767
+ ```typescript
768
+ // Try multiple layouts and pick the best
769
+ function findBestLayout(graph) {
770
+ const candidates = [
771
+ { name: 'circular', positions: circularLayout(graph) },
772
+ { name: 'spectral', positions: spectralLayout(graph) },
773
+ { name: 'spring', positions: springLayout(graph) },
774
+ ];
775
+
776
+ let best = candidates[0];
777
+ let bestScore = Infinity;
778
+
779
+ candidates.forEach(candidate => {
780
+ const quality = layoutQuality(graph, candidate.positions);
781
+ const score = quality.edgeLengthStdDev / quality.avgEdgeLength;
782
+
783
+ if (score < bestScore) {
784
+ bestScore = score;
785
+ best = candidate;
786
+ }
787
+ });
788
+
789
+ console.log(`Best layout: ${best.name} (score: ${bestScore.toFixed(3)})`);
790
+ return best.positions;
791
+ }
792
+ ```
793
+
794
+ ### Example 6: Hybrid Layouts
795
+ ```typescript
796
+ // Create a graph with clear structure
797
+ const graph = gridGraph(6, 6);
798
+
799
+ // Get geometric and force-based layouts
800
+ const grid = circularLayout(graph);
801
+ const force = springLayout(graph);
802
+
803
+ // Blend them: 40% geometric structure, 60% force optimization
804
+ const hybrid = combineLayouts([grid, force], [0.4, 0.6]);
805
+
806
+ // The result preserves some grid structure while optimizing edge lengths
807
+ ```
808
+
333
809
  ## Error Handling
334
810
 
335
811
  ```typescript
@@ -393,6 +869,46 @@ The module includes complete implementations of:
393
869
  - **Planarity algorithms** including Left-Right test for planar graphs
394
870
  - **Auto-scaling system** to automatically normalize positions
395
871
 
872
+ ## Performance Tips
873
+
874
+ 1. **Large Graphs (>1000 nodes)**:
875
+ ```typescript
876
+ // Use fast layouts first
877
+ const initial = circularLayout(graph);
878
+ // Then refine with limited iterations
879
+ const refined = springLayout(graph, null, initial, null, 50);
880
+ ```
881
+
882
+ 2. **Dense Graphs**:
883
+ ```typescript
884
+ // Use spectral layout for dense graphs
885
+ const density = (2 * m) / (n * (n - 1));
886
+ if (density > 0.5) {
887
+ const positions = spectralLayout(graph);
888
+ }
889
+ ```
890
+
891
+ 3. **Real-time Updates**:
892
+ ```typescript
893
+ // Pre-calculate layout quality
894
+ const quality = layoutQuality(graph, positions);
895
+ // Use interpolation for smooth updates
896
+ const frames = interpolateLayouts(oldPositions, newPositions, 30);
897
+ ```
898
+
899
+ 4. **Memory Optimization**:
900
+ ```typescript
901
+ // For very large graphs, use generators
902
+ function* layoutInChunks(graph, chunkSize = 100) {
903
+ const nodes = graph.nodes();
904
+ for (let i = 0; i < nodes.length; i += chunkSize) {
905
+ const chunk = nodes.slice(i, i + chunkSize);
906
+ // Process chunk...
907
+ yield chunk;
908
+ }
909
+ }
910
+ ```
911
+
396
912
  ## Contributing
397
913
 
398
914
  This project is a TypeScript port of the NetworkX Python library. For contributions and issues, visit the [GitHub repository](https://github.com/graphty-org/layout).