@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @statelyai/graph
2
2
 
3
- A TypeScript graph library built on plain JSON objects. Supports directed/undirected graphs, hierarchical nodes, graph algorithms, visual properties, and serialization to DOT, GraphML, Mermaid, and more.
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, getGraphIssues, isGraph } from '@statelyai/graph/schemas';
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(getGraphIssues(unknownValue));
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 | partial | partial | Ports round-trip through element data. |
274
- | `d3` | none | full | partial | partial | Ports round-trip through node/link objects. |
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 | partial | Best for layout exchange. |
278
- | `gexf` | full | full | partial | partial | Ports round-trip via custom attributes. |
279
- | `gml` | full | full | partial | partial | Ports round-trip through JSON-stringified metadata. |
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 | none | partial | Ports round-trip through metadata. |
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` | none | full | full | partial | Ports map directly to handles. |
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 | partial | State notes are still lossy. |
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 computational layer in between: plain JSON objects in, algorithms and mutations, plain JSON objects out.
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-CJc-ul8e.mjs";
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: undefined }
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: undefined }
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 ?? void 0
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;
@@ -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-BHHg7lGq.mjs";
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
@@ -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: "partial",
27
- style: "partial",
32
+ visual: "full",
33
+ style: "full",
28
34
  weight: "full",
29
- roundTrip: "partial"
35
+ roundTrip: "full"
30
36
  },
31
- notes: ["Uses Cytoscape JSON element data with partial layout/style fidelity.", "Ports round-trip through element data as `ports`, `sourcePort`, and `targetPort`."]
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: "none",
45
+ hierarchy: "full",
40
46
  ports: "full",
41
- visual: "partial",
42
- style: "none",
47
+ visual: "full",
48
+ style: "full",
43
49
  weight: "full",
44
- roundTrip: "partial"
50
+ roundTrip: "full"
45
51
  },
46
- notes: ["Targets force-graph structures, not compound graph metadata.", "Ports round-trip through node/link objects."]
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: "partial",
88
- weight: "none",
89
- roundTrip: "partial"
93
+ style: "full",
94
+ weight: "full",
95
+ roundTrip: "full"
90
96
  },
91
- notes: ["Optimized for layout exchange with ELK rather than exact styling parity."]
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: "partial",
102
- style: "partial",
107
+ visual: "full",
108
+ style: "full",
103
109
  weight: "full",
104
- roundTrip: "partial"
110
+ roundTrip: "full"
105
111
  },
106
- notes: ["Attribute and viz extensions round-trip partially.", "Ports round-trip via custom node/edge attributes."]
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: "partial",
117
- style: "partial",
122
+ visual: "full",
123
+ style: "full",
118
124
  weight: "full",
119
- roundTrip: "partial"
125
+ roundTrip: "full"
120
126
  },
121
- notes: ["GML support focuses on common graph/node/edge attributes.", "Ports round-trip through JSON-stringified node metadata and edge fields."]
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: "none",
147
- style: "none",
152
+ visual: "full",
153
+ style: "full",
148
154
  weight: "full",
149
- roundTrip: "partial"
155
+ roundTrip: "full"
150
156
  },
151
- notes: ["JGF preserves core graph structure and data, but not layout primitives.", "Ports round-trip through node and edge metadata."]
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: "none",
180
+ hierarchy: "full",
175
181
  ports: "full",
176
182
  visual: "full",
177
- style: "partial",
178
- weight: "none",
179
- roundTrip: "partial"
183
+ style: "full",
184
+ weight: "full",
185
+ roundTrip: "full"
180
186
  },
181
- notes: ["Ports map cleanly to handles, but styling remains adapter-specific."]
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: "partial"
305
+ roundTrip: "full"
300
306
  },
301
- notes: ["State notes are stored, but not fully round-trippable as separate graph entities."]
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: void 0,
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
  }))