@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.
Files changed (237) hide show
  1. package/README.md +400 -409
  2. package/dist/layout.js +884 -817
  3. package/dist/layout.js.map +1 -1
  4. package/dist/src/algorithms/index.d.ts +3 -2
  5. package/dist/src/algorithms/index.d.ts.map +1 -0
  6. package/dist/src/algorithms/index.js +2 -2
  7. package/dist/src/algorithms/index.js.map +1 -1
  8. package/dist/src/algorithms/optimization/index.d.ts +5 -4
  9. package/dist/src/algorithms/optimization/index.d.ts.map +1 -0
  10. package/dist/src/algorithms/optimization/index.js +3 -3
  11. package/dist/src/algorithms/optimization/index.js.map +1 -1
  12. package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts +3 -5
  13. package/dist/src/algorithms/optimization/kamada-kawai-solver.d.ts.map +1 -0
  14. package/dist/src/algorithms/optimization/kamada-kawai-solver.js +6 -9
  15. package/dist/src/algorithms/optimization/kamada-kawai-solver.js.map +1 -1
  16. package/dist/src/algorithms/optimization/lbfgs.d.ts +3 -3
  17. package/dist/src/algorithms/optimization/lbfgs.d.ts.map +1 -0
  18. package/dist/src/algorithms/optimization/lbfgs.js +5 -7
  19. package/dist/src/algorithms/optimization/lbfgs.js.map +1 -1
  20. package/dist/src/algorithms/optimization/line-search.d.ts +1 -1
  21. package/dist/src/algorithms/optimization/line-search.d.ts.map +1 -0
  22. package/dist/src/algorithms/optimization/line-search.js +0 -1
  23. package/dist/src/algorithms/optimization/line-search.js.map +1 -1
  24. package/dist/src/algorithms/optimization/types.d.ts +2 -1
  25. package/dist/src/algorithms/optimization/types.d.ts.map +1 -0
  26. package/dist/src/algorithms/planarity/check.d.ts +5 -4
  27. package/dist/src/algorithms/planarity/check.d.ts.map +1 -0
  28. package/dist/src/algorithms/planarity/check.js +6 -6
  29. package/dist/src/algorithms/planarity/check.js.map +1 -1
  30. package/dist/src/algorithms/planarity/embedding.d.ts +5 -6
  31. package/dist/src/algorithms/planarity/embedding.d.ts.map +1 -0
  32. package/dist/src/algorithms/planarity/embedding.js +15 -14
  33. package/dist/src/algorithms/planarity/embedding.js.map +1 -1
  34. package/dist/src/algorithms/planarity/index.d.ts +5 -4
  35. package/dist/src/algorithms/planarity/index.d.ts.map +1 -0
  36. package/dist/src/algorithms/planarity/index.js +4 -4
  37. package/dist/src/algorithms/planarity/index.js.map +1 -1
  38. package/dist/src/algorithms/planarity/lr-test.d.ts +3 -2
  39. package/dist/src/algorithms/planarity/lr-test.d.ts.map +1 -0
  40. package/dist/src/algorithms/planarity/lr-test.js +2 -2
  41. package/dist/src/algorithms/planarity/lr-test.js.map +1 -1
  42. package/dist/src/algorithms/planarity/special-graphs.d.ts +2 -4
  43. package/dist/src/algorithms/planarity/special-graphs.d.ts.map +1 -0
  44. package/dist/src/algorithms/planarity/special-graphs.js +18 -15
  45. package/dist/src/algorithms/planarity/special-graphs.js.map +1 -1
  46. package/dist/src/generators/basic.d.ts +2 -1
  47. package/dist/src/generators/basic.d.ts.map +1 -0
  48. package/dist/src/generators/basic.js +4 -4
  49. package/dist/src/generators/basic.js.map +1 -1
  50. package/dist/src/generators/bipartite.d.ts +2 -1
  51. package/dist/src/generators/bipartite.d.ts.map +1 -0
  52. package/dist/src/generators/bipartite.js +2 -2
  53. package/dist/src/generators/bipartite.js.map +1 -1
  54. package/dist/src/generators/grid.d.ts +2 -1
  55. package/dist/src/generators/grid.d.ts.map +1 -0
  56. package/dist/src/generators/grid.js +1 -1
  57. package/dist/src/generators/grid.js.map +1 -1
  58. package/dist/src/generators/index.d.ts +6 -5
  59. package/dist/src/generators/index.d.ts.map +1 -0
  60. package/dist/src/generators/index.js +5 -5
  61. package/dist/src/generators/index.js.map +1 -1
  62. package/dist/src/generators/random.d.ts +2 -1
  63. package/dist/src/generators/random.d.ts.map +1 -0
  64. package/dist/src/generators/random.js +2 -2
  65. package/dist/src/generators/random.js.map +1 -1
  66. package/dist/src/generators/scale-free.d.ts +2 -1
  67. package/dist/src/generators/scale-free.d.ts.map +1 -0
  68. package/dist/src/generators/scale-free.js +4 -4
  69. package/dist/src/generators/scale-free.js.map +1 -1
  70. package/dist/src/index.d.ts +5 -4
  71. package/dist/src/index.d.ts.map +1 -0
  72. package/dist/src/index.js +4 -4
  73. package/dist/src/layouts/basic/index.d.ts +2 -1
  74. package/dist/src/layouts/basic/index.d.ts.map +1 -0
  75. package/dist/src/layouts/basic/index.js +1 -1
  76. package/dist/src/layouts/basic/random.d.ts +2 -2
  77. package/dist/src/layouts/basic/random.d.ts.map +1 -0
  78. package/dist/src/layouts/basic/random.js +4 -5
  79. package/dist/src/layouts/basic/random.js.map +1 -1
  80. package/dist/src/layouts/force-directed/arf.d.ts +2 -2
  81. package/dist/src/layouts/force-directed/arf.d.ts.map +1 -0
  82. package/dist/src/layouts/force-directed/arf.js +13 -8
  83. package/dist/src/layouts/force-directed/arf.js.map +1 -1
  84. package/dist/src/layouts/force-directed/forceatlas2.d.ts +4 -4
  85. package/dist/src/layouts/force-directed/forceatlas2.d.ts.map +1 -0
  86. package/dist/src/layouts/force-directed/forceatlas2.js +83 -51
  87. package/dist/src/layouts/force-directed/forceatlas2.js.map +1 -1
  88. package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts +12 -12
  89. package/dist/src/layouts/force-directed/fruchterman-reingold.d.ts.map +1 -0
  90. package/dist/src/layouts/force-directed/fruchterman-reingold.js +18 -18
  91. package/dist/src/layouts/force-directed/fruchterman-reingold.js.map +1 -1
  92. package/dist/src/layouts/force-directed/index.d.ts +6 -5
  93. package/dist/src/layouts/force-directed/index.d.ts.map +1 -0
  94. package/dist/src/layouts/force-directed/index.js +5 -5
  95. package/dist/src/layouts/force-directed/index.js.map +1 -1
  96. package/dist/src/layouts/force-directed/kamada-kawai.d.ts +3 -3
  97. package/dist/src/layouts/force-directed/kamada-kawai.d.ts.map +1 -0
  98. package/dist/src/layouts/force-directed/kamada-kawai.js +13 -11
  99. package/dist/src/layouts/force-directed/kamada-kawai.js.map +1 -1
  100. package/dist/src/layouts/force-directed/spring.d.ts +12 -12
  101. package/dist/src/layouts/force-directed/spring.d.ts.map +1 -0
  102. package/dist/src/layouts/force-directed/spring.js +11 -12
  103. package/dist/src/layouts/force-directed/spring.js.map +1 -1
  104. package/dist/src/layouts/geometric/circular.d.ts +2 -2
  105. package/dist/src/layouts/geometric/circular.d.ts.map +1 -0
  106. package/dist/src/layouts/geometric/circular.js +11 -10
  107. package/dist/src/layouts/geometric/circular.js.map +1 -1
  108. package/dist/src/layouts/geometric/index.d.ts +4 -3
  109. package/dist/src/layouts/geometric/index.d.ts.map +1 -0
  110. package/dist/src/layouts/geometric/index.js +3 -3
  111. package/dist/src/layouts/geometric/shell.d.ts +2 -2
  112. package/dist/src/layouts/geometric/shell.d.ts.map +1 -0
  113. package/dist/src/layouts/geometric/shell.js +6 -6
  114. package/dist/src/layouts/geometric/shell.js.map +1 -1
  115. package/dist/src/layouts/geometric/spiral.d.ts +2 -2
  116. package/dist/src/layouts/geometric/spiral.d.ts.map +1 -0
  117. package/dist/src/layouts/geometric/spiral.js +6 -10
  118. package/dist/src/layouts/geometric/spiral.js.map +1 -1
  119. package/dist/src/layouts/hierarchical/bfs.d.ts +3 -3
  120. package/dist/src/layouts/hierarchical/bfs.d.ts.map +1 -0
  121. package/dist/src/layouts/hierarchical/bfs.js +6 -7
  122. package/dist/src/layouts/hierarchical/bfs.js.map +1 -1
  123. package/dist/src/layouts/hierarchical/bipartite.d.ts +3 -3
  124. package/dist/src/layouts/hierarchical/bipartite.d.ts.map +1 -0
  125. package/dist/src/layouts/hierarchical/bipartite.js +9 -10
  126. package/dist/src/layouts/hierarchical/bipartite.js.map +1 -1
  127. package/dist/src/layouts/hierarchical/index.d.ts +4 -3
  128. package/dist/src/layouts/hierarchical/index.d.ts.map +1 -0
  129. package/dist/src/layouts/hierarchical/index.js +3 -3
  130. package/dist/src/layouts/hierarchical/index.js.map +1 -1
  131. package/dist/src/layouts/hierarchical/multipartite.d.ts +3 -3
  132. package/dist/src/layouts/hierarchical/multipartite.d.ts.map +1 -0
  133. package/dist/src/layouts/hierarchical/multipartite.js +9 -10
  134. package/dist/src/layouts/hierarchical/multipartite.js.map +1 -1
  135. package/dist/src/layouts/index.d.ts +6 -5
  136. package/dist/src/layouts/index.d.ts.map +1 -0
  137. package/dist/src/layouts/index.js +5 -5
  138. package/dist/src/layouts/index.js.map +1 -1
  139. package/dist/src/layouts/specialized/index.d.ts +3 -2
  140. package/dist/src/layouts/specialized/index.d.ts.map +1 -0
  141. package/dist/src/layouts/specialized/index.js +2 -2
  142. package/dist/src/layouts/specialized/index.js.map +1 -1
  143. package/dist/src/layouts/specialized/planar.d.ts +2 -2
  144. package/dist/src/layouts/specialized/planar.d.ts.map +1 -0
  145. package/dist/src/layouts/specialized/planar.js +6 -7
  146. package/dist/src/layouts/specialized/planar.js.map +1 -1
  147. package/dist/src/layouts/specialized/spectral.d.ts +2 -2
  148. package/dist/src/layouts/specialized/spectral.d.ts.map +1 -0
  149. package/dist/src/layouts/specialized/spectral.js +31 -21
  150. package/dist/src/layouts/specialized/spectral.js.map +1 -1
  151. package/dist/src/types/embedding.d.ts +2 -1
  152. package/dist/src/types/embedding.d.ts.map +1 -0
  153. package/dist/src/types/graph.d.ts +2 -1
  154. package/dist/src/types/graph.d.ts.map +1 -0
  155. package/dist/src/types/index.d.ts +4 -3
  156. package/dist/src/types/index.d.ts.map +1 -0
  157. package/dist/src/types/index.js +3 -3
  158. package/dist/src/types/index.js.map +1 -1
  159. package/dist/src/types/layout.d.ts +2 -1
  160. package/dist/src/types/layout.d.ts.map +1 -0
  161. package/dist/src/utils/graph.d.ts +2 -5
  162. package/dist/src/utils/graph.d.ts.map +1 -0
  163. package/dist/src/utils/graph.js +4 -6
  164. package/dist/src/utils/graph.js.map +1 -1
  165. package/dist/src/utils/index.d.ts +6 -5
  166. package/dist/src/utils/index.d.ts.map +1 -0
  167. package/dist/src/utils/index.js +5 -5
  168. package/dist/src/utils/index.js.map +1 -1
  169. package/dist/src/utils/numpy.d.ts +5 -4
  170. package/dist/src/utils/numpy.d.ts.map +1 -0
  171. package/dist/src/utils/numpy.js +22 -16
  172. package/dist/src/utils/numpy.js.map +1 -1
  173. package/dist/src/utils/params.d.ts +2 -2
  174. package/dist/src/utils/params.d.ts.map +1 -0
  175. package/dist/src/utils/params.js +0 -1
  176. package/dist/src/utils/params.js.map +1 -1
  177. package/dist/src/utils/random.d.ts +18 -0
  178. package/dist/src/utils/random.d.ts.map +1 -0
  179. package/dist/src/utils/random.js +18 -1
  180. package/dist/src/utils/random.js.map +1 -1
  181. package/dist/src/utils/rescale.d.ts +2 -3
  182. package/dist/src/utils/rescale.d.ts.map +1 -0
  183. package/dist/src/utils/rescale.js +12 -10
  184. package/dist/src/utils/rescale.js.map +1 -1
  185. package/dist/tsconfig.tsbuildinfo +1 -0
  186. package/package.json +35 -34
  187. package/src/algorithms/index.ts +2 -2
  188. package/src/algorithms/optimization/index.ts +4 -8
  189. package/src/algorithms/optimization/kamada-kawai-solver.ts +170 -179
  190. package/src/algorithms/optimization/lbfgs.ts +42 -49
  191. package/src/algorithms/optimization/line-search.ts +27 -28
  192. package/src/algorithms/optimization/types.ts +2 -2
  193. package/src/algorithms/planarity/check.ts +23 -23
  194. package/src/algorithms/planarity/embedding.ts +160 -171
  195. package/src/algorithms/planarity/index.ts +4 -8
  196. package/src/algorithms/planarity/lr-test.ts +46 -46
  197. package/src/algorithms/planarity/special-graphs.ts +73 -83
  198. package/src/generators/basic.ts +57 -57
  199. package/src/generators/bipartite.ts +37 -31
  200. package/src/generators/grid.ts +28 -28
  201. package/src/generators/index.ts +5 -11
  202. package/src/generators/random.ts +26 -25
  203. package/src/generators/scale-free.ts +56 -55
  204. package/src/index.ts +5 -5
  205. package/src/layouts/basic/index.ts +1 -1
  206. package/src/layouts/basic/random.ts +20 -16
  207. package/src/layouts/force-directed/arf.ts +107 -105
  208. package/src/layouts/force-directed/forceatlas2.ts +374 -352
  209. package/src/layouts/force-directed/fruchterman-reingold.ts +133 -134
  210. package/src/layouts/force-directed/index.ts +5 -5
  211. package/src/layouts/force-directed/kamada-kawai.ts +77 -80
  212. package/src/layouts/force-directed/spring.ts +24 -25
  213. package/src/layouts/geometric/circular.ts +63 -57
  214. package/src/layouts/geometric/index.ts +3 -3
  215. package/src/layouts/geometric/shell.ts +58 -53
  216. package/src/layouts/geometric/spiral.ts +60 -64
  217. package/src/layouts/hierarchical/bfs.ts +58 -59
  218. package/src/layouts/hierarchical/bipartite.ts +62 -63
  219. package/src/layouts/hierarchical/index.ts +3 -3
  220. package/src/layouts/hierarchical/multipartite.ts +62 -63
  221. package/src/layouts/index.ts +5 -5
  222. package/src/layouts/specialized/index.ts +2 -2
  223. package/src/layouts/specialized/planar.ts +41 -42
  224. package/src/layouts/specialized/spectral.ts +110 -99
  225. package/src/types/embedding.ts +5 -5
  226. package/src/types/graph.ts +4 -4
  227. package/src/types/index.ts +3 -3
  228. package/src/types/layout.ts +2 -2
  229. package/src/utils/graph.ts +31 -35
  230. package/src/utils/index.ts +5 -5
  231. package/src/utils/numpy.ts +96 -92
  232. package/src/utils/params.ts +14 -11
  233. package/src/utils/random.ts +57 -40
  234. package/src/utils/rescale.ts +169 -174
  235. package/dist/vitest.config.d.ts +0 -2
  236. package/dist/vitest.config.js +0 -30
  237. package/dist/vitest.config.js.map +0 -1
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
- # Layout
1
+ # @graphty/layout
2
2
 
