@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.
- package/.github/workflows/ci.yml +105 -0
- package/.releaserc.json +22 -0
- package/CHANGELOG.md +24 -0
- package/CLAUDE.md +104 -0
- package/CONTRIBUTING.md +1 -0
- package/README.md +893 -34
- 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/arf-layout.html +1 -1
- 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/kamada-kawai-layout.html +1 -1
- package/examples/multipartite-layout.html +64 -51
- package/examples/planar-layout.html +1 -1
- package/examples/random-layout.html +1 -1
- package/examples/shell-layout.html +53 -34
- package/examples/spectral-layout.html +1 -1
- package/examples/spiral-layout.html +1 -1
- package/examples/spring-layout.html +12 -2
- package/layout-helpers.ts +559 -0
- package/{layout.js → layout.ts} +1261 -771
- package/package.json +22 -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/tsconfig.json +16 -0
- package/vitest.config.ts +30 -0
- package/.husky/commit-msg +0 -1
- package/.husky/prepare-commit-msg +0 -1
package/README.md
CHANGED
|
@@ -1,59 +1,918 @@
|
|
|
1
|
-
# Layout
|
|
1
|
+
# Layout
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
|
|
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
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
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
|
|
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
|
-
springLayout
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
126
|
+
### 2. Simple array of nodes
|
|
127
|
+
```typescript
|
|
128
|
+
const nodes = [0, 1, 2, 3]
|
|
129
|
+
```
|
|
52
130
|
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
918
|
+
MIT License - see LICENSE file for details.
|