@graphty/layout 1.0.0 → 1.1.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.
@@ -0,0 +1,30 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: write
10
+ pages: write
11
+ id-token: write
12
+
13
+ jobs:
14
+ publish:
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ node-version: [24.x]
20
+
21
+ steps:
22
+ - name: Checkout
23
+ uses: actions/checkout@v4
24
+ - name: Semantic Release
25
+ uses: cycjimmy/semantic-release-action@v4
26
+ with:
27
+ branch: main
28
+ env:
29
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
package/.releaserc ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "branches": ["master", "next"]
3
+ }
package/README.md CHANGED
@@ -1,59 +1,402 @@
1
- # Layout.js
1
+ # Layout
2
2
 
3
- Layout.js is a JavaScript library for node positioning in graphs. It is a JavaScript port of the [layout algorithms](https://networkx.org/documentation/stable/reference/drawing.html) found in the Python [NetworkX library](https://networkx.org/documentation/stable/).
3
+ Layout is a TypeScript library for positioning nodes in graphs. It's a TypeScript port of the [layout algorithms](https://networkx.org/documentation/stable/reference/drawing.html) from the Python [NetworkX](https://networkx.org/documentation/stable/) library.
4
4
 
5
5
  ## Features
6
6
 
7
7
  The library offers various graph layout algorithms, including:
8
8
 
9
- - [Random Layout](https://graphty-org.github.io/layout/examples/random-layout.html)
10
- - [Circular Layout](https://graphty-org.github.io/layout/examples/circular-layout.html)
11
- - [Shell Layout](https://graphty-org.github.io/layout/examples/shell-layout.html)
12
- - [Spring Layout (Fruchterman-Reingold)](https://graphty-org.github.io/layout/examples/spring-layout.html)
13
- - [Spectral Layout](https://graphty-org.github.io/layout/examples/spectral-layout.html)
14
- - [Spiral Layout](https://graphty-org.github.io/layout/examples/spiral-layout.html)
15
- - [Bipartite Layout](https://graphty-org.github.io/layout/examples/bipartite-layout.html)
16
- - [Multipartite Layout](https://graphty-org.github.io/layout/examples/multipartite-layout.html)
17
- - [BFS Layout](https://graphty-org.github.io/layout/examples/bfs-html)
18
- - [Planar Layout](https://graphty-org.github.io/layout/examples/planar.html)
19
- - [Kamada-Kawai Layout](https://graphty-org.github.io/layout/examples/kamada-kawai-layout.html)
20
- - [ForceAtlas2 Layout](https://graphty-org.github.io/layout/examples/forceatlas2-layout.html)
21
- - [ARF Layout (Attractive and Repulsive Forces)](https://graphty-org.github.io/layout/examples/arf-layout.html)
9
+ - **Random Layout** - Places nodes randomly in a unit square
10
+ - **Circular Layout** - Places nodes on a circle
11
+ - **Shell Layout** - Places nodes in concentric circles (shells)
12
+ - **Spring Layout (Fruchterman-Reingold)** - Force-directed layout with attractions and repulsions
13
+ - **Spectral Layout** - Uses eigenvectors of the graph's Laplacian matrix
14
+ - **Spiral Layout** - Places nodes along a spiral
15
+ - **Bipartite Layout** - Layout for bipartite graphs in two straight lines
16
+ - **Multipartite Layout** - Layout for multipartite graphs in levels
17
+ - **BFS Layout** - Layout based on breadth-first search algorithm
18
+ - **Planar Layout** - Planar layout without edge crossings
19
+ - **Kamada-Kawai Layout** - Layout based on path-length cost functions
20
+ - **ForceAtlas2 Layout** - Advanced force-directed algorithm
21
+ - **ARF Layout** - Layout with attractive and repulsive forces
22
22
 
23
23
  ## How to Use
24
24
 
25
- Import the library into your JavaScript project:
25
+ Import the library in your TypeScript/JavaScript project:
26
26
 
27
- ```javascript
27
+ ```typescript
28
28
  import {
29
29
  randomLayout,
30
30
  circularLayout,
31
- springLayout
32
- // other layout functions...
31
+ springLayout,
32
+ fruchtermanReingoldLayout,
33
+ spectralLayout,
34
+ spiralLayout,
35
+ bipartiteLayout,
36
+ multipartiteLayout,
37
+ bfsLayout,
38
+ planarLayout,
39
+ kamadaKawaiLayout,
40
+ forceatlas2Layout,
41
+ arfLayout,
42
+ rescaleLayout
33
43
  } from './layout.js'
34
44
  ```
35
45
 
36
- Usage example:
46
+ ## Graph Structure
37
47
 
38
- ```javascript
39
- // Create a graph (data structure with nodes() and edges())
48
+ The module accepts graphs in two formats:
49
+
50
+ ### 1. Graph Object with methods (preferred)
51
+ ```typescript
40
52
  const graph = {
41
53
  nodes: () => [0, 1, 2, 3],
42
- edges: () => [
43
- [0, 1],
44
- [1, 2],
45
- [2, 3],
46
- [3, 0]
47
- ]
54
+ edges: () => [[0, 1], [1, 2], [2, 3], [3, 0]],
55
+ getEdgeData?: (source, target, attr) => number // optional for edge weights
56
+ }
57
+ ```
58
+
59
+ ### 2. Simple array of nodes
60
+ ```typescript
61
+ const nodes = [0, 1, 2, 3]
62
+ ```
63
+
64
+ ## Usage Examples
65
+
66
+ ### Circular Layout
67
+ ```typescript
68
+ const graph = {
69
+ nodes: () => ['A', 'B', 'C', 'D'],
70
+ edges: () => [['A', 'B'], ['B', 'C'], ['C', 'D'], ['D', 'A']]
71
+ }
72
+
73
+ const positions = circularLayout(graph, 1, [0, 0], 2)
74
+ // Output: { A: [1, 0], B: [0, 1], C: [-1, 0], D: [0, -1] }
75
+ ```
76
+
77
+ ### Spring Layout (Fruchterman-Reingold)
78
+ ```typescript
79
+ // Force-directed layout with custom parameters
80
+ const positions = springLayout(
81
+ graph, // graph
82
+ null, // k: optimal distance (auto)
83
+ null, // pos: initial positions (auto)
84
+ ['A'], // fixed: fixed nodes
85
+ 100, // iterations: iterations
86
+ 1, // scale: scale
87
+ [0, 0], // center: center
88
+ 2, // dim: dimensions
89
+ 42 // seed: random seed
90
+ )
91
+
92
+ // or (same function)
93
+ const positions = fruchtermanReingoldLayout(
94
+ graph,
95
+ null, // k: optimal distance
96
+ null, // pos: initial positions
97
+ null, // fixed: fixed nodes
98
+ 50, // iterations
99
+ 1, // scale
100
+ [0, 0], // center
101
+ 2, // dim
102
+ 42 // seed
103
+ )
104
+ ```
105
+
106
+ ### Bipartite graph layout
107
+ ```typescript
108
+ const bipartiteGraph = {
109
+ nodes: () => ['A1', 'A2', 'B1', 'B2', 'B3'],
110
+ edges: () => [['A1', 'B1'], ['A1', 'B2'], ['A2', 'B2'], ['A2', 'B3']]
111
+ }
112
+
113
+ const leftNodes = ['A1', 'A2']
114
+ const positions = bipartiteLayout(bipartiteGraph, leftNodes, 'vertical')
115
+ ```
116
+
117
+ ## Common Parameters
118
+
119
+ Most layout functions share these parameters:
120
+
121
+ - **scale** (number): Scale factor for positions (default: 1)
122
+ - **center** (number[]): Center coordinates around which to center the layout (default: [0, 0])
123
+ - **dim** (number): Layout dimension - 2D or 3D (default: 2)
124
+ - **seed** (number): Seed for random generation (for reproducible layouts)
125
+
126
+ ## TypeScript Types
127
+
128
+ ```typescript
129
+ type Node = string | number
130
+ type Edge = [Node, Node]
131
+ type PositionMap = Record<Node, number[]>
132
+
133
+ interface Graph {
134
+ nodes?: () => Node[]
135
+ edges?: () => Edge[]
136
+ getEdgeData?: (source: Node, target: Node, attr: string) => any
137
+ }
138
+ ```
139
+
140
+ ## Utilities
141
+
142
+ ### Layout Rescaling
143
+ ```typescript
144
+ import { rescaleLayout } from './layout.js'
145
+
146
+ // Rescale existing positions
147
+ const scaledPositions = rescaleLayout(positions, 2.0, [10, 10])
148
+ ```
149
+
150
+ ## Available Algorithms
151
+
152
+ ### Force-Directed Layouts
153
+ - `springLayout()` / `fruchtermanReingoldLayout()` - Classic force-directed algorithm
154
+ - `forceatlas2Layout()` - Advanced algorithm with many configuration options
155
+ - `arfLayout()` - Attractive and repulsive forces
156
+ - `kamadaKawaiLayout()` - Based on shortest-path distances
157
+
158
+ ### Geometric Layouts
159
+ - `randomLayout()` - Random placement
160
+ - `circularLayout()` - Circular arrangement
161
+ - `shellLayout()` - Concentric circles
162
+ - `spiralLayout()` - Spiral arrangement
163
+
164
+ ### Specialized Layouts
165
+ - `spectralLayout()` - Based on eigenvectors of the Laplacian matrix
166
+ - `bipartiteLayout()` - For bipartite graphs
167
+ - `multipartiteLayout()` - For multi-level graphs
168
+ - `bfsLayout()` - Based on breadth-first search
169
+ - `planarLayout()` - For planar graphs without crossings
170
+
171
+ ### Utilities
172
+ - `rescaleLayout()` - Rescale and recenter positions
173
+ - `rescaleLayoutDict()` - Rescale a dictionary of positions
174
+
175
+ ## Detailed Examples for each Algorithm
176
+
177
+ ### Random Layout
178
+ ```typescript
179
+ // Random placement in a unit square [0,1]
180
+ const positions = randomLayout(graph, [0, 0], 2, 42)
181
+ // center: [0, 0], dim: 2, seed: 42
182
+ ```
183
+
184
+ ### Circular Layout
185
+ ```typescript
186
+ // Nodes arranged on a circle
187
+ const positions = circularLayout(graph, 1, [0, 0], 2)
188
+ // scale: 1, center: [0, 0], dim: 2
189
+ ```
190
+
191
+ ### Shell Layout
192
+ ```typescript
193
+ // Nodes in concentric circles
194
+ const shells = [['A'], ['B', 'C'], ['D', 'E', 'F']]
195
+ const positions = shellLayout(graph, shells, 1, [0, 0], 2)
196
+ // nlist: shells, scale: 1, center: [0, 0], dim: 2
197
+ ```
198
+
199
+ ### Spring Layout (Fruchterman-Reingold)
200
+ ```typescript
201
+ // Force layout with custom parameters
202
+ const positions = springLayout(
203
+ graph, // graph
204
+ null, // k: optimal distance (auto)
205
+ null, // pos: initial positions (auto)
206
+ ['A'], // fixed: fixed nodes
207
+ 100, // iterations: iterations
208
+ 1, // scale: scale
209
+ [0, 0], // center: center
210
+ 2, // dim: dimensions
211
+ 42 // seed: random seed
212
+ )
213
+ ```
214
+
215
+ ### Spectral Layout
216
+ ```typescript
217
+ // Layout based on eigenvectors of the Laplacian matrix
218
+ const positions = spectralLayout(graph, 1, [0, 0], 2)
219
+ ```
220
+
221
+ ### Spiral Layout
222
+ ```typescript
223
+ // Spiral arrangement
224
+ const positions = spiralLayout(
225
+ graph,
226
+ 1, // scale
227
+ [0, 0], // center
228
+ 2, // dim
229
+ 0.35, // resolution: spacing control
230
+ false // equidistant: equidistant points
231
+ )
232
+ ```
233
+
234
+ ### Bipartite Layout
235
+ ```typescript
236
+ // For bipartite graphs
237
+ const leftNodes = ['A1', 'A2']
238
+ const positions = bipartiteLayout(
239
+ graph,
240
+ leftNodes, // first group nodes
241
+ 'vertical', // align: 'vertical' or 'horizontal'
242
+ 1, // scale
243
+ [0, 0], // center
244
+ 4/3 // aspectRatio
245
+ )
246
+ ```
247
+
248
+ ### Multipartite Layout
249
+ ```typescript
250
+ // For multi-level graphs
251
+ const layers = {
252
+ 0: ['A1', 'A2'],
253
+ 1: ['B1', 'B2', 'B3'],
254
+ 2: ['C1']
255
+ }
256
+ const positions = multipartiteLayout(
257
+ graph,
258
+ layers, // subsetKey: layer mapping
259
+ 'vertical', // align
260
+ 1, // scale
261
+ [0, 0] // center
262
+ )
263
+ ```
264
+
265
+ ### BFS Layout
266
+ ```typescript
267
+ // Layout based on breadth-first search
268
+ const positions = bfsLayout(
269
+ graph,
270
+ 'A', // start: starting node
271
+ 'vertical', // align
272
+ 1, // scale
273
+ [0, 0] // center
274
+ )
275
+ ```
276
+
277
+ ### Planar Layout
278
+ ```typescript
279
+ // Planar layout (for planar graphs)
280
+ const positions = planarLayout(graph, 1, [0, 0], 2)
281
+ // Note: throws error if graph is not planar
282
+ ```
283
+
284
+ ### Kamada-Kawai Layout
285
+ ```typescript
286
+ // Layout based on shortest path distances
287
+ const positions = kamadaKawaiLayout(
288
+ graph,
289
+ null, // dist: distance matrix (auto)
290
+ null, // pos: initial positions (auto)
291
+ 'weight', // weight: edge weight attribute
292
+ 1, // scale
293
+ [0, 0], // center
294
+ 2 // dim
295
+ )
296
+ ```
297
+
298
+ ### ForceAtlas2 Layout
299
+ ```typescript
300
+ // Advanced force algorithm
301
+ const positions = forceatlas2Layout(
302
+ graph,
303
+ null, // pos: initial positions
304
+ 100, // maxIter: maximum iterations
305
+ 1.0, // jitterTolerance
306
+ 2.0, // scalingRatio
307
+ 1.0, // gravity: attraction towards center
308
+ false, // distributedAction
309
+ false, // strongGravity
310
+ null, // nodeMass: node masses
311
+ null, // nodeSize: node sizes
312
+ null, // weight: weight attribute
313
+ false, // dissuadeHubs
314
+ false, // linlog: logarithmic attraction
315
+ 42, // seed
316
+ 2 // dim
317
+ )
318
+ ```
319
+
320
+ ### ARF Layout
321
+ ```typescript
322
+ // Layout with attractive and repulsive forces
323
+ const positions = arfLayout(
324
+ graph,
325
+ null, // pos: initial positions
326
+ 1, // scaling
327
+ 1.1, // a: spring force (must be > 1)
328
+ 1000, // maxIter
329
+ 42 // seed
330
+ )
331
+ ```
332
+
333
+ ## Error Handling
334
+
335
+ ```typescript
336
+ try {
337
+ const positions = planarLayout(nonPlanarGraph)
338
+ } catch (error) {
339
+ console.error('Graph is not planar:', error.message)
48
340
  }
49
341
 
50
- // Apply a layout
51
- const positions = circularLayout(graph)
342
+ try {
343
+ const positions = arfLayout(graph, null, 1, 0.5) // a <= 1
344
+ } catch (error) {
345
+ console.error('Invalid parameter a:', error.message)
346
+ }
347
+ ```
348
+
349
+ ## Installation
52
350
 
53
- // positions will be an object with x,y coordinates for each node
54
- // { 0: [1, 0], 1: [0, 1], 2: [-1, 0], 3: [0, -1] }
351
+ ```bash
352
+ npm install @graphty/layout
55
353
  ```
56
354
 
57
- ## Requirements
355
+ ## Building from Source
356
+
357
+ If you want to build the TypeScript module from source:
358
+
359
+ 1. **Clone the repository:**
360
+ ```bash
361
+ git clone https://github.com/graphty-org/layout.git
362
+ cd layout
363
+ ```
364
+
365
+ 2. **Install dependencies:**
366
+ ```bash
367
+ npm install
368
+ ```
369
+
370
+ 3. **Compile TypeScript to JavaScript:**
371
+ ```bash
372
+ npm run build
373
+ ```
374
+
375
+ This will compile the `layout.ts` file to JavaScript and generate type declarations in the `dist/` directory.
376
+
377
+ 4. **For development with automatic compilation:**
378
+ ```bash
379
+ npm run dev
380
+ ```
381
+
382
+ This will watch for changes and automatically recompile the TypeScript files.
383
+
384
+ **Note:** The compiled JavaScript files will be available in the `dist/` directory. You can import from the compiled JavaScript files or directly use the TypeScript source files in a TypeScript project.
385
+
386
+ ## Implementation
387
+
388
+ The module includes complete implementations of:
389
+
390
+ - **Random Number Generator** with seed support for reproducible results
391
+ - **Mathematical utilities** similar to NumPy for multidimensional array operations
392
+ - **Force-directed algorithms** with L-BFGS optimization for Kamada-Kawai
393
+ - **Planarity algorithms** including Left-Right test for planar graphs
394
+ - **Auto-scaling system** to automatically normalize positions
395
+
396
+ ## Contributing
397
+
398
+ This project is a TypeScript port of the NetworkX Python library. For contributions and issues, visit the [GitHub repository](https://github.com/graphty-org/layout).
399
+
400
+ ## License
58
401
 
59
- - Modern browser with ES6 support
402
+ MIT License - see LICENSE file for details.
@@ -178,7 +178,7 @@
178
178
  </div>
179
179
 
180
180
  <script type="module">
181
- import { arfLayout } from './layout.js';
181
+ import { arfLayout } from '../dist/layout.js';
182
182
 
183
183
  let currentGraph = null;
184
184
  let currentSeed = Math.floor(Math.random() * 1000);
@@ -149,7 +149,7 @@
149
149
  </div>
150
150
 
151
151
  <script type="module">
152
- import { bfsLayout } from './layout.js';
152
+ import { bfsLayout } from '../dist/layout.js';
153
153
 
154
154
  let currentGraph = null;
155
155
 
@@ -149,7 +149,7 @@
149
149
  </div>
150
150
 
151
151
  <script type="module">
152
- import { bipartiteLayout } from './layout.js';
152
+ import { bipartiteLayout } from '../dist/layout.js';
153
153
 
154
154
  let currentGraph = null;
155
155
 
@@ -136,7 +136,7 @@
136
136
  </div>
137
137
 
138
138
  <script type="module">
139
- import { circularLayout } from './layout.js';
139
+ import { circularLayout } from '../dist/layout.js';
140
140
 
141
141
  let currentGraph = null;
142
142
 
@@ -185,7 +185,7 @@
185
185
  </div>
186
186
 
187
187
  <script type="module">
188
- import { forceatlas2Layout } from './layout.js';
188
+ import { forceatlas2Layout } from '../dist/layout.js';
189
189
 
190
190
  let currentGraph = null;
191
191
  let currentSeed = Math.floor(Math.random() * 1000);
@@ -157,7 +157,7 @@
157
157
  </div>
158
158
 
159
159
  <script type="module">
160
- import { kamadaKawaiLayout } from './layout.js';
160
+ import { kamadaKawaiLayout } from '../dist/layout.js';
161
161
 
162
162
  let currentGraph = null;
163
163
  let distances = null;
@@ -147,7 +147,7 @@
147
147
  </div>
148
148
 
149
149
  <script type="module">
150
- import { multipartiteLayout } from './layout.js';
150
+ import { multipartiteLayout } from '../dist/layout.js';
151
151
 
152
152
  let currentGraph = null;
153
153
 
@@ -161,7 +161,7 @@
161
161
  </div>
162
162
 
163
163
  <script type="module">
164
- import { planarLayout } from './layout.js';
164
+ import { planarLayout } from '../dist/layout.js';
165
165
 
166
166
  let currentGraph = null;
167
167
 
@@ -142,7 +142,7 @@
142
142
  </div>
143
143
 
144
144
  <script type="module">
145
- import { randomLayout } from './layout.js';
145
+ import { randomLayout } from '../dist/layout.js';
146
146
 
147
147
  let currentGraph = null;
148
148
 
@@ -129,7 +129,7 @@
129
129
  </div>
130
130
 
131
131
  <script type="module">
132
- import { shellLayout } from './layout.js';
132
+ import { shellLayout } from '../dist/layout.js';
133
133
 
134
134
  let currentGraph = null;
135
135
 
@@ -135,7 +135,7 @@
135
135
  </div>
136
136
 
137
137
  <script type="module">
138
- import { spectralLayout } from './layout.js';
138
+ import { spectralLayout } from '../dist/layout.js';
139
139
 
140
140
  let currentGraph = null;
141
141
 
@@ -171,7 +171,7 @@
171
171
  </div>
172
172
 
173
173
  <script type="module">
174
- import { spiralLayout } from './layout.js';
174
+ import { spiralLayout } from '../dist/layout.js';
175
175
 
176
176
  let currentGraph = null;
177
177
 
@@ -141,7 +141,7 @@
141
141
  </div>
142
142
 
143
143
  <script type="module">
144
- import { springLayout } from './layout.js';
144
+ import { springLayout } from '../dist/layout.js';
145
145
 
146
146
  let currentGraph = null;
147
147