3
- [![CI](https://github.com/graphty-org/layout/actions/workflows/ci.yml/badge.svg)](https://github.com/graphty-org/layout/actions/workflows/ci.yml)
4
- [![Coverage Status](https://coveralls.io/repos/github/graphty-org/layout/badge.svg?branch=main)](https://coveralls.io/github/graphty-org/layout?branch=main)
5
- [![npm version](https://badge.fury.io/js/%40graphty%2Flayout.svg)](https://badge.fury.io/js/%40graphty%2Flayout)
3
+ [![CI](https://github.com/graphty-org/graphty-monorepo/actions/workflows/ci.yml/badge.svg)](https://github.com/graphty-org/graphty-monorepo/actions/workflows/ci.yml)
4
+ [![Coverage Status](https://coveralls.io/repos/github/graphty-org/graphty-monorepo/badge.svg?branch=master)](https://coveralls.io/github/graphty-org/graphty-monorepo?branch=master)
5
+ [![npm version](https://img.shields.io/npm/v/@graphty/layout.svg)](https://www.npmjs.com/package/@graphty/layout)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
- [![Examples](https://img.shields.io/badge/demo-github%20pages-blue)](https://graphty-org.github.io/layout/examples/index.html)
7
+ [![Documentation](https://img.shields.io/badge/docs-vitepress-blue)](https://graphty.app/docs/layout/)
8
+ [![Examples](https://img.shields.io/badge/demo-github%20pages-blue)](https://graphty.app/layout/examples/index.html)
8
9
 
9
- **[View Interactive Examples →](https://graphty-org.github.io/layout/examples/index.html)**
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
- // 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'
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
- const positions = bipartiteLayout(graph, bipartite.setA)
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, 'degree', 3)
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
- 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
- }
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
- network,
257
- null,
258
- networkConfig.iterations,
259
- 1.0,
260
- networkConfig.scalingRatio,
261
- networkConfig.gravity
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, 'degree', 3)
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, 'bfs', 0, { root: 'A' })
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, 'k-core')
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, 'community', 5)
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
- // Graph is bipartite! Use specialized layout
302
- const positions = bipartiteLayout(graph, result.setA)
296
+ // Graph is bipartite! Use specialized layout
297
+ const positions = bipartiteLayout(graph, result.setA);
303
298
  } else {
304
- // Not bipartite, use general layout
305
- const positions = springLayout(graph)
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('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)
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 ? 'bfs' : density > 0.5 ? 'k-core' : 'degree'
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
- const { setA } = detectBipartite(graph)
403
- positions = bipartiteLayout(graph, setA)
390
+ const { setA } = detectBipartite(graph);
391
+ positions = bipartiteLayout(graph, setA);
404
392
  } else if (graph.nodes().length > 100) {
405
- // Large graph - use fast layout
406
- positions = circularLayout(graph)
393
+ // Large graph - use fast layout
394
+ positions = circularLayout(graph);
407
395
  } else {
408
- // Default to auto-configured force layout
409
- const config = autoConfigureForce(graph)
410
- positions = springLayout(graph, config.k, null, null, config.iterations)
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
- graph,
459
- config.k, // optimal distance
460
- null, // initial positions
461
- null, // fixed nodes
462
- config.iterations // iterations
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, 'vertical')
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
- const positions2 = bipartiteLayout(graph, detected.setA, 'horizontal')
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
- nodes?: () => Node[]
521
- edges?: () => Edge[]
522
- getEdgeData?: (source: Node, target: Node, attr: string) => any
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 './layout.js'
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, 'degree', 3)
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, 'k-core')
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
- graph,
610
- config.k, // optimal distance
611
- null, // initial positions
612
- null, // fixed nodes
613
- config.iterations // iterations
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
- graph,
633
- 1, // scale
634
- [0, 0], // center
635
- 2, // dim
636
- 0.35, // resolution
637
- true // equidistant points
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
- 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
- )
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, 'bfs', 0, { root: findBestRoot(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
- layerMap[i] = nodes
667
- })
654
+ layerMap[i] = nodes;
655
+ });
668
656
 
669
657
  const positions = multipartiteLayout(
670
- graph,
671
- layerMap, // subsetKey: layer mapping
672
- 'vertical', // align
673
- 1, // scale
674
- [0, 0] // center
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
- graph,
687
- root, // start: best root node
688
- 'vertical', // align
689
- 1, // scale
690
- [0, 0] // center
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
- const positions = planarLayout(graph)
692
+ const positions = planarLayout(graph);
705
693
  } else {
706
- // Fall back to non-planar layout
707
- const positions = springLayout(graph)
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
- 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
- )
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
- 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
- )
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
- 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
- )
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
- 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
- )
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, 'community', 4)
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
- nodes.forEach(node => communityMap.set(node, idx))
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
- 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
- }
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
- const positions = frames[frameIndex]
856
- // Update your visualization with these positions
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, 'k-core')
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
- 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
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
- const positions = planarLayout(nonPlanarGraph)
914
+ const positions = planarLayout(nonPlanarGraph);
927
915
  } catch (error) {
928
- console.error('Graph is not planar:', error.message)
916
+ console.error("Graph is not planar:", error.message);
929
917
  }
930
918
 
931
919
  try {
932
- const positions = arfLayout(graph, null, 1, 0.5) // a <= 1
920
+ const positions = arfLayout(graph, null, 1, 0.5); // a <= 1
933
921
  } catch (error) {
934
- console.error('Invalid parameter a:', error.message)
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
- ```bash
951
- git clone https://github.com/graphty-org/layout.git
952
- cd layout
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
- ```bash
958
- npm install
959
- ```
945
+ ```bash
946
+ npm install
947
+ ```
960
948
 
961
949
  3. **Compile TypeScript to JavaScript:**
962
950
 
963
- ```bash
964
- npm run build
965
- ```
951
+ ```bash
952
+ npm run build
953
+ ```
966
954
 
967
- This will compile the `layout.ts` file to JavaScript and generate type declarations in the `dist/` directory.
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
- ```bash
972
- npm run dev
973
- ```
959
+ ```bash
960
+ npm run dev
961
+ ```
974
962
 
975
- This will watch for changes and automatically recompile the TypeScript files.
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
- ```bash
1000
- cp .env.example .env
1001
- ```
987
+ ```bash
988
+ cp .env.example .env
989
+ ```
1002
990
 
1003
991
  2. **Configure server options in `.env`:**
1004
992
 
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
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
- # Server port (defaults to 3000)
1012
- PORT=3000
1013
- PORT=8080 # Custom port
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
- ```bash
1018
- npm run serve
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
- ```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
- ```
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
- ```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
- ```
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
- ```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
- ```
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
- ```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
- ```
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