@graphty/layout 1.0.1 → 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 (51) hide show
  1. package/.github/workflows/ci.yml +105 -0
  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 +893 -34
  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/arf-layout.html +1 -1
  17. package/examples/bfs-layout.html +37 -39
  18. package/examples/bipartite-layout.html +77 -69
  19. package/examples/circular-layout.html +13 -34
  20. package/examples/forceatlas2-layout.html +122 -28
  21. package/examples/kamada-kawai-layout.html +1 -1
  22. package/examples/multipartite-layout.html +64 -51
  23. package/examples/planar-layout.html +1 -1
  24. package/examples/random-layout.html +1 -1
  25. package/examples/shell-layout.html +53 -34
  26. package/examples/spectral-layout.html +1 -1
  27. package/examples/spiral-layout.html +1 -1
  28. package/examples/spring-layout.html +12 -2
  29. package/layout-helpers.ts +559 -0
  30. package/{layout.js → layout.ts} +1261 -771
  31. package/package.json +22 -6
  32. package/test/arf-layout.test.ts +443 -0
  33. package/test/bfs-layout.test.ts +427 -0
  34. package/test/bipartite-layout.test.ts +344 -0
  35. package/test/circular-layout.test.ts +300 -0
  36. package/test/forceatlas2-layout.test.ts +405 -0
  37. package/test/fruchterman-reingold-layout.test.ts +477 -0
  38. package/test/graph-generators.test.ts +450 -0
  39. package/test/kamada-kawai-layout.test.ts +351 -0
  40. package/test/multipartite-layout.test.ts +404 -0
  41. package/test/planar-layout.test.ts +266 -0
  42. package/test/random-layout.test.ts +254 -0
  43. package/test/rescale-layout.test.ts +373 -0
  44. package/test/shell-layout.test.ts +347 -0
  45. package/test/spectral-layout.test.ts +378 -0
  46. package/test/spiral-layout.test.ts +338 -0
  47. package/test/spring-layout.test.ts +241 -0
  48. package/tsconfig.json +16 -0
  49. package/vitest.config.ts +30 -0
  50. package/.husky/commit-msg +0 -1
  51. package/.husky/prepare-commit-msg +0 -1
package/README.md CHANGED
@@ -1,59 +1,918 @@
1
- # Layout.js
1
+ # Layout
2
2
 
