@statelyai/graph 0.3.1 → 0.5.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 +12 -11
- package/dist/{algorithms-NWSB2RWj.mjs → algorithms-DldwenLt.mjs} +11 -6
- package/dist/algorithms.d.mts +1 -1
- package/dist/algorithms.mjs +1 -1
- package/dist/{converter-CchokMDg.mjs → converter-B5CUD0r9.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 +2 -2
- package/dist/formats/converter/index.mjs +1 -1
- package/dist/formats/cytoscape/index.d.mts +1 -1
- package/dist/formats/cytoscape/index.mjs +4 -4
- package/dist/formats/d3/index.d.mts +1 -1
- package/dist/formats/d3/index.mjs +1 -1
- 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 +61 -0
- package/dist/formats/elk/index.mjs +176 -0
- package/dist/formats/gexf/index.d.mts +1 -1
- package/dist/formats/gexf/index.mjs +5 -5
- package/dist/formats/gml/index.d.mts +1 -1
- package/dist/formats/gml/index.mjs +3 -3
- package/dist/formats/graphml/index.d.mts +1 -1
- package/dist/formats/graphml/index.mjs +2 -2
- package/dist/formats/jgf/index.d.mts +1 -1
- package/dist/formats/jgf/index.mjs +4 -4
- package/dist/formats/mermaid/index.d.mts +46 -33
- package/dist/formats/mermaid/index.mjs +319 -35
- package/dist/formats/tgf/index.d.mts +1 -1
- package/dist/formats/tgf/index.mjs +1 -1
- package/dist/formats/xyflow/index.d.mts +73 -0
- package/dist/formats/xyflow/index.mjs +133 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{indexing-eNDrXdDA.mjs → indexing-DyfgLuzw.mjs} +6 -5
- package/dist/queries.d.mts +2 -2
- package/dist/queries.mjs +9 -9
- package/dist/schemas.d.mts +37 -4
- package/dist/schemas.mjs +26 -5
- package/dist/{types-BDXC1O5b.d.mts → types-FBZCrmnG.d.mts} +17 -6
- package/package.json +9 -1
- package/schemas/edge.schema.json +32 -1
- package/schemas/graph.schema.json +114 -4
- package/schemas/node.schema.json +45 -2
- /package/dist/{adjacency-list-ITO40kmn.mjs → adjacency-list-fldj-QAL.mjs} +0 -0
- /package/dist/{edge-list-CgX6bBIF.mjs → edge-list-Br05wXMg.mjs} +0 -0
package/README.md
CHANGED
|
@@ -4,17 +4,6 @@ A TypeScript graph library built on plain JSON objects. Supports directed/undire
|
|
|
4
4
|
|
|
5
5
|
Made from our experience at [stately.ai](https://stately.ai), where we build visual tools for complex systems.
|
|
6
6
|
|
|
7
|
-
## Why this library?
|
|
8
|
-
|
|
9
|
-
Graph file formats (GEXF, GraphML) define how to _store_ graphs. Visualization libraries (Cytoscape.js, D3) define how to _render_ them. Neither gives you a good way to _work with_ them in between.
|
|
10
|
-
|
|
11
|
-
This library is the computational layer: plain JSON objects in, algorithms and mutations, plain JSON objects out. No classes, no DOM, no rendering engine — just data and functions.
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
GEXF file → fromGEXF() → Graph → run algorithms, mutate → toCytoscapeJSON() → render
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Your `Graph` is a plain object that survives `JSON.stringify`, `structuredClone`, `postMessage`, and `localStorage` without adapters. Format converters are the I/O ports — read from any supported format, do your work, export to whatever your renderer or database expects.
|
|
18
7
|
|
|
19
8
|
## Install
|
|
20
9
|
|
|
@@ -126,6 +115,18 @@ const back = cytoscapeConverter.from(cyto);
|
|
|
126
115
|
const myConverter = createFormatConverter(myToFn, myFromFn);
|
|
127
116
|
```
|
|
128
117
|
|
|
118
|
+
## Why this library?
|
|
119
|
+
|
|
120
|
+
Graph file formats (GEXF, GraphML) define how to _store_ graphs. Visualization libraries (Cytoscape.js, D3) define how to _render_ them. Neither gives you a good way to _work with_ them in between.
|
|
121
|
+
|
|
122
|
+
This library is the computational layer: plain JSON objects in, algorithms and mutations, plain JSON objects out. No classes, no DOM, no rendering engine; just data and functions.
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
GEXF file → fromGEXF() → Graph → run algorithms, mutate → toCytoscapeJSON() → render
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Your `Graph` is a plain object that survives `JSON.stringify`, `structuredClone`, `postMessage`, and `localStorage` without adapters. Format converters are the I/O ports: read from any supported format, do your work, export to whatever your renderer or database expects.
|
|
129
|
+
|
|
129
130
|
## API
|
|
130
131
|
|
|
131
132
|
### Graph Creation
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { a as indexUpdateEdgeEndpoints, i as indexReparentNode, n as indexAddEdge, o as invalidateIndex, r as indexAddNode, t as getIndex } from "./indexing-
|
|
1
|
+
import { a as indexUpdateEdgeEndpoints, i as indexReparentNode, n as indexAddEdge, o as invalidateIndex, r as indexAddNode, t as getIndex } from "./indexing-DyfgLuzw.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/graph.ts
|
|
4
4
|
function resolveNode(config) {
|
|
5
|
+
if (!config.id) throw new Error("Node id must be a non-empty string");
|
|
6
|
+
if (config.parentId === "") throw new Error("Node parentId must be a non-empty string");
|
|
5
7
|
const node = {
|
|
6
8
|
type: "node",
|
|
7
9
|
id: config.id,
|
|
8
|
-
parentId: config.parentId ?? null,
|
|
9
|
-
initialNodeId: config.initialNodeId ?? null,
|
|
10
|
+
...config.parentId !== void 0 && { parentId: config.parentId ?? null },
|
|
11
|
+
...config.initialNodeId !== void 0 && { initialNodeId: config.initialNodeId ?? null },
|
|
10
12
|
label: config.label ?? "",
|
|
11
13
|
data: config.data
|
|
12
14
|
};
|
|
@@ -20,6 +22,9 @@ function resolveNode(config) {
|
|
|
20
22
|
return node;
|
|
21
23
|
}
|
|
22
24
|
function resolveEdge(config) {
|
|
25
|
+
if (!config.id) throw new Error("Edge id must be a non-empty string");
|
|
26
|
+
if (!config.sourceId) throw new Error("Edge sourceId must be a non-empty string");
|
|
27
|
+
if (!config.targetId) throw new Error("Edge targetId must be a non-empty string");
|
|
23
28
|
const edge = {
|
|
24
29
|
type: "edge",
|
|
25
30
|
id: config.id,
|
|
@@ -251,10 +256,10 @@ function hasEdge(graph, id) {
|
|
|
251
256
|
* ```
|
|
252
257
|
*/
|
|
253
258
|
function addNode(graph, config) {
|
|
259
|
+
const node = resolveNode(config);
|
|
254
260
|
const idx = getIndex(graph);
|
|
255
261
|
if (idx.nodeById.has(config.id)) throw new Error(`Node "${config.id}" already exists`);
|
|
256
|
-
if (config.parentId
|
|
257
|
-
const node = resolveNode(config);
|
|
262
|
+
if (config.parentId && !idx.nodeById.has(config.parentId)) throw new Error(`Parent node "${config.parentId}" does not exist`);
|
|
258
263
|
indexAddNode(idx, node, graph.nodes.push(node) - 1);
|
|
259
264
|
return node;
|
|
260
265
|
}
|
|
@@ -270,11 +275,11 @@ function addNode(graph, config) {
|
|
|
270
275
|
* ```
|
|
271
276
|
*/
|
|
272
277
|
function addEdge(graph, config) {
|
|
278
|
+
const edge = resolveEdge(config);
|
|
273
279
|
const idx = getIndex(graph);
|
|
274
280
|
if (idx.edgeById.has(config.id)) throw new Error(`Edge "${config.id}" already exists`);
|
|
275
281
|
if (!idx.nodeById.has(config.sourceId)) throw new Error(`Source node "${config.sourceId}" does not exist`);
|
|
276
282
|
if (!idx.nodeById.has(config.targetId)) throw new Error(`Target node "${config.targetId}" does not exist`);
|
|
277
|
-
const edge = resolveEdge(config);
|
|
278
283
|
indexAddEdge(idx, edge, graph.edges.push(edge) - 1);
|
|
279
284
|
return edge;
|
|
280
285
|
}
|
package/dist/algorithms.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as TraversalOptions, _ as MSTOptions, b as PathOptions, c as Graph, h as GraphPath, p as GraphNode, t as AllPairsShortestPathsOptions, x as SinglePathOptions } from "./types-
|
|
1
|
+
import { C as TraversalOptions, _ as MSTOptions, b as PathOptions, c as Graph, h as GraphPath, p as GraphNode, t as AllPairsShortestPathsOptions, x as SinglePathOptions } from "./types-FBZCrmnG.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/algorithms.d.ts
|
|
4
4
|
|
package/dist/algorithms.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as isAcyclic, E as joinPaths, S as hasPath, T as isTree, _ as getShortestPaths, a as genPreorders, b as getStronglyConnectedComponents, c as getAllPairsShortestPaths, d as getMinimumSpanningTree, f as getPostorder, g as getShortestPath, h as getPreorders, i as genPostorders, l as getConnectedComponents, m as getPreorder, n as dfs, o as genShortestPaths, p as getPostorders, r as genCycles, s as genSimplePaths, t as bfs, u as getCycles, v as getSimplePath, w as isConnected, x as getTopologicalSort, y as getSimplePaths } from "./algorithms-
|
|
1
|
+
import { C as isAcyclic, E as joinPaths, S as hasPath, T as isTree, _ as getShortestPaths, a as genPreorders, b as getStronglyConnectedComponents, c as getAllPairsShortestPaths, d as getMinimumSpanningTree, f as getPostorder, g as getShortestPath, h as getPreorders, i as genPostorders, l as getConnectedComponents, m as getPreorder, n as dfs, o as genShortestPaths, p as getPostorders, r as genCycles, s as genSimplePaths, t as bfs, u as getCycles, v as getSimplePath, w as isConnected, x as getTopologicalSort, y as getSimplePaths } from "./algorithms-DldwenLt.mjs";
|
|
2
2
|
|
|
3
3
|
export { bfs, dfs, genCycles, genPostorders, genPreorders, genShortestPaths, genSimplePaths, getAllPairsShortestPaths, getConnectedComponents, getCycles, getMinimumSpanningTree, getPostorder, getPostorders, getPreorder, getPreorders, getShortestPath, getShortestPaths, getSimplePath, getSimplePaths, getStronglyConnectedComponents, getTopologicalSort, hasPath, isAcyclic, isConnected, isTree, joinPaths };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as toAdjacencyList, t as fromAdjacencyList } from "./adjacency-list-
|
|
2
|
-
import { n as toEdgeList, t as fromEdgeList } from "./edge-list-
|
|
1
|
+
import { n as toAdjacencyList, t as fromAdjacencyList } from "./adjacency-list-fldj-QAL.mjs";
|
|
2
|
+
import { n as toEdgeList, t as fromEdgeList } from "./edge-list-Br05wXMg.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/formats/converter/index.ts
|
|
5
5
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as Graph, f as GraphFormatConverter } from "../../types-
|
|
1
|
+
import { c as Graph, f as GraphFormatConverter } from "../../types-FBZCrmnG.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/formats/converter/index.d.ts
|
|
4
4
|
|
|
@@ -18,7 +18,7 @@ import { c as Graph, f as GraphFormatConverter } from "../../types-BDXC1O5b.mjs"
|
|
|
18
18
|
* const graph = yamlConverter.from(yaml);
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
|
-
declare function createFormatConverter<TSerial>(to: (graph: Graph) => TSerial, from: (input: TSerial) => Graph): GraphFormatConverter<TSerial>;
|
|
21
|
+
declare function createFormatConverter<TSerial, N = any, E = any, G = any>(to: (graph: Graph<N, E, G>) => TSerial, from: (input: TSerial) => Graph<N, E, G>): GraphFormatConverter<TSerial, N, E, G>;
|
|
22
22
|
/**
|
|
23
23
|
* Bidirectional converter for adjacency-list format (`Record<string, string[]>`).
|
|
24
24
|
*
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as createFormatConverter, r as edgeListConverter, t as adjacencyListConverter } from "../../converter-
|
|
1
|
+
import { n as createFormatConverter, r as edgeListConverter, t as adjacencyListConverter } from "../../converter-B5CUD0r9.mjs";
|
|
2
2
|
|
|
3
3
|
export { adjacencyListConverter, createFormatConverter, edgeListConverter };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createFormatConverter } from "../../converter-
|
|
1
|
+
import { n as createFormatConverter } from "../../converter-B5CUD0r9.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/formats/cytoscape/index.ts
|
|
4
4
|
/**
|
|
@@ -22,7 +22,7 @@ function toCytoscapeJSON(graph) {
|
|
|
22
22
|
const graphData = {};
|
|
23
23
|
if (graph.id) graphData.id = graph.id;
|
|
24
24
|
graphData.type = graph.type;
|
|
25
|
-
if (graph.initialNodeId
|
|
25
|
+
if (graph.initialNodeId) graphData.initialNodeId = graph.initialNodeId;
|
|
26
26
|
if (graph.data !== void 0) graphData.graphData = graph.data;
|
|
27
27
|
if (graph.direction) graphData.direction = graph.direction;
|
|
28
28
|
return {
|
|
@@ -30,9 +30,9 @@ function toCytoscapeJSON(graph) {
|
|
|
30
30
|
elements: {
|
|
31
31
|
nodes: graph.nodes.map((n) => {
|
|
32
32
|
const data = { id: n.id };
|
|
33
|
-
if (n.parentId
|
|
33
|
+
if (n.parentId) data.parent = n.parentId;
|
|
34
34
|
if (n.label) data.label = n.label;
|
|
35
|
-
if (n.initialNodeId
|
|
35
|
+
if (n.initialNodeId) data.initialNodeId = n.initialNodeId;
|
|
36
36
|
if (n.data !== void 0) data.nodeData = n.data;
|
|
37
37
|
if (n.width !== void 0) data.width = n.width;
|
|
38
38
|
if (n.height !== void 0) data.height = n.height;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { D as VisualGraphFormatConverter, T as VisualGraph } from "../../types-FBZCrmnG.mjs";
|
|
2
|
+
import { ElkEdge, ElkEdgeSection, ElkExtendedEdge, ElkGraphElement, ElkLabel, ElkNode, ElkNode as ElkNode$1, ElkPoint, ElkPort, ElkPrimitiveEdge, ElkShape, LayoutOptions } from "elkjs/lib/elk-api";
|
|
3
|
+
|
|
4
|
+
//#region src/formats/elk/index.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Converts a visual graph to ELK JSON format.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { createVisualGraph } from '@statelyai/graph';
|
|
12
|
+
* import { toELK } from '@statelyai/graph/elk';
|
|
13
|
+
*
|
|
14
|
+
* const graph = createVisualGraph({
|
|
15
|
+
* nodes: [
|
|
16
|
+
* { id: 'a', x: 0, y: 0, width: 100, height: 50 },
|
|
17
|
+
* { id: 'b', x: 200, y: 0, width: 100, height: 50 },
|
|
18
|
+
* ],
|
|
19
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const elk = toELK(graph);
|
|
23
|
+
* // { id: '', children: [...], edges: [...] }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
declare function toELK(graph: VisualGraph): ElkNode$1;
|
|
27
|
+
/**
|
|
28
|
+
* Parses an ELK JSON node into a visual graph.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { fromELK } from '@statelyai/graph/elk';
|
|
33
|
+
*
|
|
34
|
+
* const graph = fromELK({
|
|
35
|
+
* id: 'root',
|
|
36
|
+
* children: [
|
|
37
|
+
* { id: 'a', x: 0, y: 0, width: 100, height: 50 },
|
|
38
|
+
* { id: 'b', x: 200, y: 0, width: 100, height: 50 },
|
|
39
|
+
* ],
|
|
40
|
+
* edges: [{ id: 'e1', sources: ['a'], targets: ['b'] }],
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* graph.nodes; // [{id: 'a', x: 0, y: 0, ...}, {id: 'b', x: 200, ...}]
|
|
44
|
+
* graph.edges; // [{sourceId: 'a', targetId: 'b', ...}]
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function fromELK(elkRoot: ElkNode$1): VisualGraph;
|
|
48
|
+
/**
|
|
49
|
+
* Bidirectional converter for ELK JSON format.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { elkConverter } from '@statelyai/graph/elk';
|
|
54
|
+
*
|
|
55
|
+
* const elk = elkConverter.to(graph);
|
|
56
|
+
* const roundTripped = elkConverter.from(elk);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare const elkConverter: VisualGraphFormatConverter<ElkNode$1>;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { type ElkEdge, type ElkEdgeSection, type ElkExtendedEdge, type ElkGraphElement, type ElkLabel, type ElkNode, type ElkPoint, type ElkPort, type ElkPrimitiveEdge, type ElkShape, type LayoutOptions, elkConverter, fromELK, toELK };
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { getChildren } from "../../queries.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/formats/elk/index.ts
|
|
4
|
+
const DIRECTION_TO_ELK = {
|
|
5
|
+
down: "DOWN",
|
|
6
|
+
up: "UP",
|
|
7
|
+
right: "RIGHT",
|
|
8
|
+
left: "LEFT"
|
|
9
|
+
};
|
|
10
|
+
const ELK_TO_DIRECTION = {
|
|
11
|
+
DOWN: "down",
|
|
12
|
+
UP: "up",
|
|
13
|
+
RIGHT: "right",
|
|
14
|
+
LEFT: "left"
|
|
15
|
+
};
|
|
16
|
+
function convertEdge(edge) {
|
|
17
|
+
const elkEdge = {
|
|
18
|
+
id: edge.id,
|
|
19
|
+
sources: [edge.sourceId],
|
|
20
|
+
targets: [edge.targetId]
|
|
21
|
+
};
|
|
22
|
+
if (edge.label) elkEdge.labels = [{ text: edge.label }];
|
|
23
|
+
return elkEdge;
|
|
24
|
+
}
|
|
25
|
+
function convertNode(graph, node) {
|
|
26
|
+
const elkNode = {
|
|
27
|
+
id: node.id,
|
|
28
|
+
x: node.x,
|
|
29
|
+
y: node.y,
|
|
30
|
+
width: node.width,
|
|
31
|
+
height: node.height
|
|
32
|
+
};
|
|
33
|
+
if (node.label) elkNode.labels = [{ text: node.label }];
|
|
34
|
+
const children = getChildren(graph, node.id);
|
|
35
|
+
if (children.length > 0) {
|
|
36
|
+
elkNode.children = children.map((child) => convertNode(graph, child));
|
|
37
|
+
const descendantIds = /* @__PURE__ */ new Set();
|
|
38
|
+
collectDescendants(graph, node.id, descendantIds);
|
|
39
|
+
const innerEdges = graph.edges.filter((e) => descendantIds.has(e.sourceId) && descendantIds.has(e.targetId));
|
|
40
|
+
if (innerEdges.length > 0) elkNode.edges = innerEdges.map(convertEdge);
|
|
41
|
+
}
|
|
42
|
+
return elkNode;
|
|
43
|
+
}
|
|
44
|
+
function collectDescendants(graph, nodeId, set) {
|
|
45
|
+
const children = getChildren(graph, nodeId);
|
|
46
|
+
for (const child of children) {
|
|
47
|
+
set.add(child.id);
|
|
48
|
+
collectDescendants(graph, child.id, set);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Converts a visual graph to ELK JSON format.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { createVisualGraph } from '@statelyai/graph';
|
|
57
|
+
* import { toELK } from '@statelyai/graph/elk';
|
|
58
|
+
*
|
|
59
|
+
* const graph = createVisualGraph({
|
|
60
|
+
* nodes: [
|
|
61
|
+
* { id: 'a', x: 0, y: 0, width: 100, height: 50 },
|
|
62
|
+
* { id: 'b', x: 200, y: 0, width: 100, height: 50 },
|
|
63
|
+
* ],
|
|
64
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* const elk = toELK(graph);
|
|
68
|
+
* // { id: '', children: [...], edges: [...] }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
function toELK(graph) {
|
|
72
|
+
const root = { id: graph.id };
|
|
73
|
+
const elkDir = DIRECTION_TO_ELK[graph.direction];
|
|
74
|
+
if (elkDir) root.layoutOptions = { "elk.direction": elkDir };
|
|
75
|
+
const roots = getChildren(graph, null);
|
|
76
|
+
if (roots.length > 0) root.children = roots.map((node) => convertNode(graph, node));
|
|
77
|
+
const allInnerEdgeIds = /* @__PURE__ */ new Set();
|
|
78
|
+
for (const node of graph.nodes) if (getChildren(graph, node.id).length > 0) {
|
|
79
|
+
const descendantIds = /* @__PURE__ */ new Set();
|
|
80
|
+
collectDescendants(graph, node.id, descendantIds);
|
|
81
|
+
for (const edge of graph.edges) if (descendantIds.has(edge.sourceId) && descendantIds.has(edge.targetId)) allInnerEdgeIds.add(edge.id);
|
|
82
|
+
}
|
|
83
|
+
const rootEdges = graph.edges.filter((e) => !allInnerEdgeIds.has(e.id));
|
|
84
|
+
if (rootEdges.length > 0) root.edges = rootEdges.map(convertEdge);
|
|
85
|
+
return root;
|
|
86
|
+
}
|
|
87
|
+
function flattenElkNodes(elkNode, parentId, nodes, edges, edgeIdx) {
|
|
88
|
+
if (elkNode.children) for (const child of elkNode.children) {
|
|
89
|
+
const label = child.labels?.[0]?.text ?? "";
|
|
90
|
+
const node = {
|
|
91
|
+
type: "node",
|
|
92
|
+
id: child.id,
|
|
93
|
+
parentId,
|
|
94
|
+
initialNodeId: null,
|
|
95
|
+
label,
|
|
96
|
+
data: void 0,
|
|
97
|
+
x: child.x ?? 0,
|
|
98
|
+
y: child.y ?? 0,
|
|
99
|
+
width: child.width ?? 0,
|
|
100
|
+
height: child.height ?? 0
|
|
101
|
+
};
|
|
102
|
+
nodes.push(node);
|
|
103
|
+
flattenElkNodes(child, child.id, nodes, edges, edgeIdx);
|
|
104
|
+
}
|
|
105
|
+
if (elkNode.edges) for (const elkEdge of elkNode.edges) for (const source of elkEdge.sources) for (const target of elkEdge.targets) {
|
|
106
|
+
const edge = {
|
|
107
|
+
type: "edge",
|
|
108
|
+
id: elkEdge.id ?? `e${edgeIdx.value++}`,
|
|
109
|
+
sourceId: source,
|
|
110
|
+
targetId: target,
|
|
111
|
+
label: elkEdge.labels?.[0]?.text ?? "",
|
|
112
|
+
data: void 0,
|
|
113
|
+
x: 0,
|
|
114
|
+
y: 0,
|
|
115
|
+
width: 0,
|
|
116
|
+
height: 0
|
|
117
|
+
};
|
|
118
|
+
edges.push(edge);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Parses an ELK JSON node into a visual graph.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { fromELK } from '@statelyai/graph/elk';
|
|
127
|
+
*
|
|
128
|
+
* const graph = fromELK({
|
|
129
|
+
* id: 'root',
|
|
130
|
+
* children: [
|
|
131
|
+
* { id: 'a', x: 0, y: 0, width: 100, height: 50 },
|
|
132
|
+
* { id: 'b', x: 200, y: 0, width: 100, height: 50 },
|
|
133
|
+
* ],
|
|
134
|
+
* edges: [{ id: 'e1', sources: ['a'], targets: ['b'] }],
|
|
135
|
+
* });
|
|
136
|
+
*
|
|
137
|
+
* graph.nodes; // [{id: 'a', x: 0, y: 0, ...}, {id: 'b', x: 200, ...}]
|
|
138
|
+
* graph.edges; // [{sourceId: 'a', targetId: 'b', ...}]
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
function fromELK(elkRoot) {
|
|
142
|
+
const nodes = [];
|
|
143
|
+
const edges = [];
|
|
144
|
+
flattenElkNodes(elkRoot, null, nodes, edges, { value: 0 });
|
|
145
|
+
const seenEdges = /* @__PURE__ */ new Map();
|
|
146
|
+
for (const edge of edges) if (!seenEdges.has(edge.id)) seenEdges.set(edge.id, edge);
|
|
147
|
+
const elkDir = elkRoot.layoutOptions?.["elk.direction"];
|
|
148
|
+
const direction = (elkDir ? ELK_TO_DIRECTION[elkDir] : void 0) ?? "down";
|
|
149
|
+
return {
|
|
150
|
+
id: elkRoot.id,
|
|
151
|
+
type: "directed",
|
|
152
|
+
initialNodeId: null,
|
|
153
|
+
nodes,
|
|
154
|
+
edges: [...seenEdges.values()],
|
|
155
|
+
data: void 0,
|
|
156
|
+
direction
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Bidirectional converter for ELK JSON format.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* import { elkConverter } from '@statelyai/graph/elk';
|
|
165
|
+
*
|
|
166
|
+
* const elk = elkConverter.to(graph);
|
|
167
|
+
* const roundTripped = elkConverter.from(elk);
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
const elkConverter = {
|
|
171
|
+
to: toELK,
|
|
172
|
+
from: fromELK
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
//#endregion
|
|
176
|
+
export { elkConverter, fromELK, toELK };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createFormatConverter } from "../../converter-
|
|
1
|
+
import { n as createFormatConverter } from "../../converter-B5CUD0r9.mjs";
|
|
2
2
|
import { XMLBuilder, XMLParser } from "fast-xml-parser";
|
|
3
3
|
|
|
4
4
|
//#region src/formats/gexf/index.ts
|
|
@@ -32,11 +32,11 @@ function toGEXF(graph) {
|
|
|
32
32
|
}];
|
|
33
33
|
const nodes = graph.nodes.map((n) => {
|
|
34
34
|
const attvalues = [];
|
|
35
|
-
if (n.parentId
|
|
35
|
+
if (n.parentId) attvalues.push({
|
|
36
36
|
"@_for": "a_parentId",
|
|
37
37
|
"@_value": n.parentId
|
|
38
38
|
});
|
|
39
|
-
if (n.initialNodeId
|
|
39
|
+
if (n.initialNodeId) attvalues.push({
|
|
40
40
|
"@_for": "a_initialNodeId",
|
|
41
41
|
"@_value": n.initialNodeId
|
|
42
42
|
});
|
|
@@ -52,7 +52,7 @@ function toGEXF(graph) {
|
|
|
52
52
|
"@_id": n.id,
|
|
53
53
|
"@_label": n.label || n.id
|
|
54
54
|
};
|
|
55
|
-
if (n.parentId
|
|
55
|
+
if (n.parentId) node["@_pid"] = n.parentId;
|
|
56
56
|
if (attvalues.length > 0) node.attvalues = { attvalue: attvalues };
|
|
57
57
|
if (n.color) {
|
|
58
58
|
const hex$1 = n.color.replace("#", "");
|
|
@@ -91,7 +91,7 @@ function toGEXF(graph) {
|
|
|
91
91
|
return edge;
|
|
92
92
|
});
|
|
93
93
|
const graphData = [];
|
|
94
|
-
if (graph.initialNodeId
|
|
94
|
+
if (graph.initialNodeId) graphData.push({
|
|
95
95
|
"@_for": "a_initialNodeId",
|
|
96
96
|
"@_value": graph.initialNodeId
|
|
97
97
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createFormatConverter } from "../../converter-
|
|
1
|
+
import { n as createFormatConverter } from "../../converter-B5CUD0r9.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/formats/gml/index.ts
|
|
4
4
|
/**
|
|
@@ -30,7 +30,7 @@ function toGML(graph) {
|
|
|
30
30
|
if (graph.id) lines.push(` id ${gmlString(graph.id)}`);
|
|
31
31
|
const childrenMap = /* @__PURE__ */ new Map();
|
|
32
32
|
for (const node of graph.nodes) {
|
|
33
|
-
const pid = node.parentId;
|
|
33
|
+
const pid = node.parentId ?? null;
|
|
34
34
|
if (!childrenMap.has(pid)) childrenMap.set(pid, []);
|
|
35
35
|
childrenMap.get(pid).push(node);
|
|
36
36
|
}
|
|
@@ -38,7 +38,7 @@ function toGML(graph) {
|
|
|
38
38
|
lines.push(`${indent}node [`);
|
|
39
39
|
lines.push(`${indent} id ${gmlString(node.id)}`);
|
|
40
40
|
if (node.label) lines.push(`${indent} label ${gmlString(node.label)}`);
|
|
41
|
-
if (node.initialNodeId
|
|
41
|
+
if (node.initialNodeId) lines.push(`${indent} initialNodeId ${gmlString(node.initialNodeId)}`);
|
|
42
42
|
if (node.data !== void 0) lines.push(`${indent} data ${gmlString(JSON.stringify(node.data))}`);
|
|
43
43
|
if (node.shape) lines.push(`${indent} shape ${gmlString(node.shape)}`);
|
|
44
44
|
if (node.color) lines.push(`${indent} color ${gmlString(node.color)}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createFormatConverter } from "../../converter-
|
|
1
|
+
import { n as createFormatConverter } from "../../converter-B5CUD0r9.mjs";
|
|
2
2
|
import { XMLBuilder, XMLParser } from "fast-xml-parser";
|
|
3
3
|
|
|
4
4
|
//#region src/formats/graphml/index.ts
|
|
@@ -72,7 +72,7 @@ function toGraphML(graph) {
|
|
|
72
72
|
"@_key": "label",
|
|
73
73
|
"#text": n.label
|
|
74
74
|
});
|
|
75
|
-
if (n.parentId
|
|
75
|
+
if (n.parentId) data.push({
|
|
76
76
|
"@_key": "parentId",
|
|
77
77
|
"#text": n.parentId
|
|
78
78
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createFormatConverter } from "../../converter-
|
|
1
|
+
import { n as createFormatConverter } from "../../converter-B5CUD0r9.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/formats/jgf/index.ts
|
|
4
4
|
/**
|
|
@@ -20,7 +20,7 @@ import { n as createFormatConverter } from "../../converter-CchokMDg.mjs";
|
|
|
20
20
|
*/
|
|
21
21
|
function toJGF(graph) {
|
|
22
22
|
const metadata = {};
|
|
23
|
-
if (graph.initialNodeId
|
|
23
|
+
if (graph.initialNodeId) metadata.initialNodeId = graph.initialNodeId;
|
|
24
24
|
if (graph.data !== void 0) metadata.data = graph.data;
|
|
25
25
|
if (graph.direction) metadata.direction = graph.direction;
|
|
26
26
|
return { graph: {
|
|
@@ -29,8 +29,8 @@ function toJGF(graph) {
|
|
|
29
29
|
...Object.keys(metadata).length > 0 && { metadata },
|
|
30
30
|
nodes: graph.nodes.map((n) => {
|
|
31
31
|
const meta = {};
|
|
32
|
-
if (n.parentId
|
|
33
|
-
if (n.initialNodeId
|
|
32
|
+
if (n.parentId) meta.parentId = n.parentId;
|
|
33
|
+
if (n.initialNodeId) meta.initialNodeId = n.initialNodeId;
|
|
34
34
|
if (n.data !== void 0) meta.data = n.data;
|
|
35
35
|
if (n.x !== void 0) meta.x = n.x;
|
|
36
36
|
if (n.y !== void 0) meta.y = n.y;
|