@graphty/layout 1.2.9 → 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.
Files changed (185) hide show
  1. package/README.md +393 -403
  2. package/dist/layout.js +867 -808
  3. package/dist/layout.js.map +1 -1
  4. package/dist/src/algorithms/index.d.ts +2 -2
  5. package/dist/src/algorithms/index.js +2 -2
  6. package/dist/src/algorithms/index.js.map +1 -1
  7. package/dist/src/algorithms/optimization/index.d.ts +4 -4
  8. package/dist/src/algorithms/optimization/index.js +3 -3
  9. package/dist/src/algorithms/optimization/index.js.map +1 -1
  10. package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +2 -5
  11. package/dist/src/algorithms/optimization/kamada-kawai-solver.js +6 -9
  12. package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -1
  13. package/dist/src/algorithms/optimization/lbfgs.d.ts +0 -1
  14. package/dist/src/algorithms/optimization/lbfgs.js +3 -5
  15. package/dist/src/algorithms/optimization/lbfgs.js.map +1 -1
  16. package/dist/src/algorithms/optimization/line-search.d.ts +0 -1
  17. package/dist/src/algorithms/optimization/line-search.js +0 -1
  18. package/dist/src/algorithms/optimization/line-search.js.map +1 -1
  19. package/dist/src/algorithms/optimization/types.d.ts +1 -1
  20. package/dist/src/algorithms/planarity/check.d.ts +2 -2
  21. package/dist/src/algorithms/planarity/check.js +4 -4
  22. package/dist/src/algorithms/planarity/check.js.map +1 -1
  23. package/dist/src/algorithms/planarity/embedding.d.ts +2 -4
  24. package/dist/src/algorithms/planarity/embedding.js +13 -12
  25. package/dist/src/algorithms/planarity/embedding.js.map +1 -1
  26. package/dist/src/algorithms/planarity/index.d.ts +4 -4
  27. package/dist/src/algorithms/planarity/index.js +4 -4
  28. package/dist/src/algorithms/planarity/index.js.map +1 -1
  29. package/dist/src/algorithms/planarity/lr-test.d.ts +2 -2
  30. package/dist/src/algorithms/planarity/lr-test.js +2 -2
  31. package/dist/src/algorithms/planarity/lr-test.js.map +1 -1
  32. package/dist/src/algorithms/planarity/special-graphs.d.ts +1 -4
  33. package/dist/src/algorithms/planarity/special-graphs.js +18 -15
  34. package/dist/src/algorithms/planarity/special-graphs.js.map +1 -1
  35. package/dist/src/generators/basic.d.ts +1 -1
  36. package/dist/src/generators/basic.js +4 -4
  37. package/dist/src/generators/basic.js.map +1 -1
  38. package/dist/src/generators/bipartite.d.ts +1 -1
  39. package/dist/src/generators/bipartite.js +2 -2
  40. package/dist/src/generators/bipartite.js.map +1 -1
  41. package/dist/src/generators/grid.d.ts +1 -1
  42. package/dist/src/generators/grid.js +1 -1
  43. package/dist/src/generators/grid.js.map +1 -1
  44. package/dist/src/generators/index.d.ts +5 -5
  45. package/dist/src/generators/index.js +5 -5
  46. package/dist/src/generators/index.js.map +1 -1
  47. package/dist/src/generators/random.d.ts +1 -1
  48. package/dist/src/generators/random.js +2 -2
  49. package/dist/src/generators/random.js.map +1 -1
  50. package/dist/src/generators/scale-free.d.ts +1 -1
  51. package/dist/src/generators/scale-free.js +4 -4
  52. package/dist/src/generators/scale-free.js.map +1 -1
  53. package/dist/src/index.d.ts +4 -4
  54. package/dist/src/index.js +4 -4
  55. package/dist/src/layouts/basic/index.d.ts +1 -1
  56. package/dist/src/layouts/basic/index.js +1 -1
  57. package/dist/src/layouts/basic/random.d.ts +1 -2
  58. package/dist/src/layouts/basic/random.js +3 -4
  59. package/dist/src/layouts/basic/random.js.map +1 -1
  60. package/dist/src/layouts/force-directed/arf.d.ts +1 -2
  61. package/dist/src/layouts/force-directed/arf.js +13 -8
  62. package/dist/src/layouts/force-directed/arf.js.map +1 -1
  63. package/dist/src/layouts/force-directed/forceatlas2.d.ts +1 -2
  64. package/dist/src/layouts/force-directed/forceatlas2.js +58 -35
  65. package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -1
  66. package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +11 -12
  67. package/dist/src/layouts/force-directed/fruchterman-reingold.js +17 -17
  68. package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -1
  69. package/dist/src/layouts/force-directed/index.d.ts +5 -5
  70. package/dist/src/layouts/force-directed/index.js +5 -5
  71. package/dist/src/layouts/force-directed/index.js.map +1 -1
  72. package/dist/src/layouts/force-directed/kamada-kawai.d.ts +2 -3
  73. package/dist/src/layouts/force-directed/kamada-kawai.js +12 -10
  74. package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -1
  75. package/dist/src/layouts/force-directed/spring.d.ts +11 -12
  76. package/dist/src/layouts/force-directed/spring.js +11 -12
  77. package/dist/src/layouts/force-directed/spring.js.map +1 -1
  78. package/dist/src/layouts/geometric/circular.d.ts +1 -2
  79. package/dist/src/layouts/geometric/circular.js +10 -9
  80. package/dist/src/layouts/geometric/circular.js.map +1 -1
  81. package/dist/src/layouts/geometric/index.d.ts +3 -3
  82. package/dist/src/layouts/geometric/index.js +3 -3
  83. package/dist/src/layouts/geometric/shell.d.ts +1 -2
  84. package/dist/src/layouts/geometric/shell.js +5 -5
  85. package/dist/src/layouts/geometric/shell.js.map +1 -1
  86. package/dist/src/layouts/geometric/spiral.d.ts +1 -2
  87. package/dist/src/layouts/geometric/spiral.js +5 -9
  88. package/dist/src/layouts/geometric/spiral.js.map +1 -1
  89. package/dist/src/layouts/hierarchical/bfs.d.ts +2 -3
  90. package/dist/src/layouts/hierarchical/bfs.js +5 -6
  91. package/dist/src/layouts/hierarchical/bfs.js.map +1 -1
  92. package/dist/src/layouts/hierarchical/bipartite.d.ts +2 -3
  93. package/dist/src/layouts/hierarchical/bipartite.js +8 -9
  94. package/dist/src/layouts/hierarchical/bipartite.js.map +1 -1
  95. package/dist/src/layouts/hierarchical/index.d.ts +3 -3
  96. package/dist/src/layouts/hierarchical/index.js +3 -3
  97. package/dist/src/layouts/hierarchical/index.js.map +1 -1
  98. package/dist/src/layouts/hierarchical/multipartite.d.ts +2 -3
  99. package/dist/src/layouts/hierarchical/multipartite.js +7 -8
  100. package/dist/src/layouts/hierarchical/multipartite.js.map +1 -1
  101. package/dist/src/layouts/index.d.ts +5 -5
  102. package/dist/src/layouts/index.js +5 -5
  103. package/dist/src/layouts/index.js.map +1 -1
  104. package/dist/src/layouts/specialized/index.d.ts +2 -2
  105. package/dist/src/layouts/specialized/index.js +2 -2
  106. package/dist/src/layouts/specialized/index.js.map +1 -1
  107. package/dist/src/layouts/specialized/planar.d.ts +1 -2
  108. package/dist/src/layouts/specialized/planar.js +5 -6
  109. package/dist/src/layouts/specialized/planar.js.map +1 -1
  110. package/dist/src/layouts/specialized/spectral.d.ts +1 -2
  111. package/dist/src/layouts/specialized/spectral.js +30 -20
  112. package/dist/src/layouts/specialized/spectral.js.map +1 -1
  113. package/dist/src/types/embedding.d.ts +1 -1
  114. package/dist/src/types/index.d.ts +3 -3
  115. package/dist/src/types/index.js +3 -3
  116. package/dist/src/types/index.js.map +1 -1
  117. package/dist/src/types/layout.d.ts +1 -1
  118. package/dist/src/utils/graph.d.ts +1 -5
  119. package/dist/src/utils/graph.js +4 -6
  120. package/dist/src/utils/graph.js.map +1 -1
  121. package/dist/src/utils/index.d.ts +5 -5
  122. package/dist/src/utils/index.js +5 -5
  123. package/dist/src/utils/index.js.map +1 -1
  124. package/dist/src/utils/numpy.js +22 -16
  125. package/dist/src/utils/numpy.js.map +1 -1
  126. package/dist/src/utils/params.d.ts +1 -2
  127. package/dist/src/utils/params.js +0 -1
  128. package/dist/src/utils/params.js.map +1 -1
  129. package/dist/src/utils/random.d.ts +17 -0
  130. package/dist/src/utils/random.js +18 -1
  131. package/dist/src/utils/random.js.map +1 -1
  132. package/dist/src/utils/rescale.d.ts +1 -3
  133. package/dist/src/utils/rescale.js +12 -10
  134. package/dist/src/utils/rescale.js.map +1 -1
  135. package/dist/vitest.config.js +14 -14
  136. package/dist/vitest.config.js.map +1 -1
  137. package/package.json +29 -24
  138. package/src/algorithms/index.ts +2 -2
  139. package/src/algorithms/optimization/index.ts +4 -8
  140. package/src/algorithms/optimization/kamada-kawai-solver.ts +170 -179
  141. package/src/algorithms/optimization/lbfgs.ts +41 -48
  142. package/src/algorithms/optimization/line-search.ts +27 -28
  143. package/src/algorithms/optimization/types.ts +2 -2
  144. package/src/algorithms/planarity/check.ts +22 -22
  145. package/src/algorithms/planarity/embedding.ts +159 -170
  146. package/src/algorithms/planarity/index.ts +4 -8
  147. package/src/algorithms/planarity/lr-test.ts +46 -46
  148. package/src/algorithms/planarity/special-graphs.ts +73 -83
  149. package/src/generators/basic.ts +57 -57
  150. package/src/generators/bipartite.ts +37 -31
  151. package/src/generators/grid.ts +28 -28
  152. package/src/generators/index.ts +5 -11
  153. package/src/generators/random.ts +26 -25
  154. package/src/generators/scale-free.ts +56 -55
  155. package/src/index.ts +5 -5
  156. package/src/layouts/basic/index.ts +1 -1
  157. package/src/layouts/basic/random.ts +20 -16
  158. package/src/layouts/force-directed/arf.ts +107 -105
  159. package/src/layouts/force-directed/forceatlas2.ts +365 -351
  160. package/src/layouts/force-directed/fruchterman-reingold.ts +133 -134
  161. package/src/layouts/force-directed/index.ts +5 -5
  162. package/src/layouts/force-directed/kamada-kawai.ts +78 -80
  163. package/src/layouts/force-directed/spring.ts +24 -25
  164. package/src/layouts/geometric/circular.ts +63 -57
  165. package/src/layouts/geometric/index.ts +3 -3
  166. package/src/layouts/geometric/shell.ts +58 -53
  167. package/src/layouts/geometric/spiral.ts +60 -64
  168. package/src/layouts/hierarchical/bfs.ts +58 -59
  169. package/src/layouts/hierarchical/bipartite.ts +62 -63
  170. package/src/layouts/hierarchical/index.ts +3 -3
  171. package/src/layouts/hierarchical/multipartite.ts +62 -63
  172. package/src/layouts/index.ts +5 -5
  173. package/src/layouts/specialized/index.ts +2 -2
  174. package/src/layouts/specialized/planar.ts +41 -42
  175. package/src/layouts/specialized/spectral.ts +110 -99
  176. package/src/types/embedding.ts +5 -5
  177. package/src/types/graph.ts +4 -4
  178. package/src/types/index.ts +3 -3
  179. package/src/types/layout.ts +2 -2
  180. package/src/utils/graph.ts +31 -35
  181. package/src/utils/index.ts +5 -5
  182. package/src/utils/numpy.ts +96 -92
  183. package/src/utils/params.ts +14 -11
  184. package/src/utils/random.ts +57 -40
  185. package/src/utils/rescale.ts +169 -174
