@statelyai/graph 2.0.0 → 2.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/README.md +67 -19
- package/dist/{algorithms-CsGNehct.d.mts → algorithms-D1cgly0g.d.mts} +145 -6
- package/dist/{algorithms-DF1pSQGv.mjs → algorithms-DBpH74hR.mjs} +673 -891
- package/dist/algorithms.d.mts +2 -2
- package/dist/algorithms.mjs +2 -2
- package/dist/config-Dt5u1gSf.mjs +793 -0
- package/dist/{converter-DyCJJfTe.mjs → converter-DB6Rg6Vd.mjs} +2 -2
- 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 +4 -4
- package/dist/formats/cytoscape/index.mjs +8 -4
- package/dist/formats/d2/index.d.mts +1 -1
- package/dist/formats/d2/index.mjs +1 -1
- package/dist/formats/d3/index.d.mts +4 -4
- package/dist/formats/d3/index.mjs +8 -4
- package/dist/formats/dot/index.d.mts +1 -1
- package/dist/formats/dot/index.mjs +1 -1
- 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 +43 -11
- package/dist/formats/gexf/index.d.mts +1 -1
- package/dist/formats/gexf/index.mjs +22 -2
- package/dist/formats/gml/index.d.mts +4 -4
- package/dist/formats/gml/index.mjs +8 -4
- package/dist/formats/graphml/index.d.mts +1 -1
- package/dist/formats/graphml/index.mjs +24 -2
- package/dist/formats/jgf/index.d.mts +4 -4
- package/dist/formats/jgf/index.mjs +8 -4
- package/dist/formats/mermaid/index.d.mts +1 -1
- package/dist/formats/mermaid/index.mjs +1 -1
- package/dist/formats/tgf/index.d.mts +4 -4
- package/dist/formats/tgf/index.mjs +4 -4
- package/dist/formats/xyflow/index.d.mts +12 -6
- package/dist/formats/xyflow/index.mjs +11 -6
- package/dist/{index-D51lJnt2.d.mts → index-BlbSWUvH.d.mts} +1 -1
- package/dist/{index-DWmo1mIp.d.mts → index-CNvqxPLJ.d.mts} +82 -14
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +152 -17
- package/dist/layout/cytoscape.d.mts +66 -0
- package/dist/layout/cytoscape.mjs +114 -0
- package/dist/layout/d3-force.d.mts +52 -0
- package/dist/layout/d3-force.mjs +127 -0
- package/dist/layout/d3-hierarchy.d.mts +39 -0
- package/dist/layout/d3-hierarchy.mjs +135 -0
- package/dist/layout/dagre.d.mts +32 -0
- package/dist/layout/dagre.mjs +99 -0
- package/dist/layout/elk.d.mts +47 -0
- package/dist/layout/elk.mjs +73 -0
- package/dist/layout/forceatlas2.d.mts +48 -0
- package/dist/layout/forceatlas2.mjs +100 -0
- package/dist/layout/graphviz.d.mts +50 -0
- package/dist/layout/graphviz.mjs +179 -0
- package/dist/layout/index.d.mts +185 -0
- package/dist/layout/index.mjs +181 -0
- package/dist/layout/webcola.d.mts +40 -0
- package/dist/layout/webcola.mjs +104 -0
- package/dist/{queries-BfXeTXRf.d.mts → queries-B6quF529.d.mts} +1 -1
- package/dist/{queries-KirMDR7e.mjs → queries-BMM0XAv_.mjs} +23 -17
- package/dist/queries.d.mts +1 -1
- package/dist/queries.mjs +1 -1
- package/dist/schemas.d.mts +19 -1
- package/dist/schemas.mjs +10 -1
- package/dist/{types-DNYdIU21.d.mts → types-BAEQTwK_.d.mts} +46 -3
- package/package.json +47 -5
- package/schemas/edge.schema.json +27 -0
- package/schemas/graph.schema.json +27 -0
- /package/dist/{adjacency-list-GeL1Cu-L.mjs → adjacency-list-DQ32Mmhx.mjs} +0 -0
- /package/dist/{edge-list-BcZ0h6zz.mjs → edge-list-CA9UTvn2.mjs} +0 -0
- /package/dist/{mode-D8OnHFBk.mjs → mode-gu_mhKKs.mjs} +0 -0
- /package/dist/{validate-TtH-x3JV.mjs → validate-BsfSOv0S.mjs} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,98 @@
|
|
|
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-
|
|
2
|
-
import {
|
|
3
|
-
import { n as isEdgeDirected, t as getEdgeMode } from "./mode-
|
|
4
|
-
import { t as getGraphIssues } from "./validate-
|
|
5
|
-
import { n 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-BMM0XAv_.mjs";
|
|
2
|
+
import { S as updateNode, _ as getNode, a as addEntities, b as updateEdge, c as createGraphEdge, d as createGraphPort, f as createVisualGraph, g as getEdge, h as deleteNode, i as addEdge, l as createGraphFromTransition, m as deleteEntities, n as toNodeConfig, o as addNode, p as deleteEdge, r as GraphInstance, s as createGraph, t as toEdgeConfig, u as createGraphNode, v as hasEdge, x as updateEntities, y as hasNode } from "./config-Dt5u1gSf.mjs";
|
|
3
|
+
import { n as isEdgeDirected, t as getEdgeMode } from "./mode-gu_mhKKs.mjs";
|
|
4
|
+
import { t as getGraphIssues } from "./validate-BsfSOv0S.mjs";
|
|
5
|
+
import { $ as getAStarPath, A as genPreorders, B as getTopologicalSort, C as getHITS, D as getPageRank, E as getOutDegreeCentrality, F as bfs, G as flatten, H as isAcyclic, I as dfs, J as getSubgraph, K as getFlattenedGraph, L as genBFS, M as getPostorders, N as getPreorder, O as getMinimumSpanningTree, P as getPreorders, Q as genSimplePaths, R as genDFS, S as getEigenvectorCentrality, T as getKatzCentrality, U as isConnected, V as hasPath, W as isTree, X as genCycles, Y as reverseGraph, Z as genShortestPaths, _ as getCoreNumbers, a as getLouvainCommunities, at as getSimplePath, b as getClosenessCentrality, c as getBiconnectedComponents, ct as joinPaths, d as getGirvanNewmanCommunities, et as getAllPairsShortestPaths, f as getGreedyModularityCommunities, g as isBipartite, h as getMaximumBipartiteMatching, i as getMinCut, it as getShortestPaths, j as getPostorder, k as genPostorders, l as getBridges, lt as mulberry32$1, m as getModularity, n as getDominatorTree, nt as getJoinedPath, o as isIsomorphic, ot as getSimplePaths, p as getLabelPropagationCommunities, q as getReversedGraph, r as getMaxFlow, rt as getShortestPath, s as getArticulationPoints, st as getStronglyConnectedComponents, t as getTransitiveReduction, tt as getCycles, u as genGirvanNewmanCommunities, v as getKCore, w as getInDegreeCentrality, x as getDegreeCentrality, y as getBetweennessCentrality, z as getConnectedComponents } from "./algorithms-DBpH74hR.mjs";
|
|
6
|
+
import { n as createFormatConverter } from "./converter-DB6Rg6Vd.mjs";
|
|
6
7
|
|
|
8
|
+
//#region src/generators.ts
|
|
9
|
+
function assertNonNegativeInteger(caller, name, value) {
|
|
10
|
+
if (!Number.isInteger(value) || value < 0) throw new Error(`${caller}: ${name} ${value} is invalid — pass a non-negative integer`);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create the complete graph K_n: every pair of distinct nodes connected by
|
|
14
|
+
* one undirected edge. Nodes are `n0..n{n-1}` (`options.idPrefix` overrides
|
|
15
|
+
* the `n` prefix); edges are `e0..`.
|
|
16
|
+
*/
|
|
17
|
+
function createCompleteGraph(n, options) {
|
|
18
|
+
assertNonNegativeInteger("createCompleteGraph", "node count", n);
|
|
19
|
+
const prefix = options?.idPrefix ?? "n";
|
|
20
|
+
const nodes = [];
|
|
21
|
+
for (let i = 0; i < n; i++) nodes.push({ id: `${prefix}${i}` });
|
|
22
|
+
const edges = [];
|
|
23
|
+
let edgeId = 0;
|
|
24
|
+
for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) edges.push({
|
|
25
|
+
id: `e${edgeId++}`,
|
|
26
|
+
sourceId: `${prefix}${i}`,
|
|
27
|
+
targetId: `${prefix}${j}`
|
|
28
|
+
});
|
|
29
|
+
return createGraph({
|
|
30
|
+
mode: "undirected",
|
|
31
|
+
nodes,
|
|
32
|
+
edges
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a `rows × cols` grid graph: node `(r, c)` is connected to its right
|
|
37
|
+
* and down neighbors by undirected edges. Nodes are `n{r}_{c}`
|
|
38
|
+
* (`options.idPrefix` overrides the `n` prefix); edges are `e0..`.
|
|
39
|
+
*/
|
|
40
|
+
function createGridGraph(rows, cols, options) {
|
|
41
|
+
assertNonNegativeInteger("createGridGraph", "row count", rows);
|
|
42
|
+
assertNonNegativeInteger("createGridGraph", "column count", cols);
|
|
43
|
+
const prefix = options?.idPrefix ?? "n";
|
|
44
|
+
const nodeId = (r, c) => `${prefix}${r}_${c}`;
|
|
45
|
+
const nodes = [];
|
|
46
|
+
const edges = [];
|
|
47
|
+
let edgeId = 0;
|
|
48
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
|
|
49
|
+
nodes.push({ id: nodeId(r, c) });
|
|
50
|
+
if (c + 1 < cols) edges.push({
|
|
51
|
+
id: `e${edgeId++}`,
|
|
52
|
+
sourceId: nodeId(r, c),
|
|
53
|
+
targetId: nodeId(r, c + 1)
|
|
54
|
+
});
|
|
55
|
+
if (r + 1 < rows) edges.push({
|
|
56
|
+
id: `e${edgeId++}`,
|
|
57
|
+
sourceId: nodeId(r, c),
|
|
58
|
+
targetId: nodeId(r + 1, c)
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return createGraph({
|
|
62
|
+
mode: "undirected",
|
|
63
|
+
nodes,
|
|
64
|
+
edges
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create an Erdős–Rényi G(n, p) random graph: each of the n·(n-1)/2 node
|
|
69
|
+
* pairs gets an undirected edge with probability `probability`. With
|
|
70
|
+
* `options.seed` the result is deterministic per seed (mulberry32);
|
|
71
|
+
* otherwise `Math.random` is used. Nodes are `n0..n{n-1}`
|
|
72
|
+
* (`options.idPrefix` overrides the `n` prefix); edges are `e0..`.
|
|
73
|
+
*/
|
|
74
|
+
function createRandomGraph(n, probability, options) {
|
|
75
|
+
assertNonNegativeInteger("createRandomGraph", "node count", n);
|
|
76
|
+
if (!(probability >= 0 && probability <= 1)) throw new Error(`createRandomGraph: probability ${probability} is invalid — pass a number between 0 and 1`);
|
|
77
|
+
const prefix = options?.idPrefix ?? "n";
|
|
78
|
+
const rng = options?.seed !== void 0 ? mulberry32$1(options.seed) : Math.random;
|
|
79
|
+
const nodes = [];
|
|
80
|
+
for (let i = 0; i < n; i++) nodes.push({ id: `${prefix}${i}` });
|
|
81
|
+
const edges = [];
|
|
82
|
+
let edgeId = 0;
|
|
83
|
+
for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) if (rng() < probability) edges.push({
|
|
84
|
+
id: `e${edgeId++}`,
|
|
85
|
+
sourceId: `${prefix}${i}`,
|
|
86
|
+
targetId: `${prefix}${j}`
|
|
87
|
+
});
|
|
88
|
+
return createGraph({
|
|
89
|
+
mode: "undirected",
|
|
90
|
+
nodes,
|
|
91
|
+
edges
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
7
96
|
//#region src/equivalence.ts
|
|
8
97
|
/** Shallow-compare two values, returning true if they differ. */
|
|
9
98
|
function differs$1(a, b) {
|
|
@@ -27,6 +116,8 @@ const LAYOUT_KEYS = {
|
|
|
27
116
|
"y",
|
|
28
117
|
"width",
|
|
29
118
|
"height",
|
|
119
|
+
"points",
|
|
120
|
+
"routing",
|
|
30
121
|
"style",
|
|
31
122
|
"color"
|
|
32
123
|
]
|
|
@@ -127,6 +218,8 @@ const EDGE_COMPARE_KEYS = [
|
|
|
127
218
|
"mode",
|
|
128
219
|
"sourcePort",
|
|
129
220
|
"targetPort",
|
|
221
|
+
"points",
|
|
222
|
+
"routing",
|
|
130
223
|
"x",
|
|
131
224
|
"y",
|
|
132
225
|
"width",
|
|
@@ -232,18 +325,18 @@ function isEmptyDiff(diff) {
|
|
|
232
325
|
*
|
|
233
326
|
* @example
|
|
234
327
|
* ```ts
|
|
235
|
-
* import { createGraph, getDiff,
|
|
328
|
+
* import { createGraph, getDiff, getInvertedDiff } from '@statelyai/graph';
|
|
236
329
|
*
|
|
237
330
|
* const a = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
|
|
238
331
|
* const b = createGraph({ nodes: [{ id: 'n2' }], edges: [] });
|
|
239
332
|
*
|
|
240
333
|
* const diff = getDiff(a, b);
|
|
241
|
-
* const inv =
|
|
334
|
+
* const inv = getInvertedDiff(diff);
|
|
242
335
|
* // inv.nodes.added contains n1 (was removed)
|
|
243
336
|
* // inv.nodes.removed contains n2 (was added)
|
|
244
337
|
* ```
|
|
245
338
|
*/
|
|
246
|
-
function
|
|
339
|
+
function getInvertedDiff(diff) {
|
|
247
340
|
return {
|
|
248
341
|
nodes: {
|
|
249
342
|
added: diff.nodes.removed.map((c) => structuredClone(c)),
|
|
@@ -266,6 +359,12 @@ function invertDiff(diff) {
|
|
|
266
359
|
};
|
|
267
360
|
}
|
|
268
361
|
/**
|
|
362
|
+
* @deprecated Use {@link getInvertedDiff}.
|
|
363
|
+
*/
|
|
364
|
+
function invertDiff(diff) {
|
|
365
|
+
return getInvertedDiff(diff);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
269
368
|
* Compute an ordered patch list from graph `a` to graph `b`.
|
|
270
369
|
* Order (see {@link toPatches}): add nodes → update edges → delete edges →
|
|
271
370
|
* delete nodes → add edges → update nodes.
|
|
@@ -290,17 +389,17 @@ function getPatches(a, b) {
|
|
|
290
389
|
*
|
|
291
390
|
* @example
|
|
292
391
|
* ```ts
|
|
293
|
-
* import { createGraph, getPatches,
|
|
392
|
+
* import { createGraph, getPatches, updateGraphWithPatches } from '@statelyai/graph';
|
|
294
393
|
*
|
|
295
394
|
* const a = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
|
|
296
395
|
* const b = createGraph({ nodes: [{ id: 'n1' }, { id: 'n2' }], edges: [] });
|
|
297
396
|
*
|
|
298
397
|
* const patches = getPatches(a, b);
|
|
299
|
-
*
|
|
398
|
+
* updateGraphWithPatches(a, patches);
|
|
300
399
|
* // a now contains both n1 and n2
|
|
301
400
|
* ```
|
|
302
401
|
*/
|
|
303
|
-
function
|
|
402
|
+
function updateGraphWithPatches(graph, patches) {
|
|
304
403
|
for (const patch of patches) switch (patch.op) {
|
|
305
404
|
case "addNode":
|
|
306
405
|
addNode(graph, patch.node);
|
|
@@ -323,6 +422,12 @@ function applyPatches(graph, patches) {
|
|
|
323
422
|
}
|
|
324
423
|
}
|
|
325
424
|
/**
|
|
425
|
+
* @deprecated Use {@link updateGraphWithPatches}.
|
|
426
|
+
*/
|
|
427
|
+
function applyPatches(graph, patches) {
|
|
428
|
+
updateGraphWithPatches(graph, patches);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
326
431
|
* Flatten a structured diff into an ordered patch list.
|
|
327
432
|
* Order: add nodes → update edges → delete edges → delete nodes → add edges → update nodes.
|
|
328
433
|
* This avoids cascading deletes removing edges that are being updated,
|
|
@@ -679,7 +784,7 @@ function* genPredefinedWalk(graph, edgeIds, options) {
|
|
|
679
784
|
/**
|
|
680
785
|
* Yield at most `n` steps from the source generator.
|
|
681
786
|
*/
|
|
682
|
-
function*
|
|
787
|
+
function* genWalkSteps(gen, n) {
|
|
683
788
|
let count = 0;
|
|
684
789
|
for (const step of gen) {
|
|
685
790
|
yield step;
|
|
@@ -687,27 +792,45 @@ function* takeSteps(gen, n) {
|
|
|
687
792
|
}
|
|
688
793
|
}
|
|
689
794
|
/**
|
|
795
|
+
* @deprecated Use {@link genWalkSteps}.
|
|
796
|
+
*/
|
|
797
|
+
function* takeSteps(gen, n) {
|
|
798
|
+
yield* genWalkSteps(gen, n);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
690
801
|
* Yield steps until a specific node is reached.
|
|
691
802
|
*/
|
|
692
|
-
function*
|
|
803
|
+
function* genWalkUntilNode(gen, nodeId) {
|
|
693
804
|
for (const step of gen) {
|
|
694
805
|
yield step;
|
|
695
806
|
if (step.node.id === nodeId) return;
|
|
696
807
|
}
|
|
697
808
|
}
|
|
698
809
|
/**
|
|
810
|
+
* @deprecated Use {@link genWalkUntilNode}.
|
|
811
|
+
*/
|
|
812
|
+
function* takeUntilNode(gen, nodeId) {
|
|
813
|
+
yield* genWalkUntilNode(gen, nodeId);
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
699
816
|
* Yield steps until a specific edge is traversed.
|
|
700
817
|
*/
|
|
701
|
-
function*
|
|
818
|
+
function* genWalkUntilEdge(gen, edgeId) {
|
|
702
819
|
for (const step of gen) {
|
|
703
820
|
yield step;
|
|
704
821
|
if (step.edge.id === edgeId) return;
|
|
705
822
|
}
|
|
706
823
|
}
|
|
707
824
|
/**
|
|
825
|
+
* @deprecated Use {@link genWalkUntilEdge}.
|
|
826
|
+
*/
|
|
827
|
+
function* takeUntilEdge(gen, edgeId) {
|
|
828
|
+
yield* genWalkUntilEdge(gen, edgeId);
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
708
831
|
* Yield steps until node coverage reaches the target (0–1).
|
|
709
832
|
*/
|
|
710
|
-
function*
|
|
833
|
+
function* genWalkUntilNodeCoverage(gen, graph, coverage, options) {
|
|
711
834
|
const totalNodes = graph.nodes.length;
|
|
712
835
|
const target = Math.ceil(coverage * totalNodes);
|
|
713
836
|
const startId = options?.from ?? graph.initialNodeId ?? graph.nodes[0]?.id;
|
|
@@ -720,9 +843,15 @@ function* takeUntilNodeCoverage(gen, graph, coverage, options) {
|
|
|
720
843
|
}
|
|
721
844
|
}
|
|
722
845
|
/**
|
|
846
|
+
* @deprecated Use {@link genWalkUntilNodeCoverage}.
|
|
847
|
+
*/
|
|
848
|
+
function* takeUntilNodeCoverage(gen, graph, coverage, options) {
|
|
849
|
+
yield* genWalkUntilNodeCoverage(gen, graph, coverage, options);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
723
852
|
* Yield steps until edge coverage reaches the target (0–1).
|
|
724
853
|
*/
|
|
725
|
-
function*
|
|
854
|
+
function* genWalkUntilEdgeCoverage(gen, graph, coverage) {
|
|
726
855
|
const totalEdges = graph.edges.length;
|
|
727
856
|
const target = Math.ceil(coverage * totalEdges);
|
|
728
857
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -734,6 +863,12 @@ function* takeUntilEdgeCoverage(gen, graph, coverage) {
|
|
|
734
863
|
}
|
|
735
864
|
}
|
|
736
865
|
/**
|
|
866
|
+
* @deprecated Use {@link genWalkUntilEdgeCoverage}.
|
|
867
|
+
*/
|
|
868
|
+
function* takeUntilEdgeCoverage(gen, graph, coverage) {
|
|
869
|
+
yield* genWalkUntilEdgeCoverage(gen, graph, coverage);
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
737
872
|
* Compute coverage statistics for a completed walk.
|
|
738
873
|
*/
|
|
739
874
|
function getCoverage(graph, steps, options) {
|
|
@@ -754,4 +889,4 @@ function getCoverage(graph, steps, options) {
|
|
|
754
889
|
}
|
|
755
890
|
|
|
756
891
|
//#endregion
|
|
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 };
|
|
892
|
+
export { GraphInstance, LAYOUT_KEYS, addEdge, addEntities, addNode, applyPatches, areEntitiesEqual, bfs, createCompleteGraph, createFormatConverter, createGraph, createGraphEdge, createGraphFromTransition, createGraphNode, createGraphPort, createGridGraph, createRandomGraph, createVisualGraph, deleteEdge, deleteEntities, deleteNode, dfs, flatten, genBFS, genCycles, genDFS, genGirvanNewmanCommunities, genPostorders, genPredefinedWalk, genPreorders, genQuickRandomWalk, genRandomWalk, genShortestPaths, genSimplePaths, genWalkSteps, genWalkUntilEdge, genWalkUntilEdgeCoverage, genWalkUntilNode, genWalkUntilNodeCoverage, genWeightedRandomWalk, getAStarPath, getAllPairsShortestPaths, getAncestors, getArticulationPoints, getBetweennessCentrality, getBiconnectedComponents, getBridges, getChildren, getClosenessCentrality, getConnectedComponents, getCoreNumbers, getCoverage, getCycles, getDegree, getDegreeCentrality, getDepth, getDescendants, getDiff, getDominatorTree, getEdge, getEdgeMode, getEdgesBetween, getEdgesByPort, getEdgesOf, getEigenvectorCentrality, getFlattenedGraph, getGirvanNewmanCommunities, getGraphIssues, getGreedyModularityCommunities, getHITS, getInDegree, getInDegreeCentrality, getInEdges, getInvertedDiff, getJoinedPath, getKCore, getKatzCentrality, getLCA, getLabelPropagationCommunities, getLouvainCommunities, getMaxFlow, getMaximumBipartiteMatching, getMinCut, getMinimumSpanningTree, getModularity, getNeighbors, getNode, getOutDegree, getOutDegreeCentrality, getOutEdges, getPageRank, getParent, getPatches, getPort, getPorts, getPostorder, getPostorders, getPredecessors, getPreorder, getPreorders, getRelativeDistance, getRelativeDistanceMap, getReversedGraph, getRoots, getShortestPath, getShortestPaths, getSiblings, getSimplePath, getSimplePaths, getSinks, getSources, getStronglyConnectedComponents, getSubgraph, getSuccessors, getTopologicalSort, getTransitiveReduction, hasEdge, hasNode, hasPath, invalidateIndex, invertDiff, isAcyclic, isBipartite, isCompound, isConnected, isEdgeDirected, isEmptyDiff, isIsomorphic, isLayoutEqual, isLeaf, isNonLayoutEqual, isTree, joinPaths, reverseGraph, takeSteps, takeUntilEdge, takeUntilEdgeCoverage, takeUntilNode, takeUntilNodeCoverage, toDiff, toPatches, updateEdge, updateEntities, updateGraphWithPatches, updateNode };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { I as VisualGraph, f as Graph } from "../types-BAEQTwK_.mjs";
|
|
2
|
+
import { LayoutOptions } from "./index.mjs";
|
|
3
|
+
import cytoscape from "cytoscape";
|
|
4
|
+
|
|
5
|
+
//#region src/layout/cytoscape.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Minimal interface an injected cytoscape factory must satisfy — the
|
|
9
|
+
* `cytoscape()` function itself. Inject your own when you've registered
|
|
10
|
+
* layout extensions via `cytoscape.use(...)` (cola, fcose, dagre, …).
|
|
11
|
+
*/
|
|
12
|
+
type CytoscapeLike = (options: cytoscape.CytoscapeOptions) => cytoscape.Core;
|
|
13
|
+
interface CytoscapeLayoutOptions extends LayoutOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Cytoscape layout name. Built-ins: `'grid'`, `'circle'`, `'concentric'`,
|
|
16
|
+
* `'breadthfirst'`, `'cose'` (default), `'random'`. Extension layouts work
|
|
17
|
+
* when registered on an injected {@link CytoscapeLayoutOptions.cy} factory.
|
|
18
|
+
*/
|
|
19
|
+
name?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Raw cytoscape layout options, merged into the layout call last (override
|
|
22
|
+
* everything, including `name`). Engine-specific tuning — spacing knobs,
|
|
23
|
+
* `boundingBox`, `roots`, iteration counts — goes here; the options vary
|
|
24
|
+
* too much between cytoscape layouts to map `LayoutOptions.spacing`
|
|
25
|
+
* generically. See https://js.cytoscape.org/#layouts
|
|
26
|
+
*/
|
|
27
|
+
layoutOptions?: Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Injected cytoscape factory — e.g. one with extensions registered via
|
|
30
|
+
* `cytoscape.use(...)`. Defaults to the imported `cytoscape`.
|
|
31
|
+
*/
|
|
32
|
+
cy?: CytoscapeLike;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Lay out a graph with Cytoscape.js (`cytoscape`, an optional peer
|
|
36
|
+
* dependency) — one call unlocks its whole layout ecosystem. Pure: returns a
|
|
37
|
+
* new {@link VisualGraph} with node positions and sizes. Cytoscape layouts
|
|
38
|
+
* position nodes only; edges keep their fields but gain no route `points`.
|
|
39
|
+
* Compound graphs are supported via cytoscape `parent`; parent nodes get
|
|
40
|
+
* cytoscape's computed compound dimensions. All coordinates are absolute
|
|
41
|
+
* (cytoscape does not produce parent-relative positions).
|
|
42
|
+
*
|
|
43
|
+
* Runs headless with `styleEnabled: true` so resolved node sizes
|
|
44
|
+
* ({@link getNodeSize}) participate in overlap avoidance and spacing.
|
|
45
|
+
*
|
|
46
|
+
* Option mapping is deliberately minimal: `measure` resolves sizes,
|
|
47
|
+
* `isFixed` nodes with an existing `x`/`y` are locked in place (cytoscape
|
|
48
|
+
* layouts skip locked nodes), and everything else — including `direction`
|
|
49
|
+
* and `spacing` — is engine-specific and belongs in
|
|
50
|
+
* {@link CytoscapeLayoutOptions.layoutOptions}. `seed` is ignored: the
|
|
51
|
+
* discrete layouts (grid, circle, concentric, breadthfirst) are
|
|
52
|
+
* deterministic, and cose is not seedable.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { getCytoscapeLayout } from '@statelyai/graph/layout/cytoscape';
|
|
57
|
+
*
|
|
58
|
+
* const laidOut = await getCytoscapeLayout(graph, {
|
|
59
|
+
* name: 'breadthfirst',
|
|
60
|
+
* layoutOptions: { roots: ['start'] },
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function getCytoscapeLayout(graph: Graph | VisualGraph, options?: CytoscapeLayoutOptions): Promise<VisualGraph>;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { CytoscapeLayoutOptions, CytoscapeLike, getCytoscapeLayout };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { f as createVisualGraph, n as toNodeConfig, t as toEdgeConfig } from "../config-Dt5u1gSf.mjs";
|
|
2
|
+
import { getNodeSize } from "./index.mjs";
|
|
3
|
+
import cytoscape from "cytoscape";
|
|
4
|
+
|
|
5
|
+
//#region src/layout/cytoscape.ts
|
|
6
|
+
/**
|
|
7
|
+
* Lay out a graph with Cytoscape.js (`cytoscape`, an optional peer
|
|
8
|
+
* dependency) — one call unlocks its whole layout ecosystem. Pure: returns a
|
|
9
|
+
* new {@link VisualGraph} with node positions and sizes. Cytoscape layouts
|
|
10
|
+
* position nodes only; edges keep their fields but gain no route `points`.
|
|
11
|
+
* Compound graphs are supported via cytoscape `parent`; parent nodes get
|
|
12
|
+
* cytoscape's computed compound dimensions. All coordinates are absolute
|
|
13
|
+
* (cytoscape does not produce parent-relative positions).
|
|
14
|
+
*
|
|
15
|
+
* Runs headless with `styleEnabled: true` so resolved node sizes
|
|
16
|
+
* ({@link getNodeSize}) participate in overlap avoidance and spacing.
|
|
17
|
+
*
|
|
18
|
+
* Option mapping is deliberately minimal: `measure` resolves sizes,
|
|
19
|
+
* `isFixed` nodes with an existing `x`/`y` are locked in place (cytoscape
|
|
20
|
+
* layouts skip locked nodes), and everything else — including `direction`
|
|
21
|
+
* and `spacing` — is engine-specific and belongs in
|
|
22
|
+
* {@link CytoscapeLayoutOptions.layoutOptions}. `seed` is ignored: the
|
|
23
|
+
* discrete layouts (grid, circle, concentric, breadthfirst) are
|
|
24
|
+
* deterministic, and cose is not seedable.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { getCytoscapeLayout } from '@statelyai/graph/layout/cytoscape';
|
|
29
|
+
*
|
|
30
|
+
* const laidOut = await getCytoscapeLayout(graph, {
|
|
31
|
+
* name: 'breadthfirst',
|
|
32
|
+
* layoutOptions: { roots: ['start'] },
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
async function getCytoscapeLayout(graph, options) {
|
|
37
|
+
const elements = [];
|
|
38
|
+
for (const node of graph.nodes) {
|
|
39
|
+
const size = getNodeSize(node, options);
|
|
40
|
+
const hasPosition = node.x !== void 0 && node.y !== void 0;
|
|
41
|
+
elements.push({
|
|
42
|
+
group: "nodes",
|
|
43
|
+
data: {
|
|
44
|
+
id: node.id,
|
|
45
|
+
...node.parentId != null && { parent: node.parentId },
|
|
46
|
+
width: size.width,
|
|
47
|
+
height: size.height
|
|
48
|
+
},
|
|
49
|
+
...hasPosition && { position: {
|
|
50
|
+
x: node.x + size.width / 2,
|
|
51
|
+
y: node.y + size.height / 2
|
|
52
|
+
} },
|
|
53
|
+
...hasPosition && options?.isFixed?.(node) && { locked: true }
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
for (const edge of graph.edges) elements.push({
|
|
57
|
+
group: "edges",
|
|
58
|
+
data: {
|
|
59
|
+
id: edge.id,
|
|
60
|
+
source: edge.sourceId,
|
|
61
|
+
target: edge.targetId
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const cy = (options?.cy ?? cytoscape)({
|
|
65
|
+
headless: true,
|
|
66
|
+
styleEnabled: true,
|
|
67
|
+
style: [{
|
|
68
|
+
selector: "node",
|
|
69
|
+
style: {
|
|
70
|
+
width: "data(width)",
|
|
71
|
+
height: "data(height)",
|
|
72
|
+
shape: "rectangle"
|
|
73
|
+
}
|
|
74
|
+
}],
|
|
75
|
+
elements
|
|
76
|
+
});
|
|
77
|
+
try {
|
|
78
|
+
const layout = cy.layout({
|
|
79
|
+
name: options?.name ?? "cose",
|
|
80
|
+
animate: false,
|
|
81
|
+
...options?.layoutOptions
|
|
82
|
+
});
|
|
83
|
+
const stopped = layout.promiseOn("layoutstop");
|
|
84
|
+
layout.run();
|
|
85
|
+
await stopped;
|
|
86
|
+
return createVisualGraph({
|
|
87
|
+
id: graph.id,
|
|
88
|
+
mode: graph.mode,
|
|
89
|
+
initialNodeId: graph.initialNodeId ?? void 0,
|
|
90
|
+
direction: options?.direction ?? graph.direction,
|
|
91
|
+
data: graph.data,
|
|
92
|
+
...graph.style !== void 0 && { style: graph.style },
|
|
93
|
+
nodes: graph.nodes.map((node) => {
|
|
94
|
+
const ele = cy.getElementById(node.id);
|
|
95
|
+
const width = ele.width();
|
|
96
|
+
const height = ele.height();
|
|
97
|
+
const position = ele.position();
|
|
98
|
+
return {
|
|
99
|
+
...toNodeConfig(node),
|
|
100
|
+
width,
|
|
101
|
+
height,
|
|
102
|
+
x: position.x - width / 2,
|
|
103
|
+
y: position.y - height / 2
|
|
104
|
+
};
|
|
105
|
+
}),
|
|
106
|
+
edges: graph.edges.map((edge) => toEdgeConfig(edge))
|
|
107
|
+
});
|
|
108
|
+
} finally {
|
|
109
|
+
cy.destroy();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
export { getCytoscapeLayout };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { I as VisualGraph, f as Graph } from "../types-BAEQTwK_.mjs";
|
|
2
|
+
import { LayoutFrame, LayoutOptions } from "./index.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/layout/d3-force.d.ts
|
|
5
|
+
interface ForceLayoutOptions extends LayoutOptions {
|
|
6
|
+
/** Target distance between linked nodes. Default: 80. */
|
|
7
|
+
linkDistance?: number;
|
|
8
|
+
/** Many-body charge strength (negative repels). Default: -300. */
|
|
9
|
+
chargeStrength?: number;
|
|
10
|
+
/** Number of simulation ticks. Default: 300 (d3's natural cooling span). */
|
|
11
|
+
iterations?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Iterative force-directed layout via d3-force (optional peer dependency).
|
|
15
|
+
* Each `next()` advances one simulation tick and yields a {@link LayoutFrame}
|
|
16
|
+
* (top-left node positions by id + remaining `alpha`); the caller owns pacing
|
|
17
|
+
* (e.g. one tick per animation frame via {@link applyLayoutFrame}) and
|
|
18
|
+
* cancellation (stop iterating). The generator's return value is the settled
|
|
19
|
+
* {@link VisualGraph}.
|
|
20
|
+
*
|
|
21
|
+
* - Deterministic: `options.seed` drives d3's `randomSource`; the same seed
|
|
22
|
+
* always produces the same layout.
|
|
23
|
+
* - `options.isFixed` pins nodes at their current position (d3 `fx`/`fy`).
|
|
24
|
+
* - Hierarchy is ignored (force layouts are flat); per-edge `mode` is
|
|
25
|
+
* irrelevant (forces are symmetric).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { genForceLayout } from '@statelyai/graph/layout/d3-force';
|
|
30
|
+
* import { applyLayoutFrame } from '@statelyai/graph/layout';
|
|
31
|
+
*
|
|
32
|
+
* for (const frame of genForceLayout(graph, { seed: 42 })) {
|
|
33
|
+
* applyLayoutFrame(graph, frame);
|
|
34
|
+
* render(graph);
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function genForceLayout(graph: Graph | VisualGraph, options?: ForceLayoutOptions): Generator<LayoutFrame, VisualGraph>;
|
|
39
|
+
/**
|
|
40
|
+
* Run {@link genForceLayout} to completion and return the settled
|
|
41
|
+
* {@link VisualGraph}. Convenience for non-animated use.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { getForceLayout } from '@statelyai/graph/layout/d3-force';
|
|
46
|
+
*
|
|
47
|
+
* const laidOut = getForceLayout(graph, { seed: 42 });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function getForceLayout(graph: Graph | VisualGraph, options?: ForceLayoutOptions): VisualGraph;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { ForceLayoutOptions, genForceLayout, getForceLayout };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { f as createVisualGraph, n as toNodeConfig, t as toEdgeConfig } from "../config-Dt5u1gSf.mjs";
|
|
2
|
+
import { getNodeSize } from "./index.mjs";
|
|
3
|
+
import { forceCenter, forceLink, forceManyBody, forceSimulation } from "d3-force";
|
|
4
|
+
|
|
5
|
+
//#region src/layout/d3-force.ts
|
|
6
|
+
/** mulberry32 — same seeded PRNG the rest of the library uses. */
|
|
7
|
+
function mulberry32(seed) {
|
|
8
|
+
let s = seed | 0;
|
|
9
|
+
return () => {
|
|
10
|
+
s = s + 1831565813 | 0;
|
|
11
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
12
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
13
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Iterative force-directed layout via d3-force (optional peer dependency).
|
|
18
|
+
* Each `next()` advances one simulation tick and yields a {@link LayoutFrame}
|
|
19
|
+
* (top-left node positions by id + remaining `alpha`); the caller owns pacing
|
|
20
|
+
* (e.g. one tick per animation frame via {@link applyLayoutFrame}) and
|
|
21
|
+
* cancellation (stop iterating). The generator's return value is the settled
|
|
22
|
+
* {@link VisualGraph}.
|
|
23
|
+
*
|
|
24
|
+
* - Deterministic: `options.seed` drives d3's `randomSource`; the same seed
|
|
25
|
+
* always produces the same layout.
|
|
26
|
+
* - `options.isFixed` pins nodes at their current position (d3 `fx`/`fy`).
|
|
27
|
+
* - Hierarchy is ignored (force layouts are flat); per-edge `mode` is
|
|
28
|
+
* irrelevant (forces are symmetric).
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { genForceLayout } from '@statelyai/graph/layout/d3-force';
|
|
33
|
+
* import { applyLayoutFrame } from '@statelyai/graph/layout';
|
|
34
|
+
*
|
|
35
|
+
* for (const frame of genForceLayout(graph, { seed: 42 })) {
|
|
36
|
+
* applyLayoutFrame(graph, frame);
|
|
37
|
+
* render(graph);
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function* genForceLayout(graph, options) {
|
|
42
|
+
const rng = mulberry32(options?.seed ?? 1);
|
|
43
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
44
|
+
const scatterRadius = Math.max(80, 30 * Math.sqrt(graph.nodes.length));
|
|
45
|
+
const simNodes = graph.nodes.map((node) => {
|
|
46
|
+
const size = getNodeSize(node, options);
|
|
47
|
+
sizes.set(node.id, size);
|
|
48
|
+
const simNode = {
|
|
49
|
+
id: node.id,
|
|
50
|
+
...size
|
|
51
|
+
};
|
|
52
|
+
if (node.x !== void 0 && node.y !== void 0) {
|
|
53
|
+
simNode.x = node.x + size.width / 2;
|
|
54
|
+
simNode.y = node.y + size.height / 2;
|
|
55
|
+
if (options?.isFixed?.(node)) {
|
|
56
|
+
simNode.fx = simNode.x;
|
|
57
|
+
simNode.fy = simNode.y;
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
const angle = rng() * 2 * Math.PI;
|
|
61
|
+
const radius = scatterRadius * Math.sqrt(rng());
|
|
62
|
+
simNode.x = Math.cos(angle) * radius;
|
|
63
|
+
simNode.y = Math.sin(angle) * radius;
|
|
64
|
+
}
|
|
65
|
+
return simNode;
|
|
66
|
+
});
|
|
67
|
+
const simLinks = graph.edges.filter((edge) => sizes.has(edge.sourceId) && sizes.has(edge.targetId) && edge.sourceId !== edge.targetId).map((edge) => ({
|
|
68
|
+
source: edge.sourceId,
|
|
69
|
+
target: edge.targetId
|
|
70
|
+
}));
|
|
71
|
+
const simulation = forceSimulation(simNodes).randomSource(mulberry32(options?.seed ?? 1)).force("link", forceLink(simLinks).id((d) => d.id).distance(options?.linkDistance ?? 80)).force("charge", forceManyBody().strength(options?.chargeStrength ?? -300)).force("center", forceCenter(0, 0)).stop();
|
|
72
|
+
const iterations = options?.iterations ?? 300;
|
|
73
|
+
for (let i = 0; i < iterations; i++) {
|
|
74
|
+
simulation.tick();
|
|
75
|
+
const positions = {};
|
|
76
|
+
for (const simNode of simNodes) positions[simNode.id] = {
|
|
77
|
+
x: (simNode.x ?? 0) - simNode.width / 2,
|
|
78
|
+
y: (simNode.y ?? 0) - simNode.height / 2
|
|
79
|
+
};
|
|
80
|
+
yield {
|
|
81
|
+
positions,
|
|
82
|
+
alpha: simulation.alpha()
|
|
83
|
+
};
|
|
84
|
+
if (simulation.alpha() < simulation.alphaMin()) break;
|
|
85
|
+
}
|
|
86
|
+
const byId = new Map(simNodes.map((simNode) => [simNode.id, simNode]));
|
|
87
|
+
return createVisualGraph({
|
|
88
|
+
id: graph.id,
|
|
89
|
+
mode: graph.mode,
|
|
90
|
+
initialNodeId: graph.initialNodeId ?? void 0,
|
|
91
|
+
direction: graph.direction,
|
|
92
|
+
data: graph.data,
|
|
93
|
+
...graph.style !== void 0 && { style: graph.style },
|
|
94
|
+
nodes: graph.nodes.map((node) => {
|
|
95
|
+
const size = sizes.get(node.id);
|
|
96
|
+
const simNode = byId.get(node.id);
|
|
97
|
+
return {
|
|
98
|
+
...toNodeConfig(node),
|
|
99
|
+
...size,
|
|
100
|
+
x: (simNode.x ?? 0) - size.width / 2,
|
|
101
|
+
y: (simNode.y ?? 0) - size.height / 2
|
|
102
|
+
};
|
|
103
|
+
}),
|
|
104
|
+
edges: graph.edges.map((edge) => toEdgeConfig(edge))
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Run {@link genForceLayout} to completion and return the settled
|
|
109
|
+
* {@link VisualGraph}. Convenience for non-animated use.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { getForceLayout } from '@statelyai/graph/layout/d3-force';
|
|
114
|
+
*
|
|
115
|
+
* const laidOut = getForceLayout(graph, { seed: 42 });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
function getForceLayout(graph, options) {
|
|
119
|
+
const generator = genForceLayout(graph, options);
|
|
120
|
+
for (;;) {
|
|
121
|
+
const step = generator.next();
|
|
122
|
+
if (step.done) return step.value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { genForceLayout, getForceLayout };
|