@graphty/graph-io 0.3.40 → 0.3.42

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.
Files changed (210) hide show
  1. package/README.md +2 -0
  2. package/dist/chunks/{children-CL3Cy0ez.js → children-CA4coBi0.js} +36 -27
  3. package/dist/chunks/children-CA4coBi0.js.map +1 -0
  4. package/dist/chunks/{choose-CS3fvGT1.js → choose-DvpbdRuz.js} +2 -2
  5. package/dist/chunks/{choose-CS3fvGT1.js.map → choose-DvpbdRuz.js.map} +1 -1
  6. package/dist/chunks/{constants-DqKZT6cH.js → constants-fT3E8JYY.js} +2 -2
  7. package/dist/chunks/{constants-DqKZT6cH.js.map → constants-fT3E8JYY.js.map} +1 -1
  8. package/dist/chunks/{escape-BJRLd3vJ.js → escape-B62ZGM_a.js} +2 -2
  9. package/dist/chunks/{escape-BJRLd3vJ.js.map → escape-B62ZGM_a.js.map} +1 -1
  10. package/dist/chunks/{export-B-aVCwyG.js → export-rKLeGQMo.js} +404 -243
  11. package/dist/chunks/export-rKLeGQMo.js.map +1 -0
  12. package/dist/chunks/{exporter-6ocL_9x_.js → exporter-8hLNemFk.js} +4 -4
  13. package/dist/chunks/{exporter-6ocL_9x_.js.map → exporter-8hLNemFk.js.map} +1 -1
  14. package/dist/chunks/{exporter-BVXnV9-9.js → exporter-BWwEuOPT.js} +29 -17
  15. package/dist/chunks/exporter-BWwEuOPT.js.map +1 -0
  16. package/dist/chunks/{exporter-Bcp-l4Bo.js → exporter-CpRyHZ3H.js} +574 -475
  17. package/dist/chunks/exporter-CpRyHZ3H.js.map +1 -0
  18. package/dist/chunks/{exporter-SFYkDzD0.js → exporter-yLxKBvLE.js} +23 -20
  19. package/dist/chunks/{exporter-SFYkDzD0.js.map → exporter-yLxKBvLE.js.map} +1 -1
  20. package/dist/chunks/{importer-DcKB97kW.js → importer-BbNb7eNg.js} +111 -71
  21. package/dist/chunks/importer-BbNb7eNg.js.map +1 -0
  22. package/dist/chunks/{importer-D_srHyj6.js → importer-C98dpSH4.js} +5 -5
  23. package/dist/chunks/{importer-D_srHyj6.js.map → importer-C98dpSH4.js.map} +1 -1
  24. package/dist/chunks/{importer-BJlGAOht.js → importer-D794XUTe.js} +3 -3
  25. package/dist/chunks/{importer-BJlGAOht.js.map → importer-D794XUTe.js.map} +1 -1
  26. package/dist/chunks/{importer-AjwOJrn2.js → importer-Dz7tI8Sa.js} +202 -181
  27. package/dist/chunks/importer-Dz7tI8Sa.js.map +1 -0
  28. package/dist/chunks/{importer-DEp_fYjf.js → importer-Ht7LNg8L.js} +92 -64
  29. package/dist/chunks/importer-Ht7LNg8L.js.map +1 -0
  30. package/dist/chunks/{importer-CPqnIY8m.js → importer-WrGRZb0N.js} +42 -29
  31. package/dist/chunks/importer-WrGRZb0N.js.map +1 -0
  32. package/dist/chunks/{importer-BpuS3J8C.js → importer-xZ-3suY1.js} +74 -61
  33. package/dist/chunks/importer-xZ-3suY1.js.map +1 -0
  34. package/dist/chunks/{json-elements-DpO6AXIc.js → json-elements-DKSqpNW1.js} +3 -3
  35. package/dist/chunks/{json-elements-DpO6AXIc.js.map → json-elements-DKSqpNW1.js.map} +1 -1
  36. package/dist/chunks/{ontology-export-50L9DOV4.js → ontology-export-CtRXjg_i.js} +2 -2
  37. package/dist/chunks/{ontology-export-50L9DOV4.js.map → ontology-export-CtRXjg_i.js.map} +1 -1
  38. package/dist/chunks/{records-r3SFHz11.js → records-Cu4bxYOm.js} +69 -36
  39. package/dist/chunks/records-Cu4bxYOm.js.map +1 -0
  40. package/dist/chunks/{text-Bjfa1xYL.js → text-DHOJXnED.js} +20 -11
  41. package/dist/chunks/text-DHOJXnED.js.map +1 -0
  42. package/dist/csv.js +323 -241
  43. package/dist/csv.js.map +1 -1
  44. package/dist/cx.js +1 -1
  45. package/dist/cx2.js +2 -2
  46. package/dist/cys.js +1 -1
  47. package/dist/dot.js +1 -1
  48. package/dist/gexf.js +195 -97
  49. package/dist/gexf.js.map +1 -1
  50. package/dist/gml.js +170 -149
  51. package/dist/gml.js.map +1 -1
  52. package/dist/graph-io.js +27 -27
  53. package/dist/graphml.js +2 -2
  54. package/dist/json.js +1 -1
  55. package/dist/neo4j.js +103 -65
  56. package/dist/neo4j.js.map +1 -1
  57. package/dist/obo.js +1 -1
  58. package/dist/pajek.js +1 -1
  59. package/dist/src/children.d.ts.map +1 -1
  60. package/dist/src/children.js +50 -27
  61. package/dist/src/children.js.map +1 -1
  62. package/dist/src/common/attributes.d.ts.map +1 -1
  63. package/dist/src/common/attributes.js +86 -42
  64. package/dist/src/common/attributes.js.map +1 -1
  65. package/dist/src/common/input.d.ts.map +1 -1
  66. package/dist/src/common/input.js +358 -184
  67. package/dist/src/common/input.js.map +1 -1
  68. package/dist/src/common/lists.js +30 -15
  69. package/dist/src/common/lists.js.map +1 -1
  70. package/dist/src/common/text.d.ts +7 -0
  71. package/dist/src/common/text.d.ts.map +1 -1
  72. package/dist/src/common/text.js +15 -7
  73. package/dist/src/common/text.js.map +1 -1
  74. package/dist/src/common/zip.d.ts.map +1 -1
  75. package/dist/src/common/zip.js +36 -19
  76. package/dist/src/common/zip.js.map +1 -1
  77. package/dist/src/formats/csv/exporter.d.ts.map +1 -1
  78. package/dist/src/formats/csv/exporter.js +288 -146
  79. package/dist/src/formats/csv/exporter.js.map +1 -1
  80. package/dist/src/formats/csv/importer.d.ts.map +1 -1
  81. package/dist/src/formats/csv/importer.js +129 -80
  82. package/dist/src/formats/csv/importer.js.map +1 -1
  83. package/dist/src/formats/csv/records.d.ts.map +1 -1
  84. package/dist/src/formats/csv/records.js +78 -36
  85. package/dist/src/formats/csv/records.js.map +1 -1
  86. package/dist/src/formats/cys/tables.d.ts.map +1 -1
  87. package/dist/src/formats/cys/tables.js +11 -8
  88. package/dist/src/formats/cys/tables.js.map +1 -1
  89. package/dist/src/formats/dot/tokenizer.d.ts +39 -0
  90. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
  91. package/dist/src/formats/dot/tokenizer.js +112 -66
  92. package/dist/src/formats/dot/tokenizer.js.map +1 -1
  93. package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
  94. package/dist/src/formats/gexf/exporter.js +65 -37
  95. package/dist/src/formats/gexf/exporter.js.map +1 -1
  96. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  97. package/dist/src/formats/gexf/importer.js +138 -50
  98. package/dist/src/formats/gexf/importer.js.map +1 -1
  99. package/dist/src/formats/gml/exporter.d.ts.map +1 -1
  100. package/dist/src/formats/gml/exporter.js +51 -35
  101. package/dist/src/formats/gml/exporter.js.map +1 -1
  102. package/dist/src/formats/gml/importer.d.ts.map +1 -1
  103. package/dist/src/formats/gml/importer.js +103 -58
  104. package/dist/src/formats/gml/importer.js.map +1 -1
  105. package/dist/src/formats/gml/syntax.d.ts.map +1 -1
  106. package/dist/src/formats/gml/syntax.js +17 -8
  107. package/dist/src/formats/gml/syntax.js.map +1 -1
  108. package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
  109. package/dist/src/formats/graphml/exporter.js +34 -24
  110. package/dist/src/formats/graphml/exporter.js.map +1 -1
  111. package/dist/src/formats/graphml/importer.d.ts.map +1 -1
  112. package/dist/src/formats/graphml/importer.js +17 -13
  113. package/dist/src/formats/graphml/importer.js.map +1 -1
  114. package/dist/src/formats/graphml/tree.js +23 -14
  115. package/dist/src/formats/graphml/tree.js.map +1 -1
  116. package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
  117. package/dist/src/formats/graphml/yfiles.js +22 -10
  118. package/dist/src/formats/graphml/yfiles.js.map +1 -1
  119. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  120. package/dist/src/formats/json/dialect.js +54 -20
  121. package/dist/src/formats/json/dialect.js.map +1 -1
  122. package/dist/src/formats/json/exporter.d.ts.map +1 -1
  123. package/dist/src/formats/json/exporter.js +38 -21
  124. package/dist/src/formats/json/exporter.js.map +1 -1
  125. package/dist/src/formats/json/importer.d.ts.map +1 -1
  126. package/dist/src/formats/json/importer.js +536 -343
  127. package/dist/src/formats/json/importer.js.map +1 -1
  128. package/dist/src/formats/json/obographs.js +29 -17
  129. package/dist/src/formats/json/obographs.js.map +1 -1
  130. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
  131. package/dist/src/formats/neo4j/exporter.js +73 -43
  132. package/dist/src/formats/neo4j/exporter.js.map +1 -1
  133. package/dist/src/formats/neo4j/header.d.ts.map +1 -1
  134. package/dist/src/formats/neo4j/header.js +37 -16
  135. package/dist/src/formats/neo4j/header.js.map +1 -1
  136. package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
  137. package/dist/src/formats/neo4j/importer.js +21 -11
  138. package/dist/src/formats/neo4j/importer.js.map +1 -1
  139. package/dist/src/formats/obo/exporter.d.ts.map +1 -1
  140. package/dist/src/formats/obo/exporter.js +18 -7
  141. package/dist/src/formats/obo/exporter.js.map +1 -1
  142. package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
  143. package/dist/src/formats/pajek/exporter.js +142 -73
  144. package/dist/src/formats/pajek/exporter.js.map +1 -1
  145. package/dist/src/formats/pajek/importer.d.ts.map +1 -1
  146. package/dist/src/formats/pajek/importer.js +26 -15
  147. package/dist/src/formats/pajek/importer.js.map +1 -1
  148. package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
  149. package/dist/src/formats/pajek/syntax.js +85 -58
  150. package/dist/src/formats/pajek/syntax.js.map +1 -1
  151. package/dist/src/formats/xgmml/emit.d.ts +7 -0
  152. package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
  153. package/dist/src/formats/xgmml/emit.js +79 -40
  154. package/dist/src/formats/xgmml/emit.js.map +1 -1
  155. package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
  156. package/dist/src/formats/xgmml/exporter.js +38 -12
  157. package/dist/src/formats/xgmml/exporter.js.map +1 -1
  158. package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
  159. package/dist/src/formats/xgmml/importer.js +38 -18
  160. package/dist/src/formats/xgmml/importer.js.map +1 -1
  161. package/dist/src/sniff.js +109 -57
  162. package/dist/src/sniff.js.map +1 -1
  163. package/dist/xgmml.js +2 -2
  164. package/package.json +2 -2
  165. package/src/children.ts +54 -27
  166. package/src/common/attributes.ts +118 -49
  167. package/src/common/input.ts +439 -197
  168. package/src/common/lists.ts +29 -15
  169. package/src/common/text.ts +24 -15
  170. package/src/common/zip.ts +47 -18
  171. package/src/formats/csv/exporter.ts +377 -175
  172. package/src/formats/csv/importer.ts +155 -84
  173. package/src/formats/csv/records.ts +90 -35
  174. package/src/formats/cys/tables.ts +11 -10
  175. package/src/formats/dot/tokenizer.ts +118 -68
  176. package/src/formats/gexf/exporter.ts +67 -36
  177. package/src/formats/gexf/importer.ts +173 -63
  178. package/src/formats/gml/exporter.ts +78 -82
  179. package/src/formats/gml/importer.ts +119 -68
  180. package/src/formats/gml/syntax.ts +18 -8
  181. package/src/formats/graphml/exporter.ts +44 -24
  182. package/src/formats/graphml/importer.ts +18 -11
  183. package/src/formats/graphml/tree.ts +22 -12
  184. package/src/formats/graphml/yfiles.ts +23 -10
  185. package/src/formats/json/dialect.ts +61 -22
  186. package/src/formats/json/exporter.ts +40 -21
  187. package/src/formats/json/importer.ts +701 -385
  188. package/src/formats/json/obographs.ts +35 -15
  189. package/src/formats/neo4j/exporter.ts +79 -41
  190. package/src/formats/neo4j/header.ts +50 -16
  191. package/src/formats/neo4j/importer.ts +21 -10
  192. package/src/formats/obo/exporter.ts +34 -17
  193. package/src/formats/pajek/exporter.ts +180 -117
  194. package/src/formats/pajek/importer.ts +27 -15
  195. package/src/formats/pajek/syntax.ts +99 -58
  196. package/src/formats/xgmml/emit.ts +88 -40
  197. package/src/formats/xgmml/exporter.ts +40 -12
  198. package/src/formats/xgmml/importer.ts +45 -23
  199. package/src/sniff.ts +115 -59
  200. package/dist/chunks/children-CL3Cy0ez.js.map +0 -1
  201. package/dist/chunks/export-B-aVCwyG.js.map +0 -1
  202. package/dist/chunks/exporter-BVXnV9-9.js.map +0 -1
  203. package/dist/chunks/exporter-Bcp-l4Bo.js.map +0 -1
  204. package/dist/chunks/importer-AjwOJrn2.js.map +0 -1
  205. package/dist/chunks/importer-BpuS3J8C.js.map +0 -1
  206. package/dist/chunks/importer-CPqnIY8m.js.map +0 -1
  207. package/dist/chunks/importer-DEp_fYjf.js.map +0 -1
  208. package/dist/chunks/importer-DcKB97kW.js.map +0 -1
  209. package/dist/chunks/records-r3SFHz11.js.map +0 -1
  210. package/dist/chunks/text-Bjfa1xYL.js.map +0 -1
