@graphty/graph-io 0.3.39 → 0.3.41
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/dist/chunks/{choose-CS3fvGT1.js → choose-CwFQamH9.js} +2 -2
- package/dist/chunks/{choose-CS3fvGT1.js.map → choose-CwFQamH9.js.map} +1 -1
- package/dist/chunks/{constants-DqKZT6cH.js → constants-Cf4Nn6tw.js} +2 -2
- package/dist/chunks/{constants-DqKZT6cH.js.map → constants-Cf4Nn6tw.js.map} +1 -1
- package/dist/chunks/{escape-BpYMQZVa.js → escape-DBb8zuNE.js} +4 -3
- package/dist/chunks/escape-DBb8zuNE.js.map +1 -0
- package/dist/chunks/{export-B-aVCwyG.js → export-DD3Lvw-F.js} +173 -116
- package/dist/chunks/export-DD3Lvw-F.js.map +1 -0
- package/dist/chunks/{exporter-CNJZF_BN.js → exporter-BqtmlxNr.js} +545 -459
- package/dist/chunks/exporter-BqtmlxNr.js.map +1 -0
- package/dist/chunks/{exporter-BqKs1Q12.js → exporter-CS35R6Fg.js} +6 -5
- package/dist/chunks/exporter-CS35R6Fg.js.map +1 -0
- package/dist/chunks/{exporter-DXqCPNrI.js → exporter-D8r1N2dU.js} +67 -42
- package/dist/chunks/exporter-D8r1N2dU.js.map +1 -0
- package/dist/chunks/{exporter-SFYkDzD0.js → exporter-NMplSnYn.js} +23 -20
- package/dist/chunks/{exporter-SFYkDzD0.js.map → exporter-NMplSnYn.js.map} +1 -1
- package/dist/chunks/{importer-CSt-gaYE.js → importer-BKC7AmCA.js} +42 -29
- package/dist/chunks/importer-BKC7AmCA.js.map +1 -0
- package/dist/chunks/{importer-DvFznt5-.js → importer-BdWHmlmG.js} +112 -62
- package/dist/chunks/importer-BdWHmlmG.js.map +1 -0
- package/dist/chunks/{importer-CqgYq4Ld.js → importer-BfbzWk6k.js} +202 -181
- package/dist/chunks/importer-BfbzWk6k.js.map +1 -0
- package/dist/chunks/{importer-PVXkeoaS.js → importer-BmaZcskq.js} +79 -66
- package/dist/chunks/importer-BmaZcskq.js.map +1 -0
- package/dist/chunks/{importer-B7Ir269L.js → importer-DviEsHy4.js} +118 -72
- package/dist/chunks/importer-DviEsHy4.js.map +1 -0
- package/dist/chunks/{importer--tQQsY4W.js → importer-S-abxJHn.js} +5 -5
- package/dist/chunks/{importer--tQQsY4W.js.map → importer-S-abxJHn.js.map} +1 -1
- package/dist/chunks/{importer-BfJ1jnNr.js → importer-r29cWnEf.js} +28 -13
- package/dist/chunks/importer-r29cWnEf.js.map +1 -0
- package/dist/chunks/{json-elements-CXs4z1iL.js → json-elements-DdF-etPT.js} +7 -5
- package/dist/chunks/{json-elements-CXs4z1iL.js.map → json-elements-DdF-etPT.js.map} +1 -1
- package/dist/chunks/{ontology-export-50L9DOV4.js → ontology-export-BC0zyPhj.js} +2 -2
- package/dist/chunks/{ontology-export-50L9DOV4.js.map → ontology-export-BC0zyPhj.js.map} +1 -1
- package/dist/chunks/{records-C9jd7yLD.js → records-Uc3LkABx.js} +150 -106
- package/dist/chunks/records-Uc3LkABx.js.map +1 -0
- package/dist/chunks/{text-Bjfa1xYL.js → text-Bh9SaAob.js} +20 -11
- package/dist/chunks/text-Bh9SaAob.js.map +1 -0
- package/dist/csv.js +67 -49
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +1 -1
- package/dist/cx2.js +2 -2
- package/dist/cys.js +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +199 -99
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +170 -149
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +26 -26
- package/dist/graphml.js +2 -2
- package/dist/json.js +1 -1
- package/dist/neo4j.js +103 -65
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +34 -9
- package/dist/src/common/attributes.js.map +1 -1
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +180 -102
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/json-elements.d.ts.map +1 -1
- package/dist/src/common/json-elements.js +4 -2
- package/dist/src/common/json-elements.js.map +1 -1
- package/dist/src/common/text.d.ts +7 -0
- package/dist/src/common/text.d.ts.map +1 -1
- package/dist/src/common/text.js +15 -7
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +2 -1
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/common/zip.d.ts.map +1 -1
- package/dist/src/common/zip.js +36 -19
- package/dist/src/common/zip.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +77 -25
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +16 -6
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +156 -110
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +108 -53
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
- package/dist/src/formats/cx2/exporter.js +2 -1
- package/dist/src/formats/cx2/exporter.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +28 -6
- package/dist/src/formats/cx2/importer.js.map +1 -1
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +11 -8
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +69 -39
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +138 -50
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +51 -35
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +103 -58
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +17 -8
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +36 -25
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +20 -15
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/tree.js +23 -14
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +22 -10
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +38 -21
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +536 -343
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/obographs.js +29 -17
- package/dist/src/formats/json/obographs.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +73 -43
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/header.d.ts.map +1 -1
- package/dist/src/formats/neo4j/header.js +37 -16
- package/dist/src/formats/neo4j/header.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +21 -11
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/obo/exporter.d.ts.map +1 -1
- package/dist/src/formats/obo/exporter.js +18 -7
- package/dist/src/formats/obo/exporter.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +142 -73
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +26 -15
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +85 -58
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +13 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +112 -50
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +92 -34
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +38 -18
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/sniff.js +109 -57
- package/dist/src/sniff.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/package.json +2 -2
- package/src/common/attributes.ts +59 -19
- package/src/common/input.ts +216 -102
- package/src/common/json-elements.ts +4 -2
- package/src/common/text.ts +24 -15
- package/src/common/xml.ts +2 -1
- package/src/common/zip.ts +47 -18
- package/src/formats/csv/exporter.ts +103 -39
- package/src/formats/csv/importer.ts +17 -6
- package/src/formats/csv/records.ts +162 -109
- package/src/formats/cx/importer.ts +140 -58
- package/src/formats/cx2/exporter.ts +2 -1
- package/src/formats/cx2/importer.ts +34 -10
- package/src/formats/cys/tables.ts +11 -10
- package/src/formats/gexf/exporter.ts +71 -38
- package/src/formats/gexf/importer.ts +173 -63
- package/src/formats/gml/exporter.ts +78 -82
- package/src/formats/gml/importer.ts +119 -68
- package/src/formats/gml/syntax.ts +18 -8
- package/src/formats/graphml/exporter.ts +46 -27
- package/src/formats/graphml/importer.ts +21 -13
- package/src/formats/graphml/tree.ts +22 -12
- package/src/formats/graphml/yfiles.ts +23 -10
- package/src/formats/json/exporter.ts +40 -21
- package/src/formats/json/importer.ts +701 -385
- package/src/formats/json/obographs.ts +35 -15
- package/src/formats/neo4j/exporter.ts +79 -41
- package/src/formats/neo4j/header.ts +50 -16
- package/src/formats/neo4j/importer.ts +21 -10
- package/src/formats/obo/exporter.ts +34 -17
- package/src/formats/pajek/exporter.ts +180 -117
- package/src/formats/pajek/importer.ts +27 -15
- package/src/formats/pajek/syntax.ts +99 -58
- package/src/formats/xgmml/emit.ts +122 -50
- package/src/formats/xgmml/exporter.ts +103 -35
- package/src/formats/xgmml/importer.ts +45 -23
- package/src/sniff.ts +115 -59
- package/dist/chunks/escape-BpYMQZVa.js.map +0 -1
- package/dist/chunks/export-B-aVCwyG.js.map +0 -1
- package/dist/chunks/exporter-BqKs1Q12.js.map +0 -1
- package/dist/chunks/exporter-CNJZF_BN.js.map +0 -1
- package/dist/chunks/exporter-DXqCPNrI.js.map +0 -1
- package/dist/chunks/importer-B7Ir269L.js.map +0 -1
- package/dist/chunks/importer-BfJ1jnNr.js.map +0 -1
- package/dist/chunks/importer-CSt-gaYE.js.map +0 -1
- package/dist/chunks/importer-CqgYq4Ld.js.map +0 -1
- package/dist/chunks/importer-DvFznt5-.js.map +0 -1
- package/dist/chunks/importer-PVXkeoaS.js.map +0 -1
- package/dist/chunks/records-C9jd7yLD.js.map +0 -1
- package/dist/chunks/text-Bjfa1xYL.js.map +0 -1
|
@@ -518,31 +518,50 @@ class GmlImport {
|
|
|
518
518
|
if (key === "graph" && !record && scalarGraph < 0) {
|
|
519
519
|
scalarGraph = v;
|
|
520
520
|
}
|
|
521
|
-
|
|
522
|
-
if (this.graphCount++ === this.which) {
|
|
523
|
-
this.graphOpen = v;
|
|
524
|
-
this.scanGraph(v);
|
|
525
|
-
}
|
|
526
|
-
} else if (key === "Creator" && !record && this.creatorToken < 0) {
|
|
527
|
-
this.creatorToken = v;
|
|
528
|
-
} else if (key === "Version" && !record && this.versionToken < 0) {
|
|
529
|
-
this.versionToken = v;
|
|
530
|
-
} else {
|
|
531
|
-
this.observe(this.topSchema, "graph", key, v, TOP_SEQ);
|
|
532
|
-
}
|
|
521
|
+
this.scanTopPair(key, record, v);
|
|
533
522
|
}
|
|
534
523
|
if (this.graphOpen < 0) {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
524
|
+
this.failNoGraph(t, scalarGraph);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Handle one top-level pair other than a scalar `graph`.
|
|
530
|
+
* @param key - the key
|
|
531
|
+
* @param record - whether the value is a `[ ... ]` block
|
|
532
|
+
* @param v - the value token
|
|
533
|
+
*/
|
|
534
|
+
private scanTopPair(key: string, record: boolean, v: number): void {
|
|
535
|
+
if (key === "graph" && record) {
|
|
536
|
+
if (this.graphCount++ === this.which) {
|
|
537
|
+
this.graphOpen = v;
|
|
538
|
+
this.scanGraph(v);
|
|
541
539
|
}
|
|
542
|
-
|
|
540
|
+
} else if (key === "Creator" && !record && this.creatorToken < 0) {
|
|
541
|
+
this.creatorToken = v;
|
|
542
|
+
} else if (key === "Version" && !record && this.versionToken < 0) {
|
|
543
|
+
this.versionToken = v;
|
|
544
|
+
} else {
|
|
545
|
+
this.observe(this.topSchema, "graph", key, v, TOP_SEQ);
|
|
543
546
|
}
|
|
544
547
|
}
|
|
545
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Fail because the input holds no graph block.
|
|
551
|
+
* @param t - the tokens
|
|
552
|
+
* @param scalarGraph - the token of a scalar `graph` key, or -1
|
|
553
|
+
*/
|
|
554
|
+
private failNoGraph(t: ReturnType<GmlImport["requireTokens"]>, scalarGraph: number): void {
|
|
555
|
+
if (scalarGraph >= 0) {
|
|
556
|
+
this.report.fail(
|
|
557
|
+
NO_GRAPH_CODE,
|
|
558
|
+
`the graph key at line ${t.line[scalarGraph]} holds ${describeValue(t, scalarGraph)}, not a [ ... ] block; the input contains no graph block`,
|
|
559
|
+
{ line: t.line[scalarGraph] },
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
this.report.fail(NO_GRAPH_CODE, "the input contains no graph [ ... ] block", { line: null });
|
|
563
|
+
}
|
|
564
|
+
|
|
546
565
|
/**
|
|
547
566
|
* Walk the graph block for the schema.
|
|
548
567
|
* @param open - the index of the block's `[`
|
|
@@ -592,31 +611,43 @@ class GmlImport {
|
|
|
592
611
|
const close = t.match[open];
|
|
593
612
|
const seq = this.seq++;
|
|
594
613
|
const schema = domain === "node" ? this.nodeSchema : this.edgeSchema;
|
|
595
|
-
const { weightFrom, restoreMangledIds } = this.options;
|
|
596
614
|
for (let p = open + 1; p < close; p = t.nextPair(p)) {
|
|
597
615
|
const key = t.textOf(p);
|
|
598
616
|
const v = p + 1;
|
|
599
|
-
if (domain
|
|
600
|
-
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
if (key === ORIGINAL_ID_KEY && restoreMangledIds) {
|
|
604
|
-
this.hasOriginalId = true;
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
} else {
|
|
608
|
-
if (key === "source" || key === "target" || key === "directed") {
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
if (key === weightFrom) {
|
|
612
|
-
this.weightKinds |= kindBit(t, v);
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
617
|
+
if (this.skipsKey(domain, key, v)) {
|
|
618
|
+
continue;
|
|
615
619
|
}
|
|
616
620
|
this.observe(schema, domain, key, v, seq);
|
|
617
621
|
}
|
|
618
622
|
}
|
|
619
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Whether a key of a node or edge block is structural (id, endpoints, weight, original id), not
|
|
626
|
+
* a column; notes the original id and the weight's kinds on the way.
|
|
627
|
+
* @param domain - node or edge
|
|
628
|
+
* @param key - the key
|
|
629
|
+
* @param v - the value token
|
|
630
|
+
* @returns true when the key is not a column
|
|
631
|
+
*/
|
|
632
|
+
private skipsKey(domain: "node" | "edge", key: string, v: number): boolean {
|
|
633
|
+
const { weightFrom, restoreMangledIds } = this.options;
|
|
634
|
+
if (domain === "node") {
|
|
635
|
+
if (key === ORIGINAL_ID_KEY && restoreMangledIds) {
|
|
636
|
+
this.hasOriginalId = true;
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
return key === "id";
|
|
640
|
+
}
|
|
641
|
+
if (key === "source" || key === "target" || key === "directed") {
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
if (key === weightFrom) {
|
|
645
|
+
this.weightKinds |= kindBit(this.requireTokens(), v);
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
|
|
620
651
|
/**
|
|
621
652
|
* Record one value of one key in a table's schema.
|
|
622
653
|
* @param schema - the table's schema
|
|
@@ -816,26 +847,12 @@ class GmlImport {
|
|
|
816
847
|
}
|
|
817
848
|
|
|
818
849
|
/**
|
|
819
|
-
*
|
|
850
|
+
* Warn about a key that mixes numbers and strings, or holds integers beyond 2^53.
|
|
820
851
|
* @param domain - the table
|
|
821
852
|
* @param entry - the schema entry
|
|
822
|
-
* @param
|
|
823
|
-
* @returns the plan
|
|
853
|
+
* @param asText - whether the integers are kept as text
|
|
824
854
|
*/
|
|
825
|
-
private
|
|
826
|
-
// long: "string" keeps every digit of an integer key that holds values beyond 2^53, as text
|
|
827
|
-
const asText = entry.unsafe && this.options.long === "string" && kindOf(entry) === "int";
|
|
828
|
-
const kind = asText ? "string" : kindOf(entry);
|
|
829
|
-
let scalar: ScalarDtype = dtypeOfKind(kind, entry);
|
|
830
|
-
if (
|
|
831
|
-
scalar === "string" &&
|
|
832
|
-
!entry.list &&
|
|
833
|
-
entry.dict !== null &&
|
|
834
|
-
entry.kinds === KIND_STRING &&
|
|
835
|
-
entry.dict.decide() === "dict"
|
|
836
|
-
) {
|
|
837
|
-
scalar = "dict";
|
|
838
|
-
}
|
|
855
|
+
private warnKinds(domain: "node" | "edge" | "graph", entry: KeySchema, asText: boolean): void {
|
|
839
856
|
if (
|
|
840
857
|
(entry.kinds & KIND_STRING) !== 0 &&
|
|
841
858
|
(entry.kinds & (KIND_INT | KIND_REAL)) !== 0 &&
|
|
@@ -858,6 +875,54 @@ class GmlImport {
|
|
|
858
875
|
`${PRECISION_CODE}:${domain}:${entry.key}`,
|
|
859
876
|
);
|
|
860
877
|
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* The plan of a node `graphics` record: the plan plus its position and rest columns.
|
|
882
|
+
* @param plan - the record's plan
|
|
883
|
+
* @param entry - the schema entry
|
|
884
|
+
* @returns the plan with its position (and rest) plans
|
|
885
|
+
*/
|
|
886
|
+
private graphicsPlan(plan: ColumnPlan, entry: KeySchema): ColumnPlan {
|
|
887
|
+
const taken = (candidate: string): boolean =>
|
|
888
|
+
this.nodeSchema.has(candidate) || this.sink.nodeColumn(candidate) !== INVALID_INDEX;
|
|
889
|
+
const position: ColumnPlan = {
|
|
890
|
+
...plan,
|
|
891
|
+
name: uniqueColumnName("position", "graphics", taken),
|
|
892
|
+
dtype: "f64",
|
|
893
|
+
itemDtype: null,
|
|
894
|
+
kind: "real",
|
|
895
|
+
list: false,
|
|
896
|
+
role: "position",
|
|
897
|
+
};
|
|
898
|
+
const rest: ColumnPlan | null = entry.rest
|
|
899
|
+
? { ...plan, dtype: "json", itemDtype: null, kind: "record", list: false }
|
|
900
|
+
: null;
|
|
901
|
+
return { ...plan, position, rest };
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* The plan of one schema entry.
|
|
906
|
+
* @param domain - the table
|
|
907
|
+
* @param entry - the schema entry
|
|
908
|
+
* @param top - whether the key is a top-level one (graph table)
|
|
909
|
+
* @returns the plan
|
|
910
|
+
*/
|
|
911
|
+
private planOf(domain: "node" | "edge" | "graph", entry: KeySchema, top = false): ColumnPlan {
|
|
912
|
+
// long: "string" keeps every digit of an integer key that holds values beyond 2^53, as text
|
|
913
|
+
const asText = entry.unsafe && this.options.long === "string" && kindOf(entry) === "int";
|
|
914
|
+
const kind = asText ? "string" : kindOf(entry);
|
|
915
|
+
let scalar: ScalarDtype = dtypeOfKind(kind, entry);
|
|
916
|
+
if (
|
|
917
|
+
scalar === "string" &&
|
|
918
|
+
!entry.list &&
|
|
919
|
+
entry.dict !== null &&
|
|
920
|
+
entry.kinds === KIND_STRING &&
|
|
921
|
+
entry.dict.decide() === "dict"
|
|
922
|
+
) {
|
|
923
|
+
scalar = "dict";
|
|
924
|
+
}
|
|
925
|
+
this.warnKinds(domain, entry, asText);
|
|
861
926
|
const name = domain === "graph" ? this.uniqueGraphName(entry.key) : entry.key;
|
|
862
927
|
let itemDtype: ScalarDtype | null = null;
|
|
863
928
|
if (entry.list) {
|
|
@@ -880,21 +945,7 @@ class GmlImport {
|
|
|
880
945
|
seq: Number.NEGATIVE_INFINITY,
|
|
881
946
|
};
|
|
882
947
|
if (domain === "node" && entry.key === "graphics" && this.positions && (entry.kinds & KIND_RECORD) !== 0) {
|
|
883
|
-
|
|
884
|
-
this.nodeSchema.has(candidate) || this.sink.nodeColumn(candidate) !== INVALID_INDEX;
|
|
885
|
-
const position: ColumnPlan = {
|
|
886
|
-
...plan,
|
|
887
|
-
name: uniqueColumnName("position", "graphics", taken),
|
|
888
|
-
dtype: "f64",
|
|
889
|
-
itemDtype: null,
|
|
890
|
-
kind: "real",
|
|
891
|
-
list: false,
|
|
892
|
-
role: "position",
|
|
893
|
-
};
|
|
894
|
-
const rest: ColumnPlan | null = entry.rest
|
|
895
|
-
? { ...plan, dtype: "json", itemDtype: null, kind: "record", list: false }
|
|
896
|
-
: null;
|
|
897
|
-
return { ...plan, position, rest };
|
|
948
|
+
return this.graphicsPlan(plan, entry);
|
|
898
949
|
}
|
|
899
950
|
return plan;
|
|
900
951
|
}
|
|
@@ -583,14 +583,7 @@ export function parseRecord(tokens: GmlTokens, open: number): Record<string, unk
|
|
|
583
583
|
const key = tokens.textOf(p);
|
|
584
584
|
const v = p + 1;
|
|
585
585
|
if (tokens.kind[v] === TOKEN_STRING) {
|
|
586
|
-
|
|
587
|
-
if (text === LIST_START_MARKER) {
|
|
588
|
-
frame.record[key] = [];
|
|
589
|
-
frame.listKeys ??= new Set();
|
|
590
|
-
frame.listKeys.add(key);
|
|
591
|
-
continue;
|
|
592
|
-
}
|
|
593
|
-
storeValue(frame, key, text === EMPTY_LIST_TEXT || text === EMPTY_TUPLE_TEXT ? [] : text);
|
|
586
|
+
storeString(frame, key, tokens.stringOf(v));
|
|
594
587
|
} else if (tokens.kind[v] === TOKEN_OPEN) {
|
|
595
588
|
stack.push({
|
|
596
589
|
record: Object.create(null) as Record<string, unknown>,
|
|
@@ -606,6 +599,23 @@ export function parseRecord(tokens: GmlTokens, open: number): Record<string, unk
|
|
|
606
599
|
return root;
|
|
607
600
|
}
|
|
608
601
|
|
|
602
|
+
/**
|
|
603
|
+
* Store a string value: the list-start marker opens an empty list for the key, the empty list and
|
|
604
|
+
* tuple texts store an empty list, anything else stores the text.
|
|
605
|
+
* @param frame - the open record
|
|
606
|
+
* @param key - the key
|
|
607
|
+
* @param text - the string
|
|
608
|
+
*/
|
|
609
|
+
function storeString(frame: RecordFrame, key: string, text: string): void {
|
|
610
|
+
if (text === LIST_START_MARKER) {
|
|
611
|
+
frame.record[key] = [];
|
|
612
|
+
frame.listKeys ??= new Set();
|
|
613
|
+
frame.listKeys.add(key);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
storeValue(frame, key, text === EMPTY_LIST_TEXT || text === EMPTY_TUPLE_TEXT ? [] : text);
|
|
617
|
+
}
|
|
618
|
+
|
|
609
619
|
/**
|
|
610
620
|
* Store one value under a key: appended to the key's list when the key is a list, paired with the
|
|
611
621
|
* earlier value when the key repeats, set otherwise.
|
|
@@ -1153,6 +1153,30 @@ function writeTreeData(keyId: string, value: unknown, indent: string, out: strin
|
|
|
1153
1153
|
out.push(`\n${indent}</data>`);
|
|
1154
1154
|
}
|
|
1155
1155
|
|
|
1156
|
+
/**
|
|
1157
|
+
* Append the `<default>` element of a key.
|
|
1158
|
+
* @param key - the key
|
|
1159
|
+
* @param column - the key's column
|
|
1160
|
+
* @param inner - the indentation of the element
|
|
1161
|
+
* @param parts - where to append
|
|
1162
|
+
*/
|
|
1163
|
+
function pushDefault(key: KeyPlan, column: Column, inner: string, parts: string[]): void {
|
|
1164
|
+
if (key.yfilesType === null) {
|
|
1165
|
+
parts.push(`\n${inner}<default>${escapeXmlText(defaultText(column, key.attrType) ?? "")}</default>`);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const value = column.meta.default;
|
|
1169
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1170
|
+
parts.push(`\n${inner}<default>`);
|
|
1171
|
+
writeXmlTree(value, `${inner} `, parts);
|
|
1172
|
+
parts.push(`\n${inner}</default>`);
|
|
1173
|
+
} else {
|
|
1174
|
+
const tree: string[] = [];
|
|
1175
|
+
writeXmlTree(value, inner, tree);
|
|
1176
|
+
parts.push(`\n${inner}<default>${tree.join("")}</default>`);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1156
1180
|
/**
|
|
1157
1181
|
* The `<key>` element of a plan.
|
|
1158
1182
|
* @param key - the key plan
|
|
@@ -1183,20 +1207,7 @@ function keyElement(key: KeyPlan, indent: string): string {
|
|
|
1183
1207
|
parts.push(`\n${inner}<desc>${escapeXmlText(desc)}</desc>`);
|
|
1184
1208
|
}
|
|
1185
1209
|
if (hasDefault) {
|
|
1186
|
-
|
|
1187
|
-
const value = column.meta.default;
|
|
1188
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1189
|
-
parts.push(`\n${inner}<default>`);
|
|
1190
|
-
writeXmlTree(value, `${inner} `, parts);
|
|
1191
|
-
parts.push(`\n${inner}</default>`);
|
|
1192
|
-
} else {
|
|
1193
|
-
const tree: string[] = [];
|
|
1194
|
-
writeXmlTree(value, inner, tree);
|
|
1195
|
-
parts.push(`\n${inner}<default>${tree.join("")}</default>`);
|
|
1196
|
-
}
|
|
1197
|
-
} else {
|
|
1198
|
-
parts.push(`\n${inner}<default>${escapeXmlText(defaultText(column, key.attrType) ?? "")}</default>`);
|
|
1199
|
-
}
|
|
1210
|
+
pushDefault(key, column, inner, parts);
|
|
1200
1211
|
}
|
|
1201
1212
|
parts.push(`\n${indent}</key>\n`);
|
|
1202
1213
|
return parts.join("");
|
|
@@ -1307,11 +1318,13 @@ function* writeGraphml(snapshot: GraphSnapshot, plan: Plan): Generator<string, v
|
|
|
1307
1318
|
// nodes in containment order, children nested under their parent
|
|
1308
1319
|
const { hierarchy } = plan;
|
|
1309
1320
|
const nested = hierarchy.childList.length > 0;
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1321
|
+
const walk: SubtreeWalk = {
|
|
1322
|
+
stackNode: [],
|
|
1323
|
+
stackCursor: [],
|
|
1324
|
+
written: nested ? new Uint8Array(snapshot.nodeCount) : null,
|
|
1325
|
+
};
|
|
1313
1326
|
for (const rootNode of hierarchy.roots) {
|
|
1314
|
-
yield* writeNodeSubtree(rootNode, plan, ids, i2, step,
|
|
1327
|
+
yield* writeNodeSubtree(rootNode, plan, ids, i2, step, walk);
|
|
1315
1328
|
}
|
|
1316
1329
|
|
|
1317
1330
|
// edges: primaries only, in index order
|
|
@@ -1378,6 +1391,16 @@ function portText(column: Column | null, e: number): string | null {
|
|
|
1378
1391
|
return typeof value === "string" ? value : String(value);
|
|
1379
1392
|
}
|
|
1380
1393
|
|
|
1394
|
+
/** The state a subtree walk reuses from one top-level node to the next. */
|
|
1395
|
+
interface SubtreeWalk {
|
|
1396
|
+
/** A reusable stack of open nodes. */
|
|
1397
|
+
readonly stackNode: number[];
|
|
1398
|
+
/** A reusable stack of child cursors. */
|
|
1399
|
+
readonly stackCursor: number[];
|
|
1400
|
+
/** Nodes already written (null when nothing is nested). */
|
|
1401
|
+
readonly written: Uint8Array | null;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1381
1404
|
/**
|
|
1382
1405
|
* Write a top-level node and, nested, every node contained in it (iteratively, so a deep chain
|
|
1383
1406
|
* cannot overflow the call stack).
|
|
@@ -1386,9 +1409,7 @@ function portText(column: Column | null, e: number): string | null {
|
|
|
1386
1409
|
* @param ids - the sanitised ids
|
|
1387
1410
|
* @param indent - the indentation of top-level nodes
|
|
1388
1411
|
* @param step - one indentation step
|
|
1389
|
-
* @param
|
|
1390
|
-
* @param stackCursor - a reusable stack of child cursors
|
|
1391
|
-
* @param written - nodes already written (null when nothing is nested)
|
|
1412
|
+
* @param walk - the reusable walk state
|
|
1392
1413
|
* @yields the node elements
|
|
1393
1414
|
* @returns nothing
|
|
1394
1415
|
*/
|
|
@@ -1398,10 +1419,9 @@ function* writeNodeSubtree(
|
|
|
1398
1419
|
ids: SanitizedIds,
|
|
1399
1420
|
indent: string,
|
|
1400
1421
|
step: string,
|
|
1401
|
-
|
|
1402
|
-
stackCursor: number[],
|
|
1403
|
-
written: Uint8Array | null,
|
|
1422
|
+
walk: SubtreeWalk,
|
|
1404
1423
|
): Generator<string, void, undefined> {
|
|
1424
|
+
const { stackNode, stackCursor, written } = walk;
|
|
1405
1425
|
const { hierarchy } = plan;
|
|
1406
1426
|
const hasChildren = (u: number): boolean => hierarchy.childStart[u + 1] > hierarchy.childStart[u];
|
|
1407
1427
|
const openNode = (u: number, depth: number): string => {
|
|
@@ -1418,9 +1438,8 @@ function* writeNodeSubtree(
|
|
|
1418
1438
|
}
|
|
1419
1439
|
if (hasChildren(u)) {
|
|
1420
1440
|
parts.splice(open, 0, ">");
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
);
|
|
1441
|
+
const graphId = `${String(ids.idAt(u))}:`;
|
|
1442
|
+
parts.push(`\n${pad}${step}<graph id="${escapeXmlAttribute(graphId)}" edgedefault="${plan.edgedefault}">`);
|
|
1424
1443
|
return parts.join("");
|
|
1425
1444
|
}
|
|
1426
1445
|
if (parts.length === open) {
|
|
@@ -1126,9 +1126,6 @@ class GraphmlReader implements XmlHandler {
|
|
|
1126
1126
|
known: ReadonlySet<string>,
|
|
1127
1127
|
line: number,
|
|
1128
1128
|
): void {
|
|
1129
|
-
if (attrs.size === 0) {
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
1129
|
for (const name of attrs.keys()) {
|
|
1133
1130
|
if (known.has(name) || name === "xmlns" || /^(xmlns|xml|xsi):/.test(name)) {
|
|
1134
1131
|
continue;
|
|
@@ -1150,14 +1147,7 @@ class GraphmlReader implements XmlHandler {
|
|
|
1150
1147
|
if (names.size > MAX_UNKNOWN_ATTRIBUTE_NAMES && !hint) {
|
|
1151
1148
|
continue;
|
|
1152
1149
|
}
|
|
1153
|
-
|
|
1154
|
-
if (hint) {
|
|
1155
|
-
message = `the <${kind}> parse hint ${name} is ignored`;
|
|
1156
|
-
} else if (SCHEMA_ATTRIBUTES.has(name)) {
|
|
1157
|
-
message = `the <${kind}> attribute ${name} is not kept`;
|
|
1158
|
-
} else {
|
|
1159
|
-
message = `the <${kind}> attribute ${name} is not a GraphML attribute; it is not kept`;
|
|
1160
|
-
}
|
|
1150
|
+
const message = unreadAttributeMessage(kind, name, hint);
|
|
1161
1151
|
this.report.warning("unsupported", code, message, { line, element: name });
|
|
1162
1152
|
}
|
|
1163
1153
|
}
|
|
@@ -2462,9 +2452,10 @@ class GraphmlReader implements XmlHandler {
|
|
|
2462
2452
|
*/
|
|
2463
2453
|
private expandStar(hyperedge: HyperedgeState, where: Where): void {
|
|
2464
2454
|
this.hyperedgeCount++;
|
|
2465
|
-
|
|
2455
|
+
const baseId = hyperedge.id ?? `hyperedge${this.hyperedgeCount}`;
|
|
2456
|
+
let hubId = baseId;
|
|
2466
2457
|
for (let n = 2; this.sink.indexOf(hubId) !== INVALID_INDEX; n++) {
|
|
2467
|
-
hubId = `${
|
|
2458
|
+
hubId = `${baseId}#${n}`;
|
|
2468
2459
|
}
|
|
2469
2460
|
const hub = this.sink.addNode(hubId);
|
|
2470
2461
|
this.declared.add(hub);
|
|
@@ -2728,3 +2719,20 @@ function unknownElementMessage(name: string): string {
|
|
|
2728
2719
|
? `element <${name}> is not allowed at this place in a GraphML file and was skipped`
|
|
2729
2720
|
: `element <${name}> is not GraphML and was skipped`;
|
|
2730
2721
|
}
|
|
2722
|
+
|
|
2723
|
+
/**
|
|
2724
|
+
* The warning text for an attribute the importer does not keep.
|
|
2725
|
+
* @param kind - the element name
|
|
2726
|
+
* @param name - the attribute name
|
|
2727
|
+
* @param hint - whether the attribute is a `parse.*` hint
|
|
2728
|
+
* @returns the message
|
|
2729
|
+
*/
|
|
2730
|
+
function unreadAttributeMessage(kind: string, name: string, hint: boolean): string {
|
|
2731
|
+
if (hint) {
|
|
2732
|
+
return `the <${kind}> parse hint ${name} is ignored`;
|
|
2733
|
+
}
|
|
2734
|
+
if (SCHEMA_ATTRIBUTES.has(name)) {
|
|
2735
|
+
return `the <${kind}> attribute ${name} is not kept`;
|
|
2736
|
+
}
|
|
2737
|
+
return `the <${kind}> attribute ${name} is not a GraphML attribute; it is not kept`;
|
|
2738
|
+
}
|
|
@@ -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
|
-
|
|
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 =
|
|
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, ...).
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|