@graphty/layout 1.2.2 → 1.2.3

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.
package/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
  [![npm version](https://badge.fury.io/js/%40graphty%2Flayout.svg)](https://badge.fury.io/js/%40graphty%2Flayout)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
+ **[View Interactive Examples →](https://graphty-org.github.io/layout/examples/index.html)**
9
+
8
10
  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.
9
11
 
10
12
  ## Features
@@ -28,6 +30,7 @@ The library offers various graph layout algorithms, including:
28
30
  Additionally, the library includes:
29
31
 
30
32
  **Graph Generators** for creating common graph types:
33
+
31
34
  - **Complete Graph** - All nodes connected to each other
32
35
  - **Cycle Graph** - Nodes connected in a circular path
33
36
  - **Star Graph** - Central hub connected to all other nodes
@@ -38,6 +41,7 @@ Additionally, the library includes:
38
41
  - **Scale-Free Graph** - Barabási–Albert preferential attachment model
39
42
 
40
43
  **Layout Helpers** for intelligent graph analysis and layout optimization:
44
+
41
45
  - **groupNodes** - Universal node grouping by degree, distance, k-core, or community
42
46
  - **detectBipartite** - Automatic bipartite graph detection
43
47
  - **findBestRoot** - Optimal root selection for tree layouts
@@ -67,7 +71,7 @@ import {
67
71
  forceatlas2Layout,
68
72
  arfLayout,
69
73
  rescaleLayout,
70
-
74
+
71
75
  // Graph generators
72
76
  completeGraph,
73
77
  cycleGraph,
@@ -77,7 +81,7 @@ import {
77
81
  randomGraph,
78
82
  bipartiteGraph,
79
83
  scaleFreeGraph,
80
-
84
+
81
85
  // Layout helpers
82
86
  groupNodes,
83
87
  detectBipartite,
@@ -93,21 +97,21 @@ import {
93
97
 
94
98
  ```typescript
95
99
  // Generate a graph
96
- const graph = scaleFreeGraph(30, 2, 42);
100
+ const graph = scaleFreeGraph(30, 2, 42)
97
101
 
98
102
  // Auto-configure and layout
99
- const config = autoConfigureForce(graph);
100
- const positions = springLayout(graph, config.k, null, null, config.iterations);
103
+ const config = autoConfigureForce(graph)
104
+ const positions = springLayout(graph, config.k, null, null, config.iterations)
101
105
 
102
106
  // Or use specialized layouts
103
- const bipartite = detectBipartite(graph);
107
+ const bipartite = detectBipartite(graph)
104
108
  if (bipartite) {
105
- const positions = bipartiteLayout(graph, bipartite.setA);
109
+ const positions = bipartiteLayout(graph, bipartite.setA)
106
110
  }
107
111
 
108
112
  // Or use shell layout with automatic grouping
109
- const shells = groupNodes(graph, 'degree', 3);
110
- const positions = shellLayout(graph, shells);
113
+ const shells = groupNodes(graph, 'degree', 3)
114
+ const positions = shellLayout(graph, shells)
111
115
  ```
112
116
 
113
117
  ## Graph Structure
@@ -115,15 +119,22 @@ const positions = shellLayout(graph, shells);
115
119
  The module accepts graphs in two formats:
116
120
 
117
121
  ### 1. Graph Object with methods (preferred)
122
+
118
123
  ```typescript
119
124
  const graph = {
120
125
  nodes: () => [0, 1, 2, 3],
121
- edges: () => [[0, 1], [1, 2], [2, 3], [3, 0]],
122
- getEdgeData?: (source, target, attr) => number // optional for edge weights
126
+ edges: () => [
127
+ [0, 1],
128
+ [1, 2],
129
+ [2, 3],
130
+ [3, 0]
131
+ ],
132
+ getEdgeData: (source, target, attr) => number // optional for edge weights
123
133
  }
124
134
  ```
125
135
 
126
136
  ### 2. Simple array of nodes
137
+
127
138
  ```typescript
128
139
  const nodes = [0, 1, 2, 3]
129
140
  ```
@@ -133,35 +144,45 @@ const nodes = [0, 1, 2, 3]
133
144
  The library includes utilities to generate common graph types for testing and demonstration:
134
145
 
135
146
  ### Complete Graph
147
+
136
148
  Creates a complete graph with all possible edges between nodes.
149
+
137
150
  ```typescript
138
151
  const graph = completeGraph(5)
139
152
  // Creates a graph with 5 nodes (0-4) and 10 edges (all pairs connected)
140
153
  ```
141
154
 
142
155
  ### Cycle Graph
156
+
143
157
  Creates a cycle graph where nodes form a closed loop.
158
+
144
159
  ```typescript
145
160
  const graph = cycleGraph(6)
146
161
  // Creates a graph with 6 nodes (0-5) connected in a cycle: 0-1-2-3-4-5-0
147
162
  ```
148
163
 
149
164
  ### Star Graph
165
+
150
166
  Creates a star graph with one central hub connected to all other nodes.
167
+
151
168
  ```typescript
152
169
  const graph = starGraph(7)
153
170
  // Creates a graph with 7 nodes where node 0 is connected to all others (1-6)
154
171
  ```
155
172
 
156
173
  ### Wheel Graph
174
+
157
175
  Creates a wheel graph - a hub connected to all nodes of a rim cycle.
176
+
158
177
  ```typescript
159
178
  const graph = wheelGraph(6)
160
179
  // Creates a graph with 6 nodes: hub (0) connected to rim cycle (1-2-3-4-5-1)
161
180
  ```
162
181
 
163
182
  ### Grid Graph
183
+
164
184
  Creates a 2D grid graph with specified rows and columns.
185
+
165
186
  ```typescript
166
187
  const graph = gridGraph(3, 4)
167
188
  // Creates a 3x4 grid with nodes named "row,col" (e.g., "0,0", "0,1", etc.)
@@ -169,16 +190,20 @@ const graph = gridGraph(3, 4)
169
190
  ```
170
191
 
171
192
  ### Random Graph
193
+
172
194
  Creates a random graph with specified edge probability.
195
+
173
196
  ```typescript
174
197
  const graph = randomGraph(10, 0.3, 42)
175
- // Creates a graph with 10 nodes (0-9)
198
+ // Creates a graph with 10 nodes (0-9)
176
199
  // Each possible edge has 30% chance of existing
177
200
  // Seed 42 ensures reproducible results
178
201
  ```
179
202
 
180
203
  ### Bipartite Graph
204
+
181
205
  Creates a bipartite graph with two sets of nodes.
206
+
182
207
  ```typescript
183
208
  const graph = bipartiteGraph(3, 4, 0.5, 123)
184
209
  // Creates two sets: A0,A1,A2 and B0,B1,B2,B3
@@ -187,7 +212,9 @@ const graph = bipartiteGraph(3, 4, 0.5, 123)
187
212
  ```
188
213
 
189
214
  ### Scale-Free Graph
215
+
190
216
  Creates a scale-free graph using the Barabási-Albert preferential attachment model.
217
+
191
218
  ```typescript
192
219
  const graph = scaleFreeGraph(20, 2, 456)
193
220
  // Creates a graph with 20 nodes
@@ -201,29 +228,35 @@ All generated graphs work seamlessly with the layout algorithms:
201
228
 
202
229
  ```typescript
203
230
  // Generate a complete graph and apply circular layout
204
- const graph = completeGraph(8);
205
- const positions = circularLayout(graph);
231
+ const graph = completeGraph(8)
232
+ const positions = circularLayout(graph)
206
233
 
207
234
  // 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);
235
+ const grid = gridGraph(5, 5)
236
+ const config = autoConfigureForce(grid)
237
+ const gridPositions = springLayout(
238
+ grid,
239
+ config.k,
240
+ null,
241
+ null,
242
+ config.iterations
243
+ )
211
244
 
212
245
  // Generate a scale-free network with optimized ForceAtlas2
213
- const network = scaleFreeGraph(50, 3, 42);
214
- const networkConfig = autoConfigureForce(network);
246
+ const network = scaleFreeGraph(50, 3, 42)
247
+ const networkConfig = autoConfigureForce(network)
215
248
  const networkPositions = forceatlas2Layout(
216
- network,
217
- null,
249
+ network,
250
+ null,
218
251
  networkConfig.iterations,
219
252
  1.0,
220
253
  networkConfig.scalingRatio,
221
254
  networkConfig.gravity
222
- );
255
+ )
223
256
 
224
257
  // Use bipartite graph with automatic detection
225
- const bipartite = bipartiteGraph(5, 7, 0.4, 123);
226
- const bipartitePositions = bipartiteLayout(bipartite, bipartite.setA);
258
+ const bipartite = bipartiteGraph(5, 7, 0.4, 123)
259
+ const bipartitePositions = bipartiteLayout(bipartite, bipartite.setA)
227
260
  ```
228
261
 
229
262
  ## Layout Helpers
@@ -236,19 +269,19 @@ Groups nodes for shell, multipartite, or custom layouts based on various metrics
236
269
 
237
270
  ```typescript
238
271
  // Group by degree (connectivity) - great for shell layouts
239
- const shells = groupNodes(graph, 'degree', 3);
240
- const positions = shellLayout(graph, shells);
272
+ const shells = groupNodes(graph, 'degree', 3)
273
+ const positions = shellLayout(graph, shells)
241
274
 
242
275
  // Group by distance from root - perfect for hierarchical layouts
243
- const layers = groupNodes(graph, 'bfs', 0, { root: 'A' });
244
- const positions = multipartiteLayout(graph, layers);
276
+ const layers = groupNodes(graph, 'bfs', 0, { root: 'A' })
277
+ const positions = multipartiteLayout(graph, layers)
245
278
 
246
279
  // Group by k-core (dense subgraphs) - ideal for social networks
247
- const cores = groupNodes(graph, 'k-core');
248
- const positions = shellLayout(graph, cores);
280
+ const cores = groupNodes(graph, 'k-core')
281
+ const positions = shellLayout(graph, cores)
249
282
 
250
283
  // Group by community detection - useful for modular networks
251
- const communities = groupNodes(graph, 'community', 5);
284
+ const communities = groupNodes(graph, 'community', 5)
252
285
  ```
253
286
 
254
287
  ### `detectBipartite()` - Automatic Bipartite Detection
@@ -256,13 +289,13 @@ const communities = groupNodes(graph, 'community', 5);
256
289
  Automatically detects if a graph is bipartite and finds the two sets:
257
290
 
258
291
  ```typescript
259
- const result = detectBipartite(graph);
292
+ const result = detectBipartite(graph)
260
293
  if (result) {
261
294
  // Graph is bipartite! Use specialized layout
262
- const positions = bipartiteLayout(graph, result.setA);
295
+ const positions = bipartiteLayout(graph, result.setA)
263
296
  } else {
264
297
  // Not bipartite, use general layout
265
- const positions = springLayout(graph);
298
+ const positions = springLayout(graph)
266
299
  }
267
300
  ```
268
301
 
@@ -271,8 +304,8 @@ if (result) {
271
304
  Finds the best starting node for tree-like layouts (BFS, hierarchical):
272
305
 
273
306
  ```typescript
274
- const root = findBestRoot(graph);
275
- const positions = bfsLayout(graph, root);
307
+ const root = findBestRoot(graph)
308
+ const positions = bfsLayout(graph, root)
276
309
  ```
277
310
 
278
311
  ### `autoConfigureForce()` - Smart Force Layout Configuration
@@ -280,14 +313,20 @@ const positions = bfsLayout(graph, root);
280
313
  Automatically configures parameters based on graph properties:
281
314
 
282
315
  ```typescript
283
- const config = autoConfigureForce(graph);
316
+ const config = autoConfigureForce(graph)
284
317
 
285
318
  // Use with Fruchterman-Reingold
286
- const positions = springLayout(graph, config.k, null, null, config.iterations);
319
+ const positions = springLayout(graph, config.k, null, null, config.iterations)
287
320
 
288
321
  // Use with ForceAtlas2
289
- const positions = forceatlas2Layout(graph, null, config.iterations, 1.0,
290
- config.scalingRatio, config.gravity);
322
+ const positions = forceatlas2Layout(
323
+ graph,
324
+ null,
325
+ config.iterations,
326
+ 1.0,
327
+ config.scalingRatio,
328
+ config.gravity
329
+ )
291
330
  ```
292
331
 
293
332
  ### `layoutQuality()` - Layout Quality Metrics
@@ -295,15 +334,15 @@ const positions = forceatlas2Layout(graph, null, config.iterations, 1.0,
295
334
  Measure and compare layout quality:
296
335
 
297
336
  ```typescript
298
- const circular = circularLayout(graph);
299
- const spring = springLayout(graph);
337
+ const circular = circularLayout(graph)
338
+ const spring = springLayout(graph)
300
339
 
301
- const metricsC = layoutQuality(graph, circular);
302
- const metricsS = layoutQuality(graph, spring);
340
+ const metricsC = layoutQuality(graph, circular)
341
+ const metricsS = layoutQuality(graph, spring)
303
342
 
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);
343
+ console.log('Circular layout - avg edge length:', metricsC.avgEdgeLength)
344
+ console.log('Spring layout - avg edge length:', metricsS.avgEdgeLength)
345
+ console.log('Spring layout - min node distance:', metricsS.minNodeDistance)
307
346
  ```
308
347
 
309
348
  ### `combineLayouts()` - Blend Multiple Layouts
@@ -311,11 +350,11 @@ console.log('Spring layout - min node distance:', metricsS.minNodeDistance);
311
350
  Create hybrid layouts by combining different algorithms:
312
351
 
313
352
  ```typescript
314
- const circular = circularLayout(graph);
315
- const spring = springLayout(graph);
353
+ const circular = circularLayout(graph)
354
+ const spring = springLayout(graph)
316
355
 
317
356
  // 30% circular structure, 70% force-directed
318
- const hybrid = combineLayouts([circular, spring], [0.3, 0.7]);
357
+ const hybrid = combineLayouts([circular, spring], [0.3, 0.7])
319
358
  ```
320
359
 
321
360
  ### `interpolateLayouts()` - Smooth Layout Transitions
@@ -323,110 +362,116 @@ const hybrid = combineLayouts([circular, spring], [0.3, 0.7]);
323
362
  Create animation frames between different layouts:
324
363
 
325
364
  ```typescript
326
- const startLayout = circularLayout(graph);
327
- const endLayout = springLayout(graph);
365
+ const startLayout = circularLayout(graph)
366
+ const endLayout = springLayout(graph)
328
367
 
329
368
  // Generate 30 frames for smooth animation
330
- const frames = interpolateLayouts(startLayout, endLayout, 30);
369
+ const frames = interpolateLayouts(startLayout, endLayout, 30)
331
370
  // Use frames[0] through frames[30] for animation
332
371
  ```
333
372
 
334
373
  ### Helper Usage Patterns
335
374
 
336
375
  #### Smart Shell Layout
376
+
337
377
  ```typescript
338
378
  // 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));
379
+ const n = graph.nodes().length
380
+ const m = graph.edges().length
381
+ const density = (2 * m) / (n * (n - 1))
342
382
 
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);
383
+ const method = density < 0.1 ? 'bfs' : density > 0.5 ? 'k-core' : 'degree'
384
+ const shells = groupNodes(graph, method)
385
+ const positions = shellLayout(graph, shells)
346
386
  ```
347
387
 
348
388
  #### Adaptive Layout Selection
389
+
349
390
  ```typescript
350
391
  // Choose layout based on graph properties
351
- let positions;
392
+ let positions
352
393
 
353
394
  if (detectBipartite(graph)) {
354
- const { setA } = detectBipartite(graph);
355
- positions = bipartiteLayout(graph, setA);
395
+ const { setA } = detectBipartite(graph)
396
+ positions = bipartiteLayout(graph, setA)
356
397
  } else if (graph.nodes().length > 100) {
357
398
  // Large graph - use fast layout
358
- positions = circularLayout(graph);
399
+ positions = circularLayout(graph)
359
400
  } else {
360
401
  // Default to auto-configured force layout
361
- const config = autoConfigureForce(graph);
362
- positions = springLayout(graph, config.k, null, null, config.iterations);
402
+ const config = autoConfigureForce(graph)
403
+ positions = springLayout(graph, config.k, null, null, config.iterations)
363
404
  }
364
405
  ```
365
406
 
366
407
  #### Progressive Layout Refinement
408
+
367
409
  ```typescript
368
410
  // 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);
411
+ const initial = circularLayout(graph)
412
+ const refined = springLayout(graph, null, initial, null, 50)
413
+ const final = kamadaKawaiLayout(graph, null, refined)
372
414
  ```
373
415
 
374
416
  ### Layout Helper Quick Reference
375
417
 
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 |
418
+ | Helper Function | Purpose | Best Use Case |
419
+ | -------------------------------- | ------------------------------- | ------------------------------------- |
420
+ | `groupNodes(graph, 'degree')` | Group by connectivity | Shell layouts for scale-free networks |
421
+ | `groupNodes(graph, 'bfs')` | Group by distance from root | Hierarchical/tree layouts |
422
+ | `groupNodes(graph, 'k-core')` | Group by subgraph density | Social network analysis |
423
+ | `groupNodes(graph, 'community')` | Group by detected communities | Modular network visualization |
424
+ | `detectBipartite(graph)` | Check if graph is bipartite | Matching problems, assignments |
425
+ | `findBestRoot(graph)` | Find optimal tree root | BFS layout, hierarchical layout |
426
+ | `autoConfigureForce(graph)` | Auto-configure force parameters | Any force-directed layout |
427
+ | `layoutQuality(graph, pos)` | Measure layout quality | Comparing different layouts |
428
+ | `combineLayouts([...], [...])` | Blend multiple layouts | Custom hybrid visualizations |
429
+ | `interpolateLayouts(from, to)` | Create animation frames | Interactive transitions |
388
430
 
389
431
  ## Usage Examples
390
432
 
391
433
  ### Circular Layout
434
+
392
435
  ```typescript
393
436
  // Use our graph generator instead of manual construction
394
- const graph = cycleGraph(8);
437
+ const graph = cycleGraph(8)
395
438
 
396
- const positions = circularLayout(graph);
439
+ const positions = circularLayout(graph)
397
440
  // Nodes arranged in a perfect circle
398
441
  ```
399
442
 
400
443
  ### Spring Layout (Fruchterman-Reingold)
444
+
401
445
  ```typescript
402
446
  // Generate a grid and apply force-directed layout with auto-configured parameters
403
- const graph = gridGraph(5, 5);
404
- const config = autoConfigureForce(graph);
447
+ const graph = gridGraph(5, 5)
448
+ const config = autoConfigureForce(graph)
405
449
 
406
450
  const positions = springLayout(
407
451
  graph,
408
- config.k, // optimal distance
409
- null, // initial positions
410
- null, // fixed nodes
411
- config.iterations // iterations
412
- );
452
+ config.k, // optimal distance
453
+ null, // initial positions
454
+ null, // fixed nodes
455
+ config.iterations // iterations
456
+ )
413
457
 
414
458
  // Or use fruchtermanReingoldLayout (same function)
415
- const positions2 = fruchtermanReingoldLayout(graph, config.k);
459
+ const positions2 = fruchtermanReingoldLayout(graph, config.k)
416
460
  ```
417
461
 
418
462
  ### Bipartite graph layout
463
+
419
464
  ```typescript
420
465
  // Generate a bipartite graph and detect sets automatically
421
- const graph = bipartiteGraph(4, 6, 0.5, 42);
466
+ const graph = bipartiteGraph(4, 6, 0.5, 42)
422
467
 
423
468
  // Option 1: Use the built-in sets
424
- const positions = bipartiteLayout(graph, graph.setA, 'vertical');
469
+ const positions = bipartiteLayout(graph, graph.setA, 'vertical')
425
470
 
426
471
  // Option 2: Auto-detect bipartite structure
427
- const detected = detectBipartite(graph);
472
+ const detected = detectBipartite(graph)
428
473
  if (detected) {
429
- const positions2 = bipartiteLayout(graph, detected.setA, 'horizontal');
474
+ const positions2 = bipartiteLayout(graph, detected.setA, 'horizontal')
430
475
  }
431
476
  ```
432
477
 
@@ -456,6 +501,7 @@ interface Graph {
456
501
  ## Utilities
457
502
 
458
503
  ### Layout Rescaling
504
+
459
505
  ```typescript
460
506
  import { rescaleLayout } from './layout.js'
461
507
 
@@ -466,342 +512,365 @@ const scaledPositions = rescaleLayout(positions, 2.0, [10, 10])
466
512
  ## Available Algorithms
467
513
 
468
514
  ### Force-Directed Layouts
515
+
469
516
  - `springLayout()` / `fruchtermanReingoldLayout()` - Classic force-directed algorithm
470
- - `forceatlas2Layout()` - Advanced algorithm with many configuration options
517
+ - `forceatlas2Layout()` - Advanced algorithm with many configuration options
471
518
  - `arfLayout()` - Attractive and repulsive forces
472
519
  - `kamadaKawaiLayout()` - Based on shortest-path distances
473
520
 
474
521
  ### Geometric Layouts
522
+
475
523
  - `randomLayout()` - Random placement
476
524
  - `circularLayout()` - Circular arrangement
477
525
  - `shellLayout()` - Concentric circles
478
526
  - `spiralLayout()` - Spiral arrangement
479
527
 
480
528
  ### Specialized Layouts
529
+
481
530
  - `spectralLayout()` - Based on eigenvectors of the Laplacian matrix
482
531
  - `bipartiteLayout()` - For bipartite graphs
483
532
  - `multipartiteLayout()` - For multi-level graphs
484
- - `bfsLayout()` - Based on breadth-first search
533
+ - `bfsLayout()` - Based on breadth-first search
485
534
  - `planarLayout()` - For planar graphs without crossings
486
535
 
487
536
  ### Utilities
537
+
488
538
  - `rescaleLayout()` - Rescale and recenter positions
489
539
  - `rescaleLayoutDict()` - Rescale a dictionary of positions
490
540
 
491
541
  ## Detailed Examples for each Algorithm
492
542
 
493
543
  ### Random Layout
544
+
494
545
  ```typescript
495
546
  // Generate any graph and apply random layout
496
- const graph = completeGraph(10);
497
- const positions = randomLayout(graph, [0, 0], 2, 42);
547
+ const graph = completeGraph(10)
548
+ const positions = randomLayout(graph, [0, 0], 2, 42)
498
549
  // Nodes randomly placed in unit square with seed 42
499
550
  ```
500
551
 
501
- ### Circular Layout
552
+ ### Circular Layout
553
+
502
554
  ```typescript
503
555
  // Perfect for cyclic or complete graphs
504
- const graph = cycleGraph(12);
505
- const positions = circularLayout(graph);
556
+ const graph = cycleGraph(12)
557
+ const positions = circularLayout(graph)
506
558
  // 12 nodes evenly spaced on a circle
507
559
  ```
508
560
 
509
561
  ### Shell Layout
562
+
510
563
  ```typescript
511
564
  // Use automatic node grouping for shell layout
512
- const graph = scaleFreeGraph(30, 2, 42);
565
+ const graph = scaleFreeGraph(30, 2, 42)
513
566
 
514
567
  // Group nodes by degree (hubs in center)
515
- const shells = groupNodes(graph, 'degree', 3);
516
- const positions = shellLayout(graph, shells);
568
+ const shells = groupNodes(graph, 'degree', 3)
569
+ const positions = shellLayout(graph, shells)
517
570
 
518
571
  // Or group by k-core for social networks
519
- const kCoreShells = groupNodes(graph, 'k-core');
520
- const positions2 = shellLayout(graph, kCoreShells);
572
+ const kCoreShells = groupNodes(graph, 'k-core')
573
+ const positions2 = shellLayout(graph, kCoreShells)
521
574
  ```
522
575
 
523
576
  ### Spring Layout (Fruchterman-Reingold)
577
+
524
578
  ```typescript
525
579
  // Auto-configure parameters based on graph size
526
- const graph = randomGraph(20, 0.2, 42);
527
- const config = autoConfigureForce(graph);
580
+ const graph = randomGraph(20, 0.2, 42)
581
+ const config = autoConfigureForce(graph)
528
582
 
529
583
  const positions = springLayout(
530
584
  graph,
531
- config.k, // optimal distance
532
- null, // initial positions
533
- null, // fixed nodes
534
- config.iterations // iterations
535
- );
585
+ config.k, // optimal distance
586
+ null, // initial positions
587
+ null, // fixed nodes
588
+ config.iterations // iterations
589
+ )
536
590
  ```
537
591
 
538
592
  ### Spectral Layout
593
+
539
594
  ```typescript
540
595
  // Great for revealing graph structure
541
- const graph = gridGraph(6, 6);
542
- const positions = spectralLayout(graph);
596
+ const graph = gridGraph(6, 6)
597
+ const positions = spectralLayout(graph)
543
598
  // Grid structure preserved in spectral embedding
544
599
  ```
545
600
 
546
601
  ### Spiral Layout
602
+
547
603
  ```typescript
548
604
  // Perfect for sequential or time-based data
549
- const graph = cycleGraph(50);
605
+ const graph = cycleGraph(50)
550
606
  const positions = spiralLayout(
551
607
  graph,
552
- 1, // scale
553
- [0, 0], // center
554
- 2, // dim
555
- 0.35, // resolution
556
- true // equidistant points
557
- );
608
+ 1, // scale
609
+ [0, 0], // center
610
+ 2, // dim
611
+ 0.35, // resolution
612
+ true // equidistant points
613
+ )
558
614
  ```
559
615
 
560
616
  ### Bipartite Layout
617
+
561
618
  ```typescript
562
619
  // Generate bipartite graph and layout automatically
563
- const graph = bipartiteGraph(5, 7, 0.4, 42);
620
+ const graph = bipartiteGraph(5, 7, 0.4, 42)
564
621
  const positions = bipartiteLayout(
565
622
  graph,
566
- graph.setA, // first group nodes (auto-generated)
567
- 'vertical', // align: 'vertical' or 'horizontal'
568
- 1, // scale
569
- [0, 0], // center
570
- 4/3 // aspectRatio
623
+ graph.setA, // first group nodes (auto-generated)
624
+ 'vertical', // align: 'vertical' or 'horizontal'
625
+ 1, // scale
626
+ [0, 0], // center
627
+ 4 / 3 // aspectRatio
571
628
  )
572
629
  ```
573
630
 
574
631
  ### Multipartite Layout
632
+
575
633
  ```typescript
576
634
  // Use automatic layer detection with groupNodes
577
- const graph = scaleFreeGraph(20, 2, 42);
578
- const layers = groupNodes(graph, 'bfs', 0, { root: findBestRoot(graph) });
635
+ const graph = scaleFreeGraph(20, 2, 42)
636
+ const layers = groupNodes(graph, 'bfs', 0, { root: findBestRoot(graph) })
579
637
 
580
638
  // Convert to multipartite format
581
- const layerMap = {};
639
+ const layerMap = {}
582
640
  layers.forEach((nodes, i) => {
583
- layerMap[i] = nodes;
584
- });
641
+ layerMap[i] = nodes
642
+ })
585
643
 
586
644
  const positions = multipartiteLayout(
587
645
  graph,
588
- layerMap, // subsetKey: layer mapping
589
- 'vertical', // align
590
- 1, // scale
591
- [0, 0] // center
646
+ layerMap, // subsetKey: layer mapping
647
+ 'vertical', // align
648
+ 1, // scale
649
+ [0, 0] // center
592
650
  )
593
651
  ```
594
652
 
595
653
  ### BFS Layout
654
+
596
655
  ```typescript
597
656
  // Use automatic root detection for tree-like graphs
598
- const graph = starGraph(10);
599
- const root = findBestRoot(graph); // Automatically finds node 0 (hub)
657
+ const graph = starGraph(10)
658
+ const root = findBestRoot(graph) // Automatically finds node 0 (hub)
600
659
 
601
660
  const positions = bfsLayout(
602
661
  graph,
603
- root, // start: best root node
604
- 'vertical', // align
605
- 1, // scale
606
- [0, 0] // center
662
+ root, // start: best root node
663
+ 'vertical', // align
664
+ 1, // scale
665
+ [0, 0] // center
607
666
  )
608
667
  ```
609
668
 
610
669
  ### Planar Layout
670
+
611
671
  ```typescript
612
672
  // Create a planar graph (grid is always planar)
613
- const graph = gridGraph(4, 4);
614
- const positions = planarLayout(graph, 1, [0, 0], 2);
673
+ const graph = gridGraph(4, 4)
674
+ const positions = planarLayout(graph, 1, [0, 0], 2)
615
675
  // Note: throws error if graph is not planar
616
676
 
617
677
  // For unknown graphs, check planarity first
618
678
  if (isPlanar(graph)) {
619
- const positions = planarLayout(graph);
679
+ const positions = planarLayout(graph)
620
680
  } else {
621
681
  // Fall back to non-planar layout
622
- const positions = springLayout(graph);
682
+ const positions = springLayout(graph)
623
683
  }
624
684
  ```
625
685
 
626
686
  ### Kamada-Kawai Layout
687
+
627
688
  ```typescript
628
689
  // Great for small to medium graphs
629
- const graph = wheelGraph(8);
690
+ const graph = wheelGraph(8)
630
691
  const positions = kamadaKawaiLayout(
631
692
  graph,
632
- null, // dist: distance matrix (auto)
633
- null, // pos: initial positions (auto)
634
- 'weight', // weight: edge weight attribute
635
- 1, // scale
636
- [0, 0], // center
637
- 2 // dim
693
+ null, // dist: distance matrix (auto)
694
+ null, // pos: initial positions (auto)
695
+ 'weight', // weight: edge weight attribute
696
+ 1, // scale
697
+ [0, 0], // center
698
+ 2 // dim
638
699
  )
639
700
  ```
640
701
 
641
702
  ### ForceAtlas2 Layout
703
+
642
704
  ```typescript
643
705
  // Auto-configure for your graph type
644
- const graph = scaleFreeGraph(50, 3, 42);
645
- const config = autoConfigureForce(graph);
706
+ const graph = scaleFreeGraph(50, 3, 42)
707
+ const config = autoConfigureForce(graph)
646
708
 
647
709
  const positions = forceatlas2Layout(
648
710
  graph,
649
- null, // pos: initial positions
650
- config.iterations, // maxIter: auto-configured
651
- 1.0, // jitterTolerance
711
+ null, // pos: initial positions
712
+ config.iterations, // maxIter: auto-configured
713
+ 1.0, // jitterTolerance
652
714
  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
715
+ config.gravity, // gravity: auto-configured
716
+ false, // distributedAction
717
+ false, // strongGravity
718
+ null, // nodeMass: node masses
719
+ null, // nodeSize: node sizes
720
+ null, // weight: weight attribute
721
+ true, // dissuadeHubs: good for scale-free
722
+ false, // linlog: logarithmic attraction
723
+ 42, // seed
724
+ 2 // dim
663
725
  )
664
726
  ```
665
727
 
666
- ### ARF Layout
728
+ ### ARF Layout
729
+
667
730
  ```typescript
668
731
  // Layout with attractive and repulsive forces
669
- const graph = completeGraph(10);
732
+ const graph = completeGraph(10)
670
733
  const positions = arfLayout(
671
734
  graph,
672
- null, // pos: initial positions
673
- 1, // scaling
674
- 1.1, // a: spring force (must be > 1)
675
- 1000, // maxIter
676
- 42 // seed
735
+ null, // pos: initial positions
736
+ 1, // scaling
737
+ 1.1, // a: spring force (must be > 1)
738
+ 1000, // maxIter
739
+ 42 // seed
677
740
  )
678
741
  ```
679
742
 
680
743
  ## Advanced Examples: Combining Generators and Helpers
681
744
 
682
745
  ### Example 1: Community-Based Visualization
746
+
683
747
  ```typescript
684
748
  // Generate a scale-free network (hubs and communities)
685
- const graph = scaleFreeGraph(100, 3, 42);
749
+ const graph = scaleFreeGraph(100, 3, 42)
686
750
 
687
751
  // Detect communities and use them for shell layout
688
- const communities = groupNodes(graph, 'community', 4);
689
- const positions = shellLayout(graph, communities);
752
+ const communities = groupNodes(graph, 'community', 4)
753
+ const positions = shellLayout(graph, communities)
690
754
 
691
755
  // Or use communities for coloring in your visualization
692
- const communityMap = new Map();
756
+ const communityMap = new Map()
693
757
  communities.forEach((nodes, idx) => {
694
- nodes.forEach(node => communityMap.set(node, idx));
695
- });
758
+ nodes.forEach(node => communityMap.set(node, idx))
759
+ })
696
760
  ```
697
761
 
698
762
  ### Example 2: Adaptive Layout Selection
763
+
699
764
  ```typescript
700
765
  function chooseOptimalLayout(graph) {
701
- const n = graph.nodes().length;
702
- const m = graph.edges().length;
703
- const density = (2 * m) / (n * (n - 1));
704
-
766
+ const n = graph.nodes().length
767
+ const m = graph.edges().length
768
+ const density = (2 * m) / (n * (n - 1))
769
+
705
770
  // Check for special graph types
706
- const bipartite = detectBipartite(graph);
771
+ const bipartite = detectBipartite(graph)
707
772
  if (bipartite) {
708
- return bipartiteLayout(graph, bipartite.setA);
773
+ return bipartiteLayout(graph, bipartite.setA)
709
774
  }
710
-
775
+
711
776
  // Choose based on graph properties
712
777
  if (n > 100) {
713
778
  // Large graph - use fast circular layout
714
- return circularLayout(graph);
779
+ return circularLayout(graph)
715
780
  } else if (density < 0.1) {
716
781
  // Sparse graph - use spring layout
717
- const config = autoConfigureForce(graph);
718
- return springLayout(graph, config.k, null, null, config.iterations);
782
+ const config = autoConfigureForce(graph)
783
+ return springLayout(graph, config.k, null, null, config.iterations)
719
784
  } else {
720
785
  // Dense graph - use spectral or kamada-kawai
721
- return n < 50 ? kamadaKawaiLayout(graph) : spectralLayout(graph);
786
+ return n < 50 ? kamadaKawaiLayout(graph) : spectralLayout(graph)
722
787
  }
723
788
  }
724
789
 
725
790
  // Usage
726
- const graph = randomGraph(30, 0.3, 42);
727
- const positions = chooseOptimalLayout(graph);
791
+ const graph = randomGraph(30, 0.3, 42)
792
+ const positions = chooseOptimalLayout(graph)
728
793
  ```
729
794
 
730
795
  ### Example 3: Animated Layout Transitions
796
+
731
797
  ```typescript
732
798
  // Start with circular layout
733
- const graph = completeGraph(15);
734
- const startLayout = circularLayout(graph);
799
+ const graph = completeGraph(15)
800
+ const startLayout = circularLayout(graph)
735
801
 
736
802
  // Optimize with force-directed
737
- const config = autoConfigureForce(graph);
738
- const endLayout = springLayout(graph, config.k, null, null, config.iterations);
803
+ const config = autoConfigureForce(graph)
804
+ const endLayout = springLayout(graph, config.k, null, null, config.iterations)
739
805
 
740
806
  // Create smooth animation frames
741
- const frames = interpolateLayouts(startLayout, endLayout, 60);
807
+ const frames = interpolateLayouts(startLayout, endLayout, 60)
742
808
 
743
809
  // Use frames[0] through frames[60] for animation
744
810
  function animate(frameIndex) {
745
- const positions = frames[frameIndex];
811
+ const positions = frames[frameIndex]
746
812
  // Update your visualization with these positions
747
813
  }
748
814
  ```
749
815
 
750
816
  ### Example 4: Hierarchical Network Analysis
817
+
751
818
  ```typescript
752
819
  // Generate a preferential attachment network
753
- const graph = scaleFreeGraph(50, 2, 42);
820
+ const graph = scaleFreeGraph(50, 2, 42)
754
821
 
755
822
  // Find natural hierarchy using k-core decomposition
756
- const kCores = groupNodes(graph, 'k-core');
823
+ const kCores = groupNodes(graph, 'k-core')
757
824
 
758
825
  // Layout with most connected nodes in center
759
- const positions = shellLayout(graph, kCores);
826
+ const positions = shellLayout(graph, kCores)
760
827
 
761
828
  // Or create a tree-like view
762
- const root = findBestRoot(graph);
763
- const bfsPositions = bfsLayout(graph, root);
829
+ const root = findBestRoot(graph)
830
+ const bfsPositions = bfsLayout(graph, root)
764
831
  ```
765
832
 
766
833
  ### Example 5: Quality-Driven Layout
834
+
767
835
  ```typescript
768
836
  // Try multiple layouts and pick the best
769
837
  function findBestLayout(graph) {
770
838
  const candidates = [
771
839
  { name: 'circular', positions: circularLayout(graph) },
772
840
  { name: 'spectral', positions: spectralLayout(graph) },
773
- { name: 'spring', positions: springLayout(graph) },
774
- ];
775
-
776
- let best = candidates[0];
777
- let bestScore = Infinity;
778
-
841
+ { name: 'spring', positions: springLayout(graph) }
842
+ ]
843
+
844
+ let best = candidates[0]
845
+ let bestScore = Infinity
846
+
779
847
  candidates.forEach(candidate => {
780
- const quality = layoutQuality(graph, candidate.positions);
781
- const score = quality.edgeLengthStdDev / quality.avgEdgeLength;
782
-
848
+ const quality = layoutQuality(graph, candidate.positions)
849
+ const score = quality.edgeLengthStdDev / quality.avgEdgeLength
850
+
783
851
  if (score < bestScore) {
784
- bestScore = score;
785
- best = candidate;
852
+ bestScore = score
853
+ best = candidate
786
854
  }
787
- });
788
-
789
- console.log(`Best layout: ${best.name} (score: ${bestScore.toFixed(3)})`);
790
- return best.positions;
855
+ })
856
+
857
+ console.log(`Best layout: ${best.name} (score: ${bestScore.toFixed(3)})`)
858
+ return best.positions
791
859
  }
792
860
  ```
793
861
 
794
862
  ### Example 6: Hybrid Layouts
863
+
795
864
  ```typescript
796
865
  // Create a graph with clear structure
797
- const graph = gridGraph(6, 6);
866
+ const graph = gridGraph(6, 6)
798
867
 
799
868
  // Get geometric and force-based layouts
800
- const grid = circularLayout(graph);
801
- const force = springLayout(graph);
869
+ const grid = circularLayout(graph)
870
+ const force = springLayout(graph)
802
871
 
803
872
  // Blend them: 40% geometric structure, 60% force optimization
804
- const hybrid = combineLayouts([grid, force], [0.4, 0.6]);
873
+ const hybrid = combineLayouts([grid, force], [0.4, 0.6])
805
874
 
806
875
  // The result preserves some grid structure while optimizing edge lengths
807
876
  ```
@@ -833,28 +902,32 @@ npm install @graphty/layout
833
902
  If you want to build the TypeScript module from source:
834
903
 
835
904
  1. **Clone the repository:**
905
+
836
906
  ```bash
837
907
  git clone https://github.com/graphty-org/layout.git
838
908
  cd layout
839
909
  ```
840
910
 
841
911
  2. **Install dependencies:**
912
+
842
913
  ```bash
843
914
  npm install
844
915
  ```
845
916
 
846
917
  3. **Compile TypeScript to JavaScript:**
918
+
847
919
  ```bash
848
920
  npm run build
849
921
  ```
850
-
922
+
851
923
  This will compile the `layout.ts` file to JavaScript and generate type declarations in the `dist/` directory.
852
924
 
853
925
  4. **For development with automatic compilation:**
926
+
854
927
  ```bash
855
928
  npm run dev
856
929
  ```
857
-
930
+
858
931
  This will watch for changes and automatically recompile the TypeScript files.
859
932
 
860
933
  ## Development Server
@@ -868,6 +941,7 @@ npm run serve
868
941
  ```
869
942
 
870
943
  This will:
944
+
871
945
  - Start a development server on port 3000
872
946
  - Automatically open your browser to `/examples/`
873
947
  - Provide hot module reloading for development
@@ -877,17 +951,19 @@ This will:
877
951
  You can customize the server configuration using environment variables:
878
952
 
879
953
  1. **Create a `.env` file** (copy from `.env.example`):
954
+
880
955
  ```bash
881
956
  cp .env.example .env
882
957
  ```
883
958
 
884
959
  2. **Configure server options in `.env`:**
960
+
885
961
  ```bash
886
962
  # Server host (defaults to true for network exposure)
887
963
  HOST=localhost # For local-only access
888
964
  HOST=0.0.0.0 # For network access
889
965
  HOST=my.server.com # For custom domain
890
-
966
+
891
967
  # Server port (defaults to 3000)
892
968
  PORT=3000
893
969
  PORT=8080 # Custom port
@@ -907,6 +983,7 @@ npm run examples
907
983
  ```
908
984
 
909
985
  This command:
986
+
910
987
  1. Builds the TypeScript files to JavaScript
911
988
  2. Starts the Vite development server
912
989
 
@@ -925,39 +1002,42 @@ The module includes complete implementations of:
925
1002
  ## Performance Tips
926
1003
 
927
1004
  1. **Large Graphs (>1000 nodes)**:
1005
+
928
1006
  ```typescript
929
1007
  // Use fast layouts first
930
- const initial = circularLayout(graph);
1008
+ const initial = circularLayout(graph)
931
1009
  // Then refine with limited iterations
932
- const refined = springLayout(graph, null, initial, null, 50);
1010
+ const refined = springLayout(graph, null, initial, null, 50)
933
1011
  ```
934
1012
 
935
1013
  2. **Dense Graphs**:
1014
+
936
1015
  ```typescript
937
1016
  // Use spectral layout for dense graphs
938
- const density = (2 * m) / (n * (n - 1));
1017
+ const density = (2 * m) / (n * (n - 1))
939
1018
  if (density > 0.5) {
940
- const positions = spectralLayout(graph);
1019
+ const positions = spectralLayout(graph)
941
1020
  }
942
1021
  ```
943
1022
 
944
1023
  3. **Real-time Updates**:
1024
+
945
1025
  ```typescript
946
1026
  // Pre-calculate layout quality
947
- const quality = layoutQuality(graph, positions);
1027
+ const quality = layoutQuality(graph, positions)
948
1028
  // Use interpolation for smooth updates
949
- const frames = interpolateLayouts(oldPositions, newPositions, 30);
1029
+ const frames = interpolateLayouts(oldPositions, newPositions, 30)
950
1030
  ```
951
1031
 
952
1032
  4. **Memory Optimization**:
953
1033
  ```typescript
954
1034
  // For very large graphs, use generators
955
1035
  function* layoutInChunks(graph, chunkSize = 100) {
956
- const nodes = graph.nodes();
1036
+ const nodes = graph.nodes()
957
1037
  for (let i = 0; i < nodes.length; i += chunkSize) {
958
- const chunk = nodes.slice(i, i + chunkSize);
1038
+ const chunk = nodes.slice(i, i + chunkSize)
959
1039
  // Process chunk...
960
- yield chunk;
1040
+ yield chunk
961
1041
  }
962
1042
  }
963
1043
  ```