package/README.md CHANGED
@@ -63,62 +63,62 @@ Import the library in your TypeScript/JavaScript project:
63
63
 
64
64
  ```typescript
65
65
  import {
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'
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
- const positions = bipartiteLayout(graph, bipartite.setA)
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, 'degree', 3)
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
- 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
- }
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
- network,
257
- null,
258
- networkConfig.iterations,
259
- 1.0,
260
- networkConfig.scalingRatio,
261
- networkConfig.gravity
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, 'degree', 3)
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, 'bfs', 0, { root: 'A' })
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, 'k-core')
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, 'community', 5)
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
- // Graph is bipartite! Use specialized layout
302
- const positions = bipartiteLayout(graph, result.setA)
295
+ // Graph is bipartite! Use specialized layout
296
+ const positions = bipartiteLayout(graph, result.setA);
303
297
  } else {
304
- // Not bipartite, use general layout
305
- const positions = springLayout(graph)
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('Circular layout - avg edge length:', metricsC.avgEdgeLength)
351
- console.log('Spring layout - avg edge length:', metricsS.avgEdgeLength)
352
- console.log('Spring layout - min node distance:', metricsS.minNodeDistance)
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 ? 'bfs' : density > 0.5 ? 'k-core' : 'degree'
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
- const { setA } = detectBipartite(graph)
403
- positions = bipartiteLayout(graph, setA)
389
+ const { setA } = detectBipartite(graph);
390
+ positions = bipartiteLayout(graph, setA);
404
391
  } else if (graph.nodes().length > 100) {
405
- // Large graph - use fast layout
406
- positions = circularLayout(graph)
392
+ // Large graph - use fast layout
393
+ positions = circularLayout(graph);
407
394
  } else {
408
- // Default to auto-configured force layout
409
- const config = autoConfigureForce(graph)
410
- positions = springLayout(graph, config.k, null, null, config.iterations)
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
- graph,
459
- config.k, // optimal distance
460
- null, // initial positions
461
- null, // fixed nodes
462
- config.iterations // iterations
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, 'vertical')
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
- const positions2 = bipartiteLayout(graph, detected.setA, 'horizontal')
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
- nodes?: () => Node[]
521
- edges?: () => Edge[]
522
- getEdgeData?: (source: Node, target: Node, attr: string) => any
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 './layout.js'
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, 'degree', 3)
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, 'k-core')
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
- graph,
610
- config.k, // optimal distance
611
- null, // initial positions
612
- null, // fixed nodes
613
- config.iterations // iterations
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
- graph,
633
- 1, // scale
634
- [0, 0], // center
635
- 2, // dim
636
- 0.35, // resolution
637
- true // equidistant points
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
- graph,
648
- graph.setA, // first group nodes (auto-generated)
649
- 'vertical', // align: 'vertical' or 'horizontal'
650
- 1, // scale
651
- [0, 0], // center
652
- 4 / 3 // aspectRatio
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, 'bfs', 0, { root: findBestRoot(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
- layerMap[i] = nodes
667
- })
653
+ layerMap[i] = nodes;
654
+ });
668
655
 
669
656
  const positions = multipartiteLayout(
670
- graph,
671
- layerMap, // subsetKey: layer mapping
672
- 'vertical', // align
673
- 1, // scale
674
- [0, 0] // center
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
- graph,
687
- root, // start: best root node
688
- 'vertical', // align
689
- 1, // scale
690
- [0, 0] // center
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
- const positions = planarLayout(graph)
691
+ const positions = planarLayout(graph);
705
692
  } else {
706
- // Fall back to non-planar layout
707
- const positions = springLayout(graph)
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
- graph,
718
- null, // dist: distance matrix (auto)
719
- null, // pos: initial positions (auto)
720
- 'weight', // weight: edge weight attribute
721
- 1, // scale
722
- [0, 0], // center
723
- 2 // dim
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
- graph,
736
- null, // pos: initial positions
737
- config.iterations, // maxIter: auto-configured
738
- 1.0, // jitterTolerance
739
- config.scalingRatio, // scalingRatio: auto-configured
740
- config.gravity, // gravity: auto-configured
741
- false, // distributedAction
742
- false, // strongGravity
743
- null, // nodeMass: node masses
744
- null, // nodeSize: node sizes
745
- null, // weight: weight attribute
746
- true, // dissuadeHubs: good for scale-free
747
- false, // linlog: logarithmic attraction
748
- 42, // seed
749
- 2 // dim: 2 for 2D, 3 for 3D
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
- graph,
755
- null,
756
- 100,
757
- 1.0,
758
- 2.0,
759
- 1.0,
760
- false,
761
- false,
762
- null,
763
- null,
764
- null,
765
- false,
766
- false,
767
- 42,
768
- 3 // 3D mode - returns [x, y, z] coordinates
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
- graph,
779
- null, // pos: initial positions
780
- 1, // scaling
781
- 1.1, // a: spring force (must be > 1)
782
- 1000, // maxIter
783
- 42 // seed
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, 'community', 4)
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
- nodes.forEach(node => communityMap.set(node, idx))
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
- const n = graph.nodes().length
811
- const m = graph.edges().length
812
- const density = (2 * m) / (n * (n - 1))
813
-
814
- // Check for special graph types
815
- const bipartite = detectBipartite(graph)
816
- if (bipartite) {
817
- return bipartiteLayout(graph, bipartite.setA)
818
- }
819
-
820
- // Choose based on graph properties
821
- if (n > 100) {
822
- // Large graph - use fast circular layout
823
- return circularLayout(graph)
824
- } else if (density < 0.1) {
825
- // Sparse graph - use spring layout
826
- const config = autoConfigureForce(graph)
827
- return springLayout(graph, config.k, null, null, config.iterations)
828
- } else {
829
- // Dense graph - use spectral or kamada-kawai
830
- return n < 50 ? kamadaKawaiLayout(graph) : spectralLayout(graph)
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
- const positions = frames[frameIndex]
856
- // Update your visualization with these positions
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, 'k-core')
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
- const candidates = [
883
- { name: 'circular', positions: circularLayout(graph) },
884
- { name: 'spectral', positions: spectralLayout(graph) },
885
- { name: 'spring', positions: springLayout(graph) }
886
- ]
887
-
888
- let best = candidates[0]
889
- let bestScore = Infinity
890
-
891
- candidates.forEach(candidate => {
892
- const quality = layoutQuality(graph, candidate.positions)
893
- const score = quality.edgeLengthStdDev / quality.avgEdgeLength
894
-
895
- if (score < bestScore) {
896
- bestScore = score
897
- best = candidate
898
- }
899
- })
900
-
901
- console.log(`Best layout: ${best.name} (score: ${bestScore.toFixed(3)})`)
902
- return best.positions
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
- const positions = planarLayout(nonPlanarGraph)
913
+ const positions = planarLayout(nonPlanarGraph);
927
914
  } catch (error) {
928
- console.error('Graph is not planar:', error.message)
915
+ console.error("Graph is not planar:", error.message);
929
916
  }
930
917
 
931
918
  try {
932
- const positions = arfLayout(graph, null, 1, 0.5) // a <= 1
919
+ const positions = arfLayout(graph, null, 1, 0.5); // a <= 1
933
920
  } catch (error) {
934
- console.error('Invalid parameter a:', error.message)
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
- ```bash
951
- git clone https://github.com/graphty-org/layout.git
952
- cd layout
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
- ```bash
958
- npm install
959
- ```
944
+ ```bash
945
+ npm install
946
+ ```
960
947
 