3
- Layout.js is a JavaScript library for node positioning in graphs. It is a JavaScript port of the [layout algorithms](https://networkx.org/documentation/stable/reference/drawing.html) found in the Python [NetworkX library](https://networkx.org/documentation/stable/).
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
+
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
6
11
 
7
12
  The library offers various graph layout algorithms, including:
8
13
 
9
- - [Random Layout](https://graphty-org.github.io/layout/examples/random-layout.html)
10
- - [Circular Layout](https://graphty-org.github.io/layout/examples/circular-layout.html)
11
- - [Shell Layout](https://graphty-org.github.io/layout/examples/shell-layout.html)
12
- - [Spring Layout (Fruchterman-Reingold)](https://graphty-org.github.io/layout/examples/spring-layout.html)
13
- - [Spectral Layout](https://graphty-org.github.io/layout/examples/spectral-layout.html)
14
- - [Spiral Layout](https://graphty-org.github.io/layout/examples/spiral-layout.html)
15
- - [Bipartite Layout](https://graphty-org.github.io/layout/examples/bipartite-layout.html)
16
- - [Multipartite Layout](https://graphty-org.github.io/layout/examples/multipartite-layout.html)
17
- - [BFS Layout](https://graphty-org.github.io/layout/examples/bfs-html)
18
- - [Planar Layout](https://graphty-org.github.io/layout/examples/planar.html)
19
- - [Kamada-Kawai Layout](https://graphty-org.github.io/layout/examples/kamada-kawai-layout.html)
20
- - [ForceAtlas2 Layout](https://graphty-org.github.io/layout/examples/forceatlas2-layout.html)
21
- - [ARF Layout (Attractive and Repulsive Forces)](https://graphty-org.github.io/layout/examples/arf-layout.html)
14
+ - **Random Layout** - Places nodes randomly in a unit square
15
+ - **Circular Layout** - Places nodes on a circle
16
+ - **Shell Layout** - Places nodes in concentric circles (shells)
17
+ - **Spring Layout (Fruchterman-Reingold)** - Force-directed layout with attractions and repulsions
18
+ - **Spectral Layout** - Uses eigenvectors of the graph's Laplacian matrix
19
+ - **Spiral Layout** - Places nodes along a spiral
20
+ - **Bipartite Layout** - Layout for bipartite graphs in two straight lines
21
+ - **Multipartite Layout** - Layout for multipartite graphs in levels
22
+ - **BFS Layout** - Layout based on breadth-first search algorithm
23
+ - **Planar Layout** - Planar layout without edge crossings
24
+ - **Kamada-Kawai Layout** - Layout based on path-length cost functions
25
+ - **ForceAtlas2 Layout** - Advanced force-directed algorithm
26
+ - **ARF Layout** - Layout with attractive and repulsive forces
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
22
48
 
23
49
  ## How to Use
24
50
 
25
- Import the library into your JavaScript project:
51
+ Import the library in your TypeScript/JavaScript project:
26
52
 
27
- ```javascript
53
+ ```typescript
28
54
  import {
55
+ // Layout algorithms
29
56
  randomLayout,
30
57
  circularLayout,
31
- springLayout
32
- // other layout functions...
58
+ springLayout,
59
+ fruchtermanReingoldLayout,
60
+ spectralLayout,
61
+ spiralLayout,
62
+ bipartiteLayout,
63
+ multipartiteLayout,
64
+ bfsLayout,
65
+ planarLayout,
66
+ kamadaKawaiLayout,
67
+ forceatlas2Layout,
68
+ arfLayout,
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
33
89
  } from './layout.js'
34
90
  ```
35
91
 
36
- Usage example:
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
+
113
+ ## Graph Structure
37
114
 
38
- ```javascript
39
- // Create a graph (data structure with nodes() and edges())
115
+ The module accepts graphs in two formats:
116
+
117
+ ### 1. Graph Object with methods (preferred)
118
+ ```typescript
40
119
  const graph = {
41
120
  nodes: () => [0, 1, 2, 3],
42
- edges: () => [
43
- [0, 1],
44
- [1, 2],
45
- [2, 3],
46
- [3, 0]
47
- ]
121
+ edges: () => [[0, 1], [1, 2], [2, 3], [3, 0]],
122
+ getEdgeData?: (source, target, attr) => number // optional for edge weights
48
123
  }
124
+ ```
49
125
 
50
- // Apply a layout
51
- const positions = circularLayout(graph)
126
+ ### 2. Simple array of nodes
127
+ ```typescript
128
+ const nodes = [0, 1, 2, 3]
129
+ ```
52
130
 
53
- // positions will be an object with x,y coordinates for each node
54
- // { 0: [1, 0], 1: [0, 1], 2: [-1, 0], 3: [0, -1] }
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
55
147
  ```
56
148
 
57
- ## Requirements
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
+
389
+ ## Usage Examples
390
+
391
+ ### Circular Layout
392
+ ```typescript
393
+ // Use our graph generator instead of manual construction
394
+ const graph = cycleGraph(8);
395
+
396
+ const positions = circularLayout(graph);
397
+ // Nodes arranged in a perfect circle
398
+ ```
399
+
400
+ ### Spring Layout (Fruchterman-Reingold)
401
+ ```typescript
402
+ // Generate a grid and apply force-directed layout with auto-configured parameters
403
+ const graph = gridGraph(5, 5);
404
+ const config = autoConfigureForce(graph);
405
+
406
+ const positions = springLayout(
407
+ graph,
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);
416
+ ```
417
+
418
+ ### Bipartite graph layout
419
+ ```typescript
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');
425
+
426
+ // Option 2: Auto-detect bipartite structure
427
+ const detected = detectBipartite(graph);
428
+ if (detected) {
429
+ const positions2 = bipartiteLayout(graph, detected.setA, 'horizontal');
430
+ }
431
+ ```
432
+
433
+ ## Common Parameters
434
+
435
+ Most layout functions share these parameters:
436
+
437
+ - **scale** (number): Scale factor for positions (default: 1)
438
+ - **center** (number[]): Center coordinates around which to center the layout (default: [0, 0])
439
+ - **dim** (number): Layout dimension - 2D or 3D (default: 2)
440
+ - **seed** (number): Seed for random generation (for reproducible layouts)
441
+
442
+ ## TypeScript Types
443
+
444
+ ```typescript
445
+ type Node = string | number
446
+ type Edge = [Node, Node]
447
+ type PositionMap = Record<Node, number[]>
448
+
449
+ interface Graph {
450
+ nodes?: () => Node[]
451
+ edges?: () => Edge[]
452
+ getEdgeData?: (source: Node, target: Node, attr: string) => any
453
+ }
454
+ ```
455
+
456
+ ## Utilities
457
+
458
+ ### Layout Rescaling
459
+ ```typescript
460
+ import { rescaleLayout } from './layout.js'
461
+
462
+ // Rescale existing positions
463
+ const scaledPositions = rescaleLayout(positions, 2.0, [10, 10])
464
+ ```
465
+
466
+ ## Available Algorithms
467
+
468
+ ### Force-Directed Layouts
469
+ - `springLayout()` / `fruchtermanReingoldLayout()` - Classic force-directed algorithm
470
+ - `forceatlas2Layout()` - Advanced algorithm with many configuration options
471
+ - `arfLayout()` - Attractive and repulsive forces
472
+ - `kamadaKawaiLayout()` - Based on shortest-path distances
473
+
474
+ ### Geometric Layouts
475
+ - `randomLayout()` - Random placement
476
+ - `circularLayout()` - Circular arrangement
477
+ - `shellLayout()` - Concentric circles
478
+ - `spiralLayout()` - Spiral arrangement
479
+
480
+ ### Specialized Layouts
481
+ - `spectralLayout()` - Based on eigenvectors of the Laplacian matrix
482
+ - `bipartiteLayout()` - For bipartite graphs
483
+ - `multipartiteLayout()` - For multi-level graphs
484
+ - `bfsLayout()` - Based on breadth-first search
485
+ - `planarLayout()` - For planar graphs without crossings
486
+
487
+ ### Utilities
488
+ - `rescaleLayout()` - Rescale and recenter positions
489
+ - `rescaleLayoutDict()` - Rescale a dictionary of positions
490
+
491
+ ## Detailed Examples for each Algorithm
492
+
493
+ ### Random Layout
494
+ ```typescript
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
499
+ ```
500
+
501
+ ### Circular Layout
502
+ ```typescript
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
507
+ ```
508
+
509
+ ### Shell Layout
510
+ ```typescript
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);
521
+ ```
522
+
523
+ ### Spring Layout (Fruchterman-Reingold)
524
+ ```typescript
525
+ // Auto-configure parameters based on graph size
526
+ const graph = randomGraph(20, 0.2, 42);
527
+ const config = autoConfigureForce(graph);
528
+
529
+ const positions = springLayout(
530
+ graph,
531
+ config.k, // optimal distance
532
+ null, // initial positions
533
+ null, // fixed nodes
534
+ config.iterations // iterations
535
+ );
536
+ ```
537
+
538
+ ### Spectral Layout
539
+ ```typescript
540
+ // Great for revealing graph structure
541
+ const graph = gridGraph(6, 6);
542
+ const positions = spectralLayout(graph);
543
+ // Grid structure preserved in spectral embedding
544
+ ```
545
+
546
+ ### Spiral Layout
547
+ ```typescript
548
+ // Perfect for sequential or time-based data
549
+ const graph = cycleGraph(50);
550
+ const positions = spiralLayout(
551
+ graph,
552
+ 1, // scale
553
+ [0, 0], // center
554
+ 2, // dim
555
+ 0.35, // resolution
556
+ true // equidistant points
557
+ );
558
+ ```
559
+
560
+ ### Bipartite Layout
561
+ ```typescript
562
+ // Generate bipartite graph and layout automatically
563
+ const graph = bipartiteGraph(5, 7, 0.4, 42);
564
+ const positions = bipartiteLayout(
565
+ 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
571
+ )
572
+ ```
573
+
574
+ ### Multipartite Layout
575
+ ```typescript
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
+
586
+ const positions = multipartiteLayout(
587
+ graph,
588
+ layerMap, // subsetKey: layer mapping
589
+ 'vertical', // align
590
+ 1, // scale
591
+ [0, 0] // center
592
+ )
593
+ ```
594
+
595
+ ### BFS Layout
596
+ ```typescript
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
+
601
+ const positions = bfsLayout(
602
+ graph,
603
+ root, // start: best root node
604
+ 'vertical', // align
605
+ 1, // scale
606
+ [0, 0] // center
607
+ )
608
+ ```
609
+
610
+ ### Planar Layout
611
+ ```typescript
612
+ // Create a planar graph (grid is always planar)
613
+ const graph = gridGraph(4, 4);
614
+ const positions = planarLayout(graph, 1, [0, 0], 2);
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
+ }
624
+ ```
625
+
626
+ ### Kamada-Kawai Layout
627
+ ```typescript
628
+ // Great for small to medium graphs
629
+ const graph = wheelGraph(8);
630
+ const positions = kamadaKawaiLayout(
631
+ 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
638
+ )
639
+ ```
640
+
641
+ ### ForceAtlas2 Layout
642
+ ```typescript
643
+ // Auto-configure for your graph type
644
+ const graph = scaleFreeGraph(50, 3, 42);
645
+ const config = autoConfigureForce(graph);
646
+
647
+ const positions = forceatlas2Layout(
648
+ graph,
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
663
+ )
664
+ ```
665
+
666
+ ### ARF Layout
667
+ ```typescript
668
+ // Layout with attractive and repulsive forces
669
+ const graph = completeGraph(10);
670
+ const positions = arfLayout(
671
+ graph,
672
+ null, // pos: initial positions
673
+ 1, // scaling
674
+ 1.1, // a: spring force (must be > 1)
675
+ 1000, // maxIter
676
+ 42 // seed
677
+ )
678
+ ```
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
+
809
+ ## Error Handling
810
+
811
+ ```typescript
812
+ try {
813
+ const positions = planarLayout(nonPlanarGraph)
814
+ } catch (error) {
815
+ console.error('Graph is not planar:', error.message)
816
+ }
817
+
818
+ try {
819
+ const positions = arfLayout(graph, null, 1, 0.5) // a <= 1
820
+ } catch (error) {
821
+ console.error('Invalid parameter a:', error.message)
822
+ }
823
+ ```
824
+
825
+ ## Installation
826
+
827
+ ```bash
828
+ npm install @graphty/layout
829
+ ```
830
+
831
+ ## Building from Source
832
+
833
+ If you want to build the TypeScript module from source:
834
+
835
+ 1. **Clone the repository:**
836
+ ```bash
837
+ git clone https://github.com/graphty-org/layout.git
838
+ cd layout
839
+ ```
840
+
841
+ 2. **Install dependencies:**
842
+ ```bash
843
+ npm install
844
+ ```
845
+
846
+ 3. **Compile TypeScript to JavaScript:**
847
+ ```bash
848
+ npm run build
849
+ ```
850
+
851
+ This will compile the `layout.ts` file to JavaScript and generate type declarations in the `dist/` directory.
852
+
853
+ 4. **For development with automatic compilation:**
854
+ ```bash
855
+ npm run dev
856
+ ```
857
+
858
+ This will watch for changes and automatically recompile the TypeScript files.
859
+
860
+ **Note:** The compiled JavaScript files will be available in the `dist/` directory. You can import from the compiled JavaScript files or directly use the TypeScript source files in a TypeScript project.
861
+
862
+ ## Implementation
863
+
864
+ The module includes complete implementations of:
865
+
866
+ - **Random Number Generator** with seed support for reproducible results
867
+ - **Mathematical utilities** similar to NumPy for multidimensional array operations
868
+ - **Force-directed algorithms** with L-BFGS optimization for Kamada-Kawai
869
+ - **Planarity algorithms** including Left-Right test for planar graphs
870
+ - **Auto-scaling system** to automatically normalize positions
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
+
912
+ ## Contributing
913
+
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).
915
+
916
+ ## License
58
917
 
59
- - Modern browser with ES6 support
918
+ MIT License - see LICENSE file for details.