@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/CHANGELOG.md +7 -0
- package/README.md +317 -237
- package/build-examples-script.js +26 -0
- package/examples/layout-helpers.js +23 -486
- package/examples/layout.js +2304 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[](https://badge.fury.io/js/%40graphty%2Flayout)
|
|
6
6
|
[](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: () => [
|
|
122
|
-
|
|
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(
|
|
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(
|
|
290
|
-
|
|
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
|
|
377
|
-
|
|
378
|
-
| `groupNodes(graph, 'degree')`
|
|
379
|
-
| `groupNodes(graph, 'bfs')`
|
|
380
|
-
| `groupNodes(graph, 'k-core')`
|
|
381
|
-
| `groupNodes(graph, 'community')` | Group by detected communities
|
|
382
|
-
| `detectBipartite(graph)`
|
|
383
|
-
| `findBestRoot(graph)`
|
|
384
|
-
| `autoConfigureForce(graph)`
|
|
385
|
-
| `layoutQuality(graph, pos)`
|
|
386
|
-
| `combineLayouts([...], [...])`
|
|
387
|
-
| `interpolateLayouts(from, to)`
|
|
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,
|
|
409
|
-
null,
|
|
410
|
-
null,
|
|
411
|
-
config.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,
|
|
532
|
-
null,
|
|
533
|
-
null,
|
|
534
|
-
config.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,
|
|
553
|
-
[0, 0],
|
|
554
|
-
2,
|
|
555
|
-
0.35,
|
|
556
|
-
true
|
|
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,
|
|
567
|
-
'vertical',
|
|
568
|
-
1,
|
|
569
|
-
[0, 0],
|
|
570
|
-
4/3
|
|
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,
|
|
589
|
-
'vertical',
|
|
590
|
-
1,
|
|
591
|
-
[0, 0]
|
|
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)
|
|
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,
|
|
604
|
-
'vertical',
|
|
605
|
-
1,
|
|
606
|
-
[0, 0]
|
|
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,
|
|
633
|
-
null,
|
|
634
|
-
'weight',
|
|
635
|
-
1,
|
|
636
|
-
[0, 0],
|
|
637
|
-
2
|
|
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,
|
|
650
|
-
config.iterations,
|
|
651
|
-
1.0,
|
|
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,
|
|
654
|
-
false,
|
|
655
|
-
false,
|
|
656
|
-
null,
|
|
657
|
-
null,
|
|
658
|
-
null,
|
|
659
|
-
true,
|
|
660
|
-
false,
|
|
661
|
-
42,
|
|
662
|
-
2
|
|
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,
|
|
673
|
-
1,
|
|
674
|
-
1.1,
|
|
675
|
-
1000,
|
|
676
|
-
42
|
|
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
|
```
|