@graphty/layout 1.2.8 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +394 -404
- package/dist/layout.js +881 -821
- package/dist/layout.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +2 -2
- package/dist/src/algorithms/index.js +2 -2
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/optimization/index.d.ts +4 -4
- package/dist/src/algorithms/optimization/index.js +3 -3
- package/dist/src/algorithms/optimization/index.js.map +1 -1
- package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +2 -5
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js +6 -9
- package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -1
- package/dist/src/algorithms/optimization/lbfgs.d.ts +0 -1
- package/dist/src/algorithms/optimization/lbfgs.js +3 -5
- package/dist/src/algorithms/optimization/lbfgs.js.map +1 -1
- package/dist/src/algorithms/optimization/line-search.d.ts +0 -1
- package/dist/src/algorithms/optimization/line-search.js +0 -1
- package/dist/src/algorithms/optimization/line-search.js.map +1 -1
- package/dist/src/algorithms/optimization/types.d.ts +1 -1
- package/dist/src/algorithms/planarity/check.d.ts +3 -3
- package/dist/src/algorithms/planarity/check.js +7 -7
- package/dist/src/algorithms/planarity/check.js.map +1 -1
- package/dist/src/algorithms/planarity/embedding.d.ts +3 -5
- package/dist/src/algorithms/planarity/embedding.js +20 -16
- package/dist/src/algorithms/planarity/embedding.js.map +1 -1
- package/dist/src/algorithms/planarity/index.d.ts +4 -4
- package/dist/src/algorithms/planarity/index.js +4 -4
- package/dist/src/algorithms/planarity/index.js.map +1 -1
- package/dist/src/algorithms/planarity/lr-test.d.ts +3 -3
- package/dist/src/algorithms/planarity/lr-test.js +5 -5
- package/dist/src/algorithms/planarity/lr-test.js.map +1 -1
- package/dist/src/algorithms/planarity/special-graphs.d.ts +1 -4
- package/dist/src/algorithms/planarity/special-graphs.js +18 -15
- package/dist/src/algorithms/planarity/special-graphs.js.map +1 -1
- package/dist/src/generators/basic.d.ts +1 -1
- package/dist/src/generators/basic.js +4 -4
- package/dist/src/generators/basic.js.map +1 -1
- package/dist/src/generators/bipartite.d.ts +1 -1
- package/dist/src/generators/bipartite.js +2 -2
- package/dist/src/generators/bipartite.js.map +1 -1
- package/dist/src/generators/grid.d.ts +1 -1
- package/dist/src/generators/grid.js +1 -1
- package/dist/src/generators/grid.js.map +1 -1
- package/dist/src/generators/index.d.ts +5 -5
- package/dist/src/generators/index.js +5 -5
- package/dist/src/generators/index.js.map +1 -1
- package/dist/src/generators/random.d.ts +1 -1
- package/dist/src/generators/random.js +2 -2
- package/dist/src/generators/random.js.map +1 -1
- package/dist/src/generators/scale-free.d.ts +1 -1
- package/dist/src/generators/scale-free.js +4 -4
- package/dist/src/generators/scale-free.js.map +1 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.js +4 -4
- package/dist/src/layouts/basic/index.d.ts +1 -1
- package/dist/src/layouts/basic/index.js +1 -1
- package/dist/src/layouts/basic/random.d.ts +1 -2
- package/dist/src/layouts/basic/random.js +3 -4
- package/dist/src/layouts/basic/random.js.map +1 -1
- package/dist/src/layouts/force-directed/arf.d.ts +1 -2
- package/dist/src/layouts/force-directed/arf.js +13 -8
- package/dist/src/layouts/force-directed/arf.js.map +1 -1
- package/dist/src/layouts/force-directed/forceatlas2.d.ts +1 -2
- package/dist/src/layouts/force-directed/forceatlas2.js +58 -35
- package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -1
- package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +11 -12
- package/dist/src/layouts/force-directed/fruchterman-reingold.js +17 -17
- package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -1
- package/dist/src/layouts/force-directed/index.d.ts +5 -5
- package/dist/src/layouts/force-directed/index.js +5 -5
- package/dist/src/layouts/force-directed/index.js.map +1 -1
- package/dist/src/layouts/force-directed/kamada-kawai.d.ts +2 -3
- package/dist/src/layouts/force-directed/kamada-kawai.js +12 -10
- package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -1
- package/dist/src/layouts/force-directed/spring.d.ts +11 -12
- package/dist/src/layouts/force-directed/spring.js +11 -12
- package/dist/src/layouts/force-directed/spring.js.map +1 -1
- package/dist/src/layouts/geometric/circular.d.ts +1 -2
- package/dist/src/layouts/geometric/circular.js +10 -9
- package/dist/src/layouts/geometric/circular.js.map +1 -1
- package/dist/src/layouts/geometric/index.d.ts +3 -3
- package/dist/src/layouts/geometric/index.js +3 -3
- package/dist/src/layouts/geometric/shell.d.ts +1 -2
- package/dist/src/layouts/geometric/shell.js +5 -5
- package/dist/src/layouts/geometric/shell.js.map +1 -1
- package/dist/src/layouts/geometric/spiral.d.ts +1 -2
- package/dist/src/layouts/geometric/spiral.js +5 -9
- package/dist/src/layouts/geometric/spiral.js.map +1 -1
- package/dist/src/layouts/hierarchical/bfs.d.ts +2 -3
- package/dist/src/layouts/hierarchical/bfs.js +5 -6
- package/dist/src/layouts/hierarchical/bfs.js.map +1 -1
- package/dist/src/layouts/hierarchical/bipartite.d.ts +2 -3
- package/dist/src/layouts/hierarchical/bipartite.js +8 -9
- package/dist/src/layouts/hierarchical/bipartite.js.map +1 -1
- package/dist/src/layouts/hierarchical/index.d.ts +3 -3
- package/dist/src/layouts/hierarchical/index.js +3 -3
- package/dist/src/layouts/hierarchical/index.js.map +1 -1
- package/dist/src/layouts/hierarchical/multipartite.d.ts +2 -3
- package/dist/src/layouts/hierarchical/multipartite.js +7 -8
- package/dist/src/layouts/hierarchical/multipartite.js.map +1 -1
- package/dist/src/layouts/index.d.ts +5 -5
- package/dist/src/layouts/index.js +5 -5
- package/dist/src/layouts/index.js.map +1 -1
- package/dist/src/layouts/specialized/index.d.ts +2 -2
- package/dist/src/layouts/specialized/index.js +2 -2
- package/dist/src/layouts/specialized/index.js.map +1 -1
- package/dist/src/layouts/specialized/planar.d.ts +3 -3
- package/dist/src/layouts/specialized/planar.js +8 -8
- package/dist/src/layouts/specialized/planar.js.map +1 -1
- package/dist/src/layouts/specialized/spectral.d.ts +1 -2
- package/dist/src/layouts/specialized/spectral.js +30 -20
- package/dist/src/layouts/specialized/spectral.js.map +1 -1
- package/dist/src/types/embedding.d.ts +1 -1
- package/dist/src/types/index.d.ts +3 -3
- package/dist/src/types/index.js +3 -3
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/layout.d.ts +1 -1
- package/dist/src/utils/graph.d.ts +1 -5
- package/dist/src/utils/graph.js +4 -6
- package/dist/src/utils/graph.js.map +1 -1
- package/dist/src/utils/index.d.ts +5 -5
- package/dist/src/utils/index.js +5 -5
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/numpy.js +22 -16
- package/dist/src/utils/numpy.js.map +1 -1
- package/dist/src/utils/params.d.ts +1 -2
- package/dist/src/utils/params.js +0 -1
- package/dist/src/utils/params.js.map +1 -1
- package/dist/src/utils/random.d.ts +17 -0
- package/dist/src/utils/random.js +18 -1
- package/dist/src/utils/random.js.map +1 -1
- package/dist/src/utils/rescale.d.ts +1 -3
- package/dist/src/utils/rescale.js +12 -10
- package/dist/src/utils/rescale.js.map +1 -1
- package/dist/vitest.config.js +14 -14
- package/dist/vitest.config.js.map +1 -1
- package/package.json +29 -24
- package/src/algorithms/index.ts +2 -2
- package/src/algorithms/optimization/index.ts +4 -8
- package/src/algorithms/optimization/kamada-kawai-solver.ts +170 -179
- package/src/algorithms/optimization/lbfgs.ts +41 -48
- package/src/algorithms/optimization/line-search.ts +27 -28
- package/src/algorithms/optimization/types.ts +2 -2
- package/src/algorithms/planarity/check.ts +22 -21
- package/src/algorithms/planarity/embedding.ts +159 -165
- package/src/algorithms/planarity/index.ts +4 -8
- package/src/algorithms/planarity/lr-test.ts +46 -45
- package/src/algorithms/planarity/special-graphs.ts +73 -83
- package/src/generators/basic.ts +57 -57
- package/src/generators/bipartite.ts +37 -31
- package/src/generators/grid.ts +28 -28
- package/src/generators/index.ts +5 -11
- package/src/generators/random.ts +26 -25
- package/src/generators/scale-free.ts +56 -55
- package/src/index.ts +5 -5
- package/src/layouts/basic/index.ts +1 -1
- package/src/layouts/basic/random.ts +20 -16
- package/src/layouts/force-directed/arf.ts +107 -105
- package/src/layouts/force-directed/forceatlas2.ts +365 -351
- package/src/layouts/force-directed/fruchterman-reingold.ts +133 -134
- package/src/layouts/force-directed/index.ts +5 -5
- package/src/layouts/force-directed/kamada-kawai.ts +78 -80
- package/src/layouts/force-directed/spring.ts +24 -25
- package/src/layouts/geometric/circular.ts +63 -57
- package/src/layouts/geometric/index.ts +3 -3
- package/src/layouts/geometric/shell.ts +58 -53
- package/src/layouts/geometric/spiral.ts +60 -64
- package/src/layouts/hierarchical/bfs.ts +58 -59
- package/src/layouts/hierarchical/bipartite.ts +62 -63
- package/src/layouts/hierarchical/index.ts +3 -3
- package/src/layouts/hierarchical/multipartite.ts +62 -63
- package/src/layouts/index.ts +5 -5
- package/src/layouts/specialized/index.ts +2 -2
- package/src/layouts/specialized/planar.ts +42 -41
- package/src/layouts/specialized/spectral.ts +110 -99
- package/src/types/embedding.ts +5 -5
- package/src/types/graph.ts +4 -4
- package/src/types/index.ts +3 -3
- package/src/types/layout.ts +2 -2
- package/src/utils/graph.ts +31 -35
- package/src/utils/index.ts +5 -5
- package/src/utils/numpy.ts +96 -92
- package/src/utils/params.ts +14 -11
- package/src/utils/random.ts +57 -40
- package/src/utils/rescale.ts +169 -174
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://coveralls.io/github/graphty-org/layout?branch=main)
|
|
5
5
|
[](https://badge.fury.io/js/%40graphty%2Flayout)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://graphty-org.github.io/layout/examples/index.html)
|
|
8
8
|
|
|
9
9
|
**[View Interactive Examples →](https://graphty-org.github.io/layout/examples/index.html)**
|
|
10
10
|
|
|
@@ -63,62 +63,62 @@ Import the library in your TypeScript/JavaScript project:
|
|
|
63
63
|
|
|
64
64
|
```typescript
|
|
65
65
|
import {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
} from
|
|
66
|
+
// Layout algorithms
|
|
67
|
+
randomLayout,
|
|
68
|
+
circularLayout,
|
|
69
|
+
springLayout,
|
|
70
|
+
fruchtermanReingoldLayout,
|
|
71
|
+
spectralLayout,
|
|
72
|
+
spiralLayout,
|
|
73
|
+
bipartiteLayout,
|
|
74
|
+
multipartiteLayout,
|
|
75
|
+
bfsLayout,
|
|
76
|
+
planarLayout,
|
|
77
|
+
kamadaKawaiLayout,
|
|
78
|
+
forceatlas2Layout,
|
|
79
|
+
arfLayout,
|
|
80
|
+
rescaleLayout,
|
|
81
|
+
|
|
82
|
+
// Graph generators
|
|
83
|
+
completeGraph,
|
|
84
|
+
cycleGraph,
|
|
85
|
+
starGraph,
|
|
86
|
+
wheelGraph,
|
|
87
|
+
gridGraph,
|
|
88
|
+
randomGraph,
|
|
89
|
+
bipartiteGraph,
|
|
90
|
+
scaleFreeGraph,
|
|
91
|
+
|
|
92
|
+
// Layout helpers
|
|
93
|
+
groupNodes,
|
|
94
|
+
detectBipartite,
|
|
95
|
+
findBestRoot,
|
|
96
|
+
autoConfigureForce,
|
|
97
|
+
layoutQuality,
|
|
98
|
+
combineLayouts,
|
|
99
|
+
interpolateLayouts,
|
|
100
|
+
} from "@graphty/layout";
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
## Quick Start
|
|
104
104
|
|
|
105
105
|
```typescript
|
|
106
106
|
// Generate a graph
|
|
107
|
-
const graph = scaleFreeGraph(30, 2, 42)
|
|
107
|
+
const graph = scaleFreeGraph(30, 2, 42);
|
|
108
108
|
|
|
109
109
|
// Auto-configure and layout
|
|
110
|
-
const config = autoConfigureForce(graph)
|
|
111
|
-
const positions = springLayout(graph, config.k, null, null, config.iterations)
|
|
110
|
+
const config = autoConfigureForce(graph);
|
|
111
|
+
const positions = springLayout(graph, config.k, null, null, config.iterations);
|
|
112
112
|
|
|
113
113
|
// Or use specialized layouts
|
|
114
|
-
const bipartite = detectBipartite(graph)
|
|
114
|
+
const bipartite = detectBipartite(graph);
|
|
115
115
|
if (bipartite) {
|
|
116
|
-
|
|
116
|
+
const positions = bipartiteLayout(graph, bipartite.setA);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
// Or use shell layout with automatic grouping
|
|
120
|
-
const shells = groupNodes(graph,
|
|
121
|
-
const positions = shellLayout(graph, shells)
|
|
120
|
+
const shells = groupNodes(graph, "degree", 3);
|
|
121
|
+
const positions = shellLayout(graph, shells);
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
## Graph Structure
|
|
@@ -129,21 +129,21 @@ The module accepts graphs in two formats:
|
|
|
129
129
|
|
|
130
130
|
```typescript
|
|
131
131
|
const graph = {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
132
|
+
nodes: () => [0, 1, 2, 3],
|
|
133
|
+
edges: () => [
|
|
134
|
+
[0, 1],
|
|
135
|
+
[1, 2],
|
|
136
|
+
[2, 3],
|
|
137
|
+
[3, 0],
|
|
138
|
+
],
|
|
139
|
+
getEdgeData: (source, target, attr) => number, // optional for edge weights
|
|
140
|
+
};
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
### 2. Simple array of nodes
|
|
144
144
|
|
|
145
145
|
```typescript
|
|
146
|
-
const nodes = [0, 1, 2, 3]
|
|
146
|
+
const nodes = [0, 1, 2, 3];
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
## Graph Generation
|
|
@@ -155,7 +155,7 @@ The library includes utilities to generate common graph types for testing and de
|
|
|
155
155
|
Creates a complete graph with all possible edges between nodes.
|
|
156
156
|
|
|
157
157
|
```typescript
|
|
158
|
-
const graph = completeGraph(5)
|
|
158
|
+
const graph = completeGraph(5);
|
|
159
159
|
// Creates a graph with 5 nodes (0-4) and 10 edges (all pairs connected)
|
|
160
160
|
```
|
|
161
161
|
|
|
@@ -164,7 +164,7 @@ const graph = completeGraph(5)
|
|
|
164
164
|
Creates a cycle graph where nodes form a closed loop.
|
|
165
165
|
|
|
166
166
|
```typescript
|
|
167
|
-
const graph = cycleGraph(6)
|
|
167
|
+
const graph = cycleGraph(6);
|
|
168
168
|
// Creates a graph with 6 nodes (0-5) connected in a cycle: 0-1-2-3-4-5-0
|
|
169
169
|
```
|
|
170
170
|
|
|
@@ -173,7 +173,7 @@ const graph = cycleGraph(6)
|
|
|
173
173
|
Creates a star graph with one central hub connected to all other nodes.
|
|
174
174
|
|
|
175
175
|
```typescript
|
|
176
|
-
const graph = starGraph(7)
|
|
176
|
+
const graph = starGraph(7);
|
|
177
177
|
// Creates a graph with 7 nodes where node 0 is connected to all others (1-6)
|
|
178
178
|
```
|
|
179
179
|
|
|
@@ -182,7 +182,7 @@ const graph = starGraph(7)
|
|
|
182
182
|
Creates a wheel graph - a hub connected to all nodes of a rim cycle.
|
|
183
183
|
|
|
184
184
|
```typescript
|
|
185
|
-
const graph = wheelGraph(6)
|
|
185
|
+
const graph = wheelGraph(6);
|
|
186
186
|
// Creates a graph with 6 nodes: hub (0) connected to rim cycle (1-2-3-4-5-1)
|
|
187
187
|
```
|
|
188
188
|
|
|
@@ -191,7 +191,7 @@ const graph = wheelGraph(6)
|
|
|
191
191
|
Creates a 2D grid graph with specified rows and columns.
|
|
192
192
|
|
|
193
193
|
```typescript
|
|
194
|
-
const graph = gridGraph(3, 4)
|
|
194
|
+
const graph = gridGraph(3, 4);
|
|
195
195
|
// Creates a 3x4 grid with nodes named "row,col" (e.g., "0,0", "0,1", etc.)
|
|
196
196
|
// Nodes are connected to their horizontal and vertical neighbors
|
|
197
197
|
```
|
|
@@ -201,7 +201,7 @@ const graph = gridGraph(3, 4)
|
|
|
201
201
|
Creates a random graph with specified edge probability.
|
|
202
202
|
|
|
203
203
|
```typescript
|
|
204
|
-
const graph = randomGraph(10, 0.3, 42)
|
|
204
|
+
const graph = randomGraph(10, 0.3, 42);
|
|
205
205
|
// Creates a graph with 10 nodes (0-9)
|
|
206
206
|
// Each possible edge has 30% chance of existing
|
|
207
207
|
// Seed 42 ensures reproducible results
|
|
@@ -212,7 +212,7 @@ const graph = randomGraph(10, 0.3, 42)
|
|
|
212
212
|
Creates a bipartite graph with two sets of nodes.
|
|
213
213
|
|
|
214
214
|
```typescript
|
|
215
|
-
const graph = bipartiteGraph(3, 4, 0.5, 123)
|
|
215
|
+
const graph = bipartiteGraph(3, 4, 0.5, 123);
|
|
216
216
|
// Creates two sets: A0,A1,A2 and B0,B1,B2,B3
|
|
217
217
|
// Each edge between sets has 50% chance of existing
|
|
218
218
|
// Returns graph with additional setA and setB properties
|
|
@@ -223,7 +223,7 @@ const graph = bipartiteGraph(3, 4, 0.5, 123)
|
|
|
223
223
|
Creates a scale-free graph using the Barabási-Albert preferential attachment model.
|
|
224
224
|
|
|
225
225
|
```typescript
|
|
226
|
-
const graph = scaleFreeGraph(20, 2, 456)
|
|
226
|
+
const graph = scaleFreeGraph(20, 2, 456);
|
|
227
227
|
// Creates a graph with 20 nodes
|
|
228
228
|
// Each new node connects to 2 existing nodes (preferential attachment)
|
|
229
229
|
// Results in a power-law degree distribution with some high-degree hubs
|
|
@@ -235,35 +235,29 @@ All generated graphs work seamlessly with the layout algorithms:
|
|
|
235
235
|
|
|
236
236
|
```typescript
|
|
237
237
|
// Generate a complete graph and apply circular layout
|
|
238
|
-
const graph = completeGraph(8)
|
|
239
|
-
const positions = circularLayout(graph)
|
|
238
|
+
const graph = completeGraph(8);
|
|
239
|
+
const positions = circularLayout(graph);
|
|
240
240
|
|
|
241
241
|
// Generate a grid with auto-configured spring layout
|
|
242
|
-
const grid = gridGraph(5, 5)
|
|
243
|
-
const config = autoConfigureForce(grid)
|
|
244
|
-
const gridPositions = springLayout(
|
|
245
|
-
grid,
|
|
246
|
-
config.k,
|
|
247
|
-
null,
|
|
248
|
-
null,
|
|
249
|
-
config.iterations
|
|
250
|
-
)
|
|
242
|
+
const grid = gridGraph(5, 5);
|
|
243
|
+
const config = autoConfigureForce(grid);
|
|
244
|
+
const gridPositions = springLayout(grid, config.k, null, null, config.iterations);
|
|
251
245
|
|
|
252
246
|
// Generate a scale-free network with optimized ForceAtlas2
|
|
253
|
-
const network = scaleFreeGraph(50, 3, 42)
|
|
254
|
-
const networkConfig = autoConfigureForce(network)
|
|
247
|
+
const network = scaleFreeGraph(50, 3, 42);
|
|
248
|
+
const networkConfig = autoConfigureForce(network);
|
|
255
249
|
const networkPositions = forceatlas2Layout(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
)
|
|
250
|
+
network,
|
|
251
|
+
null,
|
|
252
|
+
networkConfig.iterations,
|
|
253
|
+
1.0,
|
|
254
|
+
networkConfig.scalingRatio,
|
|
255
|
+
networkConfig.gravity,
|
|
256
|
+
);
|
|
263
257
|
|
|
264
258
|
// Use bipartite graph with automatic detection
|
|
265
|
-
const bipartite = bipartiteGraph(5, 7, 0.4, 123)
|
|
266
|
-
const bipartitePositions = bipartiteLayout(bipartite, bipartite.setA)
|
|
259
|
+
const bipartite = bipartiteGraph(5, 7, 0.4, 123);
|
|
260
|
+
const bipartitePositions = bipartiteLayout(bipartite, bipartite.setA);
|
|
267
261
|
```
|
|
268
262
|
|
|
269
263
|
## Layout Helpers
|
|
@@ -276,19 +270,19 @@ Groups nodes for shell, multipartite, or custom layouts based on various metrics
|
|
|
276
270
|
|
|
277
271
|
```typescript
|
|
278
272
|
// Group by degree (connectivity) - great for shell layouts
|
|
279
|
-
const shells = groupNodes(graph,
|
|
280
|
-
const positions = shellLayout(graph, shells)
|
|
273
|
+
const shells = groupNodes(graph, "degree", 3);
|
|
274
|
+
const positions = shellLayout(graph, shells);
|
|
281
275
|
|
|
282
276
|
// Group by distance from root - perfect for hierarchical layouts
|
|
283
|
-
const layers = groupNodes(graph,
|
|
284
|
-
const positions = multipartiteLayout(graph, layers)
|
|
277
|
+
const layers = groupNodes(graph, "bfs", 0, { root: "A" });
|
|
278
|
+
const positions = multipartiteLayout(graph, layers);
|
|
285
279
|
|
|
286
280
|
// Group by k-core (dense subgraphs) - ideal for social networks
|
|
287
|
-
const cores = groupNodes(graph,
|
|
288
|
-
const positions = shellLayout(graph, cores)
|
|
281
|
+
const cores = groupNodes(graph, "k-core");
|
|
282
|
+
const positions = shellLayout(graph, cores);
|
|
289
283
|
|
|
290
284
|
// Group by community detection - useful for modular networks
|
|
291
|
-
const communities = groupNodes(graph,
|
|
285
|
+
const communities = groupNodes(graph, "community", 5);
|
|
292
286
|
```
|
|
293
287
|
|
|
294
288
|
### `detectBipartite()` - Automatic Bipartite Detection
|
|
@@ -296,13 +290,13 @@ const communities = groupNodes(graph, 'community', 5)
|
|
|
296
290
|
Automatically detects if a graph is bipartite and finds the two sets:
|
|
297
291
|
|
|
298
292
|
```typescript
|
|
299
|
-
const result = detectBipartite(graph)
|
|
293
|
+
const result = detectBipartite(graph);
|
|
300
294
|
if (result) {
|
|
301
|
-
|
|
302
|
-
|
|
295
|
+
// Graph is bipartite! Use specialized layout
|
|
296
|
+
const positions = bipartiteLayout(graph, result.setA);
|
|
303
297
|
} else {
|
|
304
|
-
|
|
305
|
-
|
|
298
|
+
// Not bipartite, use general layout
|
|
299
|
+
const positions = springLayout(graph);
|
|
306
300
|
}
|
|
307
301
|
```
|
|
308
302
|
|
|
@@ -311,8 +305,8 @@ if (result) {
|
|
|
311
305
|
Finds the best starting node for tree-like layouts (BFS, hierarchical):
|
|
312
306
|
|
|
313
307
|
```typescript
|
|
314
|
-
const root = findBestRoot(graph)
|
|
315
|
-
const positions = bfsLayout(graph, root)
|
|
308
|
+
const root = findBestRoot(graph);
|
|
309
|
+
const positions = bfsLayout(graph, root);
|
|
316
310
|
```
|
|
317
311
|
|
|
318
312
|
### `autoConfigureForce()` - Smart Force Layout Configuration
|
|
@@ -320,20 +314,13 @@ const positions = bfsLayout(graph, root)
|
|
|
320
314
|
Automatically configures parameters based on graph properties:
|
|
321
315
|
|
|
322
316
|
```typescript
|
|
323
|
-
const config = autoConfigureForce(graph)
|
|
317
|
+
const config = autoConfigureForce(graph);
|
|
324
318
|
|
|
325
319
|
// Use with Fruchterman-Reingold
|
|
326
|
-
const positions = springLayout(graph, config.k, null, null, config.iterations)
|
|
320
|
+
const positions = springLayout(graph, config.k, null, null, config.iterations);
|
|
327
321
|
|
|
328
322
|
// Use with ForceAtlas2
|
|
329
|
-
const positions = forceatlas2Layout(
|
|
330
|
-
graph,
|
|
331
|
-
null,
|
|
332
|
-
config.iterations,
|
|
333
|
-
1.0,
|
|
334
|
-
config.scalingRatio,
|
|
335
|
-
config.gravity
|
|
336
|
-
)
|
|
323
|
+
const positions = forceatlas2Layout(graph, null, config.iterations, 1.0, config.scalingRatio, config.gravity);
|
|
337
324
|
```
|
|
338
325
|
|
|
339
326
|
### `layoutQuality()` - Layout Quality Metrics
|
|
@@ -341,15 +328,15 @@ const positions = forceatlas2Layout(
|
|
|
341
328
|
Measure and compare layout quality:
|
|
342
329
|
|
|
343
330
|
```typescript
|
|
344
|
-
const circular = circularLayout(graph)
|
|
345
|
-
const spring = springLayout(graph)
|
|
331
|
+
const circular = circularLayout(graph);
|
|
332
|
+
const spring = springLayout(graph);
|
|
346
333
|
|
|
347
|
-
const metricsC = layoutQuality(graph, circular)
|
|
348
|
-
const metricsS = layoutQuality(graph, spring)
|
|
334
|
+
const metricsC = layoutQuality(graph, circular);
|
|
335
|
+
const metricsS = layoutQuality(graph, spring);
|
|
349
336
|
|
|
350
|
-
console.log(
|
|
351
|
-
console.log(
|
|
352
|
-
console.log(
|
|
337
|
+
console.log("Circular layout - avg edge length:", metricsC.avgEdgeLength);
|
|
338
|
+
console.log("Spring layout - avg edge length:", metricsS.avgEdgeLength);
|
|
339
|
+
console.log("Spring layout - min node distance:", metricsS.minNodeDistance);
|
|
353
340
|
```
|
|
354
341
|
|
|
355
342
|
### `combineLayouts()` - Blend Multiple Layouts
|
|
@@ -357,11 +344,11 @@ console.log('Spring layout - min node distance:', metricsS.minNodeDistance)
|
|
|
357
344
|
Create hybrid layouts by combining different algorithms:
|
|
358
345
|
|
|
359
346
|
```typescript
|
|
360
|
-
const circular = circularLayout(graph)
|
|
361
|
-
const spring = springLayout(graph)
|
|
347
|
+
const circular = circularLayout(graph);
|
|
348
|
+
const spring = springLayout(graph);
|
|
362
349
|
|
|
363
350
|
// 30% circular structure, 70% force-directed
|
|
364
|
-
const hybrid = combineLayouts([circular, spring], [0.3, 0.7])
|
|
351
|
+
const hybrid = combineLayouts([circular, spring], [0.3, 0.7]);
|
|
365
352
|
```
|
|
366
353
|
|
|
367
354
|
### `interpolateLayouts()` - Smooth Layout Transitions
|
|
@@ -369,11 +356,11 @@ const hybrid = combineLayouts([circular, spring], [0.3, 0.7])
|
|
|
369
356
|
Create animation frames between different layouts:
|
|
370
357
|
|
|
371
358
|
```typescript
|
|
372
|
-
const startLayout = circularLayout(graph)
|
|
373
|
-
const endLayout = springLayout(graph)
|
|
359
|
+
const startLayout = circularLayout(graph);
|
|
360
|
+
const endLayout = springLayout(graph);
|
|
374
361
|
|
|
375
362
|
// Generate 30 frames for smooth animation
|
|
376
|
-
const frames = interpolateLayouts(startLayout, endLayout, 30)
|
|
363
|
+
const frames = interpolateLayouts(startLayout, endLayout, 30);
|
|
377
364
|
// Use frames[0] through frames[30] for animation
|
|
378
365
|
```
|
|
379
366
|
|
|
@@ -383,31 +370,31 @@ const frames = interpolateLayouts(startLayout, endLayout, 30)
|
|
|
383
370
|
|
|
384
371
|
```typescript
|
|
385
372
|
// Automatically choose best grouping method based on graph density
|
|
386
|
-
const n = graph.nodes().length
|
|
387
|
-
const m = graph.edges().length
|
|
388
|
-
const density = (2 * m) / (n * (n - 1))
|
|
373
|
+
const n = graph.nodes().length;
|
|
374
|
+
const m = graph.edges().length;
|
|
375
|
+
const density = (2 * m) / (n * (n - 1));
|
|
389
376
|
|
|
390
|
-
const method = density < 0.1 ?
|
|
391
|
-
const shells = groupNodes(graph, method)
|
|
392
|
-
const positions = shellLayout(graph, shells)
|
|
377
|
+
const method = density < 0.1 ? "bfs" : density > 0.5 ? "k-core" : "degree";
|
|
378
|
+
const shells = groupNodes(graph, method);
|
|
379
|
+
const positions = shellLayout(graph, shells);
|
|
393
380
|
```
|
|
394
381
|
|
|
395
382
|
#### Adaptive Layout Selection
|
|
396
383
|
|
|
397
384
|
```typescript
|
|
398
385
|
// Choose layout based on graph properties
|
|
399
|
-
let positions
|
|
386
|
+
let positions;
|
|
400
387
|
|
|
401
388
|
if (detectBipartite(graph)) {
|
|
402
|
-
|
|
403
|
-
|
|
389
|
+
const { setA } = detectBipartite(graph);
|
|
390
|
+
positions = bipartiteLayout(graph, setA);
|
|
404
391
|
} else if (graph.nodes().length > 100) {
|
|
405
|
-
|
|
406
|
-
|
|
392
|
+
// Large graph - use fast layout
|
|
393
|
+
positions = circularLayout(graph);
|
|
407
394
|
} else {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
395
|
+
// Default to auto-configured force layout
|
|
396
|
+
const config = autoConfigureForce(graph);
|
|
397
|
+
positions = springLayout(graph, config.k, null, null, config.iterations);
|
|
411
398
|
}
|
|
412
399
|
```
|
|
413
400
|
|
|
@@ -415,9 +402,9 @@ if (detectBipartite(graph)) {
|
|
|
415
402
|
|
|
416
403
|
```typescript
|
|
417
404
|
// Start with fast layout, progressively refine
|
|
418
|
-
const initial = circularLayout(graph)
|
|
419
|
-
const refined = springLayout(graph, null, initial, null, 50)
|
|
420
|
-
const final = kamadaKawaiLayout(graph, null, refined)
|
|
405
|
+
const initial = circularLayout(graph);
|
|
406
|
+
const refined = springLayout(graph, null, initial, null, 50);
|
|
407
|
+
const final = kamadaKawaiLayout(graph, null, refined);
|
|
421
408
|
```
|
|
422
409
|
|
|
423
410
|
### Layout Helper Quick Reference
|
|
@@ -441,9 +428,9 @@ const final = kamadaKawaiLayout(graph, null, refined)
|
|
|
441
428
|
|
|
442
429
|
```typescript
|
|
443
430
|
// Use our graph generator instead of manual construction
|
|
444
|
-
const graph = cycleGraph(8)
|
|
431
|
+
const graph = cycleGraph(8);
|
|
445
432
|
|
|
446
|
-
const positions = circularLayout(graph)
|
|
433
|
+
const positions = circularLayout(graph);
|
|
447
434
|
// Nodes arranged in a perfect circle
|
|
448
435
|
```
|
|
449
436
|
|
|
@@ -451,34 +438,34 @@ const positions = circularLayout(graph)
|
|
|
451
438
|
|
|
452
439
|
```typescript
|
|
453
440
|
// Generate a grid and apply force-directed layout with auto-configured parameters
|
|
454
|
-
const graph = gridGraph(5, 5)
|
|
455
|
-
const config = autoConfigureForce(graph)
|
|
441
|
+
const graph = gridGraph(5, 5);
|
|
442
|
+
const config = autoConfigureForce(graph);
|
|
456
443
|
|
|
457
444
|
const positions = springLayout(
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
)
|
|
445
|
+
graph,
|
|
446
|
+
config.k, // optimal distance
|
|
447
|
+
null, // initial positions
|
|
448
|
+
null, // fixed nodes
|
|
449
|
+
config.iterations, // iterations
|
|
450
|
+
);
|
|
464
451
|
|
|
465
452
|
// Or use fruchtermanReingoldLayout (same function)
|
|
466
|
-
const positions2 = fruchtermanReingoldLayout(graph, config.k)
|
|
453
|
+
const positions2 = fruchtermanReingoldLayout(graph, config.k);
|
|
467
454
|
```
|
|
468
455
|
|
|
469
456
|
### Bipartite graph layout
|
|
470
457
|
|
|
471
458
|
```typescript
|
|
472
459
|
// Generate a bipartite graph and detect sets automatically
|
|
473
|
-
const graph = bipartiteGraph(4, 6, 0.5, 42)
|
|
460
|
+
const graph = bipartiteGraph(4, 6, 0.5, 42);
|
|
474
461
|
|
|
475
462
|
// Option 1: Use the built-in sets
|
|
476
|
-
const positions = bipartiteLayout(graph, graph.setA,
|
|
463
|
+
const positions = bipartiteLayout(graph, graph.setA, "vertical");
|
|
477
464
|
|
|
478
465
|
// Option 2: Auto-detect bipartite structure
|
|
479
|
-
const detected = detectBipartite(graph)
|
|
466
|
+
const detected = detectBipartite(graph);
|
|
480
467
|
if (detected) {
|
|
481
|
-
|
|
468
|
+
const positions2 = bipartiteLayout(graph, detected.setA, "horizontal");
|
|
482
469
|
}
|
|
483
470
|
```
|
|
484
471
|
|
|
@@ -488,14 +475,14 @@ All layout algorithms support 3D positioning by setting the `dim` parameter to 3
|
|
|
488
475
|
|
|
489
476
|
```typescript
|
|
490
477
|
// Any layout algorithm in 3D
|
|
491
|
-
const positions3D = springLayout(graph, null, null, null, 50, 1, [0, 0, 0], 3)
|
|
478
|
+
const positions3D = springLayout(graph, null, null, null, 50, 1, [0, 0, 0], 3);
|
|
492
479
|
// Returns: { node1: [x, y, z], node2: [x, y, z], ... }
|
|
493
480
|
|
|
494
481
|
// ForceAtlas2 in 3D
|
|
495
|
-
const fa3D = forceatlas2Layout(graph, null, 100, 1, 2, 1, false, false, null, null, null, false, false, 42, 3)
|
|
482
|
+
const fa3D = forceatlas2Layout(graph, null, 100, 1, 2, 1, false, false, null, null, null, false, false, 42, 3);
|
|
496
483
|
|
|
497
484
|
// Circular layout in 3D (creates a sphere)
|
|
498
|
-
const circular3D = circularLayout(graph, 1, [0, 0, 0], 3)
|
|
485
|
+
const circular3D = circularLayout(graph, 1, [0, 0, 0], 3);
|
|
499
486
|
```
|
|
500
487
|
|
|
501
488
|
When using 3D layouts, positions will have three coordinates `[x, y, z]` instead of two.
|
|
@@ -512,14 +499,14 @@ Most layout functions share these parameters:
|
|
|
512
499
|
## TypeScript Types
|
|
513
500
|
|
|
514
501
|
```typescript
|
|
515
|
-
type Node = string | number
|
|
516
|
-
type Edge = [Node, Node]
|
|
517
|
-
type PositionMap = Record<Node, number[]
|
|
502
|
+
type Node = string | number;
|
|
503
|
+
type Edge = [Node, Node];
|
|
504
|
+
type PositionMap = Record<Node, number[]>;
|
|
518
505
|
|
|
519
506
|
interface Graph {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
507
|
+
nodes?: () => Node[];
|
|
508
|
+
edges?: () => Edge[];
|
|
509
|
+
getEdgeData?: (source: Node, target: Node, attr: string) => any;
|
|
523
510
|
}
|
|
524
511
|
```
|
|
525
512
|
|
|
@@ -528,10 +515,10 @@ interface Graph {
|
|
|
528
515
|
### Layout Rescaling
|
|
529
516
|
|
|
530
517
|
```typescript
|
|
531
|
-
import { rescaleLayout } from
|
|
518
|
+
import { rescaleLayout } from "./layout.js";
|
|
532
519
|
|
|
533
520
|
// Rescale existing positions
|
|
534
|
-
const scaledPositions = rescaleLayout(positions, 2.0, [10, 10])
|
|
521
|
+
const scaledPositions = rescaleLayout(positions, 2.0, [10, 10]);
|
|
535
522
|
```
|
|
536
523
|
|
|
537
524
|
## Available Algorithms
|
|
@@ -569,8 +556,8 @@ const scaledPositions = rescaleLayout(positions, 2.0, [10, 10])
|
|
|
569
556
|
|
|
570
557
|
```typescript
|
|
571
558
|
// Generate any graph and apply random layout
|
|
572
|
-
const graph = completeGraph(10)
|
|
573
|
-
const positions = randomLayout(graph, [0, 0], 2, 42)
|
|
559
|
+
const graph = completeGraph(10);
|
|
560
|
+
const positions = randomLayout(graph, [0, 0], 2, 42);
|
|
574
561
|
// Nodes randomly placed in unit square with seed 42
|
|
575
562
|
```
|
|
576
563
|
|
|
@@ -578,8 +565,8 @@ const positions = randomLayout(graph, [0, 0], 2, 42)
|
|
|
578
565
|
|
|
579
566
|
```typescript
|
|
580
567
|
// Perfect for cyclic or complete graphs
|
|
581
|
-
const graph = cycleGraph(12)
|
|
582
|
-
const positions = circularLayout(graph)
|
|
568
|
+
const graph = cycleGraph(12);
|
|
569
|
+
const positions = circularLayout(graph);
|
|
583
570
|
// 12 nodes evenly spaced on a circle
|
|
584
571
|
```
|
|
585
572
|
|
|
@@ -587,39 +574,39 @@ const positions = circularLayout(graph)
|
|
|
587
574
|
|
|
588
575
|
```typescript
|
|
589
576
|
// Use automatic node grouping for shell layout
|
|
590
|
-
const graph = scaleFreeGraph(30, 2, 42)
|
|
577
|
+
const graph = scaleFreeGraph(30, 2, 42);
|
|
591
578
|
|
|
592
579
|
// Group nodes by degree (hubs in center)
|
|
593
|
-
const shells = groupNodes(graph,
|
|
594
|
-
const positions = shellLayout(graph, shells)
|
|
580
|
+
const shells = groupNodes(graph, "degree", 3);
|
|
581
|
+
const positions = shellLayout(graph, shells);
|
|
595
582
|
|
|
596
583
|
// Or group by k-core for social networks
|
|
597
|
-
const kCoreShells = groupNodes(graph,
|
|
598
|
-
const positions2 = shellLayout(graph, kCoreShells)
|
|
584
|
+
const kCoreShells = groupNodes(graph, "k-core");
|
|
585
|
+
const positions2 = shellLayout(graph, kCoreShells);
|
|
599
586
|
```
|
|
600
587
|
|
|
601
588
|
### Spring Layout (Fruchterman-Reingold)
|
|
602
589
|
|
|
603
590
|
```typescript
|
|
604
591
|
// Auto-configure parameters based on graph size
|
|
605
|
-
const graph = randomGraph(20, 0.2, 42)
|
|
606
|
-
const config = autoConfigureForce(graph)
|
|
592
|
+
const graph = randomGraph(20, 0.2, 42);
|
|
593
|
+
const config = autoConfigureForce(graph);
|
|
607
594
|
|
|
608
595
|
const positions = springLayout(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
)
|
|
596
|
+
graph,
|
|
597
|
+
config.k, // optimal distance
|
|
598
|
+
null, // initial positions
|
|
599
|
+
null, // fixed nodes
|
|
600
|
+
config.iterations, // iterations
|
|
601
|
+
);
|
|
615
602
|
```
|
|
616
603
|
|
|
617
604
|
### Spectral Layout
|
|
618
605
|
|
|
619
606
|
```typescript
|
|
620
607
|
// Great for revealing graph structure
|
|
621
|
-
const graph = gridGraph(6, 6)
|
|
622
|
-
const positions = spectralLayout(graph)
|
|
608
|
+
const graph = gridGraph(6, 6);
|
|
609
|
+
const positions = spectralLayout(graph);
|
|
623
610
|
// Grid structure preserved in spectral embedding
|
|
624
611
|
```
|
|
625
612
|
|
|
@@ -627,84 +614,84 @@ const positions = spectralLayout(graph)
|
|
|
627
614
|
|
|
628
615
|
```typescript
|
|
629
616
|
// Perfect for sequential or time-based data
|
|
630
|
-
const graph = cycleGraph(50)
|
|
617
|
+
const graph = cycleGraph(50);
|
|
631
618
|
const positions = spiralLayout(
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
)
|
|
619
|
+
graph,
|
|
620
|
+
1, // scale
|
|
621
|
+
[0, 0], // center
|
|
622
|
+
2, // dim
|
|
623
|
+
0.35, // resolution
|
|
624
|
+
true, // equidistant points
|
|
625
|
+
);
|
|
639
626
|
```
|
|
640
627
|
|
|
641
628
|
### Bipartite Layout
|
|
642
629
|
|
|
643
630
|
```typescript
|
|
644
631
|
// Generate bipartite graph and layout automatically
|
|
645
|
-
const graph = bipartiteGraph(5, 7, 0.4, 42)
|
|
632
|
+
const graph = bipartiteGraph(5, 7, 0.4, 42);
|
|
646
633
|
const positions = bipartiteLayout(
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
)
|
|
634
|
+
graph,
|
|
635
|
+
graph.setA, // first group nodes (auto-generated)
|
|
636
|
+
"vertical", // align: 'vertical' or 'horizontal'
|
|
637
|
+
1, // scale
|
|
638
|
+
[0, 0], // center
|
|
639
|
+
4 / 3, // aspectRatio
|
|
640
|
+
);
|
|
654
641
|
```
|
|
655
642
|
|
|
656
643
|
### Multipartite Layout
|
|
657
644
|
|
|
658
645
|
```typescript
|
|
659
646
|
// Use automatic layer detection with groupNodes
|
|
660
|
-
const graph = scaleFreeGraph(20, 2, 42)
|
|
661
|
-
const layers = groupNodes(graph,
|
|
647
|
+
const graph = scaleFreeGraph(20, 2, 42);
|
|
648
|
+
const layers = groupNodes(graph, "bfs", 0, { root: findBestRoot(graph) });
|
|
662
649
|
|
|
663
650
|
// Convert to multipartite format
|
|
664
|
-
const layerMap = {}
|
|
651
|
+
const layerMap = {};
|
|
665
652
|
layers.forEach((nodes, i) => {
|
|
666
|
-
|
|
667
|
-
})
|
|
653
|
+
layerMap[i] = nodes;
|
|
654
|
+
});
|
|
668
655
|
|
|
669
656
|
const positions = multipartiteLayout(
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
)
|
|
657
|
+
graph,
|
|
658
|
+
layerMap, // subsetKey: layer mapping
|
|
659
|
+
"vertical", // align
|
|
660
|
+
1, // scale
|
|
661
|
+
[0, 0], // center
|
|
662
|
+
);
|
|
676
663
|
```
|
|
677
664
|
|
|
678
665
|
### BFS Layout
|
|
679
666
|
|
|
680
667
|
```typescript
|
|
681
668
|
// Use automatic root detection for tree-like graphs
|
|
682
|
-
const graph = starGraph(10)
|
|
683
|
-
const root = findBestRoot(graph) // Automatically finds node 0 (hub)
|
|
669
|
+
const graph = starGraph(10);
|
|
670
|
+
const root = findBestRoot(graph); // Automatically finds node 0 (hub)
|
|
684
671
|
|
|
685
672
|
const positions = bfsLayout(
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
)
|
|
673
|
+
graph,
|
|
674
|
+
root, // start: best root node
|
|
675
|
+
"vertical", // align
|
|
676
|
+
1, // scale
|
|
677
|
+
[0, 0], // center
|
|
678
|
+
);
|
|
692
679
|
```
|
|
693
680
|
|
|
694
681
|
### Planar Layout
|
|
695
682
|
|
|
696
683
|
```typescript
|
|
697
684
|
// Create a planar graph (grid is always planar)
|
|
698
|
-
const graph = gridGraph(4, 4)
|
|
699
|
-
const positions = planarLayout(graph, 1, [0, 0], 2)
|
|
685
|
+
const graph = gridGraph(4, 4);
|
|
686
|
+
const positions = planarLayout(graph, 1, [0, 0], 2);
|
|
700
687
|
// Note: throws error if graph is not planar
|
|
701
688
|
|
|
702
689
|
// For unknown graphs, check planarity first
|
|
703
690
|
if (isPlanar(graph)) {
|
|
704
|
-
|
|
691
|
+
const positions = planarLayout(graph);
|
|
705
692
|
} else {
|
|
706
|
-
|
|
707
|
-
|
|
693
|
+
// Fall back to non-planar layout
|
|
694
|
+
const positions = springLayout(graph);
|
|
708
695
|
}
|
|
709
696
|
```
|
|
710
697
|
|
|
@@ -712,76 +699,76 @@ if (isPlanar(graph)) {
|
|
|
712
699
|
|
|
713
700
|
```typescript
|
|
714
701
|
// Great for small to medium graphs
|
|
715
|
-
const graph = wheelGraph(8)
|
|
702
|
+
const graph = wheelGraph(8);
|
|
716
703
|
const positions = kamadaKawaiLayout(
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
)
|
|
704
|
+
graph,
|
|
705
|
+
null, // dist: distance matrix (auto)
|
|
706
|
+
null, // pos: initial positions (auto)
|
|
707
|
+
"weight", // weight: edge weight attribute
|
|
708
|
+
1, // scale
|
|
709
|
+
[0, 0], // center
|
|
710
|
+
2, // dim
|
|
711
|
+
);
|
|
725
712
|
```
|
|
726
713
|
|
|
727
714
|
### ForceAtlas2 Layout
|
|
728
715
|
|
|
729
716
|
```typescript
|
|
730
717
|
// Auto-configure for your graph type
|
|
731
|
-
const graph = scaleFreeGraph(50, 3, 42)
|
|
732
|
-
const config = autoConfigureForce(graph)
|
|
718
|
+
const graph = scaleFreeGraph(50, 3, 42);
|
|
719
|
+
const config = autoConfigureForce(graph);
|
|
733
720
|
|
|
734
721
|
const positions = forceatlas2Layout(
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
)
|
|
722
|
+
graph,
|
|
723
|
+
null, // pos: initial positions
|
|
724
|
+
config.iterations, // maxIter: auto-configured
|
|
725
|
+
1.0, // jitterTolerance
|
|
726
|
+
config.scalingRatio, // scalingRatio: auto-configured
|
|
727
|
+
config.gravity, // gravity: auto-configured
|
|
728
|
+
false, // distributedAction
|
|
729
|
+
false, // strongGravity
|
|
730
|
+
null, // nodeMass: node masses
|
|
731
|
+
null, // nodeSize: node sizes
|
|
732
|
+
null, // weight: weight attribute
|
|
733
|
+
true, // dissuadeHubs: good for scale-free
|
|
734
|
+
false, // linlog: logarithmic attraction
|
|
735
|
+
42, // seed
|
|
736
|
+
2, // dim: 2 for 2D, 3 for 3D
|
|
737
|
+
);
|
|
751
738
|
|
|
752
739
|
// 3D layout example
|
|
753
740
|
const positions3D = forceatlas2Layout(
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
)
|
|
741
|
+
graph,
|
|
742
|
+
null,
|
|
743
|
+
100,
|
|
744
|
+
1.0,
|
|
745
|
+
2.0,
|
|
746
|
+
1.0,
|
|
747
|
+
false,
|
|
748
|
+
false,
|
|
749
|
+
null,
|
|
750
|
+
null,
|
|
751
|
+
null,
|
|
752
|
+
false,
|
|
753
|
+
false,
|
|
754
|
+
42,
|
|
755
|
+
3, // 3D mode - returns [x, y, z] coordinates
|
|
756
|
+
);
|
|
770
757
|
```
|
|
771
758
|
|
|
772
759
|
### ARF Layout
|
|
773
760
|
|
|
774
761
|
```typescript
|
|
775
762
|
// Layout with attractive and repulsive forces
|
|
776
|
-
const graph = completeGraph(10)
|
|
763
|
+
const graph = completeGraph(10);
|
|
777
764
|
const positions = arfLayout(
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
)
|
|
765
|
+
graph,
|
|
766
|
+
null, // pos: initial positions
|
|
767
|
+
1, // scaling
|
|
768
|
+
1.1, // a: spring force (must be > 1)
|
|
769
|
+
1000, // maxIter
|
|
770
|
+
42, // seed
|
|
771
|
+
);
|
|
785
772
|
```
|
|
786
773
|
|
|
787
774
|
## Advanced Examples: Combining Generators and Helpers
|
|
@@ -790,70 +777,70 @@ const positions = arfLayout(
|
|
|
790
777
|
|
|
791
778
|
```typescript
|
|
792
779
|
// Generate a scale-free network (hubs and communities)
|
|
793
|
-
const graph = scaleFreeGraph(100, 3, 42)
|
|
780
|
+
const graph = scaleFreeGraph(100, 3, 42);
|
|
794
781
|
|
|
795
782
|
// Detect communities and use them for shell layout
|
|
796
|
-
const communities = groupNodes(graph,
|
|
797
|
-
const positions = shellLayout(graph, communities)
|
|
783
|
+
const communities = groupNodes(graph, "community", 4);
|
|
784
|
+
const positions = shellLayout(graph, communities);
|
|
798
785
|
|
|
799
786
|
// Or use communities for coloring in your visualization
|
|
800
|
-
const communityMap = new Map()
|
|
787
|
+
const communityMap = new Map();
|
|
801
788
|
communities.forEach((nodes, idx) => {
|
|
802
|
-
|
|
803
|
-
})
|
|
789
|
+
nodes.forEach((node) => communityMap.set(node, idx));
|
|
790
|
+
});
|
|
804
791
|
```
|
|
805
792
|
|
|
806
793
|
### Example 2: Adaptive Layout Selection
|
|
807
794
|
|
|
808
795
|
```typescript
|
|
809
796
|
function chooseOptimalLayout(graph) {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
797
|
+
const n = graph.nodes().length;
|
|
798
|
+
const m = graph.edges().length;
|
|
799
|
+
const density = (2 * m) / (n * (n - 1));
|
|
800
|
+
|
|
801
|
+
// Check for special graph types
|
|
802
|
+
const bipartite = detectBipartite(graph);
|
|
803
|
+
if (bipartite) {
|
|
804
|
+
return bipartiteLayout(graph, bipartite.setA);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Choose based on graph properties
|
|
808
|
+
if (n > 100) {
|
|
809
|
+
// Large graph - use fast circular layout
|
|
810
|
+
return circularLayout(graph);
|
|
811
|
+
} else if (density < 0.1) {
|
|
812
|
+
// Sparse graph - use spring layout
|
|
813
|
+
const config = autoConfigureForce(graph);
|
|
814
|
+
return springLayout(graph, config.k, null, null, config.iterations);
|
|
815
|
+
} else {
|
|
816
|
+
// Dense graph - use spectral or kamada-kawai
|
|
817
|
+
return n < 50 ? kamadaKawaiLayout(graph) : spectralLayout(graph);
|
|
818
|
+
}
|
|
832
819
|
}
|
|
833
820
|
|
|
834
821
|
// Usage
|
|
835
|
-
const graph = randomGraph(30, 0.3, 42)
|
|
836
|
-
const positions = chooseOptimalLayout(graph)
|
|
822
|
+
const graph = randomGraph(30, 0.3, 42);
|
|
823
|
+
const positions = chooseOptimalLayout(graph);
|
|
837
824
|
```
|
|
838
825
|
|
|
839
826
|
### Example 3: Animated Layout Transitions
|
|
840
827
|
|
|
841
828
|
```typescript
|
|
842
829
|
// Start with circular layout
|
|
843
|
-
const graph = completeGraph(15)
|
|
844
|
-
const startLayout = circularLayout(graph)
|
|
830
|
+
const graph = completeGraph(15);
|
|
831
|
+
const startLayout = circularLayout(graph);
|
|
845
832
|
|
|
846
833
|
// Optimize with force-directed
|
|
847
|
-
const config = autoConfigureForce(graph)
|
|
848
|
-
const endLayout = springLayout(graph, config.k, null, null, config.iterations)
|
|
834
|
+
const config = autoConfigureForce(graph);
|
|
835
|
+
const endLayout = springLayout(graph, config.k, null, null, config.iterations);
|
|
849
836
|
|
|
850
837
|
// Create smooth animation frames
|
|
851
|
-
const frames = interpolateLayouts(startLayout, endLayout, 60)
|
|
838
|
+
const frames = interpolateLayouts(startLayout, endLayout, 60);
|
|
852
839
|
|
|
853
840
|
// Use frames[0] through frames[60] for animation
|
|
854
841
|
function animate(frameIndex) {
|
|
855
|
-
|
|
856
|
-
|
|
842
|
+
const positions = frames[frameIndex];
|
|
843
|
+
// Update your visualization with these positions
|
|
857
844
|
}
|
|
858
845
|
```
|
|
859
846
|
|
|
@@ -861,17 +848,17 @@ function animate(frameIndex) {
|
|
|
861
848
|
|
|
862
849
|
```typescript
|
|
863
850
|
// Generate a preferential attachment network
|
|
864
|
-
const graph = scaleFreeGraph(50, 2, 42)
|
|
851
|
+
const graph = scaleFreeGraph(50, 2, 42);
|
|
865
852
|
|
|
866
853
|
// Find natural hierarchy using k-core decomposition
|
|
867
|
-
const kCores = groupNodes(graph,
|
|
854
|
+
const kCores = groupNodes(graph, "k-core");
|
|
868
855
|
|
|
869
856
|
// Layout with most connected nodes in center
|
|
870
|
-
const positions = shellLayout(graph, kCores)
|
|
857
|
+
const positions = shellLayout(graph, kCores);
|
|
871
858
|
|
|
872
859
|
// Or create a tree-like view
|
|
873
|
-
const root = findBestRoot(graph)
|
|
874
|
-
const bfsPositions = bfsLayout(graph, root)
|
|
860
|
+
const root = findBestRoot(graph);
|
|
861
|
+
const bfsPositions = bfsLayout(graph, root);
|
|
875
862
|
```
|
|
876
863
|
|
|
877
864
|
### Example 5: Quality-Driven Layout
|
|
@@ -879,27 +866,27 @@ const bfsPositions = bfsLayout(graph, root)
|
|
|
879
866
|
```typescript
|
|
880
867
|
// Try multiple layouts and pick the best
|
|
881
868
|
function findBestLayout(graph) {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
869
|
+
const candidates = [
|
|
870
|
+
{ name: "circular", positions: circularLayout(graph) },
|
|
871
|
+
{ name: "spectral", positions: spectralLayout(graph) },
|
|
872
|
+
{ name: "spring", positions: springLayout(graph) },
|
|
873
|
+
];
|
|
874
|
+
|
|
875
|
+
let best = candidates[0];
|
|
876
|
+
let bestScore = Infinity;
|
|
877
|
+
|
|
878
|
+
candidates.forEach((candidate) => {
|
|
879
|
+
const quality = layoutQuality(graph, candidate.positions);
|
|
880
|
+
const score = quality.edgeLengthStdDev / quality.avgEdgeLength;
|
|
881
|
+
|
|
882
|
+
if (score < bestScore) {
|
|
883
|
+
bestScore = score;
|
|
884
|
+
best = candidate;
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
console.log(`Best layout: ${best.name} (score: ${bestScore.toFixed(3)})`);
|
|
889
|
+
return best.positions;
|
|
903
890
|
}
|
|
904
891
|
```
|
|
905
892
|
|
|
@@ -907,14 +894,14 @@ function findBestLayout(graph) {
|
|
|
907
894
|
|
|
908
895
|
```typescript
|
|
909
896
|
// Create a graph with clear structure
|
|
910
|
-
const graph = gridGraph(6, 6)
|
|
897
|
+
const graph = gridGraph(6, 6);
|
|
911
898
|
|
|
912
899
|
// Get geometric and force-based layouts
|
|
913
|
-
const grid = circularLayout(graph)
|
|
914
|
-
const force = springLayout(graph)
|
|
900
|
+
const grid = circularLayout(graph);
|
|
901
|
+
const force = springLayout(graph);
|
|
915
902
|
|
|
916
903
|
// Blend them: 40% geometric structure, 60% force optimization
|
|
917
|
-
const hybrid = combineLayouts([grid, force], [0.4, 0.6])
|
|
904
|
+
const hybrid = combineLayouts([grid, force], [0.4, 0.6]);
|
|
918
905
|
|
|
919
906
|
// The result preserves some grid structure while optimizing edge lengths
|
|
920
907
|
```
|
|
@@ -923,15 +910,15 @@ const hybrid = combineLayouts([grid, force], [0.4, 0.6])
|
|
|
923
910
|
|
|
924
911
|
```typescript
|
|
925
912
|
try {
|
|
926
|
-
|
|
913
|
+
const positions = planarLayout(nonPlanarGraph);
|
|
927
914
|
} catch (error) {
|
|
928
|
-
|
|
915
|
+
console.error("Graph is not planar:", error.message);
|
|
929
916
|
}
|
|
930
917
|
|
|
931
918
|
try {
|
|
932
|
-
|
|
919
|
+
const positions = arfLayout(graph, null, 1, 0.5); // a <= 1
|
|
933
920
|
} catch (error) {
|
|
934
|
-
|
|
921
|
+
console.error("Invalid parameter a:", error.message);
|
|
935
922
|
}
|
|
936
923
|
```
|
|
937
924
|
|
|
@@ -947,32 +934,32 @@ If you want to build the TypeScript module from source:
|
|
|
947
934
|
|
|
948
935
|
1. **Clone the repository:**
|
|
949
936
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
937
|
+
```bash
|
|
938
|
+
git clone https://github.com/graphty-org/layout.git
|
|
939
|
+
cd layout
|
|
940
|
+
```
|
|
954
941
|
|
|
955
942
|
2. **Install dependencies:**
|
|
956
943
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
944
|
+
```bash
|
|
945
|
+
npm install
|
|
946
|
+
```
|
|
960
947
|
|
|
961
948
|
3. **Compile TypeScript to JavaScript:**
|
|
962
949
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
950
|
+
```bash
|
|
951
|
+
npm run build
|
|
952
|
+
```
|
|
966
953
|
|
|
967
|
-
|
|
954
|
+
This will compile the `layout.ts` file to JavaScript and generate type declarations in the `dist/` directory.
|
|
968
955
|
|
|
969
956
|
4. **For development with automatic compilation:**
|
|
970
957
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
958
|
+
```bash
|
|
959
|
+
npm run dev
|
|
960
|
+
```
|
|
974
961
|
|
|
975
|
-
|
|
962
|
+
This will watch for changes and automatically recompile the TypeScript files.
|
|
976
963
|
|
|
977
964
|
## Development Server
|
|
978
965
|
|
|
@@ -996,27 +983,27 @@ You can customize the server configuration using environment variables:
|
|
|
996
983
|
|
|
997
984
|
1. **Create a `.env` file** (copy from `.env.example`):
|
|
998
985
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
986
|
+
```bash
|
|
987
|
+
cp .env.example .env
|
|
988
|
+
```
|
|
1002
989
|
|
|
1003
990
|
2. **Configure server options in `.env`:**
|
|
1004
991
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
992
|
+
```bash
|
|
993
|
+
# Server host (defaults to true for network exposure)
|
|
994
|
+
HOST=localhost # For local-only access
|
|
995
|
+
HOST=0.0.0.0 # For network access
|
|
996
|
+
HOST=my.server.com # For custom domain
|
|
1010
997
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
998
|
+
# Server port (defaults to 3000)
|
|
999
|
+
PORT=3000
|
|
1000
|
+
PORT=8080 # Custom port
|
|
1001
|
+
```
|
|
1015
1002
|
|
|
1016
1003
|
3. **Start the server with your configuration:**
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1004
|
+
```bash
|
|
1005
|
+
npm run serve
|
|
1006
|
+
```
|
|
1020
1007
|
|
|
1021
1008
|
### Alternative: Build and Serve
|
|
1022
1009
|
|
|
@@ -1047,44 +1034,44 @@ The module includes complete implementations of:
|
|
|
1047
1034
|
|
|
1048
1035
|
1. **Large Graphs (>1000 nodes)**:
|
|
1049
1036
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1037
|
+
```typescript
|
|
1038
|
+
// Use fast layouts first
|
|
1039
|
+
const initial = circularLayout(graph);
|
|
1040
|
+
// Then refine with limited iterations
|
|
1041
|
+
const refined = springLayout(graph, null, initial, null, 50);
|
|
1042
|
+
```
|
|
1056
1043
|
|
|
1057
1044
|
2. **Dense Graphs**:
|
|
1058
1045
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1046
|
+
```typescript
|
|
1047
|
+
// Use spectral layout for dense graphs
|
|
1048
|
+
const density = (2 * m) / (n * (n - 1));
|
|
1049
|
+
if (density > 0.5) {
|
|
1050
|
+
const positions = spectralLayout(graph);
|
|
1051
|
+
}
|
|
1052
|
+
```
|
|
1066
1053
|
|
|
1067
1054
|
3. **Real-time Updates**:
|
|
1068
1055
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1056
|
+
```typescript
|
|
1057
|
+
// Pre-calculate layout quality
|
|
1058
|
+
const quality = layoutQuality(graph, positions);
|
|
1059
|
+
// Use interpolation for smooth updates
|
|
1060
|
+
const frames = interpolateLayouts(oldPositions, newPositions, 30);
|
|
1061
|
+
```
|
|
1075
1062
|
|
|
1076
1063
|
4. **Memory Optimization**:
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1064
|
+
```typescript
|
|
1065
|
+
// For very large graphs, use generators
|
|
1066
|
+
function* layoutInChunks(graph, chunkSize = 100) {
|
|
1067
|
+
const nodes = graph.nodes();
|
|
1068
|
+
for (let i = 0; i < nodes.length; i += chunkSize) {
|
|
1069
|
+
const chunk = nodes.slice(i, i + chunkSize);
|
|
1070
|
+
// Process chunk...
|
|
1071
|
+
yield chunk;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
```
|
|
1088
1075
|
|
|
1089
1076
|
## Development
|
|
1090
1077
|
|
|
@@ -1113,6 +1100,7 @@ npm run serve
|
|
|
1113
1100
|
```
|
|
1114
1101
|
|
|
1115
1102
|
This will:
|
|
1103
|
+
|
|
1116
1104
|
1. Build the bundled `dist/layout.js`
|
|
1117
1105
|
2. Start a Vite dev server at http://localhost:3000
|
|
1118
1106
|
3. Automatically redirect imports to use the bundled version
|
|
@@ -1127,6 +1115,7 @@ npm run build:gh-pages
|
|
|
1127
1115
|
```
|
|
1128
1116
|
|
|
1129
1117
|
This creates a `gh-pages/` directory with:
|
|
1118
|
+
|
|
1130
1119
|
- All example HTML files
|
|
1131
1120
|
- The bundled `layout.js`
|
|
1132
1121
|
- All necessary assets
|
|
@@ -1144,6 +1133,7 @@ To deploy to GitHub Pages, see `gh-pages/DEPLOY.md` after building.
|
|
|
1144
1133
|
## Recent Updates
|
|
1145
1134
|
|
|
1146
1135
|
### Version 1.2.7 (Latest)
|
|
1136
|
+
|
|
1147
1137
|
- **Fixed**: ForceAtlas2 now correctly returns numeric Z coordinates in 3D mode (previously returned NaN)
|
|
1148
1138
|
- **Fixed**: NPM package now uses the correct bundled entry point (`dist/layout.js`)
|
|
1149
1139
|
- **Added**: Full 3D support documentation and examples
|