@statelyai/graph 0.1.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -15
- package/dist/{adjacency-list-CXpOCibq.mjs → adjacency-list-ITO40kmn.mjs} +34 -1
- package/dist/{algorithms-R35X6ro4.mjs → algorithms-NWSB2RWj.mjs} +753 -19
- package/dist/algorithms.d.mts +488 -11
- package/dist/algorithms.mjs +2 -2
- package/dist/converter-CchokMDg.mjs +67 -0
- package/dist/edge-list-CgX6bBIF.mjs +71 -0
- package/dist/formats/adjacency-list/index.d.mts +44 -0
- package/dist/formats/adjacency-list/index.mjs +3 -0
- package/dist/formats/converter/index.d.mts +61 -0
- package/dist/formats/converter/index.mjs +3 -0
- package/dist/formats/cytoscape/index.d.mts +83 -0
- package/dist/formats/cytoscape/index.mjs +135 -0
- package/dist/formats/d3/index.d.mts +68 -0
- package/dist/formats/d3/index.mjs +111 -0
- package/dist/formats/dot/index.d.mts +63 -0
- package/dist/formats/dot/index.mjs +288 -0
- package/dist/formats/edge-list/index.d.mts +43 -0
- package/dist/formats/edge-list/index.mjs +3 -0
- package/dist/formats/gexf/index.d.mts +9 -0
- package/dist/formats/gexf/index.mjs +249 -0
- package/dist/formats/gml/index.d.mts +65 -0
- package/dist/formats/gml/index.mjs +291 -0
- package/dist/formats/graphml/index.d.mts +9 -0
- package/dist/{graphml-CUTNRXqd.mjs → formats/graphml/index.mjs} +18 -4
- package/dist/formats/jgf/index.d.mts +79 -0
- package/dist/formats/jgf/index.mjs +134 -0
- package/dist/formats/mermaid/index.d.mts +381 -0
- package/dist/formats/mermaid/index.mjs +2237 -0
- package/dist/formats/tgf/index.d.mts +54 -0
- package/dist/formats/tgf/index.mjs +111 -0
- package/dist/index.d.mts +332 -21
- package/dist/index.mjs +117 -13
- package/dist/{indexing-BHg1VhqN.mjs → indexing-eNDrXdDA.mjs} +31 -2
- package/dist/queries.d.mts +430 -9
- package/dist/queries.mjs +472 -9
- package/dist/{types-XV3S5Jnh.d.mts → types-BDXC1O5b.d.mts} +37 -2
- package/package.json +43 -17
- package/dist/adjacency-list-DW-lAUe8.d.mts +0 -10
- package/dist/dot-BRtq3e3c.mjs +0 -59
- package/dist/dot-HmJeUMsj.d.mts +0 -6
- package/dist/edge-list-BRujEnnU.mjs +0 -39
- package/dist/edge-list-CJmfoNu2.d.mts +0 -10
- package/dist/formats/adjacency-list.d.mts +0 -2
- package/dist/formats/adjacency-list.mjs +0 -3
- package/dist/formats/dot.d.mts +0 -2
- package/dist/formats/dot.mjs +0 -3
- package/dist/formats/edge-list.d.mts +0 -2
- package/dist/formats/edge-list.mjs +0 -3
- package/dist/formats/graphml.d.mts +0 -2
- package/dist/formats/graphml.mjs +0 -3
- package/dist/graphml-CMjPzSfY.d.mts +0 -7
package/dist/queries.mjs
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import { t as getIndex } from "./indexing-
|
|
1
|
+
import { t as getIndex } from "./indexing-eNDrXdDA.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/queries.ts
|
|
4
|
+
/**
|
|
5
|
+
* Returns all edges (incoming + outgoing) connected to a node.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const graph = createGraph({
|
|
10
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
11
|
+
* edges: [
|
|
12
|
+
* { id: 'e1', sourceId: 'a', targetId: 'b' },
|
|
13
|
+
* { id: 'e2', sourceId: 'c', targetId: 'b' },
|
|
14
|
+
* ],
|
|
15
|
+
* });
|
|
16
|
+
* getEdgesOf(graph, 'b');
|
|
17
|
+
* // => [edge e1, edge e2]
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
4
20
|
function getEdgesOf(graph, nodeId) {
|
|
5
21
|
const idx = getIndex(graph);
|
|
6
22
|
const outIds = idx.outEdges.get(nodeId) ?? [];
|
|
@@ -18,14 +34,60 @@ function getEdgesOf(graph, nodeId) {
|
|
|
18
34
|
}
|
|
19
35
|
return result;
|
|
20
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns incoming edges to a node.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const graph = createGraph({
|
|
43
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
44
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
45
|
+
* });
|
|
46
|
+
* getInEdges(graph, 'b');
|
|
47
|
+
* // => [edge e1]
|
|
48
|
+
* getInEdges(graph, 'a');
|
|
49
|
+
* // => []
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
21
52
|
function getInEdges(graph, nodeId) {
|
|
22
53
|
const idx = getIndex(graph);
|
|
23
54
|
return (idx.inEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)]);
|
|
24
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Returns outgoing edges from a node.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const graph = createGraph({
|
|
62
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
63
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
64
|
+
* });
|
|
65
|
+
* getOutEdges(graph, 'a');
|
|
66
|
+
* // => [edge e1]
|
|
67
|
+
* getOutEdges(graph, 'b');
|
|
68
|
+
* // => []
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
25
71
|
function getOutEdges(graph, nodeId) {
|
|
26
72
|
const idx = getIndex(graph);
|
|
27
73
|
return (idx.outEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)]);
|
|
28
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Returns the edge from `sourceId` to `targetId`, or `undefined` if none exists.
|
|
77
|
+
* For undirected graphs, checks both directions.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const graph = createGraph({
|
|
82
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
83
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
84
|
+
* });
|
|
85
|
+
* getEdgeBetween(graph, 'a', 'b');
|
|
86
|
+
* // => edge e1
|
|
87
|
+
* getEdgeBetween(graph, 'b', 'a');
|
|
88
|
+
* // => undefined (directed graph)
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
29
91
|
function getEdgeBetween(graph, sourceId, targetId) {
|
|
30
92
|
const idx = getIndex(graph);
|
|
31
93
|
const outIds = idx.outEdges.get(sourceId) ?? [];
|
|
@@ -43,6 +105,22 @@ function getEdgeBetween(graph, sourceId, targetId) {
|
|
|
43
105
|
}
|
|
44
106
|
}
|
|
45
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Returns direct successor nodes (targets of outgoing edges).
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const graph = createGraph({
|
|
114
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
115
|
+
* edges: [
|
|
116
|
+
* { id: 'e1', sourceId: 'a', targetId: 'b' },
|
|
117
|
+
* { id: 'e2', sourceId: 'a', targetId: 'c' },
|
|
118
|
+
* ],
|
|
119
|
+
* });
|
|
120
|
+
* getSuccessors(graph, 'a');
|
|
121
|
+
* // => [node b, node c]
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
46
124
|
function getSuccessors(graph, nodeId) {
|
|
47
125
|
const idx = getIndex(graph);
|
|
48
126
|
const edgeIds = idx.outEdges.get(nodeId) ?? [];
|
|
@@ -58,6 +136,22 @@ function getSuccessors(graph, nodeId) {
|
|
|
58
136
|
}
|
|
59
137
|
return result;
|
|
60
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Returns direct predecessor nodes (sources of incoming edges).
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* const graph = createGraph({
|
|
145
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
146
|
+
* edges: [
|
|
147
|
+
* { id: 'e1', sourceId: 'a', targetId: 'c' },
|
|
148
|
+
* { id: 'e2', sourceId: 'b', targetId: 'c' },
|
|
149
|
+
* ],
|
|
150
|
+
* });
|
|
151
|
+
* getPredecessors(graph, 'c');
|
|
152
|
+
* // => [node a, node b]
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
61
155
|
function getPredecessors(graph, nodeId) {
|
|
62
156
|
const idx = getIndex(graph);
|
|
63
157
|
const edgeIds = idx.inEdges.get(nodeId) ?? [];
|
|
@@ -73,6 +167,22 @@ function getPredecessors(graph, nodeId) {
|
|
|
73
167
|
}
|
|
74
168
|
return result;
|
|
75
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Returns all neighbor nodes (successors + predecessors).
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* const graph = createGraph({
|
|
176
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
177
|
+
* edges: [
|
|
178
|
+
* { id: 'e1', sourceId: 'a', targetId: 'b' },
|
|
179
|
+
* { id: 'e2', sourceId: 'c', targetId: 'b' },
|
|
180
|
+
* ],
|
|
181
|
+
* });
|
|
182
|
+
* getNeighbors(graph, 'b');
|
|
183
|
+
* // => [node a, node c]
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
76
186
|
function getNeighbors(graph, nodeId) {
|
|
77
187
|
const idx = getIndex(graph);
|
|
78
188
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -80,6 +190,23 @@ function getNeighbors(graph, nodeId) {
|
|
|
80
190
|
for (const eid of idx.inEdges.get(nodeId) ?? []) ids.add(graph.edges[idx.edgeById.get(eid)].sourceId);
|
|
81
191
|
return [...ids].map((id) => graph.nodes[idx.nodeById.get(id)]).filter(Boolean);
|
|
82
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Returns the total degree of a node (inDegree + outDegree).
|
|
195
|
+
* For undirected graphs, each edge is counted once.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* const graph = createGraph({
|
|
200
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
201
|
+
* edges: [
|
|
202
|
+
* { id: 'e1', sourceId: 'a', targetId: 'b' },
|
|
203
|
+
* { id: 'e2', sourceId: 'c', targetId: 'b' },
|
|
204
|
+
* ],
|
|
205
|
+
* });
|
|
206
|
+
* getDegree(graph, 'b'); // => 2
|
|
207
|
+
* getDegree(graph, 'a'); // => 1
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
83
210
|
function getDegree(graph, nodeId) {
|
|
84
211
|
const idx = getIndex(graph);
|
|
85
212
|
if (graph.type === "undirected") {
|
|
@@ -89,16 +216,78 @@ function getDegree(graph, nodeId) {
|
|
|
89
216
|
}
|
|
90
217
|
return (idx.inEdges.get(nodeId)?.length ?? 0) + (idx.outEdges.get(nodeId)?.length ?? 0);
|
|
91
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Returns the in-degree of a node (number of incoming edges).
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* const graph = createGraph({
|
|
225
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
226
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
227
|
+
* });
|
|
228
|
+
* getInDegree(graph, 'b'); // => 1
|
|
229
|
+
* getInDegree(graph, 'a'); // => 0
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
92
232
|
function getInDegree(graph, nodeId) {
|
|
93
233
|
return getIndex(graph).inEdges.get(nodeId)?.length ?? 0;
|
|
94
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Returns the out-degree of a node (number of outgoing edges).
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* const graph = createGraph({
|
|
241
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
242
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
243
|
+
* });
|
|
244
|
+
* getOutDegree(graph, 'a'); // => 1
|
|
245
|
+
* getOutDegree(graph, 'b'); // => 0
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
95
248
|
function getOutDegree(graph, nodeId) {
|
|
96
249
|
return getIndex(graph).outEdges.get(nodeId)?.length ?? 0;
|
|
97
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Returns direct children of a node in the hierarchy.
|
|
253
|
+
* Pass `null` to get root-level nodes.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* const graph = createGraph({
|
|
258
|
+
* nodes: [
|
|
259
|
+
* { id: 'parent' },
|
|
260
|
+
* { id: 'child1', parentId: 'parent' },
|
|
261
|
+
* { id: 'child2', parentId: 'parent' },
|
|
262
|
+
* ],
|
|
263
|
+
* });
|
|
264
|
+
* getChildren(graph, 'parent');
|
|
265
|
+
* // => [node child1, node child2]
|
|
266
|
+
* getChildren(graph, null);
|
|
267
|
+
* // => [node parent]
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
98
270
|
function getChildren(graph, nodeId) {
|
|
99
271
|
const idx = getIndex(graph);
|
|
100
272
|
return (idx.childNodes.get(nodeId) ?? []).map((id) => graph.nodes[idx.nodeById.get(id)]).filter(Boolean);
|
|
101
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Returns the parent node in the hierarchy, or `undefined` if root-level.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```ts
|
|
279
|
+
* const graph = createGraph({
|
|
280
|
+
* nodes: [
|
|
281
|
+
* { id: 'parent' },
|
|
282
|
+
* { id: 'child', parentId: 'parent' },
|
|
283
|
+
* ],
|
|
284
|
+
* });
|
|
285
|
+
* getParent(graph, 'child');
|
|
286
|
+
* // => node parent
|
|
287
|
+
* getParent(graph, 'parent');
|
|
288
|
+
* // => undefined
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
102
291
|
function getParent(graph, nodeId) {
|
|
103
292
|
const idx = getIndex(graph);
|
|
104
293
|
const ni = idx.nodeById.get(nodeId);
|
|
@@ -108,6 +297,22 @@ function getParent(graph, nodeId) {
|
|
|
108
297
|
const pi = idx.nodeById.get(node.parentId);
|
|
109
298
|
return pi !== void 0 ? graph.nodes[pi] : void 0;
|
|
110
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Returns all ancestors from the node up to the root (nearest parent first).
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* const graph = createGraph({
|
|
306
|
+
* nodes: [
|
|
307
|
+
* { id: 'root' },
|
|
308
|
+
* { id: 'mid', parentId: 'root' },
|
|
309
|
+
* { id: 'leaf', parentId: 'mid' },
|
|
310
|
+
* ],
|
|
311
|
+
* });
|
|
312
|
+
* getAncestors(graph, 'leaf');
|
|
313
|
+
* // => [node mid, node root]
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
111
316
|
function getAncestors(graph, nodeId) {
|
|
112
317
|
const idx = getIndex(graph);
|
|
113
318
|
const result = [];
|
|
@@ -123,6 +328,22 @@ function getAncestors(graph, nodeId) {
|
|
|
123
328
|
}
|
|
124
329
|
return result;
|
|
125
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Returns all descendants recursively (depth-first).
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* const graph = createGraph({
|
|
337
|
+
* nodes: [
|
|
338
|
+
* { id: 'root' },
|
|
339
|
+
* { id: 'child', parentId: 'root' },
|
|
340
|
+
* { id: 'grandchild', parentId: 'child' },
|
|
341
|
+
* ],
|
|
342
|
+
* });
|
|
343
|
+
* getDescendants(graph, 'root');
|
|
344
|
+
* // => [node child, node grandchild]
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
126
347
|
function getDescendants(graph, nodeId) {
|
|
127
348
|
const idx = getIndex(graph);
|
|
128
349
|
const result = [];
|
|
@@ -139,19 +360,80 @@ function getDescendants(graph, nodeId) {
|
|
|
139
360
|
collect(nodeId);
|
|
140
361
|
return result;
|
|
141
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Returns all root nodes (nodes with no parent, i.e. `parentId === null`).
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```ts
|
|
368
|
+
* const graph = createGraph({
|
|
369
|
+
* nodes: [
|
|
370
|
+
* { id: 'root1' },
|
|
371
|
+
* { id: 'root2' },
|
|
372
|
+
* { id: 'child', parentId: 'root1' },
|
|
373
|
+
* ],
|
|
374
|
+
* });
|
|
375
|
+
* getRoots(graph);
|
|
376
|
+
* // => [node root1, node root2]
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
142
379
|
function getRoots(graph) {
|
|
143
380
|
const idx = getIndex(graph);
|
|
144
381
|
return idx.childNodes.get(null)?.map((id) => graph.nodes[idx.nodeById.get(id)]).filter(Boolean) ?? [];
|
|
145
382
|
}
|
|
146
|
-
/**
|
|
383
|
+
/**
|
|
384
|
+
* Whether a node has children (is a compound/group node).
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```ts
|
|
388
|
+
* const graph = createGraph({
|
|
389
|
+
* nodes: [
|
|
390
|
+
* { id: 'parent' },
|
|
391
|
+
* { id: 'child', parentId: 'parent' },
|
|
392
|
+
* ],
|
|
393
|
+
* });
|
|
394
|
+
* isCompound(graph, 'parent'); // => true
|
|
395
|
+
* isCompound(graph, 'child'); // => false
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
147
398
|
function isCompound(graph, nodeId) {
|
|
148
399
|
return (getIndex(graph).childNodes.get(nodeId) ?? []).length > 0;
|
|
149
400
|
}
|
|
150
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* Whether a node has no children (is a leaf/atomic node).
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```ts
|
|
406
|
+
* const graph = createGraph({
|
|
407
|
+
* nodes: [
|
|
408
|
+
* { id: 'parent' },
|
|
409
|
+
* { id: 'child', parentId: 'parent' },
|
|
410
|
+
* ],
|
|
411
|
+
* });
|
|
412
|
+
* isLeaf(graph, 'child'); // => true
|
|
413
|
+
* isLeaf(graph, 'parent'); // => false
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
151
416
|
function isLeaf(graph, nodeId) {
|
|
152
417
|
return !isCompound(graph, nodeId);
|
|
153
418
|
}
|
|
154
|
-
/**
|
|
419
|
+
/**
|
|
420
|
+
* Depth of a node in the hierarchy (root = 0).
|
|
421
|
+
* Returns -1 if the node is not found.
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```ts
|
|
425
|
+
* const graph = createGraph({
|
|
426
|
+
* nodes: [
|
|
427
|
+
* { id: 'root' },
|
|
428
|
+
* { id: 'child', parentId: 'root' },
|
|
429
|
+
* { id: 'grandchild', parentId: 'child' },
|
|
430
|
+
* ],
|
|
431
|
+
* });
|
|
432
|
+
* getDepth(graph, 'root'); // => 0
|
|
433
|
+
* getDepth(graph, 'child'); // => 1
|
|
434
|
+
* getDepth(graph, 'grandchild'); // => 2
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
155
437
|
function getDepth(graph, nodeId) {
|
|
156
438
|
const idx = getIndex(graph);
|
|
157
439
|
let d = 0;
|
|
@@ -166,7 +448,23 @@ function getDepth(graph, nodeId) {
|
|
|
166
448
|
}
|
|
167
449
|
return d;
|
|
168
450
|
}
|
|
169
|
-
/**
|
|
451
|
+
/**
|
|
452
|
+
* Sibling nodes (same parentId, excluding the node itself).
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```ts
|
|
456
|
+
* const graph = createGraph({
|
|
457
|
+
* nodes: [
|
|
458
|
+
* { id: 'parent' },
|
|
459
|
+
* { id: 'a', parentId: 'parent' },
|
|
460
|
+
* { id: 'b', parentId: 'parent' },
|
|
461
|
+
* { id: 'c', parentId: 'parent' },
|
|
462
|
+
* ],
|
|
463
|
+
* });
|
|
464
|
+
* getSiblings(graph, 'a');
|
|
465
|
+
* // => [node b, node c]
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
170
468
|
function getSiblings(graph, nodeId) {
|
|
171
469
|
const idx = getIndex(graph);
|
|
172
470
|
const ni = idx.nodeById.get(nodeId);
|
|
@@ -175,8 +473,24 @@ function getSiblings(graph, nodeId) {
|
|
|
175
473
|
return (idx.childNodes.get(node.parentId) ?? []).filter((id) => id !== nodeId).map((id) => graph.nodes[idx.nodeById.get(id)]).filter(Boolean);
|
|
176
474
|
}
|
|
177
475
|
/**
|
|
178
|
-
* Least Common Ancestor
|
|
476
|
+
* Least Common Ancestor -- deepest proper ancestor of all given nodes.
|
|
179
477
|
* A proper ancestor excludes the input nodes themselves.
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* ```ts
|
|
481
|
+
* const graph = createGraph({
|
|
482
|
+
* nodes: [
|
|
483
|
+
* { id: 'root' },
|
|
484
|
+
* { id: 'a', parentId: 'root' },
|
|
485
|
+
* { id: 'b', parentId: 'root' },
|
|
486
|
+
* { id: 'a1', parentId: 'a' },
|
|
487
|
+
* ],
|
|
488
|
+
* });
|
|
489
|
+
* getLCA(graph, 'a1', 'b');
|
|
490
|
+
* // => node root
|
|
491
|
+
* getLCA(graph, 'a', 'b');
|
|
492
|
+
* // => node root
|
|
493
|
+
* ```
|
|
180
494
|
*/
|
|
181
495
|
function getLCA(graph, ...nodeIds) {
|
|
182
496
|
if (nodeIds.length === 0) return void 0;
|
|
@@ -206,16 +520,165 @@ function getLCA(graph, ...nodeIds) {
|
|
|
206
520
|
const ni = idx.nodeById.get(lcaId);
|
|
207
521
|
return ni !== void 0 ? graph.nodes[ni] : void 0;
|
|
208
522
|
}
|
|
209
|
-
/**
|
|
523
|
+
/**
|
|
524
|
+
* Returns a map of nodeId → shortest-path distance for all sibling nodes
|
|
525
|
+
* (same parentId). Distance is measured from the parent's `initialNodeId`
|
|
526
|
+
* (or `graph.initialNodeId` for root-level nodes).
|
|
527
|
+
*
|
|
528
|
+
* Only follows edges between siblings. Unreachable siblings are omitted.
|
|
529
|
+
*
|
|
530
|
+
* @example Root-level nodes (uses `graph.initialNodeId`):
|
|
531
|
+
* ```ts
|
|
532
|
+
* const graph = createGraph({
|
|
533
|
+
* initialNodeId: 'a',
|
|
534
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
535
|
+
* edges: [
|
|
536
|
+
* { id: 'e1', sourceId: 'a', targetId: 'b' },
|
|
537
|
+
* { id: 'e2', sourceId: 'b', targetId: 'c' },
|
|
538
|
+
* ],
|
|
539
|
+
* });
|
|
540
|
+
* getRelativeDistanceMap(graph, null);
|
|
541
|
+
* // => { a: 0, b: 1, c: 2 }
|
|
542
|
+
* ```
|
|
543
|
+
*
|
|
544
|
+
* @example Nested nodes (uses parent's `initialNodeId`):
|
|
545
|
+
* ```ts
|
|
546
|
+
* const graph = createGraph({
|
|
547
|
+
* nodes: [
|
|
548
|
+
* { id: 'parent', initialNodeId: 's1' },
|
|
549
|
+
* { id: 's1', parentId: 'parent' },
|
|
550
|
+
* { id: 's2', parentId: 'parent' },
|
|
551
|
+
* { id: 's3', parentId: 'parent' },
|
|
552
|
+
* ],
|
|
553
|
+
* edges: [
|
|
554
|
+
* { id: 'e1', sourceId: 's1', targetId: 's2' },
|
|
555
|
+
* { id: 'e2', sourceId: 's2', targetId: 's3' },
|
|
556
|
+
* ],
|
|
557
|
+
* });
|
|
558
|
+
* getRelativeDistanceMap(graph, 'parent');
|
|
559
|
+
* // => { s1: 0, s2: 1, s3: 2 }
|
|
560
|
+
* ```
|
|
561
|
+
*/
|
|
562
|
+
function getRelativeDistanceMap(graph, parentId) {
|
|
563
|
+
const idx = getIndex(graph);
|
|
564
|
+
let sourceId = null;
|
|
565
|
+
if (parentId !== null) {
|
|
566
|
+
const pi = idx.nodeById.get(parentId);
|
|
567
|
+
if (pi !== void 0) sourceId = graph.nodes[pi].initialNodeId;
|
|
568
|
+
} else sourceId = graph.initialNodeId;
|
|
569
|
+
if (!sourceId) return {};
|
|
570
|
+
const siblingSet = new Set(idx.childNodes.get(parentId) ?? []);
|
|
571
|
+
if (!siblingSet.has(sourceId)) return {};
|
|
572
|
+
const dist = /* @__PURE__ */ new Map();
|
|
573
|
+
dist.set(sourceId, 0);
|
|
574
|
+
const queue = [sourceId];
|
|
575
|
+
while (queue.length > 0) {
|
|
576
|
+
const id = queue.shift();
|
|
577
|
+
const d = dist.get(id);
|
|
578
|
+
for (const eid of idx.outEdges.get(id) ?? []) {
|
|
579
|
+
const ai = idx.edgeById.get(eid);
|
|
580
|
+
if (ai === void 0) continue;
|
|
581
|
+
const neighborId = graph.edges[ai].targetId;
|
|
582
|
+
if (siblingSet.has(neighborId) && !dist.has(neighborId)) {
|
|
583
|
+
dist.set(neighborId, d + 1);
|
|
584
|
+
queue.push(neighborId);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (graph.type === "undirected") for (const eid of idx.inEdges.get(id) ?? []) {
|
|
588
|
+
const ai = idx.edgeById.get(eid);
|
|
589
|
+
if (ai === void 0) continue;
|
|
590
|
+
const neighborId = graph.edges[ai].sourceId;
|
|
591
|
+
if (siblingSet.has(neighborId) && !dist.has(neighborId)) {
|
|
592
|
+
dist.set(neighborId, d + 1);
|
|
593
|
+
queue.push(neighborId);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const result = {};
|
|
598
|
+
for (const [id, d] of dist) result[id] = d;
|
|
599
|
+
return result;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Returns the shortest-path distance of a node from its parent's initial node.
|
|
603
|
+
* Automatically scopes to the node's sibling group (same `parentId`).
|
|
604
|
+
*
|
|
605
|
+
* Returns `undefined` if the node is not found or unreachable.
|
|
606
|
+
*
|
|
607
|
+
* @example
|
|
608
|
+
* ```ts
|
|
609
|
+
* const graph = createGraph({
|
|
610
|
+
* initialNodeId: 'a',
|
|
611
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
612
|
+
* edges: [
|
|
613
|
+
* { id: 'e1', sourceId: 'a', targetId: 'b' },
|
|
614
|
+
* { id: 'e2', sourceId: 'b', targetId: 'c' },
|
|
615
|
+
* ],
|
|
616
|
+
* });
|
|
617
|
+
* getRelativeDistance(graph, 'a'); // => 0
|
|
618
|
+
* getRelativeDistance(graph, 'b'); // => 1
|
|
619
|
+
* getRelativeDistance(graph, 'c'); // => 2
|
|
620
|
+
* ```
|
|
621
|
+
*
|
|
622
|
+
* @example Nested nodes:
|
|
623
|
+
* ```ts
|
|
624
|
+
* const graph = createGraph({
|
|
625
|
+
* nodes: [
|
|
626
|
+
* { id: 'parent', initialNodeId: 's1' },
|
|
627
|
+
* { id: 's1', parentId: 'parent' },
|
|
628
|
+
* { id: 's2', parentId: 'parent' },
|
|
629
|
+
* ],
|
|
630
|
+
* edges: [{ id: 'e1', sourceId: 's1', targetId: 's2' }],
|
|
631
|
+
* });
|
|
632
|
+
* getRelativeDistance(graph, 's1'); // => 0
|
|
633
|
+
* getRelativeDistance(graph, 's2'); // => 1
|
|
634
|
+
* ```
|
|
635
|
+
*/
|
|
636
|
+
function getRelativeDistance(graph, nodeId) {
|
|
637
|
+
const ni = getIndex(graph).nodeById.get(nodeId);
|
|
638
|
+
if (ni === void 0) return void 0;
|
|
639
|
+
const node = graph.nodes[ni];
|
|
640
|
+
return getRelativeDistanceMap(graph, node.parentId)[nodeId];
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Nodes with no incoming edges (inDegree 0).
|
|
644
|
+
*
|
|
645
|
+
* @example
|
|
646
|
+
* ```ts
|
|
647
|
+
* const graph = createGraph({
|
|
648
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
649
|
+
* edges: [
|
|
650
|
+
* { id: 'e1', sourceId: 'a', targetId: 'b' },
|
|
651
|
+
* { id: 'e2', sourceId: 'b', targetId: 'c' },
|
|
652
|
+
* ],
|
|
653
|
+
* });
|
|
654
|
+
* getSources(graph);
|
|
655
|
+
* // => [node a]
|
|
656
|
+
* ```
|
|
657
|
+
*/
|
|
210
658
|
function getSources(graph) {
|
|
211
659
|
const idx = getIndex(graph);
|
|
212
660
|
return graph.nodes.filter((n) => (idx.inEdges.get(n.id)?.length ?? 0) === 0);
|
|
213
661
|
}
|
|
214
|
-
/**
|
|
662
|
+
/**
|
|
663
|
+
* Nodes with no outgoing edges (outDegree 0).
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* ```ts
|
|
667
|
+
* const graph = createGraph({
|
|
668
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
669
|
+
* edges: [
|
|
670
|
+
* { id: 'e1', sourceId: 'a', targetId: 'b' },
|
|
671
|
+
* { id: 'e2', sourceId: 'b', targetId: 'c' },
|
|
672
|
+
* ],
|
|
673
|
+
* });
|
|
674
|
+
* getSinks(graph);
|
|
675
|
+
* // => [node c]
|
|
676
|
+
* ```
|
|
677
|
+
*/
|
|
215
678
|
function getSinks(graph) {
|
|
216
679
|
const idx = getIndex(graph);
|
|
217
680
|
return graph.nodes.filter((n) => (idx.outEdges.get(n.id)?.length ?? 0) === 0);
|
|
218
681
|
}
|
|
219
682
|
|
|
220
683
|
//#endregion
|
|
221
|
-
export { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf };
|
|
684
|
+
export { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRelativeDistance, getRelativeDistanceMap, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf };
|
|
@@ -94,7 +94,7 @@ interface GraphEdge<TEdgeData = any> {
|
|
|
94
94
|
style?: Record<string, string | number>;
|
|
95
95
|
}
|
|
96
96
|
interface VisualNode<TNodeData = any> extends Omit<GraphNode<TNodeData>, keyof EntityRect>, EntityRect {
|
|
97
|
-
shape
|
|
97
|
+
shape?: string;
|
|
98
98
|
}
|
|
99
99
|
interface VisualEdge<TEdgeData = any> extends Omit<GraphEdge<TEdgeData>, keyof EntityRect>, EntityRect {}
|
|
100
100
|
interface VisualGraph<TNodeData = any, TEdgeData = any, TGraphData = any> extends Omit<Graph<TNodeData, TEdgeData, TGraphData>, 'nodes' | 'edges'> {
|
|
@@ -215,5 +215,40 @@ type GraphPatch<TNodeData = any, TEdgeData = any> = {
|
|
|
215
215
|
id: string;
|
|
216
216
|
description?: string;
|
|
217
217
|
};
|
|
218
|
+
/**
|
|
219
|
+
* A bidirectional converter between `Graph` and a serialized format.
|
|
220
|
+
*
|
|
221
|
+
* Implement this interface to create a custom format converter.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```ts
|
|
225
|
+
* const myConverter: GraphFormatConverter<string> = {
|
|
226
|
+
* to(graph) { return JSON.stringify(graph); },
|
|
227
|
+
* from(input) { return JSON.parse(input); },
|
|
228
|
+
* };
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
interface GraphFormatConverter<TSerial> {
|
|
232
|
+
/** Convert a Graph to the serialized format. */
|
|
233
|
+
to(graph: Graph): TSerial;
|
|
234
|
+
/** Convert from the serialized format to a Graph. */
|
|
235
|
+
from(input: TSerial): Graph;
|
|
236
|
+
}
|
|
237
|
+
interface TransitionOptions<TState, TEvent> {
|
|
238
|
+
/** Initial state to begin BFS exploration from. */
|
|
239
|
+
initialState: TState;
|
|
240
|
+
/** Events to try at each state. Array or function of state. */
|
|
241
|
+
events: TEvent[] | ((state: TState) => TEvent[]);
|
|
242
|
+
/** Serialize state to unique string for node dedup. Default: JSON.stringify */
|
|
243
|
+
serializeState?: (state: TState) => string;
|
|
244
|
+
/** Serialize event to string for edge labels/IDs. Default: JSON.stringify */
|
|
245
|
+
serializeEvent?: (event: TEvent) => string;
|
|
246
|
+
/** Max BFS iterations before throwing. Default: Infinity */
|
|
247
|
+
limit?: number;
|
|
248
|
+
/** When true, node is kept but outgoing transitions are not explored. */
|
|
249
|
+
stopWhen?: (state: TState) => boolean;
|
|
250
|
+
/** Optional graph ID. */
|
|
251
|
+
id?: string;
|
|
252
|
+
}
|
|
218
253
|
//#endregion
|
|
219
|
-
export {
|
|
254
|
+
export { TraversalOptions as C, VisualNode as D, VisualGraphConfig as E, TransitionOptions as S, VisualGraph as T, MSTOptions as _, EntitiesConfig as a, PathOptions as b, Graph as c, GraphEdge as d, GraphFormatConverter as f, GraphStep as g, GraphPath as h, EdgeConfig as i, GraphConfig as l, GraphPatch as m, DeleteNodeOptions as n, EntitiesUpdate as o, GraphNode as p, EdgeChange as r, EntityRect as s, AllPairsShortestPathsOptions as t, GraphDiff as u, NodeChange as v, VisualEdge as w, SinglePathOptions as x, NodeConfig as y };
|