@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.
- package/.github/workflows/ci.yml +89 -14
- package/.releaserc.json +22 -0
- package/CHANGELOG.md +24 -0
- package/CLAUDE.md +104 -0
- package/CONTRIBUTING.md +1 -0
- package/README.md +609 -93
- package/dist/layout-helpers.d.ts +123 -0
- package/dist/layout-helpers.js +457 -0
- package/dist/layout-helpers.js.map +1 -0
- package/dist/layout.d.ts +275 -0
- package/dist/layout.js +2280 -0
- package/dist/layout.js.map +1 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +30 -0
- package/dist/vitest.config.js.map +1 -0
- package/examples/bfs-layout.html +37 -39
- package/examples/bipartite-layout.html +77 -69
- package/examples/circular-layout.html +13 -34
- package/examples/forceatlas2-layout.html +122 -28
- package/examples/multipartite-layout.html +64 -51
- package/examples/shell-layout.html +53 -34
- package/examples/spring-layout.html +11 -1
- package/layout-helpers.ts +559 -0
- package/layout.ts +277 -1
- package/package.json +17 -6
- package/test/arf-layout.test.ts +443 -0
- package/test/bfs-layout.test.ts +427 -0
- package/test/bipartite-layout.test.ts +344 -0
- package/test/circular-layout.test.ts +300 -0
- package/test/forceatlas2-layout.test.ts +405 -0
- package/test/fruchterman-reingold-layout.test.ts +477 -0
- package/test/graph-generators.test.ts +450 -0
- package/test/kamada-kawai-layout.test.ts +351 -0
- package/test/multipartite-layout.test.ts +404 -0
- package/test/planar-layout.test.ts +266 -0
- package/test/random-layout.test.ts +254 -0
- package/test/rescale-layout.test.ts +373 -0
- package/test/shell-layout.test.ts +347 -0
- package/test/spectral-layout.test.ts +378 -0
- package/test/spiral-layout.test.ts +338 -0
- package/test/spring-layout.test.ts +241 -0
- package/vitest.config.ts +30 -0
- package/.releaserc +0 -3
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Layout
|
|
2
2
|
|
|
3
|
+
[](https://github.com/graphty-org/layout/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/graphty-org/layout)
|
|
5
|
+
[](https://badge.fury.io/js/%40graphty%2Flayout)
|
|
6
|
+
[](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
|
-
|
|
69
|
-
|
|
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
|
|
74
|
-
//
|
|
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
|
-
//
|
|
80
|
-
const
|
|
81
|
-
|
|
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
|
-
|
|
93
|
-
const positions = fruchtermanReingoldLayout(
|
|
406
|
+
const positions = springLayout(
|
|
94
407
|
graph,
|
|
95
|
-
|
|
96
|
-
null,
|
|
97
|
-
null,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
const
|
|
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
|
-
//
|
|
180
|
-
const
|
|
181
|
-
|
|
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
|
-
//
|
|
187
|
-
const
|
|
188
|
-
|
|
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
|
-
//
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
//
|
|
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
|
-
//
|
|
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,
|
|
204
|
-
|
|
205
|
-
null,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
//
|
|
218
|
-
const
|
|
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
|
-
//
|
|
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
|
|
230
|
-
|
|
231
|
-
)
|
|
555
|
+
0.35, // resolution
|
|
556
|
+
true // equidistant points
|
|
557
|
+
);
|
|
232
558
|
```
|
|
233
559
|
|
|
234
560
|
### Bipartite Layout
|
|
235
561
|
```typescript
|
|
236
|
-
//
|
|
237
|
-
const
|
|
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
|
-
|
|
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
|
-
//
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
280
|
-
const
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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,
|
|
304
|
-
|
|
305
|
-
1.0,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
false,
|
|
309
|
-
false,
|
|
310
|
-
null,
|
|
311
|
-
null,
|
|
312
|
-
null,
|
|
313
|
-
|
|
314
|
-
false,
|
|
315
|
-
42,
|
|
316
|
-
2
|
|
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).
|