@@ -270,18 +270,7 @@ function writeElements(name: string, value: unknown, indent: string, out: string
270
270
  return;
271
271
  }
272
272
  const object = value as XmlTreeObject;
273
- let attrs = "";
274
- let text: string | null = null;
275
- let hasChildren = false;
276
- for (const [key, child] of Object.entries(object)) {
277
- if (key === TEXT_KEY) {
278
- text = scalarText(child);
279
- } else if (key.startsWith(ATTRIBUTE_PREFIX)) {
280
- attrs += ` ${key.slice(ATTRIBUTE_PREFIX.length)}="${escapeXmlAttribute(scalarText(child))}"`;
281
- } else {
282
- hasChildren = true;
283
- }
284
- }
273
+ const { attrs, text, hasChildren } = scanElement(object);
285
274
  if (!hasChildren) {
286
275
  out.push(
287
276
  text === null || text.length === 0
@@ -303,6 +292,27 @@ function writeElements(name: string, value: unknown, indent: string, out: string
303
292
  out.push(`\n${indent}</${name}>`);
304
293
  }
305
294
 
295
+ /**
296
+ * Split an element object into its attribute text, its text content and whether it has children.
297
+ * @param object - the element object
298
+ * @returns the attribute text, the text content (null when absent) and the children flag
299
+ */
300
+ function scanElement(object: XmlTreeObject): { attrs: string; text: string | null; hasChildren: boolean } {
301
+ let attrs = "";
302
+ let text: string | null = null;
303
+ let hasChildren = false;
304
+ for (const [key, child] of Object.entries(object)) {
305
+ if (key === TEXT_KEY) {
306
+ text = scalarText(child);
307
+ } else if (key.startsWith(ATTRIBUTE_PREFIX)) {
308
+ attrs += ` ${key.slice(ATTRIBUTE_PREFIX.length)}="${escapeXmlAttribute(scalarText(child))}"`;
309
+ } else {
310
+ hasChildren = true;
311
+ }
312
+ }
313
+ return { attrs, text, hasChildren };
314
+ }
315
+
306
316
  /**
307
317
  * The text of a scalar tree value.
308
318
  * @param value - a string, number, boolean or null
@@ -109,16 +109,7 @@ export function staleGraphicsRows(column: Column, table: Iterable<Column>, domai
109
109
  }
110
110
  let stale = 0;
111
111
  for (let r = 0; r < column.length; r++) {
112
- const wants = new Set<string>();
113
- for (const tree of trees) {
114
- const want = graphicsValues(domain, tree.values[r]).find(([f]) => f === field);
115
- if (want !== undefined) {
116
- wants.add(JSON.stringify(want[1]));
117
- }
118
- }
119
- if (wants.size === 0) {
120
- wants.add(JSON.stringify(undefined));
121
- }
112
+ const wants = wantedGraphics(trees, domain, field, r);
122
113
  const value = column.value(r);
123
114
  const have = column.meta.components > 1 && value !== undefined ? Array.from(value as ArrayLike<number>) : value;
124
115
  if (!wants.has(JSON.stringify(have))) {
@@ -128,6 +119,28 @@ export function staleGraphicsRows(column: Column, table: Iterable<Column>, domai
128
119
  return stale;
129
120
  }
130
121
 
122
+ /**
123
+ * The serialized values the trees want for one row of a mapped column.
124
+ * @param trees - the yFiles tree columns of the table
125
+ * @param domain - node or edge
126
+ * @param field - the mapped field name
127
+ * @param r - the row
128
+ * @returns the wanted values as JSON text; just `undefined`'s when no tree wants one
129
+ */
130
+ function wantedGraphics(trees: JsonColumn[], domain: "node" | "edge", field: string, r: number): Set<string> {
131
+ const wants = new Set<string>();
132
+ for (const tree of trees) {
133
+ const want = graphicsValues(domain, tree.values[r]).find(([f]) => f === field);
134
+ if (want !== undefined) {
135
+ wants.add(JSON.stringify(want[1]));
136
+ }
137
+ }
138
+ if (wants.size === 0) {
139
+ wants.add(JSON.stringify(undefined));
140
+ }
141
+ return wants;
142
+ }
143
+
131
144
  /**
132
145
  * The mapped values of one `<data>` tree of a yFiles key: a ShapeNode's for a node, a
133
146
  * PolyLineEdge's for an edge; nothing for any other graphics (GenericNode, BezierEdge, ...).
@@ -170,6 +170,17 @@ export function sniffJsonDialect(root: unknown): JsonImportDialect | null {
170
170
  if (!isJsonObject(root)) {
171
171
  return null;
172
172
  }
173
+ const decided = sniffWrapperDialect(root) ?? sniffEdgelessDialect(root);
174
+ return decided === undefined ? sniffEdgeListDialect(root) : decided;
175
+ }
176
+
177
+ /**
178
+ * The dialects a top-level wrapper key settles: Cytoscape `elements`, JGF `graph.nodes` /
179
+ * `graph.edges`, and a `graphs[]` array (OBO Graphs when any graph looks like one, else JGF).
180
+ * @param root - the parsed document object
181
+ * @returns the dialect, or undefined when no wrapper key decides it
182
+ */
183
+ function sniffWrapperDialect(root: Record<string, unknown>): JsonImportDialect | undefined {
173
184
  if (hasKey(root, "elements")) {
174
185
  return "cytoscape";
175
186
  }
@@ -180,14 +191,31 @@ export function sniffJsonDialect(root: unknown): JsonImportDialect | null {
180
191
  // any graph decides: an ontology's first graph may be an empty stub before its imports
181
192
  return root.graphs.some((g: unknown) => isJsonObject(g) && isOboGraph(g)) ? "obographs" : "jgf";
182
193
  }
183
- if (!hasKey(root, "edges") && !hasKey(root, "links")) {
184
- if (hasKey(root, "nodes") && hasKey(root, "adjacency")) {
185
- return "adjacency";
186
- }
187
- if (!hasKey(root, "nodes")) {
188
- return hasKey(root, "children") ? "tree" : null;
189
- }
194
+ return undefined;
195
+ }
196
+
197
+ /**
198
+ * The dialect of a document with neither `edges` nor `links`: NetworkX adjacency_data (`nodes`
199
+ * and `adjacency`), tree_data (`children` without `nodes`), or no graph at all.
200
+ * @param root - the parsed document object
201
+ * @returns the dialect; null for no graph; undefined when the edge-list rules decide
202
+ */
203
+ function sniffEdgelessDialect(root: Record<string, unknown>): JsonImportDialect | null | undefined {
204
+ if (hasKey(root, "edges") || hasKey(root, "links")) {
205
+ return undefined;
206
+ }
207
+ if (hasKey(root, "nodes")) {
208
+ return hasKey(root, "adjacency") ? "adjacency" : undefined;
190
209
  }
210
+ return hasKey(root, "children") ? "tree" : null;
211
+ }
212
+
213
+ /**
214
+ * The dialect of a node and edge list document: graphology, vis, d3 or NetworkX node-link.
215
+ * @param root - the parsed document object
216
+ * @returns the dialect
217
+ */
218
+ function sniffEdgeListDialect(root: Record<string, unknown>): JsonImportDialect {
191
219
  const firstNode = firstJsonObject(root.nodes);
192
220
  const firstEdge = firstJsonObject(hasKey(root, "edges") ? root.edges : root.links);
193
221
  if (isJsonObject(root.options) && (hasKey(root.options, "type") || hasKey(root.options, "multi"))) {
@@ -197,25 +225,36 @@ export function sniffJsonDialect(root: unknown): JsonImportDialect | null {
197
225
  // like a graphology or vis field (`attributes`, `undirected`, `key`, `from`) must not re-route it
198
226
  const networkx =
199
227
  hasKey(root, "links") || hasKey(root, "directed") || hasKey(root, "multigraph") || hasKey(root, "graph");
200
- if (!networkx && firstNode !== null && hasKey(firstNode, "key") && !hasKey(firstNode, "id")) {
228
+ const record = networkx ? undefined : sniffRecordDialect(firstNode, firstEdge);
229
+ if (record !== undefined) {
230
+ return record;
231
+ }
232
+ const bare = !hasKey(root, "directed") && !hasKey(root, "multigraph") && !hasKey(root, "graph");
233
+ return bare && hasKey(root, "links") ? "d3" : "node-link";
234
+ }
235
+
236
+ /**
237
+ * The graphology or vis dialect, read from the first node and edge records of a document without
238
+ * NetworkX's keys: graphology for a `key` node without `id`, or an edge without `from` that has
239
+ * `undirected` or an `attributes` record; vis for an edge with `from` and `to` but no `source`.
240
+ * @param firstNode - the first node record, or null
241
+ * @param firstEdge - the first edge record, or null
242
+ * @returns the dialect, or undefined when the records name neither
243
+ */
244
+ function sniffRecordDialect(
245
+ firstNode: Record<string, unknown> | null,
246
+ firstEdge: Record<string, unknown> | null,
247
+ ): JsonImportDialect | undefined {
248
+ if (firstNode !== null && hasKey(firstNode, "key") && !hasKey(firstNode, "id")) {
201
249
  return "graphology";
202
250
  }
203
- if (!networkx && firstEdge !== null && !hasKey(firstEdge, "from")) {
204
- if (hasKey(firstEdge, "undirected") || isJsonObject(firstEdge.attributes)) {
205
- return "graphology";
206
- }
251
+ if (firstEdge === null) {
252
+ return undefined;
207
253
  }
208
- if (
209
- !networkx &&
210
- firstEdge !== null &&
211
- hasKey(firstEdge, "from") &&
212
- hasKey(firstEdge, "to") &&
213
- !hasKey(firstEdge, "source")
214
- ) {
215
- return "vis";
254
+ if (!hasKey(firstEdge, "from")) {
255
+ return hasKey(firstEdge, "undirected") || isJsonObject(firstEdge.attributes) ? "graphology" : undefined;
216
256
  }
217
- const bare = !hasKey(root, "directed") && !hasKey(root, "multigraph") && !hasKey(root, "graph");
218
- return bare && hasKey(root, "links") ? "d3" : "node-link";
257
+ return hasKey(firstEdge, "to") && !hasKey(firstEdge, "source") ? "vis" : undefined;
219
258
  }
220
259
 
221
260
  /** The node types of OBO Graphs (the OWL entity kinds). */
@@ -1609,7 +1609,6 @@ function* writeCytoscape(snapshot: GraphSnapshot, p: Plan): Generator<string, vo
1609
1609
  const c: Cursor = { w: new JsonWriter(resolved.indent), ids: snapshot.ids, nonfinite: { count: 0 } };
1610
1610
  const { w } = c;
1611
1611
  const parentSlots: ReadonlySet<Slot> = new Set(["parent"]);
1612
- const positionSlots: ReadonlySet<Slot> = new Set(["cytoscapePosition"]);
1613
1612
  w.open("{");
1614
1613
  w.key("elements");
1615
1614
  w.open("{");
@@ -1625,18 +1624,7 @@ function* writeCytoscape(snapshot: GraphSnapshot, p: Plan): Generator<string, vo
1625
1624
  writeSlots(c, p.nodes, i, parentSlots);
1626
1625
  writeSlots(c, p.nodes, i, ATTRIBUTE_SLOTS);
1627
1626
  w.close("}");
1628
- for (const item of p.nodes) {
1629
- if (positionSlots.has(item.slot) && item.column.isSet(i)) {
1630
- const value = item.column.value(i) as ArrayLike<number>;
1631
- const f32 = item.column.dtype === "f32";
1632
- w.key("position");
1633
- w.open("{");
1634
- w.member("x", numberText(value[0], f32, c.nonfinite));
1635
- // stored y-up; Cytoscape's y grows downward
1636
- w.member("y", numberText(flipY(value[1]), f32, c.nonfinite));
1637
- w.close("}");
1638
- }
1639
- }
1627
+ writeCytoscapePosition(c, p.nodes, i);
1640
1628
  writeSlots(c, p.nodes, i, ELEMENT_SLOTS);
1641
1629
  w.close("}");
1642
1630
  yield w.take();
@@ -1673,18 +1661,49 @@ function* writeCytoscape(snapshot: GraphSnapshot, p: Plan): Generator<string, vo
1673
1661
  w.key("data");
1674
1662
  writeGraphObject(c, p);
1675
1663
  }
1676
- const extra = resolved.shape.cytoscape;
1677
- if (extra !== undefined) {
1678
- for (const key of Object.keys(extra)) {
1679
- if (key !== "elements" && key !== "data") {
1680
- w.member(key, valueText(extra[key], false, c.nonfinite));
1681
- }
1682
- }
1683
- }
1664
+ writeCytoscapeExtra(c, resolved.shape.cytoscape);
1684
1665
  w.close("}");
1685
1666
  yield w.take();
1686
1667
  }
1687
1668
 
1669
+ /**
1670
+ * Write a node's `position` member (Cytoscape.js), when its position is set.
1671
+ * @param c - the cursor
1672
+ * @param items - the node plan items
1673
+ * @param i - the node index
1674
+ */
1675
+ function writeCytoscapePosition(c: Cursor, items: readonly ColumnPlan[], i: number): void {
1676
+ const { w } = c;
1677
+ for (const item of items) {
1678
+ if (item.slot === "cytoscapePosition" && item.column.isSet(i)) {
1679
+ const value = item.column.value(i) as ArrayLike<number>;
1680
+ const f32 = item.column.dtype === "f32";
1681
+ w.key("position");
1682
+ w.open("{");
1683
+ w.member("x", numberText(value[0], f32, c.nonfinite));
1684
+ // stored y-up; Cytoscape's y grows downward
1685
+ w.member("y", numberText(flipY(value[1]), f32, c.nonfinite));
1686
+ w.close("}");
1687
+ }
1688
+ }
1689
+ }
1690
+
1691
+ /**
1692
+ * Write the extra top-level members of a Cytoscape.js document, beyond `elements` and `data`.
1693
+ * @param c - the cursor
1694
+ * @param extra - the extra members, when any
1695
+ */
1696
+ function writeCytoscapeExtra(c: Cursor, extra: Readonly<Record<string, unknown>> | undefined): void {
1697
+ if (extra === undefined) {
1698
+ return;
1699
+ }
1700
+ for (const key of Object.keys(extra)) {
1701
+ if (key !== "elements" && key !== "data") {
1702
+ c.w.member(key, valueText(extra[key], false, c.nonfinite));
1703
+ }
1704
+ }
1705
+ }
1706
+
1688
1707
  /**
1689
1708
  * A generator of canonical `e<index>` edge ids for the edges without one (design section 4.6),
1690
1709
  * skipping any text a node id or an explicit edge id already uses (Cytoscape ids share one