@statelyai/graph 1.0.0 → 2.0.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/README.md +55 -26
- package/dist/{adjacency-list-VsUaH9SJ.mjs → adjacency-list-GeL1Cu-L.mjs} +3 -1
- package/dist/{algorithms-fTqmvhzP.d.mts → algorithms-CsGNehct.d.mts} +137 -2
- package/dist/{algorithms-Ba7o7niK.mjs → algorithms-DF1pSQGv.mjs} +1476 -343
- package/dist/algorithms.d.mts +2 -2
- package/dist/algorithms.mjs +2 -2
- package/dist/{converter-udLITX36.mjs → converter-DyCJJfTe.mjs} +2 -2
- package/dist/format-support.mjs +38 -11
- package/dist/formats/adjacency-list/index.d.mts +1 -1
- package/dist/formats/adjacency-list/index.mjs +1 -1
- package/dist/formats/converter/index.d.mts +1 -1
- package/dist/formats/converter/index.mjs +1 -1
- package/dist/formats/cytoscape/index.d.mts +1 -1
- package/dist/formats/cytoscape/index.mjs +3 -1
- package/dist/formats/d2/index.d.mts +1 -1
- package/dist/formats/d2/index.mjs +26 -12
- package/dist/formats/d3/index.d.mts +1 -1
- package/dist/formats/d3/index.mjs +3 -1
- package/dist/formats/dot/index.d.mts +1 -1
- package/dist/formats/dot/index.mjs +22 -6
- package/dist/formats/edge-list/index.d.mts +1 -1
- package/dist/formats/edge-list/index.mjs +1 -1
- package/dist/formats/elk/index.d.mts +1 -1
- package/dist/formats/elk/index.mjs +21 -14
- package/dist/formats/gexf/index.d.mts +1 -1
- package/dist/formats/gexf/index.mjs +22 -15
- package/dist/formats/gml/index.d.mts +1 -1
- package/dist/formats/gml/index.mjs +21 -12
- package/dist/formats/graphml/index.d.mts +1 -1
- package/dist/formats/graphml/index.mjs +73 -22
- package/dist/formats/jgf/index.d.mts +1 -1
- package/dist/formats/jgf/index.mjs +5 -2
- package/dist/formats/mermaid/index.d.mts +1 -1
- package/dist/formats/mermaid/index.mjs +49 -12
- package/dist/formats/tgf/index.d.mts +1 -1
- package/dist/formats/tgf/index.mjs +1 -1
- package/dist/formats/xyflow/index.d.mts +1 -1
- package/dist/formats/xyflow/index.mjs +31 -4
- package/dist/{index-D9Kj6Fe3.d.mts → index-D51lJnt2.d.mts} +1 -1
- package/dist/{index-CHoriXZD.d.mts → index-DWmo1mIp.d.mts} +77 -18
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +143 -295
- package/dist/{queries-BlkA1HAN.d.mts → queries-BfXeTXRf.d.mts} +43 -12
- package/dist/queries-KirMDR7e.mjs +980 -0
- package/dist/queries.d.mts +1 -1
- package/dist/queries.mjs +1 -768
- package/dist/schemas.d.mts +1 -1
- package/dist/schemas.mjs +23 -84
- package/dist/{types-3-FS9NV2.d.mts → types-DNYdIU21.d.mts} +54 -5
- package/dist/validate-TtH-x3JV.mjs +190 -0
- package/package.json +13 -3
- package/dist/indexing-DR8M1vBy.mjs +0 -137
- /package/dist/{edge-list-DP4otyPU.mjs → edge-list-BcZ0h6zz.mjs} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { $ as
|
|
1
|
+
import { C as getSinks, D as isLeaf, E as isCompound, N as invalidateIndex, S as getSiblings, T as getSuccessors, _ as getPorts, a as getDescendants, b as getRelativeDistanceMap, c as getEdgesOf, d as getLCA, f as getNeighbors, g as getPort, h as getParent, i as getDepth, l as getInDegree, m as getOutEdges, n as getChildren, o as getEdgesBetween, p as getOutDegree, r as getDegree, s as getEdgesByPort, t as getAncestors, u as getInEdges, v as getPredecessors, w as getSources, x as getRoots, y as getRelativeDistance } from "./queries-KirMDR7e.mjs";
|
|
2
|
+
import { $ as joinPaths, A as dfs, B as toEdgeConfig, C as genPostorders, D as getPreorder, E as getPostorders, F as isConnected, G as getAStarPath, H as genCycles, I as isTree, J as getShortestPath, K as getAllPairsShortestPaths, L as flatten, M as getTopologicalSort, N as hasPath, O as getPreorders, P as isAcyclic, Q as getStronglyConnectedComponents, R as getSubgraph, S as getMinimumSpanningTree, T as getPostorder, U as genShortestPaths, V as toNodeConfig, W as genSimplePaths, X as getSimplePath, Y as getShortestPaths, Z as getSimplePaths, _ as getEigenvectorCentrality, _t as updateEdge, a as isIsomorphic, at as createGraphEdge, b as getOutDegreeCentrality, c as getBridges, ct as createGraphPort, d as getGreedyModularityCommunities, dt as deleteEntities, et as GraphInstance, f as getLabelPropagationCommunities, ft as deleteNode, g as getDegreeCentrality, gt as hasNode, h as getClosenessCentrality, ht as hasEdge, i as getLouvainCommunities, it as createGraph, j as getConnectedComponents, k as bfs, l as genGirvanNewmanCommunities, lt as createVisualGraph, m as getBetweennessCentrality, mt as getNode, n as getDominatorTree, nt as addEntities, o as getArticulationPoints, ot as createGraphFromTransition, p as getModularity, pt as getEdge, q as getCycles, r as getMaxFlow, rt as addNode, s as getBiconnectedComponents, st as createGraphNode, t as getTransitiveReduction, tt as addEdge, u as getGirvanNewmanCommunities, ut as deleteEdge, v as getHITS, vt as updateEntities, w as genPreorders, x as getPageRank, y as getInDegreeCentrality, yt as updateNode, z as reverseGraph } from "./algorithms-DF1pSQGv.mjs";
|
|
3
3
|
import { n as isEdgeDirected, t as getEdgeMode } from "./mode-D8OnHFBk.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { n as createFormatConverter } from "./converter-
|
|
4
|
+
import { t as getGraphIssues } from "./validate-TtH-x3JV.mjs";
|
|
5
|
+
import { n as createFormatConverter } from "./converter-DyCJJfTe.mjs";
|
|
6
6
|
|
|
7
7
|
//#region src/equivalence.ts
|
|
8
8
|
/** Shallow-compare two values, returning true if they differ. */
|
|
@@ -52,7 +52,7 @@ const LAYOUT_KEY_SET = {
|
|
|
52
52
|
* ```
|
|
53
53
|
*/
|
|
54
54
|
function areEntitiesEqual(a, b, keys) {
|
|
55
|
-
const compareKeys = keys && keys.length > 0 ? keys : Object.keys(a);
|
|
55
|
+
const compareKeys = keys && keys.length > 0 ? keys : [...new Set([...Object.keys(a), ...Object.keys(b)])];
|
|
56
56
|
for (const key of compareKeys) if (differs$1(a[key], b[key])) return false;
|
|
57
57
|
return true;
|
|
58
58
|
}
|
|
@@ -87,47 +87,16 @@ function isLayoutEqual(a, b) {
|
|
|
87
87
|
*/
|
|
88
88
|
function isNonLayoutEqual(a, b) {
|
|
89
89
|
const skip = LAYOUT_KEY_SET[a.type];
|
|
90
|
-
const keys = Object.keys(a);
|
|
91
|
-
for (
|
|
92
|
-
if (skip.has(
|
|
93
|
-
if (differs$1(a[
|
|
90
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
91
|
+
for (const key of keys) {
|
|
92
|
+
if (skip.has(key)) continue;
|
|
93
|
+
if (differs$1(a[key], b[key])) return false;
|
|
94
94
|
}
|
|
95
95
|
return true;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
//#endregion
|
|
99
99
|
//#region src/diff.ts
|
|
100
|
-
function nodeToConfig$1(node) {
|
|
101
|
-
const config = { id: node.id };
|
|
102
|
-
if (node.parentId) config.parentId = node.parentId;
|
|
103
|
-
if (node.initialNodeId) config.initialNodeId = node.initialNodeId;
|
|
104
|
-
if (node.label !== "") config.label = node.label;
|
|
105
|
-
if (node.data !== void 0) config.data = node.data;
|
|
106
|
-
if (node.x !== void 0) config.x = node.x;
|
|
107
|
-
if (node.y !== void 0) config.y = node.y;
|
|
108
|
-
if (node.width !== void 0) config.width = node.width;
|
|
109
|
-
if (node.height !== void 0) config.height = node.height;
|
|
110
|
-
if (node.shape !== void 0) config.shape = node.shape;
|
|
111
|
-
if (node.color !== void 0) config.color = node.color;
|
|
112
|
-
if (node.style !== void 0) config.style = node.style;
|
|
113
|
-
return config;
|
|
114
|
-
}
|
|
115
|
-
function edgeToConfig$1(edge) {
|
|
116
|
-
const config = {
|
|
117
|
-
id: edge.id,
|
|
118
|
-
sourceId: edge.sourceId,
|
|
119
|
-
targetId: edge.targetId
|
|
120
|
-
};
|
|
121
|
-
if (edge.label !== "") config.label = edge.label;
|
|
122
|
-
if (edge.data !== void 0) config.data = edge.data;
|
|
123
|
-
if (edge.x !== void 0) config.x = edge.x;
|
|
124
|
-
if (edge.y !== void 0) config.y = edge.y;
|
|
125
|
-
if (edge.width !== void 0) config.width = edge.width;
|
|
126
|
-
if (edge.height !== void 0) config.height = edge.height;
|
|
127
|
-
if (edge.color !== void 0) config.color = edge.color;
|
|
128
|
-
if (edge.style !== void 0) config.style = edge.style;
|
|
129
|
-
return config;
|
|
130
|
-
}
|
|
131
100
|
/** Shallow-compare two values, returning true if they differ. */
|
|
132
101
|
function differs(a, b) {
|
|
133
102
|
if (a === b) return false;
|
|
@@ -140,6 +109,7 @@ const NODE_COMPARE_KEYS = [
|
|
|
140
109
|
"initialNodeId",
|
|
141
110
|
"label",
|
|
142
111
|
"data",
|
|
112
|
+
"ports",
|
|
143
113
|
"x",
|
|
144
114
|
"y",
|
|
145
115
|
"width",
|
|
@@ -153,6 +123,10 @@ const EDGE_COMPARE_KEYS = [
|
|
|
153
123
|
"targetId",
|
|
154
124
|
"label",
|
|
155
125
|
"data",
|
|
126
|
+
"weight",
|
|
127
|
+
"mode",
|
|
128
|
+
"sourcePort",
|
|
129
|
+
"targetPort",
|
|
156
130
|
"x",
|
|
157
131
|
"y",
|
|
158
132
|
"width",
|
|
@@ -194,13 +168,17 @@ function getDiff(a, b) {
|
|
|
194
168
|
};
|
|
195
169
|
for (const [id, nodeB] of bNodeMap) {
|
|
196
170
|
const nodeA = aNodeMap.get(id);
|
|
197
|
-
if (!nodeA) diff.nodes.added.push(
|
|
171
|
+
if (!nodeA) diff.nodes.added.push(toNodeConfig(nodeB));
|
|
198
172
|
else {
|
|
199
173
|
const oldPartial = {};
|
|
200
174
|
const newPartial = {};
|
|
201
|
-
for (const key of NODE_COMPARE_KEYS)
|
|
202
|
-
|
|
203
|
-
|
|
175
|
+
for (const key of NODE_COMPARE_KEYS) {
|
|
176
|
+
const oldValue = nodeA[key] ?? null;
|
|
177
|
+
const newValue = nodeB[key] ?? null;
|
|
178
|
+
if (differs(oldValue, newValue)) {
|
|
179
|
+
oldPartial[key] = oldValue;
|
|
180
|
+
newPartial[key] = newValue;
|
|
181
|
+
}
|
|
204
182
|
}
|
|
205
183
|
if (Object.keys(oldPartial).length > 0) diff.nodes.updated.push({
|
|
206
184
|
id,
|
|
@@ -209,16 +187,20 @@ function getDiff(a, b) {
|
|
|
209
187
|
});
|
|
210
188
|
}
|
|
211
189
|
}
|
|
212
|
-
for (const [id, nodeA] of aNodeMap) if (!bNodeMap.has(id)) diff.nodes.removed.push(
|
|
190
|
+
for (const [id, nodeA] of aNodeMap) if (!bNodeMap.has(id)) diff.nodes.removed.push(toNodeConfig(nodeA));
|
|
213
191
|
for (const [id, edgeB] of bEdgeMap) {
|
|
214
192
|
const edgeA = aEdgeMap.get(id);
|
|
215
|
-
if (!edgeA) diff.edges.added.push(
|
|
193
|
+
if (!edgeA) diff.edges.added.push(toEdgeConfig(edgeB));
|
|
216
194
|
else {
|
|
217
195
|
const oldPartial = {};
|
|
218
196
|
const newPartial = {};
|
|
219
|
-
for (const key of EDGE_COMPARE_KEYS)
|
|
220
|
-
|
|
221
|
-
|
|
197
|
+
for (const key of EDGE_COMPARE_KEYS) {
|
|
198
|
+
const oldValue = edgeA[key] ?? null;
|
|
199
|
+
const newValue = edgeB[key] ?? null;
|
|
200
|
+
if (differs(oldValue, newValue)) {
|
|
201
|
+
oldPartial[key] = oldValue;
|
|
202
|
+
newPartial[key] = newValue;
|
|
203
|
+
}
|
|
222
204
|
}
|
|
223
205
|
if (Object.keys(oldPartial).length > 0) diff.edges.updated.push({
|
|
224
206
|
id,
|
|
@@ -227,7 +209,7 @@ function getDiff(a, b) {
|
|
|
227
209
|
});
|
|
228
210
|
}
|
|
229
211
|
}
|
|
230
|
-
for (const [id, edgeA] of aEdgeMap) if (!bEdgeMap.has(id)) diff.edges.removed.push(
|
|
212
|
+
for (const [id, edgeA] of aEdgeMap) if (!bEdgeMap.has(id)) diff.edges.removed.push(toEdgeConfig(edgeA));
|
|
231
213
|
return diff;
|
|
232
214
|
}
|
|
233
215
|
/**
|
|
@@ -264,28 +246,29 @@ function isEmptyDiff(diff) {
|
|
|
264
246
|
function invertDiff(diff) {
|
|
265
247
|
return {
|
|
266
248
|
nodes: {
|
|
267
|
-
added: diff.nodes.removed,
|
|
268
|
-
removed: diff.nodes.added,
|
|
249
|
+
added: diff.nodes.removed.map((c) => structuredClone(c)),
|
|
250
|
+
removed: diff.nodes.added.map((c) => structuredClone(c)),
|
|
269
251
|
updated: diff.nodes.updated.map((c) => ({
|
|
270
252
|
id: c.id,
|
|
271
|
-
old: c.new,
|
|
272
|
-
new: c.old
|
|
253
|
+
old: structuredClone(c.new),
|
|
254
|
+
new: structuredClone(c.old)
|
|
273
255
|
}))
|
|
274
256
|
},
|
|
275
257
|
edges: {
|
|
276
|
-
added: diff.edges.removed,
|
|
277
|
-
removed: diff.edges.added,
|
|
258
|
+
added: diff.edges.removed.map((c) => structuredClone(c)),
|
|
259
|
+
removed: diff.edges.added.map((c) => structuredClone(c)),
|
|
278
260
|
updated: diff.edges.updated.map((c) => ({
|
|
279
261
|
id: c.id,
|
|
280
|
-
old: c.new,
|
|
281
|
-
new: c.old
|
|
262
|
+
old: structuredClone(c.new),
|
|
263
|
+
new: structuredClone(c.old)
|
|
282
264
|
}))
|
|
283
265
|
}
|
|
284
266
|
};
|
|
285
267
|
}
|
|
286
268
|
/**
|
|
287
269
|
* Compute an ordered patch list from graph `a` to graph `b`.
|
|
288
|
-
* Order
|
|
270
|
+
* Order (see {@link toPatches}): add nodes → update edges → delete edges →
|
|
271
|
+
* delete nodes → add edges → update nodes.
|
|
289
272
|
*
|
|
290
273
|
* @example
|
|
291
274
|
* ```ts
|
|
@@ -365,7 +348,7 @@ function toPatches(diff) {
|
|
|
365
348
|
});
|
|
366
349
|
for (const change of diff.edges.updated) {
|
|
367
350
|
const data = {};
|
|
368
|
-
for (const [key, value] of Object.entries(change.new)) data[key] = value;
|
|
351
|
+
for (const [key, value] of Object.entries(change.new)) data[key] = value ?? null;
|
|
369
352
|
patches.push({
|
|
370
353
|
op: "updateEdge",
|
|
371
354
|
id: change.id,
|
|
@@ -386,7 +369,7 @@ function toPatches(diff) {
|
|
|
386
369
|
});
|
|
387
370
|
for (const change of diff.nodes.updated) {
|
|
388
371
|
const data = {};
|
|
389
|
-
for (const [key, value] of Object.entries(change.new)) data[key] = value;
|
|
372
|
+
for (const [key, value] of Object.entries(change.new)) data[key] = value ?? null;
|
|
390
373
|
patches.push({
|
|
391
374
|
op: "updateNode",
|
|
392
375
|
id: change.id,
|
|
@@ -460,201 +443,6 @@ function toDiff(patches) {
|
|
|
460
443
|
return diff;
|
|
461
444
|
}
|
|
462
445
|
|
|
463
|
-
//#endregion
|
|
464
|
-
//#region src/transforms.ts
|
|
465
|
-
/**
|
|
466
|
-
* Flattens a hierarchical graph into a flat graph with only leaf nodes.
|
|
467
|
-
*
|
|
468
|
-
* - Edges targeting a compound node resolve to its initial child (recursively).
|
|
469
|
-
* - Edges originating from a compound node expand to all leaf descendants.
|
|
470
|
-
* - Only leaf nodes (nodes with no children) appear in the result.
|
|
471
|
-
* - Duplicate edges (same source + target) are deduplicated.
|
|
472
|
-
*
|
|
473
|
-
* @example
|
|
474
|
-
* ```ts
|
|
475
|
-
* import { createGraph, flatten } from '@statelyai/graph';
|
|
476
|
-
*
|
|
477
|
-
* const graph = createGraph({
|
|
478
|
-
* nodes: [
|
|
479
|
-
* { id: 'parent', initialNodeId: 'child1' },
|
|
480
|
-
* { id: 'child1', parentId: 'parent' },
|
|
481
|
-
* { id: 'child2', parentId: 'parent' },
|
|
482
|
-
* { id: 'other' },
|
|
483
|
-
* ],
|
|
484
|
-
* edges: [{ id: 'e1', sourceId: 'other', targetId: 'parent' }],
|
|
485
|
-
* });
|
|
486
|
-
*
|
|
487
|
-
* const flat = flatten(graph);
|
|
488
|
-
* // flat.nodes → [child1, child2, other] (leaf nodes only)
|
|
489
|
-
* // flat.edges → edge from 'other' → 'child1' (resolved via initialNodeId)
|
|
490
|
-
* ```
|
|
491
|
-
*/
|
|
492
|
-
function flatten(graph) {
|
|
493
|
-
const idx = getIndex(graph);
|
|
494
|
-
const leaves = /* @__PURE__ */ new Set();
|
|
495
|
-
for (const node of graph.nodes) if ((idx.childNodes.get(node.id) ?? []).length === 0) leaves.add(node.id);
|
|
496
|
-
function resolveInitial(nodeId) {
|
|
497
|
-
if (leaves.has(nodeId)) return nodeId;
|
|
498
|
-
const ni = idx.nodeById.get(nodeId);
|
|
499
|
-
if (ni === void 0) return null;
|
|
500
|
-
const node = graph.nodes[ni];
|
|
501
|
-
if (node.initialNodeId) return resolveInitial(node.initialNodeId);
|
|
502
|
-
const childIds = idx.childNodes.get(nodeId) ?? [];
|
|
503
|
-
if (childIds.length > 0) return resolveInitial(childIds[0]);
|
|
504
|
-
return nodeId;
|
|
505
|
-
}
|
|
506
|
-
function getLeafDescendants(nodeId) {
|
|
507
|
-
if (leaves.has(nodeId)) return [nodeId];
|
|
508
|
-
const result = [];
|
|
509
|
-
const collect = (id) => {
|
|
510
|
-
const childIds = idx.childNodes.get(id) ?? [];
|
|
511
|
-
for (const childId of childIds) if (leaves.has(childId)) result.push(childId);
|
|
512
|
-
else collect(childId);
|
|
513
|
-
};
|
|
514
|
-
collect(nodeId);
|
|
515
|
-
return result;
|
|
516
|
-
}
|
|
517
|
-
const edgeSeen = /* @__PURE__ */ new Set();
|
|
518
|
-
const flatEdges = [];
|
|
519
|
-
for (const edge of graph.edges) {
|
|
520
|
-
const sources = leaves.has(edge.sourceId) ? [edge.sourceId] : getLeafDescendants(edge.sourceId);
|
|
521
|
-
const target = leaves.has(edge.targetId) ? edge.targetId : resolveInitial(edge.targetId);
|
|
522
|
-
if (target === null) continue;
|
|
523
|
-
for (const source of sources) {
|
|
524
|
-
if (source === target) continue;
|
|
525
|
-
const key = `${source}->${target}`;
|
|
526
|
-
if (edgeSeen.has(key)) continue;
|
|
527
|
-
edgeSeen.add(key);
|
|
528
|
-
flatEdges.push({
|
|
529
|
-
type: "edge",
|
|
530
|
-
id: `${edge.id}:${source}->${target}`,
|
|
531
|
-
sourceId: source,
|
|
532
|
-
targetId: target,
|
|
533
|
-
label: edge.label,
|
|
534
|
-
data: edge.data
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
const leafNodes = graph.nodes.filter((n) => leaves.has(n.id)).map((n) => ({
|
|
539
|
-
id: n.id,
|
|
540
|
-
label: n.label,
|
|
541
|
-
data: n.data
|
|
542
|
-
}));
|
|
543
|
-
return createGraph({
|
|
544
|
-
id: graph.id,
|
|
545
|
-
mode: graph.mode,
|
|
546
|
-
nodes: leafNodes,
|
|
547
|
-
edges: flatEdges,
|
|
548
|
-
data: graph.data
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
function nodeToConfig(node, nodeIdSet) {
|
|
552
|
-
const config = {
|
|
553
|
-
id: node.id,
|
|
554
|
-
label: node.label,
|
|
555
|
-
data: node.data
|
|
556
|
-
};
|
|
557
|
-
if (node.parentId !== void 0 && node.parentId !== null) config.parentId = nodeIdSet && !nodeIdSet.has(node.parentId) ? void 0 : node.parentId;
|
|
558
|
-
if (node.initialNodeId !== void 0) config.initialNodeId = node.initialNodeId ?? void 0;
|
|
559
|
-
if (node.x !== void 0) config.x = node.x;
|
|
560
|
-
if (node.y !== void 0) config.y = node.y;
|
|
561
|
-
if (node.width !== void 0) config.width = node.width;
|
|
562
|
-
if (node.height !== void 0) config.height = node.height;
|
|
563
|
-
if (node.shape !== void 0) config.shape = node.shape;
|
|
564
|
-
if (node.color !== void 0) config.color = node.color;
|
|
565
|
-
if (node.style !== void 0) config.style = node.style;
|
|
566
|
-
return config;
|
|
567
|
-
}
|
|
568
|
-
function edgeToConfig(edge) {
|
|
569
|
-
const config = {
|
|
570
|
-
id: edge.id,
|
|
571
|
-
sourceId: edge.sourceId,
|
|
572
|
-
targetId: edge.targetId,
|
|
573
|
-
label: edge.label,
|
|
574
|
-
data: edge.data
|
|
575
|
-
};
|
|
576
|
-
if (edge.weight !== void 0) config.weight = edge.weight;
|
|
577
|
-
if (edge.x !== void 0) config.x = edge.x;
|
|
578
|
-
if (edge.y !== void 0) config.y = edge.y;
|
|
579
|
-
if (edge.width !== void 0) config.width = edge.width;
|
|
580
|
-
if (edge.height !== void 0) config.height = edge.height;
|
|
581
|
-
if (edge.color !== void 0) config.color = edge.color;
|
|
582
|
-
if (edge.style !== void 0) config.style = edge.style;
|
|
583
|
-
return config;
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Returns the induced subgraph containing only the given node IDs
|
|
587
|
-
* and edges whose endpoints are both in the set.
|
|
588
|
-
*
|
|
589
|
-
* Parent references to nodes outside the set are removed.
|
|
590
|
-
*
|
|
591
|
-
* @example
|
|
592
|
-
* ```ts
|
|
593
|
-
* import { createGraph, getSubgraph } from '@statelyai/graph';
|
|
594
|
-
*
|
|
595
|
-
* const graph = createGraph({
|
|
596
|
-
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
597
|
-
* edges: [
|
|
598
|
-
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
599
|
-
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
600
|
-
* ],
|
|
601
|
-
* });
|
|
602
|
-
*
|
|
603
|
-
* const sub = getSubgraph(graph, ['a', 'b']);
|
|
604
|
-
* // sub.nodes: [a, b], sub.edges: [ab]
|
|
605
|
-
* ```
|
|
606
|
-
*/
|
|
607
|
-
function getSubgraph(graph, nodeIds) {
|
|
608
|
-
const nodeIdSet = new Set(nodeIds);
|
|
609
|
-
return createGraph({
|
|
610
|
-
id: graph.id,
|
|
611
|
-
mode: graph.mode,
|
|
612
|
-
initialNodeId: graph.initialNodeId && nodeIdSet.has(graph.initialNodeId) ? graph.initialNodeId : void 0,
|
|
613
|
-
nodes: graph.nodes.filter((n) => nodeIdSet.has(n.id)).map((n) => nodeToConfig(n, nodeIdSet)),
|
|
614
|
-
edges: graph.edges.filter((e) => nodeIdSet.has(e.sourceId) && nodeIdSet.has(e.targetId)).map(edgeToConfig),
|
|
615
|
-
data: graph.data
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Returns a new graph with all edge directions flipped (source ↔ target).
|
|
620
|
-
* Optionally filters which edges to include.
|
|
621
|
-
*
|
|
622
|
-
* @example
|
|
623
|
-
* ```ts
|
|
624
|
-
* import { createGraph, reverseGraph } from '@statelyai/graph';
|
|
625
|
-
*
|
|
626
|
-
* const graph = createGraph({
|
|
627
|
-
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
628
|
-
* edges: [
|
|
629
|
-
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
630
|
-
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
631
|
-
* ],
|
|
632
|
-
* });
|
|
633
|
-
*
|
|
634
|
-
* const rev = reverseGraph(graph);
|
|
635
|
-
* // rev edges: b→a, c→b
|
|
636
|
-
*
|
|
637
|
-
* const filtered = reverseGraph(graph, (e) => e.id !== 'bc');
|
|
638
|
-
* // filtered edges: b→a (only ab reversed, bc excluded)
|
|
639
|
-
* ```
|
|
640
|
-
*/
|
|
641
|
-
function reverseGraph(graph, filterEdge) {
|
|
642
|
-
const edges = filterEdge ? graph.edges.filter(filterEdge) : graph.edges;
|
|
643
|
-
return createGraph({
|
|
644
|
-
id: graph.id,
|
|
645
|
-
mode: graph.mode,
|
|
646
|
-
initialNodeId: graph.initialNodeId ?? void 0,
|
|
647
|
-
nodes: graph.nodes.map((n) => nodeToConfig(n)),
|
|
648
|
-
edges: edges.map((e) => {
|
|
649
|
-
const config = edgeToConfig(e);
|
|
650
|
-
config.sourceId = e.targetId;
|
|
651
|
-
config.targetId = e.sourceId;
|
|
652
|
-
return config;
|
|
653
|
-
}),
|
|
654
|
-
data: graph.data
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
|
|
658
446
|
//#endregion
|
|
659
447
|
//#region src/walks.ts
|
|
660
448
|
function mulberry32(seed) {
|
|
@@ -680,7 +468,24 @@ function resolveFrom(graph, from) {
|
|
|
680
468
|
throw new Error("Cannot determine start node: provide `from`, set graph.initialNodeId, or have exactly one source node.");
|
|
681
469
|
}
|
|
682
470
|
/**
|
|
683
|
-
*
|
|
471
|
+
* Edges traversable from a node, with the node reached by taking each one.
|
|
472
|
+
* Out-edges always; in-edges too when their effective mode is not 'directed'.
|
|
473
|
+
*/
|
|
474
|
+
function getTraversableEdges(graph, nodeId) {
|
|
475
|
+
const result = [];
|
|
476
|
+
for (const edge of getOutEdges(graph, nodeId)) result.push({
|
|
477
|
+
edge,
|
|
478
|
+
nextId: edge.targetId
|
|
479
|
+
});
|
|
480
|
+
for (const edge of getInEdges(graph, nodeId)) if (edge.sourceId !== edge.targetId && getEdgeMode(graph, edge) !== "directed") result.push({
|
|
481
|
+
edge,
|
|
482
|
+
nextId: edge.sourceId
|
|
483
|
+
});
|
|
484
|
+
return result;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Random walk. At each node, picks a uniformly random traversable edge
|
|
488
|
+
* (outgoing edges, plus non-directed edges both ways).
|
|
684
489
|
* Yields steps indefinitely (may revisit nodes) until a sink node is reached.
|
|
685
490
|
*/
|
|
686
491
|
function* genRandomWalk(graph, options) {
|
|
@@ -693,11 +498,11 @@ function* genRandomWalk(graph, options) {
|
|
|
693
498
|
stepCount: 0
|
|
694
499
|
};
|
|
695
500
|
while (true) {
|
|
696
|
-
let
|
|
697
|
-
if (options?.filter)
|
|
698
|
-
if (
|
|
699
|
-
const edge =
|
|
700
|
-
const node = getNode(graph,
|
|
501
|
+
let traversable = getTraversableEdges(graph, currentId);
|
|
502
|
+
if (options?.filter) traversable = traversable.filter(({ edge: edge$1 }) => options.filter(edge$1, ctx));
|
|
503
|
+
if (traversable.length === 0) return;
|
|
504
|
+
const { edge, nextId } = traversable[Math.floor(rng() * traversable.length)];
|
|
505
|
+
const node = getNode(graph, nextId);
|
|
701
506
|
const step = {
|
|
702
507
|
edge,
|
|
703
508
|
node
|
|
@@ -725,30 +530,30 @@ function* genWeightedRandomWalk(graph, options) {
|
|
|
725
530
|
stepCount: 0
|
|
726
531
|
};
|
|
727
532
|
while (true) {
|
|
728
|
-
let
|
|
729
|
-
if (options?.filter)
|
|
730
|
-
if (
|
|
731
|
-
const weights =
|
|
533
|
+
let traversable = getTraversableEdges(graph, currentId);
|
|
534
|
+
if (options?.filter) traversable = traversable.filter(({ edge }) => options.filter(edge, ctx));
|
|
535
|
+
if (traversable.length === 0) return;
|
|
536
|
+
const weights = traversable.map(({ edge }) => Math.max(0, getWeight(edge)));
|
|
732
537
|
const total = weights.reduce((a, b) => a + b, 0);
|
|
733
538
|
if (total === 0) return;
|
|
734
539
|
let r = rng() * total;
|
|
735
|
-
let chosen =
|
|
736
|
-
for (let i = 0; i <
|
|
540
|
+
let chosen = traversable[0];
|
|
541
|
+
for (let i = 0; i < traversable.length; i++) {
|
|
737
542
|
r -= weights[i];
|
|
738
543
|
if (r <= 0) {
|
|
739
|
-
chosen =
|
|
544
|
+
chosen = traversable[i];
|
|
740
545
|
break;
|
|
741
546
|
}
|
|
742
547
|
}
|
|
743
|
-
const node = getNode(graph, chosen.
|
|
548
|
+
const node = getNode(graph, chosen.nextId);
|
|
744
549
|
const step = {
|
|
745
|
-
edge: chosen,
|
|
550
|
+
edge: chosen.edge,
|
|
746
551
|
node
|
|
747
552
|
};
|
|
748
553
|
currentId = node.id;
|
|
749
554
|
ctx.currentNodeId = currentId;
|
|
750
555
|
ctx.visitedNodes.add(currentId);
|
|
751
|
-
ctx.visitedEdges.add(chosen.id);
|
|
556
|
+
ctx.visitedEdges.add(chosen.edge.id);
|
|
752
557
|
ctx.stepCount++;
|
|
753
558
|
options?.onStep?.(step, ctx);
|
|
754
559
|
yield step;
|
|
@@ -756,8 +561,9 @@ function* genWeightedRandomWalk(graph, options) {
|
|
|
756
561
|
}
|
|
757
562
|
/**
|
|
758
563
|
* Quick random walk targeting unvisited edges.
|
|
759
|
-
* If unvisited
|
|
760
|
-
* Otherwise,
|
|
564
|
+
* If unvisited traversable edges exist at the current node, picks one randomly.
|
|
565
|
+
* Otherwise, walks the fewest-hop path (BFS, honoring `filter` and edge modes)
|
|
566
|
+
* to the nearest unvisited edge. Ends when no unvisited edge is reachable.
|
|
761
567
|
*/
|
|
762
568
|
function* genQuickRandomWalk(graph, options) {
|
|
763
569
|
const rng = makeRng(options?.seed);
|
|
@@ -770,13 +576,16 @@ function* genQuickRandomWalk(graph, options) {
|
|
|
770
576
|
visitedEdges,
|
|
771
577
|
stepCount: 0
|
|
772
578
|
};
|
|
579
|
+
const allowedEdges = (nodeId) => {
|
|
580
|
+
let traversable = getTraversableEdges(graph, nodeId);
|
|
581
|
+
if (options?.filter) traversable = traversable.filter(({ edge }) => options.filter(edge, ctx));
|
|
582
|
+
return traversable;
|
|
583
|
+
};
|
|
773
584
|
while (visitedEdges.size < allEdgeIds.size) {
|
|
774
|
-
|
|
775
|
-
if (options?.filter) edges = edges.filter((e) => options.filter(e, ctx));
|
|
776
|
-
const unvisited = edges.filter((e) => !visitedEdges.has(e.id));
|
|
585
|
+
const unvisited = allowedEdges(currentId).filter(({ edge }) => !visitedEdges.has(edge.id));
|
|
777
586
|
if (unvisited.length > 0) {
|
|
778
|
-
const edge = unvisited[Math.floor(rng() * unvisited.length)];
|
|
779
|
-
const node = getNode(graph,
|
|
587
|
+
const { edge, nextId } = unvisited[Math.floor(rng() * unvisited.length)];
|
|
588
|
+
const node = getNode(graph, nextId);
|
|
780
589
|
const step = {
|
|
781
590
|
edge,
|
|
782
591
|
node
|
|
@@ -789,22 +598,54 @@ function* genQuickRandomWalk(graph, options) {
|
|
|
789
598
|
options?.onStep?.(step, ctx);
|
|
790
599
|
yield step;
|
|
791
600
|
} else {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
601
|
+
const prevStep = /* @__PURE__ */ new Map();
|
|
602
|
+
const seen = new Set([currentId]);
|
|
603
|
+
const queue = [currentId];
|
|
604
|
+
let found;
|
|
605
|
+
while (queue.length > 0 && !found) {
|
|
606
|
+
const id = queue.shift();
|
|
607
|
+
for (const t of allowedEdges(id)) {
|
|
608
|
+
if (!visitedEdges.has(t.edge.id)) {
|
|
609
|
+
found = {
|
|
610
|
+
atId: id,
|
|
611
|
+
edge: t.edge,
|
|
612
|
+
nextId: t.nextId
|
|
613
|
+
};
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
if (!seen.has(t.nextId)) {
|
|
617
|
+
seen.add(t.nextId);
|
|
618
|
+
prevStep.set(t.nextId, {
|
|
619
|
+
edge: t.edge,
|
|
620
|
+
fromId: id
|
|
621
|
+
});
|
|
622
|
+
queue.push(t.nextId);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
796
625
|
}
|
|
797
|
-
if (!
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
626
|
+
if (!found) return;
|
|
627
|
+
const pathSteps = [{
|
|
628
|
+
edge: found.edge,
|
|
629
|
+
nextId: found.nextId
|
|
630
|
+
}];
|
|
631
|
+
let cursor = found.atId;
|
|
632
|
+
while (cursor !== currentId) {
|
|
633
|
+
const p = prevStep.get(cursor);
|
|
634
|
+
pathSteps.unshift({
|
|
635
|
+
edge: p.edge,
|
|
636
|
+
nextId: cursor
|
|
637
|
+
});
|
|
638
|
+
cursor = p.fromId;
|
|
639
|
+
}
|
|
640
|
+
for (const { edge, nextId } of pathSteps) {
|
|
641
|
+
const step = {
|
|
642
|
+
edge,
|
|
643
|
+
node: getNode(graph, nextId)
|
|
644
|
+
};
|
|
645
|
+
currentId = nextId;
|
|
805
646
|
ctx.currentNodeId = currentId;
|
|
806
647
|
ctx.visitedNodes.add(currentId);
|
|
807
|
-
visitedEdges.add(
|
|
648
|
+
visitedEdges.add(edge.id);
|
|
808
649
|
ctx.stepCount++;
|
|
809
650
|
options?.onStep?.(step, ctx);
|
|
810
651
|
yield step;
|
|
@@ -815,14 +656,19 @@ function* genQuickRandomWalk(graph, options) {
|
|
|
815
656
|
/**
|
|
816
657
|
* Walk a predefined sequence of edge IDs.
|
|
817
658
|
* Validates each edge exists and connects from the current position.
|
|
659
|
+
* Edges whose effective mode is not `'directed'` may be traversed
|
|
660
|
+
* target → source as well.
|
|
818
661
|
*/
|
|
819
662
|
function* genPredefinedWalk(graph, edgeIds, options) {
|
|
820
663
|
let currentId = resolveFrom(graph, options?.from);
|
|
821
664
|
for (const edgeId of edgeIds) {
|
|
822
665
|
const edge = graph.edges.find((e) => e.id === edgeId);
|
|
823
666
|
if (!edge) throw new Error(`Edge "${edgeId}" not found in graph.`);
|
|
824
|
-
|
|
825
|
-
|
|
667
|
+
let nextId;
|
|
668
|
+
if (edge.sourceId === currentId) nextId = edge.targetId;
|
|
669
|
+
else if (edge.targetId === currentId && getEdgeMode(graph, edge) !== "directed") nextId = edge.sourceId;
|
|
670
|
+
else throw new Error(`Edge "${edgeId}" connects "${edge.sourceId}" → "${edge.targetId}" but current position is "${currentId}".`);
|
|
671
|
+
const node = getNode(graph, nextId);
|
|
826
672
|
currentId = node.id;
|
|
827
673
|
yield {
|
|
828
674
|
edge,
|
|
@@ -866,6 +712,7 @@ function* takeUntilNodeCoverage(gen, graph, coverage, options) {
|
|
|
866
712
|
const target = Math.ceil(coverage * totalNodes);
|
|
867
713
|
const startId = options?.from ?? graph.initialNodeId ?? graph.nodes[0]?.id;
|
|
868
714
|
const visited = new Set(startId ? [startId] : []);
|
|
715
|
+
if (visited.size >= target) return;
|
|
869
716
|
for (const step of gen) {
|
|
870
717
|
visited.add(step.node.id);
|
|
871
718
|
yield step;
|
|
@@ -879,6 +726,7 @@ function* takeUntilEdgeCoverage(gen, graph, coverage) {
|
|
|
879
726
|
const totalEdges = graph.edges.length;
|
|
880
727
|
const target = Math.ceil(coverage * totalEdges);
|
|
881
728
|
const visited = /* @__PURE__ */ new Set();
|
|
729
|
+
if (target <= 0) return;
|
|
882
730
|
for (const step of gen) {
|
|
883
731
|
visited.add(step.edge.id);
|
|
884
732
|
yield step;
|
|
@@ -906,4 +754,4 @@ function getCoverage(graph, steps, options) {
|
|
|
906
754
|
}
|
|
907
755
|
|
|
908
756
|
//#endregion
|
|
909
|
-
export { GraphInstance, LAYOUT_KEYS, addEdge, addEntities, addNode, applyPatches, areEntitiesEqual, bfs, createFormatConverter, createGraph, createGraphEdge, createGraphFromTransition, createGraphNode, createGraphPort, createVisualGraph, deleteEdge, deleteEntities, deleteNode, dfs, flatten, genCycles, genGirvanNewmanCommunities, genPostorders, genPredefinedWalk, genPreorders, genQuickRandomWalk, genRandomWalk, genShortestPaths, genSimplePaths, genWeightedRandomWalk, getAStarPath, getAllPairsShortestPaths, getAncestors, getArticulationPoints, getBetweennessCentrality, getBiconnectedComponents, getBridges, getChildren, getClosenessCentrality, getConnectedComponents, getCoverage, getCycles, getDegree, getDegreeCentrality, getDepth, getDescendants, getDiff, getEdge, getEdgeMode, getEdgesBetween, getEdgesByPort, getEdgesOf, getEigenvectorCentrality, getGirvanNewmanCommunities, getGreedyModularityCommunities, getHITS, getInDegree, getInDegreeCentrality, getInEdges, getLCA, getLabelPropagationCommunities, getMinimumSpanningTree, getModularity, getNeighbors, getNode, getOutDegree, getOutDegreeCentrality, getOutEdges, getPageRank, getParent, getPatches, getPort, getPorts, getPostorder, getPostorders, getPredecessors, getPreorder, getPreorders, getRelativeDistance, getRelativeDistanceMap, getRoots, getShortestPath, getShortestPaths, getSiblings, getSimplePath, getSimplePaths, getSinks, getSources, getStronglyConnectedComponents, getSubgraph, getSuccessors, getTopologicalSort, hasEdge, hasNode, hasPath, invalidateIndex, invertDiff, isAcyclic, isCompound, isConnected, isEdgeDirected, isEmptyDiff, isIsomorphic, isLayoutEqual, isLeaf, isNonLayoutEqual, isTree, joinPaths, reverseGraph, takeSteps, takeUntilEdge, takeUntilEdgeCoverage, takeUntilNode, takeUntilNodeCoverage, toDiff, toPatches, updateEdge, updateEntities, updateNode };
|
|
757
|
+
export { GraphInstance, LAYOUT_KEYS, addEdge, addEntities, addNode, applyPatches, areEntitiesEqual, bfs, createFormatConverter, createGraph, createGraphEdge, createGraphFromTransition, createGraphNode, createGraphPort, createVisualGraph, deleteEdge, deleteEntities, deleteNode, dfs, flatten, genCycles, genGirvanNewmanCommunities, genPostorders, genPredefinedWalk, genPreorders, genQuickRandomWalk, genRandomWalk, genShortestPaths, genSimplePaths, genWeightedRandomWalk, getAStarPath, getAllPairsShortestPaths, getAncestors, getArticulationPoints, getBetweennessCentrality, getBiconnectedComponents, getBridges, getChildren, getClosenessCentrality, getConnectedComponents, getCoverage, getCycles, getDegree, getDegreeCentrality, getDepth, getDescendants, getDiff, getDominatorTree, getEdge, getEdgeMode, getEdgesBetween, getEdgesByPort, getEdgesOf, getEigenvectorCentrality, getGirvanNewmanCommunities, getGraphIssues, getGreedyModularityCommunities, getHITS, getInDegree, getInDegreeCentrality, getInEdges, getLCA, getLabelPropagationCommunities, getLouvainCommunities, getMaxFlow, getMinimumSpanningTree, getModularity, getNeighbors, getNode, getOutDegree, getOutDegreeCentrality, getOutEdges, getPageRank, getParent, getPatches, getPort, getPorts, getPostorder, getPostorders, getPredecessors, getPreorder, getPreorders, getRelativeDistance, getRelativeDistanceMap, getRoots, getShortestPath, getShortestPaths, getSiblings, getSimplePath, getSimplePaths, getSinks, getSources, getStronglyConnectedComponents, getSubgraph, getSuccessors, getTopologicalSort, getTransitiveReduction, hasEdge, hasNode, hasPath, invalidateIndex, invertDiff, isAcyclic, isCompound, isConnected, isEdgeDirected, isEmptyDiff, isIsomorphic, isLayoutEqual, isLeaf, isNonLayoutEqual, isTree, joinPaths, reverseGraph, takeSteps, takeUntilEdge, takeUntilEdgeCoverage, takeUntilNode, takeUntilNodeCoverage, toDiff, toPatches, updateEdge, updateEntities, updateNode };
|