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