@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.
- package/.github/workflows/ci.yml +30 -0
- package/.releaserc +3 -0
- package/README.md +377 -34
- package/examples/arf-layout.html +1 -1
- package/examples/bfs-layout.html +1 -1
- package/examples/bipartite-layout.html +1 -1
- package/examples/circular-layout.html +1 -1
- package/examples/forceatlas2-layout.html +1 -1
- package/examples/kamada-kawai-layout.html +1 -1
- package/examples/multipartite-layout.html +1 -1
- package/examples/planar-layout.html +1 -1
- package/examples/random-layout.html +1 -1
- package/examples/shell-layout.html +1 -1
- package/examples/spectral-layout.html +1 -1
- package/examples/spiral-layout.html +1 -1
- package/examples/spring-layout.html +1 -1
- package/{layout.js → layout.ts} +984 -770
- package/package.json +9 -4
- package/tsconfig.json +16 -0
- package/.husky/commit-msg +0 -1
- package/.husky/prepare-commit-msg +0 -1
|
@@ -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
package/README.md
CHANGED
|
@@ -1,59 +1,402 @@
|
|
|
1
|
-
# Layout
|
|
1
|
+
# Layout
|
|
2
2
|
|
|
3
|
-
Layout
|
|
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
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
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
|
|
25
|
+
Import the library in your TypeScript/JavaScript project:
|
|
26
26
|
|
|
27
|
-
```
|
|
27
|
+
```typescript
|
|
28
28
|
import {
|
|
29
29
|
randomLayout,
|
|
30
30
|
circularLayout,
|
|
31
|
-
springLayout
|
|
32
|
-
|
|
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
|
-
|
|
46
|
+
## Graph Structure
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
51
|
-
const positions =
|
|
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
|
-
|
|
54
|
-
|
|
351
|
+
```bash
|
|
352
|
+
npm install @graphty/layout
|
|
55
353
|
```
|
|
56
354
|
|
|
57
|
-
##
|
|
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
|
-
-
|
|
402
|
+
MIT License - see LICENSE file for details.
|
package/examples/arf-layout.html
CHANGED
package/examples/bfs-layout.html
CHANGED