@statelyai/graph 0.12.0 → 0.13.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 +23 -15
- package/dist/{algorithms-BHHg7lGq.mjs → algorithms-BNDQcHU3.mjs} +7 -7
- package/dist/algorithms.mjs +1 -1
- package/dist/format-support.d.mts +6 -0
- package/dist/format-support.mjs +38 -32
- package/dist/formats/cytoscape/index.mjs +16 -0
- package/dist/formats/d3/index.d.mts +7 -0
- package/dist/formats/d3/index.mjs +34 -6
- package/dist/formats/elk/index.mjs +86 -23
- package/dist/formats/gexf/index.mjs +93 -1
- package/dist/formats/gml/index.mjs +40 -13
- package/dist/formats/jgf/index.mjs +16 -0
- package/dist/formats/mermaid/index.d.mts +1 -0
- package/dist/formats/mermaid/index.mjs +39 -9
- package/dist/formats/xyflow/index.d.mts +1 -0
- package/dist/formats/xyflow/index.mjs +97 -40
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{indexing-CJc-ul8e.mjs → indexing-DUl3kTqm.mjs} +18 -4
- package/dist/queries.mjs +1 -1
- package/dist/schemas.d.mts +8 -7
- package/dist/schemas.mjs +127 -4
- package/package.json +1 -1
- package/schemas/graph.schema.json +1 -0
- package/schemas/node.schema.json +1 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @statelyai/graph
|
|
2
2
|
|
|
3
|
-
A TypeScript graph library
|
|
3
|
+
A TypeScript graph library for JSON-serializable graph IR. Use it to validate, analyze, transform, and round-trip directed, undirected, hierarchical, port-aware, and visual graphs across tools.
|
|
4
4
|
|
|
5
5
|
Made from our experience at [stately.ai](https://stately.ai), where we build visual tools for complex systems.
|
|
6
6
|
|
|
@@ -25,13 +25,13 @@ Optional peers are only needed for specific adapters:
|
|
|
25
25
|
|
|
26
26
|
## Highlights
|
|
27
27
|
|
|
28
|
-
- Plain JSON graphs with no runtime wrappers required
|
|
28
|
+
- Plain JSON graphs with no runtime wrappers required; omitted `data` defaults to `null`
|
|
29
29
|
- Standalone functions with a consistent `get*`/`gen*`/`is*`/`add*` naming model
|
|
30
30
|
- Directed, undirected, hierarchical, and visual graph support
|
|
31
31
|
- Ports for node-editor and dataflow-style graphs
|
|
32
32
|
- Algorithms for traversal, paths, centrality, communities, connectivity, isomorphism, ordering, MST, and walks
|
|
33
33
|
- Diff/patch utilities for graph state changes
|
|
34
|
-
- Multi-format conversion via package subpaths
|
|
34
|
+
- Multi-format conversion via package subpaths, with fidelity claims tested against fixtures
|
|
35
35
|
- Small, fast test suite with broad format coverage
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
@@ -149,17 +149,17 @@ getEdgesByPort(graph, 'render', 'input'); // [e1]
|
|
|
149
149
|
|
|
150
150
|
<!-- validation helpers exported from src/schemas.ts -->
|
|
151
151
|
|
|
152
|
-
Use the `@statelyai/graph/schemas` subpath when you want runtime validation or JSON Schema generation.
|
|
152
|
+
Use the `@statelyai/graph/schemas` subpath when you want runtime validation or JSON Schema generation. `validateGraph()` combines shape checks with graph invariants such as duplicate ids, dangling edges, missing parents, missing initial nodes, duplicate ports, invalid port references, and parent cycles.
|
|
153
153
|
|
|
154
154
|
```ts
|
|
155
|
-
import { GraphSchema,
|
|
155
|
+
import { GraphSchema, isGraph, validateGraph } from '@statelyai/graph/schemas';
|
|
156
156
|
|
|
157
157
|
const unknownValue: unknown = JSON.parse(input);
|
|
158
158
|
|
|
159
159
|
if (isGraph(unknownValue)) {
|
|
160
160
|
// fully typed Graph
|
|
161
161
|
} else {
|
|
162
|
-
console.error(
|
|
162
|
+
console.error(validateGraph(unknownValue));
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
const parsed = GraphSchema.parse(unknownValue);
|
|
@@ -263,6 +263,11 @@ const cyto = cytoscapeConverter.to(graph);
|
|
|
263
263
|
const back = cytoscapeConverter.from(cyto);
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
+
Round-trip fidelity may use adapter-specific graph, node, and edge `data`
|
|
267
|
+
metadata when the target format does not have a native field for a source
|
|
268
|
+
concept. A `partial` round-trip entry means the adapter still drops meaningful
|
|
269
|
+
source information instead of preserving it as metadata.
|
|
270
|
+
|
|
266
271
|
## Format Support
|
|
267
272
|
|
|
268
273
|
<!-- format support matrix derived from src/formats/support.ts -->
|
|
@@ -270,17 +275,17 @@ const back = cytoscapeConverter.from(cyto);
|
|
|
270
275
|
| Format | Hierarchy | Ports | Visual | Round-trip | Notes |
|
|
271
276
|
| ------------------- | --------- | ------- | ------- | ---------- | -------------------------------------------------------------------------- |
|
|
272
277
|
| `adjacency-list` | none | none | none | partial | Connectivity only; edge metadata is lost. |
|
|
273
|
-
| `cytoscape` | full | full |
|
|
274
|
-
| `d3` |
|
|
278
|
+
| `cytoscape` | full | full | full | full | Graph, node, and edge metadata round-trip through element data. |
|
|
279
|
+
| `d3` | full | full | full | full | Graph, node, and edge metadata round-trip through the loose JSON shape. |
|
|
275
280
|
| `dot` | partial | partial | partial | partial | Edge port ids round-trip, but `:port:compass` mapping is still incomplete. |
|
|
276
281
|
| `edge-list` | none | none | none | partial | Endpoints only. |
|
|
277
|
-
| `elk` | full | full | full |
|
|
278
|
-
| `gexf` | full | full |
|
|
279
|
-
| `gml` | full | full |
|
|
282
|
+
| `elk` | full | full | full | full | Metadata round-trips through reserved layout options. |
|
|
283
|
+
| `gexf` | full | full | full | full | Custom attributes preserve metadata beyond the standard viz module. |
|
|
284
|
+
| `gml` | full | full | full | full | Graph, node, and edge metadata round-trip through direct and JSON fields. |
|
|
280
285
|
| `graphml` | full | full | partial | partial | Ports round-trip through `<data>` fields. |
|
|
281
|
-
| `jgf` | full | full |
|
|
286
|
+
| `jgf` | full | full | full | full | Graph, node, and edge metadata round-trip through `metadata` objects. |
|
|
282
287
|
| `tgf` | none | none | none | partial | Minimal ids and labels only. |
|
|
283
|
-
| `xyflow` |
|
|
288
|
+
| `xyflow` | full | full | full | full | Metadata round-trips through reserved data fields. |
|
|
284
289
|
| `mermaid/block` | partial | none | partial | partial | Syntax-driven, not port-aware. |
|
|
285
290
|
| `mermaid/class` | none | none | none | partial | Class syntax is stored conservatively. |
|
|
286
291
|
| `mermaid/er` | none | none | none | partial | Focuses on entities and cardinality. |
|
|
@@ -288,7 +293,7 @@ const back = cytoscapeConverter.from(cyto);
|
|
|
288
293
|
| `mermaid/ishikawa` | full | none | none | partial | Preserves hierarchy, not fishbone layout. |
|
|
289
294
|
| `mermaid/mindmap` | full | none | partial | partial | Icon syntax is not fully re-emitted. |
|
|
290
295
|
| `mermaid/sequence` | partial | none | none | partial | Actor links and menu syntax are incomplete. |
|
|
291
|
-
| `mermaid/state` | full | none | partial |
|
|
296
|
+
| `mermaid/state` | full | none | partial | full | State syntax round-trips through graph and node data. |
|
|
292
297
|
|
|
293
298
|
Some formats have optional peer dependencies: `fast-xml-parser` (GEXF, GraphML) and `dotparser` (DOT). All other formats are dependency-free.
|
|
294
299
|
|
|
@@ -327,13 +332,14 @@ The repo includes runnable examples under [`examples/`](./examples):
|
|
|
327
332
|
```bash
|
|
328
333
|
pnpm install
|
|
329
334
|
pnpm verify
|
|
335
|
+
pnpm bench
|
|
330
336
|
```
|
|
331
337
|
|
|
332
338
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) for contributor conventions, format-module checklist, and release notes guidance.
|
|
333
339
|
|
|
334
340
|
## Why this library?
|
|
335
341
|
|
|
336
|
-
Graph file formats define how to _store_ graphs. Visualization libraries define how to _render_ them. This library is the
|
|
342
|
+
Graph file formats define how to _store_ graphs. Visualization libraries define how to _render_ them. This library is the trusted interchange and analysis layer in between: plain JSON objects in, validation, algorithms, transforms, diffing, and format-preserving conversion out.
|
|
337
343
|
|
|
338
344
|
```
|
|
339
345
|
GEXF file → fromGEXF() → Graph → run algorithms, mutate → toCytoscapeJSON() → render
|
|
@@ -341,6 +347,8 @@ GEXF file → fromGEXF() → Graph → run algorithms, mutate → toCytoscapeJSO
|
|
|
341
347
|
|
|
342
348
|
Your `Graph` is a plain object that survives `JSON.stringify`, `structuredClone`, `postMessage`, and `localStorage` without adapters.
|
|
343
349
|
|
|
350
|
+
A canonical graph is a deterministic projection of a graph for comparison, hashing, snapshots, or caches. A future pure helper would return a new graph with stable node/edge ordering and normalized optional fields. A hash would be a digest of that canonical JSON. A summary would be a small structural report, for example node count, edge count, roots, sinks, component count, compound depth, port count, and whether the graph is acyclic. A pure `sortGraph()` would return a sorted copy and never mutate the input.
|
|
351
|
+
|
|
344
352
|
## License
|
|
345
353
|
|
|
346
354
|
MIT
|
|
@@ -1,4 +1,4 @@
|
|
|
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-DUl3kTqm.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/graph.ts
|
|
4
4
|
/**
|
|
@@ -7,7 +7,7 @@ import { a as indexUpdateEdgeEndpoints, i as indexReparentNode, n as indexAddEdg
|
|
|
7
7
|
* @example
|
|
8
8
|
* ```ts
|
|
9
9
|
* const port = createGraphPort({ name: 'output', direction: 'out' });
|
|
10
|
-
* // { name: 'output', direction: 'out', data:
|
|
10
|
+
* // { name: 'output', direction: 'out', data: null }
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
function createGraphPort(config) {
|
|
@@ -15,7 +15,7 @@ function createGraphPort(config) {
|
|
|
15
15
|
const port = {
|
|
16
16
|
name: config.name,
|
|
17
17
|
direction: config.direction ?? "inout",
|
|
18
|
-
data: config.data
|
|
18
|
+
data: config.data ?? null
|
|
19
19
|
};
|
|
20
20
|
if (config.label !== void 0) port.label = config.label;
|
|
21
21
|
if (config.x !== void 0) port.x = config.x;
|
|
@@ -50,7 +50,7 @@ function createGraphNode(config) {
|
|
|
50
50
|
...config.parentId !== void 0 && { parentId: config.parentId ?? null },
|
|
51
51
|
...config.initialNodeId !== void 0 && { initialNodeId: config.initialNodeId ?? null },
|
|
52
52
|
label: config.label ?? null,
|
|
53
|
-
data: config.data
|
|
53
|
+
data: config.data ?? null
|
|
54
54
|
};
|
|
55
55
|
if (config.ports !== void 0 && config.ports.length > 0) {
|
|
56
56
|
validatePortNames(config.ports);
|
|
@@ -71,7 +71,7 @@ function createGraphNode(config) {
|
|
|
71
71
|
* @example
|
|
72
72
|
* ```ts
|
|
73
73
|
* const edge = createGraphEdge({ id: 'e1', sourceId: 'a', targetId: 'b' });
|
|
74
|
-
* // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data:
|
|
74
|
+
* // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data: null }
|
|
75
75
|
* ```
|
|
76
76
|
*/
|
|
77
77
|
function createGraphEdge(config) {
|
|
@@ -84,7 +84,7 @@ function createGraphEdge(config) {
|
|
|
84
84
|
sourceId: config.sourceId,
|
|
85
85
|
targetId: config.targetId,
|
|
86
86
|
label: config.label ?? null,
|
|
87
|
-
data: config.data
|
|
87
|
+
data: config.data ?? null
|
|
88
88
|
};
|
|
89
89
|
if (config.sourcePort !== void 0) edge.sourcePort = config.sourcePort;
|
|
90
90
|
if (config.targetPort !== void 0) edge.targetPort = config.targetPort;
|
|
@@ -115,7 +115,7 @@ function createGraph(config) {
|
|
|
115
115
|
initialNodeId: config?.initialNodeId ?? null,
|
|
116
116
|
nodes: (config?.nodes ?? []).map(createGraphNode),
|
|
117
117
|
edges: (config?.edges ?? []).map(createGraphEdge),
|
|
118
|
-
data: config?.data ??
|
|
118
|
+
data: config?.data ?? null
|
|
119
119
|
};
|
|
120
120
|
if (config?.direction !== void 0) graph.direction = config.direction;
|
|
121
121
|
if (config?.style !== void 0) graph.style = config.style;
|
package/dist/algorithms.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as isAcyclic, B as getShortestPaths, C as getPreorder, D as getConnectedComponents, E as dfs, F as genSimplePaths, H as getSimplePaths, I as getAStarPath, L as getAllPairsShortestPaths, M as isTree, N as genCycles, O as getTopologicalSort, P as genShortestPaths, R as getCycles, S as getPostorders, T as bfs, U as getStronglyConnectedComponents, V as getSimplePath, W as joinPaths, _ as getPageRank, a as genGirvanNewmanCommunities, b as genPreorders, c as getLabelPropagationCommunities, d as getClosenessCentrality, f as getDegreeCentrality, g as getOutDegreeCentrality, h as getInDegreeCentrality, i as getBridges, j as isConnected, k as hasPath, l as getModularity, m as getHITS, n as getArticulationPoints, o as getGirvanNewmanCommunities, p as getEigenvectorCentrality, r as getBiconnectedComponents, s as getGreedyModularityCommunities, t as isIsomorphic, u as getBetweennessCentrality, v as getMinimumSpanningTree, w as getPreorders, x as getPostorder, y as genPostorders, z as getShortestPath } from "./algorithms-
|
|
1
|
+
import { A as isAcyclic, B as getShortestPaths, C as getPreorder, D as getConnectedComponents, E as dfs, F as genSimplePaths, H as getSimplePaths, I as getAStarPath, L as getAllPairsShortestPaths, M as isTree, N as genCycles, O as getTopologicalSort, P as genShortestPaths, R as getCycles, S as getPostorders, T as bfs, U as getStronglyConnectedComponents, V as getSimplePath, W as joinPaths, _ as getPageRank, a as genGirvanNewmanCommunities, b as genPreorders, c as getLabelPropagationCommunities, d as getClosenessCentrality, f as getDegreeCentrality, g as getOutDegreeCentrality, h as getInDegreeCentrality, i as getBridges, j as isConnected, k as hasPath, l as getModularity, m as getHITS, n as getArticulationPoints, o as getGirvanNewmanCommunities, p as getEigenvectorCentrality, r as getBiconnectedComponents, s as getGreedyModularityCommunities, t as isIsomorphic, u as getBetweennessCentrality, v as getMinimumSpanningTree, w as getPreorders, x as getPostorder, y as genPostorders, z as getShortestPath } from "./algorithms-BNDQcHU3.mjs";
|
|
2
2
|
|
|
3
3
|
export { bfs, dfs, genCycles, genGirvanNewmanCommunities, genPostorders, genPreorders, genShortestPaths, genSimplePaths, getAStarPath, getAllPairsShortestPaths, getArticulationPoints, getBetweennessCentrality, getBiconnectedComponents, getBridges, getClosenessCentrality, getConnectedComponents, getCycles, getDegreeCentrality, getEigenvectorCentrality, getGirvanNewmanCommunities, getGreedyModularityCommunities, getHITS, getInDegreeCentrality, getLabelPropagationCommunities, getMinimumSpanningTree, getModularity, getOutDegreeCentrality, getPageRank, getPostorder, getPostorders, getPreorder, getPreorders, getShortestPath, getShortestPaths, getSimplePath, getSimplePaths, getStronglyConnectedComponents, getTopologicalSort, hasPath, isAcyclic, isConnected, isIsomorphic, isTree, joinPaths };
|
|
@@ -16,6 +16,12 @@ interface FormatSupportEntry {
|
|
|
16
16
|
features: FormatSupportFeatures;
|
|
17
17
|
notes: string[];
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Round-trip support is allowed to use adapter-specific graph, node, and edge
|
|
21
|
+
* `data` metadata when the target format has no native field for a source
|
|
22
|
+
* concept. A `partial` value means the adapter still drops meaningful source
|
|
23
|
+
* information instead of preserving it as metadata.
|
|
24
|
+
*/
|
|
19
25
|
declare const FORMAT_SUPPORT_MATRIX: FormatSupportEntry[];
|
|
20
26
|
declare function getFormatSupportEntry(id: string): FormatSupportEntry | undefined;
|
|
21
27
|
//#endregion
|
package/dist/format-support.mjs
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
//#region src/formats/support.ts
|
|
2
|
+
/**
|
|
3
|
+
* Round-trip support is allowed to use adapter-specific graph, node, and edge
|
|
4
|
+
* `data` metadata when the target format has no native field for a source
|
|
5
|
+
* concept. A `partial` value means the adapter still drops meaningful source
|
|
6
|
+
* information instead of preserving it as metadata.
|
|
7
|
+
*/
|
|
2
8
|
const FORMAT_SUPPORT_MATRIX = [
|
|
3
9
|
{
|
|
4
10
|
id: "adjacency-list",
|
|
@@ -23,12 +29,12 @@ const FORMAT_SUPPORT_MATRIX = [
|
|
|
23
29
|
undirected: "full",
|
|
24
30
|
hierarchy: "full",
|
|
25
31
|
ports: "full",
|
|
26
|
-
visual: "
|
|
27
|
-
style: "
|
|
32
|
+
visual: "full",
|
|
33
|
+
style: "full",
|
|
28
34
|
weight: "full",
|
|
29
|
-
roundTrip: "
|
|
35
|
+
roundTrip: "full"
|
|
30
36
|
},
|
|
31
|
-
notes: ["Uses Cytoscape JSON element data with
|
|
37
|
+
notes: ["Uses Cytoscape JSON element data with graph, node, and edge metadata stored in element data.", "Ports round-trip through element data as `ports`, `sourcePort`, and `targetPort`."]
|
|
32
38
|
},
|
|
33
39
|
{
|
|
34
40
|
id: "d3",
|
|
@@ -36,14 +42,14 @@ const FORMAT_SUPPORT_MATRIX = [
|
|
|
36
42
|
features: {
|
|
37
43
|
directed: "full",
|
|
38
44
|
undirected: "full",
|
|
39
|
-
hierarchy: "
|
|
45
|
+
hierarchy: "full",
|
|
40
46
|
ports: "full",
|
|
41
|
-
visual: "
|
|
42
|
-
style: "
|
|
47
|
+
visual: "full",
|
|
48
|
+
style: "full",
|
|
43
49
|
weight: "full",
|
|
44
|
-
roundTrip: "
|
|
50
|
+
roundTrip: "full"
|
|
45
51
|
},
|
|
46
|
-
notes: ["Targets force-graph structures,
|
|
52
|
+
notes: ["Targets force-graph structures, but graph, node, and edge metadata can be preserved on the loose JSON shape.", "Ports round-trip through node/link objects."]
|
|
47
53
|
},
|
|
48
54
|
{
|
|
49
55
|
id: "dot",
|
|
@@ -84,11 +90,11 @@ const FORMAT_SUPPORT_MATRIX = [
|
|
|
84
90
|
hierarchy: "full",
|
|
85
91
|
ports: "full",
|
|
86
92
|
visual: "full",
|
|
87
|
-
style: "
|
|
88
|
-
weight: "
|
|
89
|
-
roundTrip: "
|
|
93
|
+
style: "full",
|
|
94
|
+
weight: "full",
|
|
95
|
+
roundTrip: "full"
|
|
90
96
|
},
|
|
91
|
-
notes: ["
|
|
97
|
+
notes: ["ELK-native layout fields are preserved directly; graph, node, port, and edge metadata round-trip through reserved layout options."]
|
|
92
98
|
},
|
|
93
99
|
{
|
|
94
100
|
id: "gexf",
|
|
@@ -98,12 +104,12 @@ const FORMAT_SUPPORT_MATRIX = [
|
|
|
98
104
|
undirected: "full",
|
|
99
105
|
hierarchy: "full",
|
|
100
106
|
ports: "full",
|
|
101
|
-
visual: "
|
|
102
|
-
style: "
|
|
107
|
+
visual: "full",
|
|
108
|
+
style: "full",
|
|
103
109
|
weight: "full",
|
|
104
|
-
roundTrip: "
|
|
110
|
+
roundTrip: "full"
|
|
105
111
|
},
|
|
106
|
-
notes: ["
|
|
112
|
+
notes: ["Custom attributes preserve graph, node, and edge metadata beyond the standard viz module.", "Ports round-trip via custom node/edge attributes."]
|
|
107
113
|
},
|
|
108
114
|
{
|
|
109
115
|
id: "gml",
|
|
@@ -113,12 +119,12 @@ const FORMAT_SUPPORT_MATRIX = [
|
|
|
113
119
|
undirected: "full",
|
|
114
120
|
hierarchy: "full",
|
|
115
121
|
ports: "full",
|
|
116
|
-
visual: "
|
|
117
|
-
style: "
|
|
122
|
+
visual: "full",
|
|
123
|
+
style: "full",
|
|
118
124
|
weight: "full",
|
|
119
|
-
roundTrip: "
|
|
125
|
+
roundTrip: "full"
|
|
120
126
|
},
|
|
121
|
-
notes: ["GML
|
|
127
|
+
notes: ["GML stores graph, node, and edge metadata directly or as JSON-stringified fields.", "Ports round-trip through JSON-stringified node metadata and edge fields."]
|
|
122
128
|
},
|
|
123
129
|
{
|
|
124
130
|
id: "graphml",
|
|
@@ -143,12 +149,12 @@ const FORMAT_SUPPORT_MATRIX = [
|
|
|
143
149
|
undirected: "full",
|
|
144
150
|
hierarchy: "full",
|
|
145
151
|
ports: "full",
|
|
146
|
-
visual: "
|
|
147
|
-
style: "
|
|
152
|
+
visual: "full",
|
|
153
|
+
style: "full",
|
|
148
154
|
weight: "full",
|
|
149
|
-
roundTrip: "
|
|
155
|
+
roundTrip: "full"
|
|
150
156
|
},
|
|
151
|
-
notes: ["JGF preserves
|
|
157
|
+
notes: ["JGF preserves graph, node, and edge metadata via `metadata` objects.", "Ports round-trip through node and edge metadata."]
|
|
152
158
|
},
|
|
153
159
|
{
|
|
154
160
|
id: "tgf",
|
|
@@ -171,14 +177,14 @@ const FORMAT_SUPPORT_MATRIX = [
|
|
|
171
177
|
features: {
|
|
172
178
|
directed: "full",
|
|
173
179
|
undirected: "partial",
|
|
174
|
-
hierarchy: "
|
|
180
|
+
hierarchy: "full",
|
|
175
181
|
ports: "full",
|
|
176
182
|
visual: "full",
|
|
177
|
-
style: "
|
|
178
|
-
weight: "
|
|
179
|
-
roundTrip: "
|
|
183
|
+
style: "full",
|
|
184
|
+
weight: "full",
|
|
185
|
+
roundTrip: "full"
|
|
180
186
|
},
|
|
181
|
-
notes: ["
|
|
187
|
+
notes: ["xyflow-native fields are preserved directly; graph, node, edge, style, weight, and port metadata round-trip through reserved data fields."]
|
|
182
188
|
},
|
|
183
189
|
{
|
|
184
190
|
id: "mermaid/block",
|
|
@@ -296,9 +302,9 @@ const FORMAT_SUPPORT_MATRIX = [
|
|
|
296
302
|
visual: "partial",
|
|
297
303
|
style: "partial",
|
|
298
304
|
weight: "none",
|
|
299
|
-
roundTrip: "
|
|
305
|
+
roundTrip: "full"
|
|
300
306
|
},
|
|
301
|
-
notes: ["State notes
|
|
307
|
+
notes: ["State-specific syntax such as notes, classes, descriptions, directions, hierarchy, and parallel regions round-trips through node and graph data."]
|
|
302
308
|
}
|
|
303
309
|
];
|
|
304
310
|
function getFormatSupportEntry(id) {
|
|
@@ -25,6 +25,7 @@ function toCytoscapeJSON(graph) {
|
|
|
25
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
|
+
if (graph.style !== void 0) graphData.style = graph.style;
|
|
28
29
|
return {
|
|
29
30
|
...Object.keys(graphData).length > 0 && { data: graphData },
|
|
30
31
|
elements: {
|
|
@@ -38,6 +39,7 @@ function toCytoscapeJSON(graph) {
|
|
|
38
39
|
if (n.height !== void 0) data.height = n.height;
|
|
39
40
|
if (n.shape) data.shape = n.shape;
|
|
40
41
|
if (n.color) data.color = n.color;
|
|
42
|
+
if (n.style !== void 0) data.style = n.style;
|
|
41
43
|
if (n.ports !== void 0) data.ports = n.ports;
|
|
42
44
|
const node = { data };
|
|
43
45
|
if (n.x !== void 0 && n.y !== void 0) node.position = {
|
|
@@ -54,7 +56,13 @@ function toCytoscapeJSON(graph) {
|
|
|
54
56
|
};
|
|
55
57
|
if (e.label) data.label = e.label;
|
|
56
58
|
if (e.data !== void 0) data.edgeData = e.data;
|
|
59
|
+
if (e.weight !== void 0) data.weight = e.weight;
|
|
60
|
+
if (e.x !== void 0) data.x = e.x;
|
|
61
|
+
if (e.y !== void 0) data.y = e.y;
|
|
62
|
+
if (e.width !== void 0) data.width = e.width;
|
|
63
|
+
if (e.height !== void 0) data.height = e.height;
|
|
57
64
|
if (e.color) data.color = e.color;
|
|
65
|
+
if (e.style !== void 0) data.style = e.style;
|
|
58
66
|
if (e.sourcePort !== void 0) data.sourcePort = e.sourcePort;
|
|
59
67
|
if (e.targetPort !== void 0) data.targetPort = e.targetPort;
|
|
60
68
|
return { data };
|
|
@@ -88,6 +96,7 @@ function fromCytoscapeJSON(cyto) {
|
|
|
88
96
|
initialNodeId: cyto.data?.initialNodeId ?? null,
|
|
89
97
|
data: cyto.data?.graphData,
|
|
90
98
|
...cyto.data?.direction && { direction: cyto.data.direction },
|
|
99
|
+
...cyto.data?.style !== void 0 && { style: cyto.data.style },
|
|
91
100
|
nodes: cyto.elements.nodes.map((n) => ({
|
|
92
101
|
type: "node",
|
|
93
102
|
id: n.data.id,
|
|
@@ -103,6 +112,7 @@ function fromCytoscapeJSON(cyto) {
|
|
|
103
112
|
...n.data.height !== void 0 && { height: n.data.height },
|
|
104
113
|
...n.data.shape && { shape: n.data.shape },
|
|
105
114
|
...n.data.color && { color: n.data.color },
|
|
115
|
+
...n.data.style !== void 0 && { style: n.data.style },
|
|
106
116
|
...n.data.ports !== void 0 && { ports: n.data.ports }
|
|
107
117
|
})),
|
|
108
118
|
edges: cyto.elements.edges.map((e, i) => ({
|
|
@@ -112,7 +122,13 @@ function fromCytoscapeJSON(cyto) {
|
|
|
112
122
|
targetId: e.data.target,
|
|
113
123
|
label: e.data.label ?? "",
|
|
114
124
|
data: e.data.edgeData,
|
|
125
|
+
...e.data.weight !== void 0 && { weight: e.data.weight },
|
|
126
|
+
...e.data.x !== void 0 && { x: e.data.x },
|
|
127
|
+
...e.data.y !== void 0 && { y: e.data.y },
|
|
128
|
+
...e.data.width !== void 0 && { width: e.data.width },
|
|
129
|
+
...e.data.height !== void 0 && { height: e.data.height },
|
|
115
130
|
...e.data.color && { color: e.data.color },
|
|
131
|
+
...e.data.style !== void 0 && { style: e.data.style },
|
|
116
132
|
...e.data.sourcePort !== void 0 && { sourcePort: e.data.sourcePort },
|
|
117
133
|
...e.data.targetPort !== void 0 && { targetPort: e.data.targetPort }
|
|
118
134
|
}))
|
|
@@ -11,8 +11,15 @@ interface D3Link {
|
|
|
11
11
|
[key: string]: any;
|
|
12
12
|
}
|
|
13
13
|
interface D3Graph {
|
|
14
|
+
id?: string;
|
|
15
|
+
type?: Graph['type'];
|
|
16
|
+
initialNodeId?: string | null;
|
|
17
|
+
data?: any;
|
|
18
|
+
direction?: Graph['direction'];
|
|
19
|
+
style?: Graph['style'];
|
|
14
20
|
nodes: D3Node[];
|
|
15
21
|
links: D3Link[];
|
|
22
|
+
[key: string]: any;
|
|
16
23
|
}
|
|
17
24
|
/**
|
|
18
25
|
* Converts a graph to D3.js force-directed format.
|
|
@@ -20,14 +20,25 @@ import { n as createFormatConverter } from "../../converter-Dspillnn.mjs";
|
|
|
20
20
|
*/
|
|
21
21
|
function toD3Graph(graph) {
|
|
22
22
|
return {
|
|
23
|
+
...graph.id && { id: graph.id },
|
|
24
|
+
type: graph.type,
|
|
25
|
+
initialNodeId: graph.initialNodeId,
|
|
26
|
+
...graph.data !== void 0 && { data: graph.data },
|
|
27
|
+
...graph.direction && { direction: graph.direction },
|
|
28
|
+
...graph.style !== void 0 && { style: graph.style },
|
|
23
29
|
nodes: graph.nodes.map((n) => {
|
|
24
30
|
const node = { id: n.id };
|
|
31
|
+
if (n.parentId) node.parentId = n.parentId;
|
|
32
|
+
if (n.initialNodeId) node.initialNodeId = n.initialNodeId;
|
|
25
33
|
if (n.label) node.label = n.label;
|
|
26
34
|
if (n.data !== void 0) node.data = n.data;
|
|
27
35
|
if (n.x !== void 0) node.x = n.x;
|
|
28
36
|
if (n.y !== void 0) node.y = n.y;
|
|
37
|
+
if (n.width !== void 0) node.width = n.width;
|
|
38
|
+
if (n.height !== void 0) node.height = n.height;
|
|
29
39
|
if (n.color) node.color = n.color;
|
|
30
40
|
if (n.shape) node.shape = n.shape;
|
|
41
|
+
if (n.style !== void 0) node.style = n.style;
|
|
31
42
|
if (n.ports !== void 0) node.ports = n.ports;
|
|
32
43
|
return node;
|
|
33
44
|
}),
|
|
@@ -39,7 +50,13 @@ function toD3Graph(graph) {
|
|
|
39
50
|
if (e.id) link.id = e.id;
|
|
40
51
|
if (e.label) link.label = e.label;
|
|
41
52
|
if (e.data !== void 0) link.data = e.data;
|
|
53
|
+
if (e.weight !== void 0) link.weight = e.weight;
|
|
54
|
+
if (e.x !== void 0) link.x = e.x;
|
|
55
|
+
if (e.y !== void 0) link.y = e.y;
|
|
56
|
+
if (e.width !== void 0) link.width = e.width;
|
|
57
|
+
if (e.height !== void 0) link.height = e.height;
|
|
42
58
|
if (e.color) link.color = e.color;
|
|
59
|
+
if (e.style !== void 0) link.style = e.style;
|
|
43
60
|
if (e.sourcePort !== void 0) link.sourcePort = e.sourcePort;
|
|
44
61
|
if (e.targetPort !== void 0) link.targetPort = e.targetPort;
|
|
45
62
|
return link;
|
|
@@ -64,21 +81,26 @@ function fromD3Graph(d3) {
|
|
|
64
81
|
if (!Array.isArray(d3.nodes)) throw new Error("D3: \"nodes\" must be an array");
|
|
65
82
|
if (!Array.isArray(d3.links)) throw new Error("D3: \"links\" must be an array");
|
|
66
83
|
return {
|
|
67
|
-
id: "",
|
|
68
|
-
type: "directed",
|
|
69
|
-
initialNodeId: null,
|
|
70
|
-
data:
|
|
84
|
+
id: d3.id ?? "",
|
|
85
|
+
type: d3.type === "undirected" ? "undirected" : "directed",
|
|
86
|
+
initialNodeId: d3.initialNodeId ?? null,
|
|
87
|
+
data: d3.data,
|
|
88
|
+
...d3.direction && { direction: d3.direction },
|
|
89
|
+
...d3.style !== void 0 && { style: d3.style },
|
|
71
90
|
nodes: d3.nodes.map((n) => ({
|
|
72
91
|
type: "node",
|
|
73
92
|
id: n.id,
|
|
74
|
-
parentId: null,
|
|
75
|
-
initialNodeId: null,
|
|
93
|
+
parentId: n.parentId ?? null,
|
|
94
|
+
initialNodeId: n.initialNodeId ?? null,
|
|
76
95
|
label: n.label ?? "",
|
|
77
96
|
data: n.data,
|
|
78
97
|
...n.x !== void 0 && { x: n.x },
|
|
79
98
|
...n.y !== void 0 && { y: n.y },
|
|
99
|
+
...n.width !== void 0 && { width: n.width },
|
|
100
|
+
...n.height !== void 0 && { height: n.height },
|
|
80
101
|
...n.color && { color: n.color },
|
|
81
102
|
...n.shape && { shape: n.shape },
|
|
103
|
+
...n.style !== void 0 && { style: n.style },
|
|
82
104
|
...n.ports !== void 0 && { ports: n.ports }
|
|
83
105
|
})),
|
|
84
106
|
edges: d3.links.map((l, i) => ({
|
|
@@ -88,7 +110,13 @@ function fromD3Graph(d3) {
|
|
|
88
110
|
targetId: typeof l.target === "string" ? l.target : l.target.id,
|
|
89
111
|
label: l.label ?? "",
|
|
90
112
|
data: l.data,
|
|
113
|
+
...l.weight !== void 0 && { weight: l.weight },
|
|
114
|
+
...l.x !== void 0 && { x: l.x },
|
|
115
|
+
...l.y !== void 0 && { y: l.y },
|
|
116
|
+
...l.width !== void 0 && { width: l.width },
|
|
117
|
+
...l.height !== void 0 && { height: l.height },
|
|
91
118
|
...l.color && { color: l.color },
|
|
119
|
+
...l.style !== void 0 && { style: l.style },
|
|
92
120
|
...l.sourcePort !== void 0 && { sourcePort: l.sourcePort },
|
|
93
121
|
...l.targetPort !== void 0 && { targetPort: l.targetPort }
|
|
94
122
|
}))
|