961
948
  3. **Compile TypeScript to JavaScript:**
962
949
 
963
- ```bash
964
- npm run build
965
- ```
950
+ ```bash
951
+ npm run build
952
+ ```
966
953
 
967
- This will compile the `layout.ts` file to JavaScript and generate type declarations in the `dist/` directory.
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
- ```bash
972
- npm run dev
973
- ```
958
+ ```bash
959
+ npm run dev
960
+ ```
974
961
 
975
- This will watch for changes and automatically recompile the TypeScript files.
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
- ```bash
1000
- cp .env.example .env
1001
- ```
986
+ ```bash
987
+ cp .env.example .env
988
+ ```
1002
989
 
1003
990
  2. **Configure server options in `.env`:**
1004
991
 
1005
- ```bash
1006
- # Server host (defaults to true for network exposure)
1007
- HOST=localhost # For local-only access
1008
- HOST=0.0.0.0 # For network access
1009
- HOST=my.server.com # For custom domain
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
- # Server port (defaults to 3000)
1012
- PORT=3000
1013
- PORT=8080 # Custom port
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
- ```bash
1018
- npm run serve
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
- ```typescript
1051
- // Use fast layouts first
1052
- const initial = circularLayout(graph)
1053
- // Then refine with limited iterations
1054
- const refined = springLayout(graph, null, initial, null, 50)
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
- ```typescript
1060
- // Use spectral layout for dense graphs
1061
- const density = (2 * m) / (n * (n - 1))
1062
- if (density > 0.5) {
1063
- const positions = spectralLayout(graph)
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
- ```typescript
1070
- // Pre-calculate layout quality
1071
- const quality = layoutQuality(graph, positions)
1072
- // Use interpolation for smooth updates
1073
- const frames = interpolateLayouts(oldPositions, newPositions, 30)
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
- ```typescript
1078
- // For very large graphs, use generators
1079
- function* layoutInChunks(graph, chunkSize = 100) {
1080
- const nodes = graph.nodes()
1081
- for (let i = 0; i < nodes.length; i += chunkSize) {
1082
- const chunk = nodes.slice(i, i + chunkSize)
1083
- // Process chunk...
1084
- yield chunk
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