@graphty/graph-io 0.3.48 → 0.3.50
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/{approximations-C2riR4NT.js → approximations-BRXsll0T.js} +2 -2
- package/dist/chunks/{approximations-C2riR4NT.js.map → approximations-BRXsll0T.js.map} +1 -1
- package/dist/chunks/bends-BGXjv_U4.js +129 -0
- package/dist/chunks/bends-BGXjv_U4.js.map +1 -0
- package/dist/chunks/{block-Cu22ewuS.js → block-yl0f_qLO.js} +931 -164
- package/dist/chunks/block-yl0f_qLO.js.map +1 -0
- package/dist/chunks/choose-CczZJaX_.js +717 -0
- package/dist/chunks/choose-CczZJaX_.js.map +1 -0
- package/dist/chunks/{codes-CV_Bhcyi.js → codes-BE2xir2k.js} +6 -2
- package/dist/chunks/codes-BE2xir2k.js.map +1 -0
- package/dist/chunks/{constants-D0LqcsLJ.js → constants-CKkYLPtt.js} +2 -2
- package/dist/chunks/{constants-D0LqcsLJ.js.map → constants-CKkYLPtt.js.map} +1 -1
- package/dist/chunks/{context-cgmmOxZ2.js → context-DizmppHy.js} +5 -4
- package/dist/chunks/{context-cgmmOxZ2.js.map → context-DizmppHy.js.map} +1 -1
- package/dist/chunks/{convert-BcQ8_jMo.js → convert-Ct2zelbQ.js} +7 -2
- package/dist/chunks/convert-Ct2zelbQ.js.map +1 -0
- package/dist/chunks/{credentials-BiQsnMTb.js → credentials-CmvJXYeH.js} +67 -16
- package/dist/chunks/credentials-CmvJXYeH.js.map +1 -0
- package/dist/chunks/{cx-export-BEeF6ymo.js → cx-export-Bt6NoqTQ.js} +3 -3
- package/dist/chunks/{cx-export-BEeF6ymo.js.map → cx-export-Bt6NoqTQ.js.map} +1 -1
- package/dist/chunks/descriptor-B28U25x4.js +60 -0
- package/dist/chunks/descriptor-B28U25x4.js.map +1 -0
- package/dist/chunks/{descriptor-BJ-W0Jd-.js → descriptor-Dhez_Cew.js} +11 -2
- package/dist/chunks/descriptor-Dhez_Cew.js.map +1 -0
- package/dist/chunks/{emit-CD8xnY8s.js → emit-7Jkpg0yd.js} +1788 -133
- package/dist/chunks/emit-7Jkpg0yd.js.map +1 -0
- package/dist/chunks/{escape-DH2Egt-l.js → escape-CuCIzNMB.js} +8 -8
- package/dist/chunks/{escape-DH2Egt-l.js.map → escape-CuCIzNMB.js.map} +1 -1
- package/dist/chunks/{export-CZ7VRm4I.js → export-Cha4dRFz.js} +45 -387
- package/dist/chunks/export-Cha4dRFz.js.map +1 -0
- package/dist/chunks/{exporter-ChjL5fmP.js → exporter-1Ajud15J.js} +9 -8
- package/dist/chunks/{exporter-ChjL5fmP.js.map → exporter-1Ajud15J.js.map} +1 -1
- package/dist/chunks/{exporter-BIdaoIgh.js → exporter-B6yvL3wy.js} +7 -7
- package/dist/chunks/{exporter-BIdaoIgh.js.map → exporter-B6yvL3wy.js.map} +1 -1
- package/dist/chunks/{exporter-Dcw7Ldtq.js → exporter-BP6otMO6.js} +6 -6
- package/dist/chunks/{exporter-Dcw7Ldtq.js.map → exporter-BP6otMO6.js.map} +1 -1
- package/dist/chunks/{exporter-DtVU80Fa.js → exporter-CA5q8ROP.js} +9 -8
- package/dist/chunks/{exporter-DtVU80Fa.js.map → exporter-CA5q8ROP.js.map} +1 -1
- package/dist/chunks/{exporter-bxxjOrDZ.js → exporter-CQQZFJ2v.js} +1284 -156
- package/dist/chunks/exporter-CQQZFJ2v.js.map +1 -0
- package/dist/chunks/format-BXiAb-1h.js +408 -0
- package/dist/chunks/format-BXiAb-1h.js.map +1 -0
- package/dist/chunks/{generic-CP6YD34u.js → generic-Dc21MAdg.js} +387 -31
- package/dist/chunks/generic-Dc21MAdg.js.map +1 -0
- package/dist/chunks/{importer-B87_fmVx.js → importer-C6NVpCXp.js} +327 -84
- package/dist/chunks/importer-C6NVpCXp.js.map +1 -0
- package/dist/chunks/{importer-DPZDN40-.js → importer-CKFe4q54.js} +25 -17
- package/dist/chunks/importer-CKFe4q54.js.map +1 -0
- package/dist/chunks/{importer-Bji2MeWr.js → importer-CKbUbsJA.js} +13 -10
- package/dist/chunks/{importer-Bji2MeWr.js.map → importer-CKbUbsJA.js.map} +1 -1
- package/dist/chunks/{importer-CB_zV5_L.js → importer-ClCsdgTJ.js} +208 -1104
- package/dist/chunks/importer-ClCsdgTJ.js.map +1 -0
- package/dist/chunks/{importer-e3huMZbm.js → importer-CyVZTrf0.js} +14 -10
- package/dist/chunks/importer-CyVZTrf0.js.map +1 -0
- package/dist/chunks/{importer-CVT-JyDE.js → importer-DgG4v1Ms.js} +1537 -355
- package/dist/chunks/importer-DgG4v1Ms.js.map +1 -0
- package/dist/chunks/{importer-Cyy6UseH.js → importer-asFUrIsj.js} +66 -13
- package/dist/chunks/importer-asFUrIsj.js.map +1 -0
- package/dist/chunks/{input-Ccg9I22A.js → input-CAxt_aV-.js} +4 -4
- package/dist/chunks/{input-Ccg9I22A.js.map → input-CAxt_aV-.js.map} +1 -1
- package/dist/chunks/{json-elements-By172SVq.js → json-elements-Cq76WjnZ.js} +4 -4
- package/dist/chunks/{json-elements-By172SVq.js.map → json-elements-Cq76WjnZ.js.map} +1 -1
- package/dist/chunks/{lexicon-u2SI5xcx.js → lexicon-IVRq2Uoo.js} +282 -74
- package/dist/chunks/lexicon-IVRq2Uoo.js.map +1 -0
- package/dist/chunks/{ontology-export-BwgMyuF0.js → ontology-export-OMo91mb0.js} +4 -4
- package/dist/chunks/{ontology-export-BwgMyuF0.js.map → ontology-export-OMo91mb0.js.map} +1 -1
- package/dist/chunks/{records-Cgvxz-Vx.js → records-NJiYvekn.js} +2 -2
- package/dist/chunks/{records-Cgvxz-Vx.js.map → records-NJiYvekn.js.map} +1 -1
- package/dist/chunks/{report-CgdsL9Hb.js → report-CwMfYa9J.js} +23 -23
- package/dist/chunks/{report-CgdsL9Hb.js.map → report-CwMfYa9J.js.map} +1 -1
- package/dist/chunks/round-trip-Cy3_SHRZ.js +79 -0
- package/dist/chunks/round-trip-Cy3_SHRZ.js.map +1 -0
- package/dist/chunks/{sigv4-LkAzsLAu.js → sigv4-BXwVbza3.js} +5 -5
- package/dist/chunks/sigv4-BXwVbza3.js.map +1 -0
- package/dist/chunks/{styles-CaABMoO1.js → styles-CAVmBLgp.js} +1237 -138
- package/dist/chunks/styles-CAVmBLgp.js.map +1 -0
- package/dist/chunks/styles-CPczw1Jz.js +1928 -0
- package/dist/chunks/styles-CPczw1Jz.js.map +1 -0
- package/dist/chunks/styles-boK6Ln54.js +2847 -0
- package/dist/chunks/styles-boK6Ln54.js.map +1 -0
- package/dist/chunks/{styles-yed-Cs1nmrjn.js → styles-yed-XeFBQ6VS.js} +506 -175
- package/dist/chunks/styles-yed-XeFBQ6VS.js.map +1 -0
- package/dist/chunks/{text-BuKQVMz2.js → text-ystE2bYi.js} +2 -2
- package/dist/chunks/{text-BuKQVMz2.js.map → text-ystE2bYi.js.map} +1 -1
- package/dist/csv.js +7 -7
- package/dist/cx.js +2 -2
- package/dist/cx2.js +2 -2
- package/dist/cys.js +1 -1
- package/dist/dot.js +8 -4
- package/dist/dot.js.map +1 -1
- package/dist/gexf.js +42 -12
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +848 -194
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +230 -193
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +3 -3
- package/dist/json.js +3 -2
- package/dist/neo4j.js +6 -6
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/positions.js +45 -3
- package/dist/positions.js.map +1 -1
- package/dist/sources-cypher.js +141 -23
- package/dist/sources-cypher.js.map +1 -1
- package/dist/sources-ndex.js +2 -2
- package/dist/sources-rest.js +32 -6
- package/dist/sources-rest.js.map +1 -1
- package/dist/sources-sparql.d.ts +1 -0
- package/dist/sources-sparql.js +439 -0
- package/dist/sources-sparql.js.map +1 -0
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +5 -1
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx/styles.d.ts +24 -1
- package/dist/src/formats/cx/styles.d.ts.map +1 -1
- package/dist/src/formats/cx/styles.js +117 -11
- package/dist/src/formats/cx/styles.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +1 -0
- package/dist/src/formats/cx2/importer.js.map +1 -1
- package/dist/src/formats/cx2/styles.d.ts.map +1 -1
- package/dist/src/formats/cx2/styles.js +3 -1
- package/dist/src/formats/cx2/styles.js.map +1 -1
- package/dist/src/formats/cys/importer.d.ts.map +1 -1
- package/dist/src/formats/cys/importer.js +1 -0
- package/dist/src/formats/cys/importer.js.map +1 -1
- package/dist/src/formats/cys/styles.d.ts +2 -0
- package/dist/src/formats/cys/styles.d.ts.map +1 -1
- package/dist/src/formats/cys/styles.js +15 -7
- package/dist/src/formats/cys/styles.js.map +1 -1
- package/dist/src/formats/dot/drawing.d.ts +98 -0
- package/dist/src/formats/dot/drawing.d.ts.map +1 -0
- package/dist/src/formats/dot/drawing.js +734 -0
- package/dist/src/formats/dot/drawing.js.map +1 -0
- package/dist/src/formats/dot/exporter.d.ts.map +1 -1
- package/dist/src/formats/dot/exporter.js +34 -2
- package/dist/src/formats/dot/exporter.js.map +1 -1
- package/dist/src/formats/dot/importer.d.ts +6 -1
- package/dist/src/formats/dot/importer.d.ts.map +1 -1
- package/dist/src/formats/dot/importer.js +170 -33
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/dot/index.d.ts +3 -1
- package/dist/src/formats/dot/index.d.ts.map +1 -1
- package/dist/src/formats/dot/index.js +2 -1
- package/dist/src/formats/dot/index.js.map +1 -1
- package/dist/src/formats/dot/label-places.d.ts +41 -0
- package/dist/src/formats/dot/label-places.d.ts.map +1 -0
- package/dist/src/formats/dot/label-places.js +185 -0
- package/dist/src/formats/dot/label-places.js.map +1 -0
- package/dist/src/formats/dot/names.d.ts +13 -0
- package/dist/src/formats/dot/names.d.ts.map +1 -1
- package/dist/src/formats/dot/names.js +13 -0
- package/dist/src/formats/dot/names.js.map +1 -1
- package/dist/src/formats/dot/record.d.ts +50 -0
- package/dist/src/formats/dot/record.d.ts.map +1 -0
- package/dist/src/formats/dot/record.js +275 -0
- package/dist/src/formats/dot/record.js.map +1 -0
- package/dist/src/formats/dot/styles.d.ts +72 -2
- package/dist/src/formats/dot/styles.d.ts.map +1 -1
- package/dist/src/formats/dot/styles.js +1500 -119
- package/dist/src/formats/dot/styles.js.map +1 -1
- package/dist/src/formats/gexf/dialog-metrics.d.ts +15 -0
- package/dist/src/formats/gexf/dialog-metrics.d.ts.map +1 -0
- package/dist/src/formats/gexf/dialog-metrics.js +59 -0
- package/dist/src/formats/gexf/dialog-metrics.js.map +1 -0
- package/dist/src/formats/gexf/importer.d.ts +7 -0
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +41 -5
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/preview.d.ts +77 -0
- package/dist/src/formats/gexf/preview.d.ts.map +1 -0
- package/dist/src/formats/gexf/preview.js +191 -0
- package/dist/src/formats/gexf/preview.js.map +1 -0
- package/dist/src/formats/gexf/schema.d.ts.map +1 -1
- package/dist/src/formats/gexf/schema.js +2 -1
- package/dist/src/formats/gexf/schema.js.map +1 -1
- package/dist/src/formats/gexf/styles.d.ts +58 -31
- package/dist/src/formats/gexf/styles.d.ts.map +1 -1
- package/dist/src/formats/gexf/styles.js +848 -225
- package/dist/src/formats/gexf/styles.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts +5 -2
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +258 -71
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/index.d.ts +1 -1
- package/dist/src/formats/gml/index.d.ts.map +1 -1
- package/dist/src/formats/gml/index.js +1 -1
- package/dist/src/formats/gml/index.js.map +1 -1
- package/dist/src/formats/gml/positions.d.ts +7 -0
- package/dist/src/formats/gml/positions.d.ts.map +1 -1
- package/dist/src/formats/gml/positions.js +29 -7
- package/dist/src/formats/gml/positions.js.map +1 -1
- package/dist/src/formats/gml/styles.d.ts.map +1 -1
- package/dist/src/formats/gml/styles.js +754 -102
- package/dist/src/formats/gml/styles.js.map +1 -1
- package/dist/src/formats/graphml/styles-plain.d.ts.map +1 -1
- package/dist/src/formats/graphml/styles-plain.js +6 -10
- package/dist/src/formats/graphml/styles-plain.js.map +1 -1
- package/dist/src/formats/graphml/styles-yed.d.ts +21 -15
- package/dist/src/formats/graphml/styles-yed.d.ts.map +1 -1
- package/dist/src/formats/graphml/styles-yed.js +695 -164
- package/dist/src/formats/graphml/styles-yed.js.map +1 -1
- package/dist/src/formats/graphml/styles-yfiles3.d.ts +1 -1
- package/dist/src/formats/graphml/styles-yfiles3.d.ts.map +1 -1
- package/dist/src/formats/graphml/styles-yfiles3.js +431 -64
- package/dist/src/formats/graphml/styles-yfiles3.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts +3 -3
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +16 -6
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts +3 -2
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +39 -5
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +13 -2
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +42 -6
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/index.d.ts +1 -1
- package/dist/src/formats/json/index.d.ts.map +1 -1
- package/dist/src/formats/json/index.js +1 -1
- package/dist/src/formats/json/index.js.map +1 -1
- package/dist/src/formats/json/positions.d.ts +5 -2
- package/dist/src/formats/json/positions.d.ts.map +1 -1
- package/dist/src/formats/json/positions.js +20 -4
- package/dist/src/formats/json/positions.js.map +1 -1
- package/dist/src/formats/json/styles.d.ts +12 -1
- package/dist/src/formats/json/styles.d.ts.map +1 -1
- package/dist/src/formats/json/styles.js +1843 -136
- package/dist/src/formats/json/styles.js.map +1 -1
- package/dist/src/formats/pajek/draw.d.ts +104 -0
- package/dist/src/formats/pajek/draw.d.ts.map +1 -0
- package/dist/src/formats/pajek/draw.js +382 -0
- package/dist/src/formats/pajek/draw.js.map +1 -0
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +70 -51
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts +9 -0
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +233 -102
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/index.d.ts +1 -0
- package/dist/src/formats/pajek/index.d.ts.map +1 -1
- package/dist/src/formats/pajek/index.js.map +1 -1
- package/dist/src/formats/pajek/styles.d.ts +15 -7
- package/dist/src/formats/pajek/styles.d.ts.map +1 -1
- package/dist/src/formats/pajek/styles.js +1395 -114
- package/dist/src/formats/pajek/styles.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts +10 -1
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +26 -2
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +1 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +65 -5
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/styles.d.ts +23 -3
- package/dist/src/formats/xgmml/styles.d.ts.map +1 -1
- package/dist/src/formats/xgmml/styles.js +277 -60
- package/dist/src/formats/xgmml/styles.js.map +1 -1
- package/dist/src/index.d.ts +6 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/positions/convert.d.ts.map +1 -1
- package/dist/src/positions/convert.js +11 -1
- package/dist/src/positions/convert.js.map +1 -1
- package/dist/src/positions/extract.d.ts.map +1 -1
- package/dist/src/positions/extract.js +53 -1
- package/dist/src/positions/extract.js.map +1 -1
- package/dist/src/positions/index.d.ts +1 -1
- package/dist/src/positions/index.d.ts.map +1 -1
- package/dist/src/positions/types.d.ts +59 -0
- package/dist/src/positions/types.d.ts.map +1 -1
- package/dist/src/sources/builtins.d.ts +1 -1
- package/dist/src/sources/builtins.d.ts.map +1 -1
- package/dist/src/sources/builtins.js +8 -2
- package/dist/src/sources/builtins.js.map +1 -1
- package/dist/src/sources/context.d.ts.map +1 -1
- package/dist/src/sources/context.js +1 -0
- package/dist/src/sources/context.js.map +1 -1
- package/dist/src/sources/credentials.d.ts +50 -1
- package/dist/src/sources/credentials.d.ts.map +1 -1
- package/dist/src/sources/credentials.js +69 -0
- package/dist/src/sources/credentials.js.map +1 -1
- package/dist/src/sources/cypher/descriptor.d.ts.map +1 -1
- package/dist/src/sources/cypher/descriptor.js +5 -0
- package/dist/src/sources/cypher/descriptor.js.map +1 -1
- package/dist/src/sources/cypher/index.d.ts.map +1 -1
- package/dist/src/sources/cypher/index.js +125 -18
- package/dist/src/sources/cypher/index.js.map +1 -1
- package/dist/src/sources/cypher/params.d.ts +1 -1
- package/dist/src/sources/cypher/params.d.ts.map +1 -1
- package/dist/src/sources/cypher/query-builder.d.ts +17 -0
- package/dist/src/sources/cypher/query-builder.d.ts.map +1 -1
- package/dist/src/sources/cypher/query-builder.js +31 -0
- package/dist/src/sources/cypher/query-builder.js.map +1 -1
- package/dist/src/sources/ids.d.ts +5 -0
- package/dist/src/sources/ids.d.ts.map +1 -1
- package/dist/src/sources/ids.js +5 -0
- package/dist/src/sources/ids.js.map +1 -1
- package/dist/src/sources/params.d.ts +3 -0
- package/dist/src/sources/params.d.ts.map +1 -1
- package/dist/src/sources/rest/descriptor.d.ts.map +1 -1
- package/dist/src/sources/rest/descriptor.js +9 -0
- package/dist/src/sources/rest/descriptor.js.map +1 -1
- package/dist/src/sources/rest/openalex.d.ts +4 -2
- package/dist/src/sources/rest/openalex.d.ts.map +1 -1
- package/dist/src/sources/rest/openalex.js +4 -2
- package/dist/src/sources/rest/openalex.js.map +1 -1
- package/dist/src/sources/rest/source.d.ts.map +1 -1
- package/dist/src/sources/rest/source.js +38 -0
- package/dist/src/sources/rest/source.js.map +1 -1
- package/dist/src/sources/run.d.ts +2 -0
- package/dist/src/sources/run.d.ts.map +1 -1
- package/dist/src/sources/run.js +30 -3
- package/dist/src/sources/run.js.map +1 -1
- package/dist/src/sources/sigv4.d.ts +5 -3
- package/dist/src/sources/sigv4.d.ts.map +1 -1
- package/dist/src/sources/sigv4.js +6 -4
- package/dist/src/sources/sigv4.js.map +1 -1
- package/dist/src/sources/sparql/descriptor.d.ts +10 -0
- package/dist/src/sources/sparql/descriptor.d.ts.map +1 -0
- package/dist/src/sources/sparql/descriptor.js +64 -0
- package/dist/src/sources/sparql/descriptor.js.map +1 -0
- package/dist/src/sources/sparql/index.d.ts +28 -0
- package/dist/src/sources/sparql/index.d.ts.map +1 -0
- package/dist/src/sources/sparql/index.js +327 -0
- package/dist/src/sources/sparql/index.js.map +1 -0
- package/dist/src/sources/sparql/ntriples.d.ts +69 -0
- package/dist/src/sources/sparql/ntriples.d.ts.map +1 -0
- package/dist/src/sources/sparql/ntriples.js +180 -0
- package/dist/src/sources/sparql/ntriples.js.map +1 -0
- package/dist/src/sources/sparql/params.d.ts +27 -0
- package/dist/src/sources/sparql/params.d.ts.map +1 -0
- package/dist/src/sources/sparql/params.js +5 -0
- package/dist/src/sources/sparql/params.js.map +1 -0
- package/dist/src/sources/sparql/query.d.ts +73 -0
- package/dist/src/sources/sparql/query.d.ts.map +1 -0
- package/dist/src/sources/sparql/query.js +164 -0
- package/dist/src/sources/sparql/query.js.map +1 -0
- package/dist/src/sources/types.d.ts +60 -0
- package/dist/src/sources/types.d.ts.map +1 -1
- package/dist/src/styles/annotations.d.ts +61 -5
- package/dist/src/styles/annotations.d.ts.map +1 -1
- package/dist/src/styles/annotations.js +130 -22
- package/dist/src/styles/annotations.js.map +1 -1
- package/dist/src/styles/canon/bends.d.ts +46 -0
- package/dist/src/styles/canon/bends.d.ts.map +1 -1
- package/dist/src/styles/canon/bends.js +123 -0
- package/dist/src/styles/canon/bends.js.map +1 -1
- package/dist/src/styles/canon/color-tables.d.ts +6 -1
- package/dist/src/styles/canon/color-tables.d.ts.map +1 -1
- package/dist/src/styles/canon/color-tables.js +10 -4
- package/dist/src/styles/canon/color-tables.js.map +1 -1
- package/dist/src/styles/canon/color.d.ts +3 -1
- package/dist/src/styles/canon/color.d.ts.map +1 -1
- package/dist/src/styles/canon/color.js +103 -3
- package/dist/src/styles/canon/color.js.map +1 -1
- package/dist/src/styles/canon/enums.d.ts +2 -2
- package/dist/src/styles/canon/enums.d.ts.map +1 -1
- package/dist/src/styles/canon/enums.js +89 -7
- package/dist/src/styles/canon/enums.js.map +1 -1
- package/dist/src/styles/canon/graphviz.d.ts +12 -3
- package/dist/src/styles/canon/graphviz.d.ts.map +1 -1
- package/dist/src/styles/canon/graphviz.js +57 -9
- package/dist/src/styles/canon/graphviz.js.map +1 -1
- package/dist/src/styles/canon/number.d.ts +8 -0
- package/dist/src/styles/canon/number.d.ts.map +1 -1
- package/dist/src/styles/canon/number.js +18 -0
- package/dist/src/styles/canon/number.js.map +1 -1
- package/dist/src/styles/canon/text.d.ts +29 -2
- package/dist/src/styles/canon/text.d.ts.map +1 -1
- package/dist/src/styles/canon/text.js +156 -16
- package/dist/src/styles/canon/text.js.map +1 -1
- package/dist/src/styles/codes.d.ts +2 -0
- package/dist/src/styles/codes.d.ts.map +1 -1
- package/dist/src/styles/codes.js +2 -0
- package/dist/src/styles/codes.js.map +1 -1
- package/dist/src/styles/cyjs/cascade.d.ts.map +1 -1
- package/dist/src/styles/cyjs/cascade.js +1139 -79
- package/dist/src/styles/cyjs/cascade.js.map +1 -1
- package/dist/src/styles/cyjs/defaults-table.d.ts.map +1 -1
- package/dist/src/styles/cyjs/defaults-table.js +4 -0
- package/dist/src/styles/cyjs/defaults-table.js.map +1 -1
- package/dist/src/styles/cyjs/emit.d.ts.map +1 -1
- package/dist/src/styles/cyjs/emit.js +232 -10
- package/dist/src/styles/cyjs/emit.js.map +1 -1
- package/dist/src/styles/cyjs/face.d.ts +20 -0
- package/dist/src/styles/cyjs/face.d.ts.map +1 -0
- package/dist/src/styles/cyjs/face.js +332 -0
- package/dist/src/styles/cyjs/face.js.map +1 -0
- package/dist/src/styles/cyjs/mappers.d.ts +44 -1
- package/dist/src/styles/cyjs/mappers.d.ts.map +1 -1
- package/dist/src/styles/cyjs/mappers.js +475 -26
- package/dist/src/styles/cyjs/mappers.js.map +1 -1
- package/dist/src/styles/cyjs/routes.d.ts +53 -0
- package/dist/src/styles/cyjs/routes.d.ts.map +1 -0
- package/dist/src/styles/cyjs/routes.js +360 -0
- package/dist/src/styles/cyjs/routes.js.map +1 -0
- package/dist/src/styles/cytoscape/charts.d.ts +23 -0
- package/dist/src/styles/cytoscape/charts.d.ts.map +1 -0
- package/dist/src/styles/cytoscape/charts.js +560 -0
- package/dist/src/styles/cytoscape/charts.js.map +1 -0
- package/dist/src/styles/cytoscape/emit.d.ts.map +1 -1
- package/dist/src/styles/cytoscape/emit.js +156 -48
- package/dist/src/styles/cytoscape/emit.js.map +1 -1
- package/dist/src/styles/cytoscape/lexicon.d.ts +91 -3
- package/dist/src/styles/cytoscape/lexicon.d.ts.map +1 -1
- package/dist/src/styles/cytoscape/lexicon.js +387 -77
- package/dist/src/styles/cytoscape/lexicon.js.map +1 -1
- package/dist/src/styles/cytoscape/mappings.d.ts +30 -0
- package/dist/src/styles/cytoscape/mappings.d.ts.map +1 -1
- package/dist/src/styles/cytoscape/mappings.js +92 -0
- package/dist/src/styles/cytoscape/mappings.js.map +1 -1
- package/dist/src/styles/cytoscape/resolve.d.ts +42 -2
- package/dist/src/styles/cytoscape/resolve.d.ts.map +1 -1
- package/dist/src/styles/cytoscape/resolve.js +623 -17
- package/dist/src/styles/cytoscape/resolve.js.map +1 -1
- package/dist/src/styles/defaults.d.ts +46 -26
- package/dist/src/styles/defaults.d.ts.map +1 -1
- package/dist/src/styles/defaults.js +308 -71
- package/dist/src/styles/defaults.js.map +1 -1
- package/dist/src/styles/index.d.ts +4 -3
- package/dist/src/styles/index.d.ts.map +1 -1
- package/dist/src/styles/index.js +3 -2
- package/dist/src/styles/index.js.map +1 -1
- package/dist/src/styles/internal.d.ts +1 -0
- package/dist/src/styles/internal.d.ts.map +1 -1
- package/dist/src/styles/internal.js +1 -0
- package/dist/src/styles/internal.js.map +1 -1
- package/dist/src/styles/io/generic.d.ts.map +1 -1
- package/dist/src/styles/io/generic.js +56 -16
- package/dist/src/styles/io/generic.js.map +1 -1
- package/dist/src/styles/io/round-trip.d.ts +56 -0
- package/dist/src/styles/io/round-trip.d.ts.map +1 -0
- package/dist/src/styles/io/round-trip.js +138 -0
- package/dist/src/styles/io/round-trip.js.map +1 -0
- package/dist/src/styles/io/writer.d.ts.map +1 -1
- package/dist/src/styles/io/writer.js +6 -1
- package/dist/src/styles/io/writer.js.map +1 -1
- package/dist/src/styles/notes.d.ts.map +1 -1
- package/dist/src/styles/notes.js +1 -0
- package/dist/src/styles/notes.js.map +1 -1
- package/dist/src/styles/plan/features.d.ts +1 -1
- package/dist/src/styles/plan/features.d.ts.map +1 -1
- package/dist/src/styles/plan/features.js +22 -2
- package/dist/src/styles/plan/features.js.map +1 -1
- package/dist/src/styles/plan/index.d.ts +1 -1
- package/dist/src/styles/plan/index.d.ts.map +1 -1
- package/dist/src/styles/plan/index.js +1 -1
- package/dist/src/styles/plan/index.js.map +1 -1
- package/dist/src/styles/plan/plan.d.ts.map +1 -1
- package/dist/src/styles/plan/plan.js +61 -4
- package/dist/src/styles/plan/plan.js.map +1 -1
- package/dist/src/styles/plan/resolve.d.ts +11 -0
- package/dist/src/styles/plan/resolve.d.ts.map +1 -1
- package/dist/src/styles/plan/resolve.js +40 -4
- package/dist/src/styles/plan/resolve.js.map +1 -1
- package/dist/src/styles/slots.d.ts +14 -0
- package/dist/src/styles/slots.d.ts.map +1 -0
- package/dist/src/styles/slots.js +20 -0
- package/dist/src/styles/slots.js.map +1 -0
- package/dist/src/styles/types.d.ts +687 -6
- package/dist/src/styles/types.d.ts.map +1 -1
- package/dist/src/styles/vocabulary.d.ts +3 -2
- package/dist/src/styles/vocabulary.d.ts.map +1 -1
- package/dist/src/styles/vocabulary.js +224 -0
- package/dist/src/styles/vocabulary.js.map +1 -1
- package/dist/styles.js +30 -239
- package/dist/styles.js.map +1 -1
- package/dist/xgmml.js +3 -3
- package/package.json +27 -7
- package/src/formats/cx/importer.ts +6 -1
- package/src/formats/cx/styles.ts +143 -9
- package/src/formats/cx2/importer.ts +1 -0
- package/src/formats/cx2/styles.ts +3 -1
- package/src/formats/cys/importer.ts +1 -0
- package/src/formats/cys/styles.ts +27 -5
- package/src/formats/dot/drawing.ts +799 -0
- package/src/formats/dot/exporter.ts +37 -1
- package/src/formats/dot/importer.ts +192 -36
- package/src/formats/dot/index.ts +3 -1
- package/src/formats/dot/label-places.ts +241 -0
- package/src/formats/dot/names.ts +15 -0
- package/src/formats/dot/record.ts +336 -0
- package/src/formats/dot/styles.ts +1702 -149
- package/src/formats/gexf/dialog-metrics.ts +61 -0
- package/src/formats/gexf/importer.ts +50 -5
- package/src/formats/gexf/preview.ts +217 -0
- package/src/formats/gexf/schema.ts +2 -1
- package/src/formats/gexf/styles.ts +1099 -245
- package/src/formats/gml/importer.ts +291 -76
- package/src/formats/gml/index.ts +1 -1
- package/src/formats/gml/positions.ts +29 -7
- package/src/formats/gml/styles.ts +852 -110
- package/src/formats/graphml/styles-plain.ts +6 -15
- package/src/formats/graphml/styles-yed.ts +765 -176
- package/src/formats/graphml/styles-yfiles3.ts +487 -63
- package/src/formats/graphml/yfiles.ts +19 -6
- package/src/formats/json/dialect.ts +41 -5
- package/src/formats/json/importer.ts +59 -7
- package/src/formats/json/index.ts +1 -0
- package/src/formats/json/positions.ts +22 -5
- package/src/formats/json/styles.ts +2123 -171
- package/src/formats/pajek/draw.ts +469 -0
- package/src/formats/pajek/exporter.ts +81 -49
- package/src/formats/pajek/importer.ts +278 -103
- package/src/formats/pajek/index.ts +1 -0
- package/src/formats/pajek/styles.ts +1506 -133
- package/src/formats/pajek/syntax.ts +27 -2
- package/src/formats/xgmml/emit.ts +73 -5
- package/src/formats/xgmml/styles.ts +330 -71
- package/src/index.ts +12 -1
- package/src/positions/convert.ts +12 -1
- package/src/positions/extract.ts +55 -1
- package/src/positions/index.ts +1 -0
- package/src/positions/types.ts +60 -0
- package/src/sources/builtins.ts +8 -1
- package/src/sources/context.ts +1 -0
- package/src/sources/credentials.ts +90 -1
- package/src/sources/cypher/descriptor.ts +7 -1
- package/src/sources/cypher/index.ts +149 -18
- package/src/sources/cypher/params.ts +1 -1
- package/src/sources/cypher/query-builder.ts +33 -0
- package/src/sources/ids.ts +6 -0
- package/src/sources/params.ts +3 -0
- package/src/sources/rest/descriptor.ts +9 -0
- package/src/sources/rest/openalex.ts +4 -2
- package/src/sources/rest/source.ts +41 -0
- package/src/sources/run.ts +35 -3
- package/src/sources/sigv4.ts +6 -4
- package/src/sources/sparql/descriptor.ts +67 -0
- package/src/sources/sparql/index.ts +371 -0
- package/src/sources/sparql/ntriples.ts +219 -0
- package/src/sources/sparql/params.ts +28 -0
- package/src/sources/sparql/query.ts +216 -0
- package/src/sources/types.ts +68 -0
- package/src/styles/annotations.ts +201 -27
- package/src/styles/canon/bends.ts +137 -0
- package/src/styles/canon/color-tables.ts +10 -4
- package/src/styles/canon/color.ts +105 -3
- package/src/styles/canon/enums.ts +107 -8
- package/src/styles/canon/graphviz.ts +71 -10
- package/src/styles/canon/number.ts +19 -0
- package/src/styles/canon/text.ts +187 -16
- package/src/styles/codes.ts +2 -0
- package/src/styles/cyjs/cascade.ts +1303 -84
- package/src/styles/cyjs/defaults-table.ts +4 -0
- package/src/styles/cyjs/emit.ts +268 -14
- package/src/styles/cyjs/face.ts +372 -0
- package/src/styles/cyjs/mappers.ts +506 -27
- package/src/styles/cyjs/routes.ts +432 -0
- package/src/styles/cytoscape/charts.ts +684 -0
- package/src/styles/cytoscape/emit.ts +170 -48
- package/src/styles/cytoscape/lexicon.ts +431 -78
- package/src/styles/cytoscape/mappings.ts +108 -0
- package/src/styles/cytoscape/resolve.ts +741 -17
- package/src/styles/defaults.ts +417 -108
- package/src/styles/index.ts +5 -0
- package/src/styles/internal.ts +1 -0
- package/src/styles/io/generic.ts +79 -23
- package/src/styles/io/round-trip.ts +202 -0
- package/src/styles/io/writer.ts +6 -1
- package/src/styles/notes.ts +1 -0
- package/src/styles/plan/features.ts +23 -2
- package/src/styles/plan/index.ts +1 -1
- package/src/styles/plan/plan.ts +69 -5
- package/src/styles/plan/resolve.ts +47 -4
- package/src/styles/slots.ts +25 -0
- package/src/styles/types.ts +698 -5
- package/src/styles/vocabulary.ts +232 -2
- package/dist/chunks/block-Cu22ewuS.js.map +0 -1
- package/dist/chunks/choose-Cdx6Enl2.js +0 -57
- package/dist/chunks/choose-Cdx6Enl2.js.map +0 -1
- package/dist/chunks/codes-CV_Bhcyi.js.map +0 -1
- package/dist/chunks/convert-BcQ8_jMo.js.map +0 -1
- package/dist/chunks/credentials-BiQsnMTb.js.map +0 -1
- package/dist/chunks/descriptor-BJ-W0Jd-.js.map +0 -1
- package/dist/chunks/emit-CD8xnY8s.js.map +0 -1
- package/dist/chunks/export-CZ7VRm4I.js.map +0 -1
- package/dist/chunks/exporter-bxxjOrDZ.js.map +0 -1
- package/dist/chunks/format-BMS-1Nxb.js +0 -64
- package/dist/chunks/format-BMS-1Nxb.js.map +0 -1
- package/dist/chunks/generic-CP6YD34u.js.map +0 -1
- package/dist/chunks/importer-B87_fmVx.js.map +0 -1
- package/dist/chunks/importer-CB_zV5_L.js.map +0 -1
- package/dist/chunks/importer-CVT-JyDE.js.map +0 -1
- package/dist/chunks/importer-Cyy6UseH.js.map +0 -1
- package/dist/chunks/importer-DPZDN40-.js.map +0 -1
- package/dist/chunks/importer-e3huMZbm.js.map +0 -1
- package/dist/chunks/lexicon-u2SI5xcx.js.map +0 -1
- package/dist/chunks/sigv4-LkAzsLAu.js.map +0 -1
- package/dist/chunks/styles-CaABMoO1.js.map +0 -1
- package/dist/chunks/styles-P_gmFAOi.js +0 -770
- package/dist/chunks/styles-P_gmFAOi.js.map +0 -1
- package/dist/chunks/styles-yed-Cs1nmrjn.js.map +0 -1
- package/dist/chunks/writer-DvHTl36J.js +0 -449
- package/dist/chunks/writer-DvHTl36J.js.map +0 -1
|
@@ -16,27 +16,33 @@
|
|
|
16
16
|
|
|
17
17
|
import type { GraphSnapshot } from "@graphty/graph-format";
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type BendPoint,
|
|
21
|
+
bendsToPoints,
|
|
22
|
+
canonBends,
|
|
23
|
+
canonNodeOffsets,
|
|
24
|
+
nodeOffsetsToPoints,
|
|
25
|
+
} from "../../styles/canon/bends.js";
|
|
20
26
|
import { canonColor, colorOpacity } from "../../styles/canon/color.js";
|
|
21
27
|
import { canonEnum } from "../../styles/canon/enums.js";
|
|
22
|
-
import { canonNumber } from "../../styles/canon/number.js";
|
|
28
|
+
import { canonNumber, canonSigned, num6 } from "../../styles/canon/number.js";
|
|
23
29
|
import { type CanonSite, MAX_STYLE_VALUE_LENGTH, noteAt, scalarText } from "../../styles/canon/site.js";
|
|
24
30
|
import { canonText } from "../../styles/canon/text.js";
|
|
25
31
|
import { STYLE_ISSUE } from "../../styles/codes.js";
|
|
26
|
-
import { CYTOSCAPE_LEXICON } from "../../styles/cytoscape/lexicon.js";
|
|
32
|
+
import { CYTOSCAPE_LEXICON, javaFamily } from "../../styles/cytoscape/lexicon.js";
|
|
27
33
|
import { StyleApproximations } from "../../styles/io/approximations.js";
|
|
28
34
|
import {
|
|
29
|
-
blockStyles,
|
|
30
35
|
parseStyleBlock,
|
|
31
36
|
STYLE_BLOCK_NAME,
|
|
32
37
|
styleBlockLosses,
|
|
33
38
|
styleTag,
|
|
34
39
|
writeStyleBlock,
|
|
35
40
|
} from "../../styles/io/block.js";
|
|
41
|
+
import { elementValues, takeFromBlock } from "../../styles/io/round-trip.js";
|
|
36
42
|
import { dataRead, type ExporterStyleInput } from "../../styles/io/writer.js";
|
|
37
43
|
import { StyleNotes } from "../../styles/notes.js";
|
|
38
44
|
import { styleFingerprint } from "../../styles/plan/features.js";
|
|
39
|
-
import { plainCell
|
|
45
|
+
import { plainCell } from "../../styles/plan/resolve.js";
|
|
40
46
|
import type {
|
|
41
47
|
ImporterStyles,
|
|
42
48
|
StyleColumn,
|
|
@@ -48,9 +54,19 @@ import type {
|
|
|
48
54
|
StyleWriteCapabilities,
|
|
49
55
|
} from "../../styles/types.js";
|
|
50
56
|
import type { LossNote } from "../../types.js";
|
|
57
|
+
import { GRAPHVIZ_GLYPH_SHAPES } from "../dot/drawing.js";
|
|
58
|
+
import {
|
|
59
|
+
graphvizArrowValue,
|
|
60
|
+
graphvizHasNoOutline,
|
|
61
|
+
graphvizOutline,
|
|
62
|
+
graphvizRouteBends,
|
|
63
|
+
PERIPHERY_GAP,
|
|
64
|
+
putGlyph,
|
|
65
|
+
SHAPE_PERIPHERIES,
|
|
66
|
+
} from "../dot/styles.js";
|
|
51
67
|
import { gephiRules } from "../gexf/styles.js";
|
|
52
68
|
import {
|
|
53
|
-
|
|
69
|
+
configurationOutline,
|
|
54
70
|
FONT_STYLES,
|
|
55
71
|
type ValueColumns,
|
|
56
72
|
valueColumns,
|
|
@@ -58,16 +74,18 @@ import {
|
|
|
58
74
|
YED_LINES,
|
|
59
75
|
YED_SHAPES,
|
|
60
76
|
yedColor,
|
|
77
|
+
yedFontName,
|
|
61
78
|
yedSpelled,
|
|
62
79
|
yedValue,
|
|
63
80
|
} from "../graphml/styles-yed.js";
|
|
81
|
+
import { writtenByGv2gml } from "./positions.js";
|
|
64
82
|
import { KEY_MANGLED_CODE, mangleGmlKey } from "./syntax.js";
|
|
65
83
|
|
|
66
84
|
/** A GML record as the importer keeps it (a json cell). */
|
|
67
85
|
type Rec = Record<string, unknown>;
|
|
68
86
|
|
|
69
87
|
/** The key vocabulary a file's graphics are read with. */
|
|
70
|
-
type Vocabulary = "yed" | "cytoscape" | "ogdf" | "graphlet";
|
|
88
|
+
type Vocabulary = "yed" | "cytoscape" | "ogdf" | "graphlet" | "graphviz";
|
|
71
89
|
|
|
72
90
|
/** Who wrote a GML file, as far as its styles go. */
|
|
73
91
|
interface GmlWriter {
|
|
@@ -83,6 +101,8 @@ interface GmlWriter {
|
|
|
83
101
|
readonly units: "px" | "relative";
|
|
84
102
|
/** Whether it draws the `label` key of an element that has graphics (the GML draft does; yEd and Gephi do not). */
|
|
85
103
|
readonly labelKey: boolean;
|
|
104
|
+
/** Whether the graph is directed, where its edges' arrows depend on it (gv2gml's). */
|
|
105
|
+
readonly directed?: boolean;
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
/** The writers recognized by their `Creator` text, first match wins. */
|
|
@@ -110,7 +130,8 @@ const WRITERS: readonly (readonly [RegExp, GmlWriter])[] = [
|
|
|
110
130
|
vocabulary: "graphlet",
|
|
111
131
|
spelling: "css",
|
|
112
132
|
units: "relative",
|
|
113
|
-
|
|
133
|
+
// Gephi's preview draws node labels as the GEXF reader reads them (formats/gexf/styles.ts)
|
|
134
|
+
labelKey: true,
|
|
114
135
|
},
|
|
115
136
|
],
|
|
116
137
|
[
|
|
@@ -134,6 +155,19 @@ const UNKNOWN_WRITER: GmlWriter = {
|
|
|
134
155
|
labelKey: true,
|
|
135
156
|
};
|
|
136
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Graphviz's gv2gml (no `Creator`; `version 2` inside the graph): Graphviz's attribute values (shape names, colors,
|
|
160
|
+
* arrow names, sizes in points, y up) under GML keys, with Graphviz's defaults beneath.
|
|
161
|
+
*/
|
|
162
|
+
const GV2GML_WRITER: GmlWriter = {
|
|
163
|
+
dialect: "graphviz",
|
|
164
|
+
tool: "graphviz",
|
|
165
|
+
vocabulary: "graphviz",
|
|
166
|
+
spelling: "graphviz",
|
|
167
|
+
units: "px",
|
|
168
|
+
labelKey: true,
|
|
169
|
+
};
|
|
170
|
+
|
|
137
171
|
/** A file graphty wrote: yEd keys, and the `graphty.styles` block gives the rules. */
|
|
138
172
|
const GRAPHTY_WRITER: GmlWriter = { ...WRITERS[0][1], tool: null };
|
|
139
173
|
|
|
@@ -175,8 +209,29 @@ function graphText(snapshot: GraphSnapshot, name: string): string | null {
|
|
|
175
209
|
}
|
|
176
210
|
|
|
177
211
|
/**
|
|
178
|
-
*
|
|
179
|
-
* the
|
|
212
|
+
* A node label's `anchor` as a label position: yEd's `n`, `s`, `e`, `w` and the corners `nw`, `ne`, `sw`, `se` place it
|
|
213
|
+
* outside the node; `c` and the inside places `t`, `b`, `l`, `r`, `tl`, `tr`, `bl`, `br` keep it inside, which graphty
|
|
214
|
+
* draws centered.
|
|
215
|
+
*/
|
|
216
|
+
const GML_ANCHORS: ReadonlyMap<string, string> = new Map([
|
|
217
|
+
...["c", "t", "b", "l", "r", "tl", "tr", "bl", "br"].map((a): [string, string] => [a, "center"]),
|
|
218
|
+
["n", "top"],
|
|
219
|
+
["s", "bottom"],
|
|
220
|
+
["e", "right"],
|
|
221
|
+
["w", "left"],
|
|
222
|
+
["nw", "top-left"],
|
|
223
|
+
["ne", "top-right"],
|
|
224
|
+
["sw", "bottom-left"],
|
|
225
|
+
["se", "bottom-right"],
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
/** Node keys only yEd writes: its group hierarchy. */
|
|
229
|
+
const YED_NODE_KEYS: readonly string[] = ["isGroup", "gid"];
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Who wrote a GML file: the `Creator` line (top level, or a key of the graph block as Gephi writes it), graphty when
|
|
233
|
+
* the file holds a `graphty.styles` block, or yEd when a file with no `Creator` holds keys only yEd writes (`isGroup`,
|
|
234
|
+
* `gid`, the graph's `hierarchic`).
|
|
180
235
|
* @param snapshot - the graph
|
|
181
236
|
* @returns the writer
|
|
182
237
|
*/
|
|
@@ -190,7 +245,14 @@ function writerOf(snapshot: GraphSnapshot): GmlWriter {
|
|
|
190
245
|
return writer;
|
|
191
246
|
}
|
|
192
247
|
}
|
|
193
|
-
|
|
248
|
+
// keys only yEd writes name it even without a Creator line (BioNetGen writes yEd GML with none)
|
|
249
|
+
if (
|
|
250
|
+
creator.trim() === "" &&
|
|
251
|
+
(YED_NODE_KEYS.some((k) => snapshot.nodes.has(k)) || snapshot.graph.has("hierarchic"))
|
|
252
|
+
) {
|
|
253
|
+
return WRITERS[0][1];
|
|
254
|
+
}
|
|
255
|
+
return creator.trim() === "" && writtenByGv2gml(snapshot) ? GV2GML_WRITER : UNKNOWN_WRITER;
|
|
194
256
|
}
|
|
195
257
|
|
|
196
258
|
// ============================================================ reading
|
|
@@ -209,19 +271,68 @@ const ARROW_ENDS: ReadonlyMap<string, readonly [string, string]> = new Map([
|
|
|
209
271
|
["both", ["triangle", "triangle"]],
|
|
210
272
|
]);
|
|
211
273
|
|
|
212
|
-
/**
|
|
274
|
+
/**
|
|
275
|
+
* The node shape names Cytoscape's GML reader knows (NodeShapeVisualProperty, any case, with Cytoscape 2.x's `rect`
|
|
276
|
+
* and `round_rect`); any other name it ignores, so its default style's shape is drawn.
|
|
277
|
+
*/
|
|
278
|
+
const CYTOSCAPE_GML_SHAPES: ReadonlyMap<string, string> = new Map([
|
|
279
|
+
["ellipse", "ellipse"],
|
|
280
|
+
["round_rectangle", "round-rectangle"],
|
|
281
|
+
["round_rect", "round-rectangle"],
|
|
282
|
+
["rectangle", "rectangle"],
|
|
283
|
+
["rect", "rectangle"],
|
|
284
|
+
["hexagon", "hexagon"],
|
|
285
|
+
["octagon", "octagon"],
|
|
286
|
+
["parallelogram", "parallelogram"],
|
|
287
|
+
["triangle", "triangle"],
|
|
288
|
+
["vee", "vee"],
|
|
289
|
+
["diamond", "diamond"],
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
/** OGDF's stroke types (`stipple`): by number as older OGDF writes them, or by name as GraphIO::writeGML does. */
|
|
213
293
|
const OGDF_STROKES: readonly string[] = ["none", "solid", "dashed", "dotted", "dash-dot", "dash-dot"];
|
|
294
|
+
const OGDF_STROKE_NAMES: ReadonlyMap<string, string> = new Map([
|
|
295
|
+
["none", "none"],
|
|
296
|
+
["solid", "solid"],
|
|
297
|
+
["dash", "dashed"],
|
|
298
|
+
["dot", "dotted"],
|
|
299
|
+
["dashdot", "dash-dot"],
|
|
300
|
+
["dashdotdot", "dash-dot"],
|
|
301
|
+
]);
|
|
214
302
|
|
|
215
|
-
/** OGDF's shape names the common table does not know. */
|
|
303
|
+
/** OGDF's shape names the common table does not know (an Image node without its image draws as its box). */
|
|
216
304
|
const OGDF_SHAPES: ReadonlyMap<string, string> = new Map([
|
|
217
305
|
["rect", "rectangle"],
|
|
306
|
+
["roundedrect", "round-rectangle"],
|
|
218
307
|
["rhomb", "diamond"],
|
|
219
|
-
["
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
308
|
+
["image", "rectangle"],
|
|
309
|
+
]);
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* OGDF's shapes that GraphIO::drawSVG draws as polygons no named shape matches, with their corners as `shapePoints`
|
|
313
|
+
* (fractions of the half width and height, y up), measured in its drawing of
|
|
314
|
+
* graph-io/test/fidelity/specimens/gml/specimen-node-shapes-p5.gml: the triangles, the pentagon, the hexagon and the
|
|
315
|
+
* octagon are regular, their corners on the ellipse of the node's box (so they are drawn where the points say, not
|
|
316
|
+
* stretched to the box: `OGDF_REGULAR`); the trapezes and parallelograms fill the box, their short side half its width.
|
|
317
|
+
*/
|
|
318
|
+
const OGDF_POLYGONS: ReadonlyMap<string, string> = new Map([
|
|
319
|
+
["triangle", "0,1 0.866,-0.5 -0.866,-0.5"],
|
|
320
|
+
["invtriangle", "0,-1 0.866,0.5 -0.866,0.5"],
|
|
321
|
+
["pentagon", "0,1 0.951,0.309 0.588,-0.809 -0.588,-0.809 -0.951,0.309"],
|
|
322
|
+
["hexagon", "1,0 0.5,0.866 -0.5,0.866 -1,0 -0.5,-0.866 0.5,-0.866"],
|
|
323
|
+
[
|
|
324
|
+
"octagon",
|
|
325
|
+
"0.924,0.383 0.383,0.924 -0.383,0.924 -0.924,0.383 -0.924,-0.383 -0.383,-0.924 0.383,-0.924 0.924,-0.383",
|
|
326
|
+
],
|
|
327
|
+
["trapeze", "-1,-1 1,-1 0.5,1 -0.5,1"],
|
|
328
|
+
["invtrapeze", "-1,1 1,1 0.5,-1 -0.5,-1"],
|
|
329
|
+
["parallelogram", "-1,-1 0.5,-1 1,1 -0.5,1"],
|
|
330
|
+
["invparallelogram", "-1,1 0.5,1 1,-1 -0.5,-1"],
|
|
223
331
|
]);
|
|
224
332
|
|
|
333
|
+
/** OGDF's regular polygons, whose corners lie on the ellipse of the node's box rather than reaching its sides. */
|
|
334
|
+
const OGDF_REGULAR: ReadonlySet<string> = new Set(["triangle", "invtriangle", "pentagon", "hexagon", "octagon"]);
|
|
335
|
+
|
|
225
336
|
/** Keys only yEd writes in a label record: its `fill` is the label's background, not the text color. */
|
|
226
337
|
const YED_LABEL_KEYS: readonly string[] = [
|
|
227
338
|
"outline",
|
|
@@ -308,16 +419,48 @@ function strokeStyle(raw: unknown, kind: "borderStyle" | "lineStyle", writer: Gm
|
|
|
308
419
|
return null;
|
|
309
420
|
}
|
|
310
421
|
if (writer.vocabulary === "ogdf") {
|
|
311
|
-
const stroke =
|
|
312
|
-
if (stroke === undefined
|
|
422
|
+
const stroke = ogdfStroke(raw);
|
|
423
|
+
if (stroke === undefined) {
|
|
313
424
|
noteAt(at, STYLE_ISSUE.UNKNOWN_VALUE, sampleOf(raw));
|
|
314
425
|
return null;
|
|
315
426
|
}
|
|
316
|
-
|
|
427
|
+
// stroke "none" draws no line at all: the callers turn the border or the edge off
|
|
428
|
+
return stroke === "none" ? null : stroke;
|
|
317
429
|
}
|
|
318
430
|
return canonEnum(kind, raw, writer.spelling, at);
|
|
319
431
|
}
|
|
320
432
|
|
|
433
|
+
/**
|
|
434
|
+
* An OGDF `stipple`: a StrokeType by number or by name.
|
|
435
|
+
* @param raw - the value
|
|
436
|
+
* @returns the line style, "none", or undefined for a value OGDF does not know
|
|
437
|
+
*/
|
|
438
|
+
function ogdfStroke(raw: unknown): string | undefined {
|
|
439
|
+
const text = typeof raw === "string" || typeof raw === "number" ? String(raw).trim() : "";
|
|
440
|
+
return /^\d+$/.test(text) ? OGDF_STROKES[Number(text)] : OGDF_STROKE_NAMES.get(text.toLowerCase());
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Reads an OGDF node `type` (a Shape name): a polygon where OGDF draws one, else the shape it names.
|
|
445
|
+
* @param raw - the value
|
|
446
|
+
* @param writer - the writer
|
|
447
|
+
* @param put - receives the values
|
|
448
|
+
* @param at - the sites
|
|
449
|
+
*/
|
|
450
|
+
function readOgdfShape(raw: string, writer: GmlWriter, put: Put, at: At): void {
|
|
451
|
+
const name = raw.trim().toLowerCase();
|
|
452
|
+
const points = OGDF_POLYGONS.get(name);
|
|
453
|
+
if (points !== undefined) {
|
|
454
|
+
put("shape", "polygon");
|
|
455
|
+
put("shapePoints", points);
|
|
456
|
+
if (OGDF_REGULAR.has(name)) {
|
|
457
|
+
put("shapePointsStretch", false);
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
put("shape", canonEnum("shape", OGDF_SHAPES.get(name) ?? raw, writer.spelling, at("shape")));
|
|
462
|
+
}
|
|
463
|
+
|
|
321
464
|
/**
|
|
322
465
|
* Reads one node's `graphics` record.
|
|
323
466
|
* @param g - the record
|
|
@@ -327,21 +470,198 @@ function strokeStyle(raw: unknown, kind: "borderStyle" | "lineStyle", writer: Gm
|
|
|
327
470
|
*/
|
|
328
471
|
function readNodeGraphics(g: Rec, writer: GmlWriter, put: Put, at: At): void {
|
|
329
472
|
const v = writer.vocabulary;
|
|
473
|
+
if (v === "graphviz") {
|
|
474
|
+
readGv2gmlNode(g, writer, put, at);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
330
477
|
readNodeFill(g, writer, put, at);
|
|
331
478
|
readNodeOutline(g, writer, put, at);
|
|
332
479
|
put(
|
|
333
480
|
"borderStyle",
|
|
334
481
|
strokeStyle(v === "ogdf" ? g.stipple : g.outlineStyle, "borderStyle", writer, at("borderStyle")),
|
|
335
482
|
);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
483
|
+
if (v === "ogdf" && g.stipple !== undefined && ogdfStroke(g.stipple) === "none") {
|
|
484
|
+
put("borderWidth", 0);
|
|
485
|
+
}
|
|
486
|
+
readNodeSize(g, writer, put, at);
|
|
487
|
+
if (v === "ogdf") {
|
|
488
|
+
readOgdfLabelOffset(g.label, put, at);
|
|
489
|
+
}
|
|
490
|
+
readNodeShape(g, writer, put, at);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Reads a node's size from its `graphics` record.
|
|
495
|
+
* @param g - the record
|
|
496
|
+
* @param writer - the writer
|
|
497
|
+
* @param put - receives the values
|
|
498
|
+
* @param at - the sites
|
|
499
|
+
*/
|
|
500
|
+
function readNodeSize(g: Rec, writer: GmlWriter, put: Put, at: At): void {
|
|
501
|
+
if (writer.tool === "gephi") {
|
|
502
|
+
// Gephi's w is a node's size, a radius: its disc is twice as wide, and its h is never read
|
|
503
|
+
const size = g.w === undefined ? null : canonNumber(g.w, at("width"));
|
|
504
|
+
put("width", size === null ? null : 2 * size);
|
|
505
|
+
put("height", size === null ? null : 2 * size);
|
|
506
|
+
} else {
|
|
507
|
+
put("width", g.w === undefined ? null : canonNumber(g.w, at("width")));
|
|
508
|
+
put("height", g.h === undefined ? null : canonNumber(g.h, at("height")));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Reads a node's shape from its `graphics` record, in the writer's vocabulary.
|
|
514
|
+
* @param g - the record
|
|
515
|
+
* @param writer - the writer
|
|
516
|
+
* @param put - receives the values
|
|
517
|
+
* @param at - the sites
|
|
518
|
+
*/
|
|
519
|
+
function readNodeShape(g: Rec, writer: GmlWriter, put: Put, at: At): void {
|
|
520
|
+
const v = writer.vocabulary;
|
|
521
|
+
if (v === "ogdf" && typeof g.type === "string") {
|
|
522
|
+
readOgdfShape(g.type, writer, put, at);
|
|
523
|
+
} else if (v === "cytoscape" && g.type !== undefined) {
|
|
524
|
+
const name = scalarText(g.type, at("shape"))?.trim().toLowerCase() ?? "";
|
|
525
|
+
const shape = CYTOSCAPE_GML_SHAPES.get(name);
|
|
526
|
+
if (shape === undefined) {
|
|
527
|
+
noteAt(at("shape"), STYLE_ISSUE.UNKNOWN_VALUE, name.slice(0, MAX_STYLE_VALUE_LENGTH));
|
|
528
|
+
}
|
|
529
|
+
put("shape", shape ?? null);
|
|
530
|
+
} else if (g.type !== undefined) {
|
|
531
|
+
put("shape", canonEnum("shape", g.type, writer.spelling, at("shape")));
|
|
342
532
|
} else if (v === "yed" && typeof g.customconfiguration === "string") {
|
|
343
533
|
// yEd's flowchart palette keeps a node's shape as a configuration, not a `type`.
|
|
344
|
-
|
|
534
|
+
const outline = configurationOutline(g.customconfiguration);
|
|
535
|
+
put("shape", outline?.shape ?? null);
|
|
536
|
+
if (outline?.points !== undefined) {
|
|
537
|
+
put("shapePoints", outline.points);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Reads where OGDF draws a node's label: GraphIO::writeGML writes the label's center as an offset from the node's
|
|
544
|
+
* center (`graphics [ label [ x y z ] ]`, GraphAttributes::xLabel / yLabel, y down), and GraphIO::drawSVG draws it there.
|
|
545
|
+
* @param label - the graphics record's `label` value
|
|
546
|
+
* @param put - receives the values
|
|
547
|
+
* @param at - the sites
|
|
548
|
+
*/
|
|
549
|
+
function readOgdfLabelOffset(label: unknown, put: Put, at: At): void {
|
|
550
|
+
if (typeof label !== "object" || label === null || Array.isArray(label)) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const { x, y } = label as Rec;
|
|
554
|
+
put("labelOffsetX", x === undefined ? null : canonSigned(x, at("labelOffsetX")));
|
|
555
|
+
put("labelOffsetY", y === undefined ? null : canonSigned(y, at("labelOffsetY")));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Reads one node's `graphics` record as Graphviz's gv2gml writes it (cmd/tools/gv2gml.c emitNodeAttrs): `fill` is the
|
|
560
|
+
* node's `fillcolor` (or its `color`), drawn only where `hasFill 1` says `style=filled`; `outline` its pen color,
|
|
561
|
+
* `width` its pen width, `w` and `H` its size in points, `type` its Graphviz shape, `visible 0` its `style=invis`.
|
|
562
|
+
* @param g - the record
|
|
563
|
+
* @param writer - the writer
|
|
564
|
+
* @param put - receives the values
|
|
565
|
+
* @param at - the sites
|
|
566
|
+
*/
|
|
567
|
+
function readGv2gmlNode(g: Rec, writer: GmlWriter, put: Put, at: At): void {
|
|
568
|
+
const shape = typeof g.type === "string" ? g.type.trim().toLowerCase() : "";
|
|
569
|
+
if (Number(g.hasFill) === 1 || shape === "point") {
|
|
570
|
+
// filled: the opacity is stated even when opaque, over Graphviz's default of no fill
|
|
571
|
+
const c = canonColor(g.fill ?? "black", writer.spelling, at("fill"), { use: "fill" });
|
|
572
|
+
put("fill", c?.color ?? null);
|
|
573
|
+
put("fillOpacity", c?.alpha ?? null);
|
|
574
|
+
} else if (g.fill !== undefined) {
|
|
575
|
+
// Graphviz draws an unfilled node's inside see-through (the tool default), whatever its color
|
|
576
|
+
noteAt(at("fill"), STYLE_ISSUE.UNFILLED, sampleOf(g.fill));
|
|
577
|
+
}
|
|
578
|
+
if (graphvizHasNoOutline(shape)) {
|
|
579
|
+
put("borderWidth", 0);
|
|
580
|
+
} else {
|
|
581
|
+
readColor(g.outline, "borderColor", null, writer, put, at);
|
|
582
|
+
put("borderWidth", g.width === undefined ? null : canonNumber(g.width, at("borderWidth")));
|
|
583
|
+
}
|
|
584
|
+
const borderStyle = strokeStyle(g.outlineStyle, "borderStyle", writer, at("borderStyle"));
|
|
585
|
+
put("borderStyle", borderStyle);
|
|
586
|
+
readGv2gmlRings(g, shape, borderStyle, writer, put, at);
|
|
587
|
+
// gv2gml's w and H are the node's outer size: Graphviz draws each further outline PERIPHERY_GAP outside the one
|
|
588
|
+
// within, so graphty's node (the innermost outline, its ring outside it) is that much smaller
|
|
589
|
+
const rings = 2 * PERIPHERY_GAP * ((SHAPE_PERIPHERIES.get(shape) ?? 1) - 1);
|
|
590
|
+
const inner = (raw: unknown, property: string): number | null => {
|
|
591
|
+
const n = raw === undefined ? null : canonNumber(raw, at(property));
|
|
592
|
+
return n === null ? null : Math.max(0, n - rings);
|
|
593
|
+
};
|
|
594
|
+
put("width", inner(g.w, "width"));
|
|
595
|
+
// gv2gml writes the height as H (gv2gml.c emitReal("H", ...)); h where a later edit spelled it as GML does
|
|
596
|
+
put("height", inner(first(g, "H", "h"), "height"));
|
|
597
|
+
if (Number(g.visible) === 0 && g.visible !== undefined) {
|
|
598
|
+
put("visible", false);
|
|
599
|
+
}
|
|
600
|
+
const outline = graphvizOutline(shape);
|
|
601
|
+
if (shape === "polygon") {
|
|
602
|
+
// ponytail: Graphviz's polygon is a box unless its sides, skew or distortion say otherwise; gv2gml copies
|
|
603
|
+
// those as node keys beside graphics, which this record does not see. Read them when a file has them.
|
|
604
|
+
put("shape", "rectangle");
|
|
605
|
+
} else if (outline !== undefined) {
|
|
606
|
+
put("shape", "polygon");
|
|
607
|
+
put("shapePoints", outline);
|
|
608
|
+
// a Graphviz polygon's corners are where Graphviz draws them, as the DOT reader reads them
|
|
609
|
+
put("shapePointsStretch", false);
|
|
610
|
+
} else if (GRAPHVIZ_GLYPH_SHAPES.has(shape)) {
|
|
611
|
+
const size = (raw: unknown, property: string): number | null =>
|
|
612
|
+
raw === undefined ? null : canonNumber(raw, at(property));
|
|
613
|
+
putGlyph(
|
|
614
|
+
shape,
|
|
615
|
+
size(g.w, "width"),
|
|
616
|
+
size(first(g, "H", "h"), "height"),
|
|
617
|
+
put,
|
|
618
|
+
() => {
|
|
619
|
+
const pen = g.width === undefined ? 1 : Number(g.width);
|
|
620
|
+
return Number.isFinite(pen) ? Math.max(0, pen) : 1;
|
|
621
|
+
},
|
|
622
|
+
() =>
|
|
623
|
+
canonColor(g.outline ?? "black", writer.spelling, at("outerBorderColor"), { use: "line" })?.color ??
|
|
624
|
+
null,
|
|
625
|
+
);
|
|
626
|
+
} else if (g.type !== undefined) {
|
|
627
|
+
put("shape", canonEnum("shape", g.type, "graphviz", at("shape")));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Reads the second outline Graphviz draws round a `doublecircle`, `doubleoctagon` or `tripleoctagon` (shapes.c):
|
|
633
|
+
* `PERIPHERY_GAP` points outside the first, in its color, width and dashes, as the DOT reader reads it. graphty draws
|
|
634
|
+
* one outer ring, so a third outline is noted as not carried.
|
|
635
|
+
* @param g - the record
|
|
636
|
+
* @param shape - its Graphviz shape, lowercase
|
|
637
|
+
* @param style - its outline's dashes, as read
|
|
638
|
+
* @param writer - the writer
|
|
639
|
+
* @param put - receives the values
|
|
640
|
+
* @param at - the sites
|
|
641
|
+
*/
|
|
642
|
+
function readGv2gmlRings(g: Rec, shape: string, style: string | null, writer: GmlWriter, put: Put, at: At): void {
|
|
643
|
+
// ponytail: gv2gml copies a `peripheries` attribute as a node key beside graphics, which this record does not
|
|
644
|
+
// see; the shapes' own outlines are read. Read the key when a file sets it.
|
|
645
|
+
const count = SHAPE_PERIPHERIES.get(shape) ?? 1;
|
|
646
|
+
if (count < 2) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const c = canonColor(g.outline ?? "black", writer.spelling, at("outerBorderColor"), { use: "line" });
|
|
650
|
+
if (c !== null && c.alpha === 0) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const parsed = g.width === undefined ? 1 : Number(g.width);
|
|
654
|
+
const pen = Number.isFinite(parsed) ? Math.max(0, parsed) : 1;
|
|
655
|
+
put("outerBorderColor", c?.color ?? "#000000");
|
|
656
|
+
put("outerBorderOpacity", c !== null && c.alpha < 1 ? c.alpha : null);
|
|
657
|
+
put("outerBorderWidth", pen);
|
|
658
|
+
// the gap between the outline's outer edge and the ring's inner edge
|
|
659
|
+
put("outerBorderOffset", Math.max(0, PERIPHERY_GAP - pen));
|
|
660
|
+
if (style === "dashed" || style === "dotted") {
|
|
661
|
+
put("outerBorderStyle", style);
|
|
662
|
+
}
|
|
663
|
+
if (count > 2) {
|
|
664
|
+
noteAt(at("outerBorderWidth"), STYLE_ISSUE.NOT_CARRIED, `type ${shape}`);
|
|
345
665
|
}
|
|
346
666
|
}
|
|
347
667
|
|
|
@@ -377,7 +697,8 @@ function readNodeOutline(g: Rec, writer: GmlWriter, put: Put, at: At): void {
|
|
|
377
697
|
put("borderWidth", 0);
|
|
378
698
|
return;
|
|
379
699
|
}
|
|
380
|
-
|
|
700
|
+
// OGDF's GraphIO::writeGML writes the stroke color as `outline`; older OGDF wrote `line`
|
|
701
|
+
readColor(v === "ogdf" ? first(g, "outline", "line") : g.outline, "borderColor", null, writer, put, at);
|
|
381
702
|
const width = outlineWidthOf(g, v);
|
|
382
703
|
put("borderWidth", width === undefined ? null : canonNumber(width, at("borderWidth")));
|
|
383
704
|
}
|
|
@@ -410,10 +731,32 @@ function outlineWidthOf(g: Rec, v: GmlWriter["vocabulary"]): unknown {
|
|
|
410
731
|
*/
|
|
411
732
|
function readEdgeGraphics(g: Rec, writer: GmlWriter, put: Put, at: At): void {
|
|
412
733
|
const v = writer.vocabulary;
|
|
734
|
+
if (v === "graphviz") {
|
|
735
|
+
readGv2gmlEdge(g, writer, put, at);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
413
738
|
readColor(g.fill, "color", "opacity", writer, put, at);
|
|
414
739
|
const width = v === "ogdf" ? first(g, "lineWidth", "width") : g.width;
|
|
415
740
|
put("width", width === undefined ? null : canonNumber(width, at("width")));
|
|
416
741
|
put("lineStyle", strokeStyle(v === "ogdf" ? g.stipple : g.style, "lineStyle", writer, at("lineStyle")));
|
|
742
|
+
if (v === "ogdf" && g.stipple !== undefined && ogdfStroke(g.stipple) === "none") {
|
|
743
|
+
put("visible", false);
|
|
744
|
+
}
|
|
745
|
+
readEdgeArrows(g, writer, put, at);
|
|
746
|
+
if (v === "yed") {
|
|
747
|
+
readYedCurve(g, put);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Reads an edge's arrows from its `graphics` record: `arrow` names the ends that have one, and `sourceArrow` and
|
|
753
|
+
* `targetArrow` (or their snake-case spellings) name each end's arrow.
|
|
754
|
+
* @param g - the record
|
|
755
|
+
* @param writer - the writer
|
|
756
|
+
* @param put - receives the values
|
|
757
|
+
* @param at - the sites
|
|
758
|
+
*/
|
|
759
|
+
function readEdgeArrows(g: Rec, writer: GmlWriter, put: Put, at: At): void {
|
|
417
760
|
if (typeof g.arrow === "string") {
|
|
418
761
|
const ends = ARROW_ENDS.get(g.arrow.trim().toLowerCase());
|
|
419
762
|
if (ends === undefined) {
|
|
@@ -433,14 +776,72 @@ function readEdgeGraphics(g: Rec, writer: GmlWriter, put: Put, at: At): void {
|
|
|
433
776
|
}
|
|
434
777
|
put(
|
|
435
778
|
property,
|
|
436
|
-
|
|
779
|
+
writer.vocabulary === "cytoscape" && cytoscapeArrow !== undefined
|
|
437
780
|
? cytoscapeArrow(raw, at(property))
|
|
438
781
|
: canonEnum("arrow", raw, writer.spelling, at(property)),
|
|
439
782
|
);
|
|
440
783
|
}
|
|
441
|
-
|
|
442
|
-
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/** gv2gml's `arrow`, from Graphviz's `dir`: `first` is `dir=forward` (the head's arrow), `last` is `dir=back`. */
|
|
787
|
+
const GV2GML_DIRECTIONS: ReadonlyMap<string, readonly [boolean, boolean]> = new Map([
|
|
788
|
+
["first", [false, true]],
|
|
789
|
+
["last", [true, false]],
|
|
790
|
+
["both", [true, true]],
|
|
791
|
+
["none", [false, false]],
|
|
792
|
+
]);
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Reads one edge's `graphics` record as Graphviz's gv2gml writes it (gv2gml.c emitEdgeAttrs): `fill` its color,
|
|
796
|
+
* `width` its pen width (2 for `style=bold`), `style` its dashes, `visible 0` its `style=invis`, `targetArrow` and
|
|
797
|
+
* `sourceArrow` its `arrowhead` and `arrowtail`, and `arrow` its `dir`. With none of the arrow keys an edge of a
|
|
798
|
+
* directed graph has Graphviz's default arrowhead, and one of an undirected graph none.
|
|
799
|
+
* @param g - the record
|
|
800
|
+
* @param writer - the writer
|
|
801
|
+
* @param put - receives the values
|
|
802
|
+
* @param at - the sites
|
|
803
|
+
*/
|
|
804
|
+
function readGv2gmlEdge(g: Rec, writer: GmlWriter, put: Put, at: At): void {
|
|
805
|
+
readColor(g.fill, "color", "opacity", writer, put, at);
|
|
806
|
+
put("width", g.width === undefined ? null : canonNumber(g.width, at("width")));
|
|
807
|
+
put("lineStyle", strokeStyle(g.style, "lineStyle", writer, at("lineStyle")));
|
|
808
|
+
if (Number(g.visible) === 0 && g.visible !== undefined) {
|
|
809
|
+
put("visible", false);
|
|
810
|
+
}
|
|
811
|
+
const ends = gv2gmlArrowEnds(g, writer.directed === true);
|
|
812
|
+
if (ends === null) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
for (const [end, raw, on] of [
|
|
816
|
+
["target", g.targetArrow, ends[1]],
|
|
817
|
+
["source", g.sourceArrow, ends[0]],
|
|
818
|
+
] as const) {
|
|
819
|
+
const name = typeof raw === "string" ? raw : "normal";
|
|
820
|
+
const arrow = on ? graphvizArrowValue(name, at(`${end}Arrow`)) : null;
|
|
821
|
+
put(`${end}Arrow`, arrow === null ? "none" : arrow.value);
|
|
822
|
+
if (arrow?.side !== null && arrow?.side !== undefined) {
|
|
823
|
+
put(`${end}ArrowSide`, arrow.side);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Which ends of a gv2gml edge Graphviz draws an arrow at, as its `dir` says (forward in a directed graph when the
|
|
830
|
+
* record names no arrow), or null for an undirected graph's edge that names none.
|
|
831
|
+
* @param g - the edge's `graphics` record
|
|
832
|
+
* @param directed - whether the graph is directed (its `dir` defaults to forward)
|
|
833
|
+
* @returns [source, target], or null
|
|
834
|
+
*/
|
|
835
|
+
function gv2gmlArrowEnds(g: Rec, directed: boolean): readonly [boolean, boolean] | null {
|
|
836
|
+
if (g.arrow === undefined && g.targetArrow === undefined && g.sourceArrow === undefined) {
|
|
837
|
+
// Graphviz's dir is forward in a digraph and its arrowhead normal: gv2gml writes neither when unset
|
|
838
|
+
return directed ? [false, true] : null;
|
|
839
|
+
}
|
|
840
|
+
let dir = directed ? "first" : "none";
|
|
841
|
+
if (typeof g.arrow === "string") {
|
|
842
|
+
dir = g.arrow.trim().toLowerCase();
|
|
443
843
|
}
|
|
844
|
+
return GV2GML_DIRECTIONS.get(dir) ?? null;
|
|
444
845
|
}
|
|
445
846
|
|
|
446
847
|
/**
|
|
@@ -460,10 +861,11 @@ function readYedCurve(g: Rec, put: Put): void {
|
|
|
460
861
|
* Reads an element's label records: the first visible one with text; further visible ones are noted.
|
|
461
862
|
* @param raw - the `LabelGraphics` value (one record or several)
|
|
462
863
|
* @param writer - the writer
|
|
864
|
+
* @param node - whether the label is a node's: its `anchor` places it (`c`, `n`, `t`, `se`, ...)
|
|
463
865
|
* @param put - receives the values
|
|
464
866
|
* @param at - the sites
|
|
465
867
|
*/
|
|
466
|
-
function readLabelGraphics(raw: unknown, writer: GmlWriter, put: Put, at: At): void {
|
|
868
|
+
function readLabelGraphics(raw: unknown, writer: GmlWriter, node: boolean, put: Put, at: At): void {
|
|
467
869
|
const visible = (Array.isArray(raw) ? raw : [raw]).filter((l): l is Rec => isRec(l) && Number(l.visible) !== 0);
|
|
468
870
|
const lg = visible.at(0);
|
|
469
871
|
if (lg === undefined) {
|
|
@@ -474,30 +876,57 @@ function readLabelGraphics(raw: unknown, writer: GmlWriter, put: Put, at: At): v
|
|
|
474
876
|
noteAt(at("label"), STYLE_ISSUE.EXTRA_LABELS, `${String(extra + 1)} labels`);
|
|
475
877
|
}
|
|
476
878
|
if (lg.text !== undefined) {
|
|
477
|
-
put(
|
|
879
|
+
put(
|
|
880
|
+
"label",
|
|
881
|
+
writer.vocabulary === "graphviz" ? graphvizText(lg.text, at("label")) : canonText(lg.text, at("label")),
|
|
882
|
+
);
|
|
478
883
|
}
|
|
479
884
|
// the GML draft's `fill` is the text color; yEd's is the label's background box, its `color` the text color
|
|
480
885
|
const yedLabel = writer.vocabulary === "yed" || YED_LABEL_KEYS.some((k) => Object.hasOwn(lg, k));
|
|
481
|
-
if (lg.color !== undefined) {
|
|
482
|
-
|
|
886
|
+
if (lg.color !== undefined || lg.fontColor !== undefined) {
|
|
887
|
+
// gv2gml writes a label's fontcolor as fontColor (as color with -y); yEd's #RRGGBBAA alpha is the words'
|
|
888
|
+
readColor(lg.color ?? lg.fontColor, "labelColor", "labelOpacity", writer, put, at);
|
|
483
889
|
} else if (lg.fill !== undefined && !yedLabel) {
|
|
484
890
|
readColor(lg.fill, "labelColor", null, writer, put, at);
|
|
485
891
|
}
|
|
486
892
|
if (lg.fill !== undefined && yedLabel) {
|
|
487
893
|
noteAt(at("label"), STYLE_ISSUE.NOT_CARRIED, "label background");
|
|
488
894
|
}
|
|
489
|
-
|
|
895
|
+
// yEd and Cytoscape are Java programs: a Java logical font (Dialog, SansSerif, ...) is drawn in Java's face for it
|
|
896
|
+
readLabelFont(lg, yedLabel || writer.vocabulary === "cytoscape", put, at);
|
|
897
|
+
if (node && lg.anchor !== undefined) {
|
|
898
|
+
const anchor = scalarText(lg.anchor, at("labelPosition"))?.trim().toLowerCase() ?? "";
|
|
899
|
+
const known = GML_ANCHORS.get(anchor);
|
|
900
|
+
if (known === undefined) {
|
|
901
|
+
noteAt(at("labelPosition"), STYLE_ISSUE.UNKNOWN_VALUE, anchor.slice(0, MAX_STYLE_VALUE_LENGTH));
|
|
902
|
+
}
|
|
903
|
+
put("labelPosition", known ?? null);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* A label's text as Graphviz draws it: gv2gml copies a DOT label as written, so its line breaks are still the escapes
|
|
909
|
+
* `\n`, `\l` and `\r` (centered, left and right lines, drawn here as plain line breaks).
|
|
910
|
+
* @param raw - the text
|
|
911
|
+
* @param at - the site
|
|
912
|
+
* @returns the text
|
|
913
|
+
*/
|
|
914
|
+
function graphvizText(raw: unknown, at: CanonSite): string | null {
|
|
915
|
+
const text = canonText(raw, at);
|
|
916
|
+
return text === null ? null : text.replaceAll(/\\[nlr]/g, "\n").replace(/\n$/, "");
|
|
490
917
|
}
|
|
491
918
|
|
|
492
919
|
/**
|
|
493
920
|
* Reads a label record's font: its size, name, weight and slant.
|
|
494
921
|
* @param lg - the record
|
|
922
|
+
* @param java - whether a Java program wrote it, whose logical font names are drawn in Java's faces
|
|
495
923
|
* @param put - receives the values
|
|
496
924
|
* @param at - the sites
|
|
497
925
|
*/
|
|
498
|
-
function readLabelFont(lg: Rec, put: Put, at: At): void {
|
|
926
|
+
function readLabelFont(lg: Rec, java: boolean, put: Put, at: At): void {
|
|
499
927
|
put("labelSize", lg.fontSize === undefined ? null : canonNumber(lg.fontSize, at("labelSize")));
|
|
500
|
-
|
|
928
|
+
const font = lg.fontName === undefined ? null : canonText(lg.fontName, at("labelFont"));
|
|
929
|
+
put("labelFont", java && typeof font === "string" ? javaFamily(font) : font);
|
|
501
930
|
const style = scalarText(lg.fontStyle, at("labelWeight"))?.trim().toLowerCase();
|
|
502
931
|
if (style !== undefined) {
|
|
503
932
|
const known = FONT_STYLES.get(style);
|
|
@@ -538,16 +967,90 @@ function readElement(
|
|
|
538
967
|
(target === "node" ? readNodeGraphics : readEdgeGraphics)(g, writer, put, at);
|
|
539
968
|
}
|
|
540
969
|
if (lg !== null && lg !== undefined) {
|
|
541
|
-
readLabelGraphics(lg, writer, putLabel, at);
|
|
970
|
+
readLabelGraphics(lg, writer, target === "node", putLabel, at);
|
|
542
971
|
}
|
|
543
972
|
// the GML draft draws the label key; Cytoscape reads it as the node's name, which its default style shows
|
|
544
973
|
const drawn =
|
|
545
974
|
writer.vocabulary === "cytoscape" ? target === "node" : writer.labelKey && (isRec(g) || lg !== undefined);
|
|
546
975
|
if (!text && drawn && label !== null && label !== undefined) {
|
|
547
|
-
put(
|
|
976
|
+
put(
|
|
977
|
+
"label",
|
|
978
|
+
writer.vocabulary === "graphviz" ? graphvizText(label, at("label")) : canonText(label, at("label")),
|
|
979
|
+
);
|
|
980
|
+
// the GML draft names no place for a node's label; a plain GML file's is centered on its node, as OGDF and
|
|
981
|
+
// Cytoscape draw it (yEd's GML keeps its own label models, in LabelGraphics)
|
|
982
|
+
if (target === "node" && writer.tool === null && writer.vocabulary === "graphlet") {
|
|
983
|
+
put("labelPosition", "center");
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/** The font size GraphIO::drawSVG draws every label in (SVGSettings), by which it sets a cluster's label off its box. */
|
|
989
|
+
const OGDF_SVG_FONT_SIZE = 10;
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Places each OGDF cluster's label where GraphIO::drawSVG draws it (SvgPrinter::drawCluster): one font size below the
|
|
993
|
+
* cluster's box where more of the drawing lies below it than above, else above it; starting at the box's left side
|
|
994
|
+
* where more of the drawing lies left of it than right, else ending at its right side. The importer reads a cluster as
|
|
995
|
+
* a group node with the id `cluster<id>`; its box is its center and its `w` and `h`.
|
|
996
|
+
* @param snapshot - the graph
|
|
997
|
+
* @param values - the node values read so far, given the label's place
|
|
998
|
+
*/
|
|
999
|
+
function placeOgdfClusterLabels(snapshot: GraphSnapshot, values: ValueColumns): void {
|
|
1000
|
+
const n = snapshot.nodeCount;
|
|
1001
|
+
const boxOf = (i: number): [number, number, number, number] => {
|
|
1002
|
+
const [x, y] = centerOf(snapshot, i);
|
|
1003
|
+
const [w, h] = sizeOf(snapshot, i);
|
|
1004
|
+
return [x - w / 2, y - h / 2, x + w / 2, y + h / 2];
|
|
1005
|
+
};
|
|
1006
|
+
// ponytail: drawSVG's bounding box also holds the edges' bends; nodes and clusters decide every sheet so far
|
|
1007
|
+
const all = [Infinity, Infinity, -Infinity, -Infinity];
|
|
1008
|
+
for (let i = 0; i < n; i++) {
|
|
1009
|
+
const b = boxOf(i);
|
|
1010
|
+
if (b.every(Number.isFinite)) {
|
|
1011
|
+
all[0] = Math.min(all[0], b[0]);
|
|
1012
|
+
all[1] = Math.min(all[1], b[1]);
|
|
1013
|
+
all[2] = Math.max(all[2], b[2]);
|
|
1014
|
+
all[3] = Math.max(all[3], b[3]);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
const put = (property: string, i: number, value: StyleValue): void => {
|
|
1018
|
+
values[property] ??= new Array<StyleValue | null>(n).fill(null);
|
|
1019
|
+
values[property][i] = value;
|
|
1020
|
+
};
|
|
1021
|
+
for (let i = 0; i < n; i++) {
|
|
1022
|
+
const id = snapshot.ids.idOf(i);
|
|
1023
|
+
const [x0, y0, x1, y1] = boxOf(i);
|
|
1024
|
+
if (typeof id !== "string" || !id.startsWith("cluster") || ![x0, y0, x1, y1].every(Number.isFinite)) {
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
const below = all[3] - y1 > y0 - all[1];
|
|
1028
|
+
const start = x0 - all[0] > all[2] - x1;
|
|
1029
|
+
put("labelPosition", i, "center");
|
|
1030
|
+
put("labelOffsetX", i, ((start ? -1 : 1) * (x1 - x0)) / 2);
|
|
1031
|
+
put("labelShiftX", i, start ? 0.5 : -0.5);
|
|
1032
|
+
put("labelOffsetY", i, (below ? 1 : -1) * ((y1 - y0) / 2 + OGDF_SVG_FONT_SIZE));
|
|
548
1033
|
}
|
|
549
1034
|
}
|
|
550
1035
|
|
|
1036
|
+
/**
|
|
1037
|
+
* An element's `label` key. Cytoscape's GML reader names a node by its label and, where the label is empty or
|
|
1038
|
+
* missing, by its id, and its default style draws that name.
|
|
1039
|
+
* @param snapshot - the graph
|
|
1040
|
+
* @param writer - the writer
|
|
1041
|
+
* @param target - nodes or edges
|
|
1042
|
+
* @param i - the row
|
|
1043
|
+
* @returns the label
|
|
1044
|
+
*/
|
|
1045
|
+
function labelOf(snapshot: GraphSnapshot, writer: GmlWriter, target: StyleTarget, i: number): unknown {
|
|
1046
|
+
const table = target === "node" ? snapshot.nodes : snapshot.edges;
|
|
1047
|
+
const label = cellOf(table, "label", i);
|
|
1048
|
+
if (writer.vocabulary === "cytoscape" && target === "node" && (label === undefined || label === "")) {
|
|
1049
|
+
return String(snapshot.ids.idOf(i));
|
|
1050
|
+
}
|
|
1051
|
+
return label;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
551
1054
|
/**
|
|
552
1055
|
* The value of a column, or undefined when the table has no such column.
|
|
553
1056
|
* @param table - the node or edge table
|
|
@@ -576,25 +1079,224 @@ function centerOf(snapshot: GraphSnapshot, i: number): BendPoint {
|
|
|
576
1079
|
}
|
|
577
1080
|
|
|
578
1081
|
/**
|
|
579
|
-
*
|
|
580
|
-
*
|
|
1082
|
+
* A node's width and height: its `graphics` `w` and `h`.
|
|
1083
|
+
* @param snapshot - the graph
|
|
1084
|
+
* @param i - the node's index
|
|
1085
|
+
* @returns the size; NaN where the node has none
|
|
1086
|
+
*/
|
|
1087
|
+
function sizeOf(snapshot: GraphSnapshot, i: number): BendPoint {
|
|
1088
|
+
const g = cellOf(snapshot.nodes, "graphics", i);
|
|
1089
|
+
return isRec(g) ? [Number(g.w ?? Number.NaN), Number(g.h ?? Number.NaN)] : [Number.NaN, Number.NaN];
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* An edge's route from its `graphics` `Line`, whose first and last points are where it attaches to its source and
|
|
1094
|
+
* target (its ports: the centers unless yEd moved them) and whose points between are its bends: the bends relative to
|
|
1095
|
+
* the two node centers (a self-loop's to its node's size), and each port off its node's center relative to the node.
|
|
1096
|
+
* OGDF's `Line` holds the bends alone (GraphIO::writeGML writes no end point).
|
|
581
1097
|
* @param snapshot - the graph
|
|
582
1098
|
* @param g - the edge's `graphics` value
|
|
583
1099
|
* @param e - the edge's row
|
|
584
|
-
* @
|
|
1100
|
+
* @param bendsOnly - whether the points are the bends alone (OGDF)
|
|
1101
|
+
* @param put - receives the values
|
|
585
1102
|
*/
|
|
586
|
-
function
|
|
1103
|
+
function readLineRoute(
|
|
1104
|
+
snapshot: GraphSnapshot,
|
|
1105
|
+
g: unknown,
|
|
1106
|
+
e: number,
|
|
1107
|
+
bendsOnly: boolean,
|
|
1108
|
+
put: (property: string, value: string) => void,
|
|
1109
|
+
): void {
|
|
587
1110
|
const raw = isRec(g) && isRec(g.Line) ? g.Line.point : undefined;
|
|
588
|
-
|
|
589
|
-
|
|
1111
|
+
const list = isRec(raw) ? [raw] : raw;
|
|
1112
|
+
if (!Array.isArray(list) || list.length < (bendsOnly ? 1 : 2)) {
|
|
1113
|
+
return;
|
|
590
1114
|
}
|
|
591
|
-
const
|
|
592
|
-
|
|
1115
|
+
const all = list.map((p): BendPoint => (isRec(p) ? [Number(p.x), Number(p.y)] : [Number.NaN, 0]));
|
|
1116
|
+
const [s, t] = [snapshot.edgeSource(e), snapshot.edgeTarget(e)];
|
|
1117
|
+
const points = bendsOnly ? all : all.slice(1, -1);
|
|
1118
|
+
const bends =
|
|
1119
|
+
s === t
|
|
1120
|
+
? canonNodeOffsets(centerOf(snapshot, s), sizeOf(snapshot, s), points, true)
|
|
1121
|
+
: canonBends(centerOf(snapshot, s), centerOf(snapshot, t), points, true);
|
|
1122
|
+
if (bends !== null) {
|
|
1123
|
+
put("bends", bends);
|
|
1124
|
+
}
|
|
1125
|
+
if (bendsOnly) {
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
for (const [property, k, end] of [
|
|
1129
|
+
["sourcePort", s, all[0]],
|
|
1130
|
+
["targetPort", t, all.at(-1) ?? all[0]],
|
|
1131
|
+
] as const) {
|
|
1132
|
+
const center = centerOf(snapshot, k);
|
|
1133
|
+
const port =
|
|
1134
|
+
end[0] === center[0] && end[1] === center[1]
|
|
1135
|
+
? null
|
|
1136
|
+
: canonNodeOffsets(center, sizeOf(snapshot, k), [end], true);
|
|
1137
|
+
if (port !== null) {
|
|
1138
|
+
put(property, port);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* A gv2gml edge's route: its `Line` is the edge's Graphviz `pos`, the B-spline's `3n + 1` control points from the tail
|
|
1145
|
+
* to the head (y up), with the arrow tips Graphviz keeps apart (`s,` and `e,`) written as plain first and last points;
|
|
1146
|
+
* the points beyond `3n + 1` are those tips (the source's first when there are two or it has the arrow). A route on
|
|
1147
|
+
* the line between the centers is the straight edge it looks like and gives no bends.
|
|
1148
|
+
* @param snapshot - the graph
|
|
1149
|
+
* @param g - the edge's `graphics` value
|
|
1150
|
+
* @param e - the edge's row
|
|
1151
|
+
* @param put - receives the values
|
|
1152
|
+
*/
|
|
1153
|
+
function readGv2gmlRoute(
|
|
1154
|
+
snapshot: GraphSnapshot,
|
|
1155
|
+
g: unknown,
|
|
1156
|
+
e: number,
|
|
1157
|
+
put: (property: string, value: string) => void,
|
|
1158
|
+
): void {
|
|
1159
|
+
const raw = isRec(g) && isRec(g.Line) ? g.Line.point : undefined;
|
|
1160
|
+
const list = isRec(raw) ? [raw] : raw;
|
|
1161
|
+
if (!Array.isArray(list) || !isRec(g)) {
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
let points = list.map((p): BendPoint => (isRec(p) ? [Number(p.x), Number(p.y)] : [Number.NaN, 0]));
|
|
1165
|
+
const tips = (points.length - 1) % 3;
|
|
1166
|
+
const sourceTip = tips === 2 || (tips === 1 && gv2gmlArrowEnds(g, snapshot.directed)?.[0] === true);
|
|
1167
|
+
points = points.slice(sourceTip ? 1 : 0, points.length - (tips === 2 || (tips === 1 && !sourceTip) ? 1 : 0));
|
|
1168
|
+
const bends = graphvizRouteBends(
|
|
593
1169
|
centerOf(snapshot, snapshot.edgeSource(e)),
|
|
594
1170
|
centerOf(snapshot, snapshot.edgeTarget(e)),
|
|
595
|
-
points,
|
|
596
|
-
true,
|
|
1171
|
+
[points],
|
|
597
1172
|
);
|
|
1173
|
+
if (bends !== null) {
|
|
1174
|
+
put("bends", bends);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* Where graphty-element hangs an edge's label on a route drawn through these points (its centers and bends): the
|
|
1180
|
+
* middle of its middle segment, or the joint between its two middle ones; `labelOffsetX` / `labelOffsetY` move it
|
|
1181
|
+
* from there.
|
|
1182
|
+
* @param points - the route, from the source's center to the target's
|
|
1183
|
+
* @returns the point
|
|
1184
|
+
*/
|
|
1185
|
+
function routeMiddle(points: readonly BendPoint[]): BendPoint {
|
|
1186
|
+
const m = points.length - 1;
|
|
1187
|
+
if (m % 2 === 0) {
|
|
1188
|
+
return points[m / 2];
|
|
1189
|
+
}
|
|
1190
|
+
const [a, b] = [points[(m - 1) / 2], points[(m + 1) / 2]];
|
|
1191
|
+
return [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Puts an edge label's offset from where graphty-element hangs it, when the program draws it elsewhere.
|
|
1196
|
+
* @param at - the label's center, in the file's coordinates
|
|
1197
|
+
* @param middle - where graphty-element hangs it, in the same coordinates
|
|
1198
|
+
* @param yUp - whether the file's y grows upward
|
|
1199
|
+
* @param put - receives the values
|
|
1200
|
+
*/
|
|
1201
|
+
function putEdgeLabelOffset(
|
|
1202
|
+
at: BendPoint,
|
|
1203
|
+
middle: BendPoint,
|
|
1204
|
+
yUp: boolean,
|
|
1205
|
+
put: (property: string, value: StyleValue) => void,
|
|
1206
|
+
): void {
|
|
1207
|
+
const dx = num6(at[0] - middle[0]);
|
|
1208
|
+
const dy = num6((yUp ? -1 : 1) * (at[1] - middle[1]));
|
|
1209
|
+
if (Number.isFinite(dx) && Number.isFinite(dy) && (dx !== 0 || dy !== 0)) {
|
|
1210
|
+
// moved from the line's middle itself, not from graphty's default place beside the line
|
|
1211
|
+
put("labelPosition", "center");
|
|
1212
|
+
put("labelOffsetX", dx);
|
|
1213
|
+
put("labelOffsetY", dy);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* A gv2gml edge label's place: gv2gml copies Graphviz's `lp` (the label's center, in points, y up) as a plain edge
|
|
1219
|
+
* key, and Graphviz draws the label there, beside its line rather than on it.
|
|
1220
|
+
* @param snapshot - the graph
|
|
1221
|
+
* @param e - the edge's row
|
|
1222
|
+
* @param bent - whether its route has bends
|
|
1223
|
+
* @param put - receives the values
|
|
1224
|
+
*/
|
|
1225
|
+
function readGv2gmlLabelPlace(
|
|
1226
|
+
snapshot: GraphSnapshot,
|
|
1227
|
+
e: number,
|
|
1228
|
+
bent: boolean,
|
|
1229
|
+
put: (property: string, value: StyleValue) => void,
|
|
1230
|
+
): void {
|
|
1231
|
+
const lp = cellOf(snapshot.edges, "lp", e);
|
|
1232
|
+
const m = typeof lp === "string" ? /^\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*$/.exec(lp) : null;
|
|
1233
|
+
// ponytail: a curved route's middle is where graphty-element samples its curve; only a straight one is placed
|
|
1234
|
+
if (m === null || bent) {
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
const [s, t] = [centerOf(snapshot, snapshot.edgeSource(e)), centerOf(snapshot, snapshot.edgeTarget(e))];
|
|
1238
|
+
putEdgeLabelOffset([Number(m[1]), Number(m[2])], routeMiddle([s, t]), true, put);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* An OGDF edge label's place: GraphIO::drawSVG (SvgPrinter::drawEdge) draws it at the middle of the route's first
|
|
1243
|
+
* segment, from the source's center to its first bend, not at the route's middle.
|
|
1244
|
+
* @param snapshot - the graph
|
|
1245
|
+
* @param g - the edge's `graphics` value
|
|
1246
|
+
* @param e - the edge's row
|
|
1247
|
+
* @param put - receives the values
|
|
1248
|
+
*/
|
|
1249
|
+
function readOgdfLabelPlace(
|
|
1250
|
+
snapshot: GraphSnapshot,
|
|
1251
|
+
g: unknown,
|
|
1252
|
+
e: number,
|
|
1253
|
+
put: (property: string, value: StyleValue) => void,
|
|
1254
|
+
): void {
|
|
1255
|
+
const raw = isRec(g) && isRec(g.Line) ? g.Line.point : undefined;
|
|
1256
|
+
const list = isRec(raw) ? [raw] : raw;
|
|
1257
|
+
const [s, t] = [centerOf(snapshot, snapshot.edgeSource(e)), centerOf(snapshot, snapshot.edgeTarget(e))];
|
|
1258
|
+
if (!Array.isArray(list) || snapshot.edgeSource(e) === snapshot.edgeTarget(e)) {
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const same = (a: BendPoint, b: BendPoint): boolean => Math.hypot(a[0] - b[0], a[1] - b[1]) < 1e-6;
|
|
1262
|
+
// the Line OGDF writes may start and end at the centers
|
|
1263
|
+
const bends = list
|
|
1264
|
+
.map((p): BendPoint => (isRec(p) ? [Number(p.x), Number(p.y)] : [Number.NaN, 0]))
|
|
1265
|
+
.filter((p) => !same(p, s) && !same(p, t));
|
|
1266
|
+
if (bends.length === 0) {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
const route = [s, ...bends, t];
|
|
1270
|
+
putEdgeLabelOffset(
|
|
1271
|
+
[(route[0][0] + route[1][0]) / 2, (route[0][1] + route[1][1]) / 2],
|
|
1272
|
+
routeMiddle(route),
|
|
1273
|
+
false,
|
|
1274
|
+
put,
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* Hides the edges Gephi's own GML importer leaves out: an edge whose `directed` key disagrees with the graph's
|
|
1280
|
+
* `directed` header (Gephi 0.11 loads 4 of `specimen-arrows-p6`'s 5 edges). A file with no header is read as mixed and
|
|
1281
|
+
* every edge is loaded. The edge and its data stay; Gephi draws nothing there, so graphty draws nothing.
|
|
1282
|
+
* @param snapshot - the graph
|
|
1283
|
+
* @param values - the edge values read so far, given `visible`
|
|
1284
|
+
*/
|
|
1285
|
+
function hideGephiDroppedEdges(snapshot: GraphSnapshot, values: ValueColumns): void {
|
|
1286
|
+
const header = (snapshot.meta.extra.gml as Record<string, unknown> | undefined)?.directed;
|
|
1287
|
+
const directed = snapshot.edges.byRole("directed");
|
|
1288
|
+
// with no `directed` header Gephi reads a mixed graph and loads every edge (Gephi Toolkit 0.11.3, checked)
|
|
1289
|
+
if (directed === null || header === undefined) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const graphDirected = Number(header) === 1;
|
|
1293
|
+
for (let e = 0; e < snapshot.edgeCount; e++) {
|
|
1294
|
+
const v = plainCell(directed.value(e));
|
|
1295
|
+
if (typeof v === "boolean" && v !== graphDirected) {
|
|
1296
|
+
values.visible ??= new Array<StyleValue | null>(snapshot.edgeCount).fill(null);
|
|
1297
|
+
values.visible[e] = false;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
598
1300
|
}
|
|
599
1301
|
|
|
600
1302
|
/**
|
|
@@ -626,7 +1328,7 @@ function readValues(
|
|
|
626
1328
|
target,
|
|
627
1329
|
cellOf(table, "graphics", i),
|
|
628
1330
|
cellOf(table, "LabelGraphics", i),
|
|
629
|
-
|
|
1331
|
+
labelOf(snapshot, writer, target, i),
|
|
630
1332
|
writer,
|
|
631
1333
|
(property, value) => {
|
|
632
1334
|
if (value !== null) {
|
|
@@ -636,12 +1338,32 @@ function readValues(
|
|
|
636
1338
|
},
|
|
637
1339
|
at,
|
|
638
1340
|
);
|
|
639
|
-
if (target === "edge") {
|
|
640
|
-
|
|
641
|
-
|
|
1341
|
+
if (target === "edge" && writer.vocabulary === "graphviz") {
|
|
1342
|
+
let bent = false;
|
|
1343
|
+
readGv2gmlRoute(snapshot, cellOf(table, "graphics", i), i, (property, value) => {
|
|
642
1344
|
found = true;
|
|
643
|
-
|
|
644
|
-
|
|
1345
|
+
bent ||= property === "bends";
|
|
1346
|
+
out.put(i, property, value);
|
|
1347
|
+
});
|
|
1348
|
+
readGv2gmlLabelPlace(snapshot, i, bent, (property, value) => {
|
|
1349
|
+
out.put(i, property, value);
|
|
1350
|
+
});
|
|
1351
|
+
} else if (target === "edge" && writer.vocabulary === "ogdf") {
|
|
1352
|
+
readOgdfLabelPlace(snapshot, cellOf(table, "graphics", i), i, (property, value) => {
|
|
1353
|
+
out.put(i, property, value);
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
if (target === "edge" && writer.vocabulary !== "graphviz") {
|
|
1357
|
+
readLineRoute(
|
|
1358
|
+
snapshot,
|
|
1359
|
+
cellOf(table, "graphics", i),
|
|
1360
|
+
i,
|
|
1361
|
+
writer.vocabulary === "ogdf",
|
|
1362
|
+
(property, value) => {
|
|
1363
|
+
found = true;
|
|
1364
|
+
out.put(i, property, value);
|
|
1365
|
+
},
|
|
1366
|
+
);
|
|
645
1367
|
}
|
|
646
1368
|
}
|
|
647
1369
|
return out.values;
|
|
@@ -720,8 +1442,11 @@ function styleColumns(table: GraphSnapshot["nodes"]): string[] {
|
|
|
720
1442
|
*/
|
|
721
1443
|
export function gmlStyles(snapshot: GraphSnapshot, context: StyleReadContext): ImporterStyles | null {
|
|
722
1444
|
const notes = new StyleNotes();
|
|
723
|
-
const writer = writerOf(snapshot);
|
|
1445
|
+
const writer = { ...writerOf(snapshot), directed: snapshot.directed };
|
|
724
1446
|
const values = readValues(snapshot, writer, notes, context);
|
|
1447
|
+
if (writer.vocabulary === "ogdf") {
|
|
1448
|
+
placeOgdfClusterLabels(snapshot, values.node);
|
|
1449
|
+
}
|
|
725
1450
|
const blockText = graphText(snapshot, GML_STYLE_BLOCK_KEY);
|
|
726
1451
|
const block = blockText === null ? null : blockWithValues(snapshot, blockText);
|
|
727
1452
|
if (block !== null) {
|
|
@@ -730,10 +1455,32 @@ export function gmlStyles(snapshot: GraphSnapshot, context: StyleReadContext): I
|
|
|
730
1455
|
if (writer.tool === null && !values.found && block === null) {
|
|
731
1456
|
return null;
|
|
732
1457
|
}
|
|
1458
|
+
if (writer.tool === "gephi" && block === null) {
|
|
1459
|
+
// Gephi draws every edge in its end nodes' mixed color (EdgeColor MIXED), whatever its own
|
|
1460
|
+
delete values.edge.color;
|
|
1461
|
+
// and as thick as its weight, the `value` Gephi writes, also where the reader kept `value` as plain data
|
|
1462
|
+
if (snapshot.edges.byRole("weight") === null && snapshot.edges.has("value")) {
|
|
1463
|
+
values.edge.width = Array.from({ length: snapshot.edgeCount }, (_, e) => {
|
|
1464
|
+
const w = cellOf(snapshot.edges, "value", e);
|
|
1465
|
+
if (typeof w !== "number" || !Number.isFinite(w)) {
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
// a weight of 0 or below is drawn 1 wide, so the edge stays visible
|
|
1469
|
+
return w > 0 ? w : 1;
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
hideGephiDroppedEdges(snapshot, values.edge);
|
|
1473
|
+
}
|
|
1474
|
+
const none = { rules: [], columns: new Map<string, StyleColumn>() };
|
|
733
1475
|
const gephi =
|
|
734
1476
|
writer.tool === "gephi"
|
|
735
|
-
? gephiRules(
|
|
736
|
-
|
|
1477
|
+
? gephiRules(
|
|
1478
|
+
snapshot,
|
|
1479
|
+
{ fill: values.node.fill, width: values.node.width },
|
|
1480
|
+
{ color: values.edge.color, width: values.edge.width },
|
|
1481
|
+
context.budget,
|
|
1482
|
+
)
|
|
1483
|
+
: { node: none, edge: none };
|
|
737
1484
|
return {
|
|
738
1485
|
canonical: true,
|
|
739
1486
|
dialect: writer.dialect,
|
|
@@ -783,6 +1530,8 @@ export const GML_STYLE_CAPABILITIES: StyleWriteCapabilities = Object.freeze({
|
|
|
783
1530
|
"targetArrow",
|
|
784
1531
|
"curved",
|
|
785
1532
|
"bends",
|
|
1533
|
+
"sourcePort",
|
|
1534
|
+
"targetPort",
|
|
786
1535
|
"label",
|
|
787
1536
|
"labelColor",
|
|
788
1537
|
"labelSize",
|
|
@@ -915,7 +1664,7 @@ function edgeGraphics(get: Get, approximate: Approximate): Rec {
|
|
|
915
1664
|
for (const property of ["sourceArrow", "targetArrow"]) {
|
|
916
1665
|
const arrow = get(property);
|
|
917
1666
|
if (typeof arrow === "string") {
|
|
918
|
-
g[property] = yedSpelled(YED_ARROWS, arrow, "
|
|
1667
|
+
g[property] = yedSpelled(YED_ARROWS, arrow, "delta", property, approximate);
|
|
919
1668
|
}
|
|
920
1669
|
}
|
|
921
1670
|
const curved = get("curved");
|
|
@@ -946,7 +1695,8 @@ function labelGraphics(get: Get): Rec | null {
|
|
|
946
1695
|
}
|
|
947
1696
|
const font = get("labelFont");
|
|
948
1697
|
if (typeof font === "string") {
|
|
949
|
-
|
|
1698
|
+
// a Java face is written back as the logical font yEd draws in it
|
|
1699
|
+
lg.fontName = yedFontName(font);
|
|
950
1700
|
}
|
|
951
1701
|
const weight = get("labelWeight");
|
|
952
1702
|
const italic = get("labelItalic");
|
|
@@ -993,23 +1743,6 @@ function mergedLabels(old: unknown, written: Rec | null): unknown {
|
|
|
993
1743
|
return head === undefined ? rest : [head, ...rest];
|
|
994
1744
|
}
|
|
995
1745
|
|
|
996
|
-
/**
|
|
997
|
-
* Each element's values of the properties the writer writes, as a getter per element.
|
|
998
|
-
* @param picture - each element's value of a property
|
|
999
|
-
* @param target - nodes or edges
|
|
1000
|
-
* @returns the getter
|
|
1001
|
-
*/
|
|
1002
|
-
function getter(
|
|
1003
|
-
picture: (target: StyleTarget, property: string) => readonly (StyleValue | null)[],
|
|
1004
|
-
target: StyleTarget,
|
|
1005
|
-
): (i: number) => Get {
|
|
1006
|
-
const columns = new Map<string, readonly (StyleValue | null)[]>();
|
|
1007
|
-
for (const p of GML_STYLE_CAPABILITIES[target]) {
|
|
1008
|
-
columns.set(p, picture(target, p));
|
|
1009
|
-
}
|
|
1010
|
-
return (i) => (property) => columns.get(property)?.[i] ?? null;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
1746
|
/**
|
|
1014
1747
|
* For a file graphty wrote: where a value read from the graphics is what the writer writes for the block's own value
|
|
1015
1748
|
* (a pentagon written as a rectangle, an opacity rounded to 255 steps), the block's exact value is the truth, so a
|
|
@@ -1025,29 +1758,16 @@ function exactFromBlock(
|
|
|
1025
1758
|
text: string,
|
|
1026
1759
|
read: { readonly node: ValueColumns; readonly edge: ValueColumns },
|
|
1027
1760
|
): void {
|
|
1028
|
-
const parsed = parseStyleBlock(text, styleFingerprint(snapshot));
|
|
1029
|
-
const ex = parsed.ok ? blockStyles(parsed.block, snapshot, "gml") : null;
|
|
1030
|
-
if (ex === null) {
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
1761
|
const notes = new StyleNotes();
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const want = get(i);
|
|
1762
|
+
takeFromBlock(snapshot, text, "gml", (target) => ({
|
|
1763
|
+
values: read[target],
|
|
1764
|
+
properties: GML_STYLE_CAPABILITIES[target],
|
|
1765
|
+
writes: GML_STYLE_CAPABILITIES[target],
|
|
1766
|
+
readBack: (_i, want) => {
|
|
1040
1767
|
const back = writtenAndReadBack(target, want, notes);
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
if (native !== w && native === (back.get(property) ?? null)) {
|
|
1045
|
-
values[property] ??= new Array<StyleValue | null>(n).fill(null);
|
|
1046
|
-
values[property][i] = w;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1768
|
+
return (property) => back.get(property) ?? null;
|
|
1769
|
+
},
|
|
1770
|
+
}));
|
|
1051
1771
|
}
|
|
1052
1772
|
|
|
1053
1773
|
/**
|
|
@@ -1155,13 +1875,18 @@ function writeGraphicsColumns(
|
|
|
1155
1875
|
const approximate: Approximate = (property, value) => {
|
|
1156
1876
|
approximated.add(target, property, value);
|
|
1157
1877
|
};
|
|
1158
|
-
const get =
|
|
1878
|
+
const get = elementValues((t, p) => input.picture(t, p as never), target, GML_STYLE_CAPABILITIES[target]);
|
|
1879
|
+
const getNode = elementValues((t, p) => input.picture(t, p as never), "node", GML_STYLE_CAPABILITIES.node);
|
|
1880
|
+
const size = (k: number): BendPoint => {
|
|
1881
|
+
const [w, h] = [getNode(k)("width"), getNode(k)("height")];
|
|
1882
|
+
return typeof w === "number" && typeof h === "number" ? [w, h] : sizeOf(snapshot, k);
|
|
1883
|
+
};
|
|
1159
1884
|
const graphics: unknown[] = [];
|
|
1160
1885
|
const labels: unknown[] = [];
|
|
1161
1886
|
for (let i = 0; i < n; i++) {
|
|
1162
1887
|
const g = target === "node" ? nodeGraphics(get(i), approximate) : edgeGraphics(get(i), approximate);
|
|
1163
1888
|
if (target === "edge") {
|
|
1164
|
-
const line =
|
|
1889
|
+
const line = routeLine(snapshot, get(i), i, size);
|
|
1165
1890
|
if (line !== null) {
|
|
1166
1891
|
g.Line = line;
|
|
1167
1892
|
}
|
|
@@ -1182,21 +1907,38 @@ function writeGraphicsColumns(
|
|
|
1182
1907
|
}
|
|
1183
1908
|
|
|
1184
1909
|
/**
|
|
1185
|
-
* An edge's `Line` from its `bends`
|
|
1186
|
-
*
|
|
1910
|
+
* An edge's `Line` from its `bends`, `sourcePort` and `targetPort` values: from its source port (the node's center
|
|
1911
|
+
* without one) through the bends placed between the node centers (a self-loop's around its node) to its target port,
|
|
1912
|
+
* so the route reads back as written.
|
|
1187
1913
|
* @param snapshot - the graph handed to the exporter
|
|
1188
|
-
* @param
|
|
1914
|
+
* @param get - the edge's values
|
|
1189
1915
|
* @param e - the edge's row
|
|
1916
|
+
* @param size - each node's written width and height
|
|
1190
1917
|
* @returns the `Line` record, or null without a value or without both centers
|
|
1191
1918
|
*/
|
|
1192
|
-
function
|
|
1193
|
-
const
|
|
1194
|
-
const
|
|
1195
|
-
const
|
|
1196
|
-
if (
|
|
1919
|
+
function routeLine(snapshot: GraphSnapshot, get: Get, e: number, size: (k: number) => BendPoint): Rec | null {
|
|
1920
|
+
const [s, t] = [snapshot.edgeSource(e), snapshot.edgeTarget(e)];
|
|
1921
|
+
const source = centerOf(snapshot, s);
|
|
1922
|
+
const target = centerOf(snapshot, t);
|
|
1923
|
+
if (![...source, ...target].every(Number.isFinite)) {
|
|
1924
|
+
return null;
|
|
1925
|
+
}
|
|
1926
|
+
const bends = get("bends");
|
|
1927
|
+
let points: BendPoint[] | null = null;
|
|
1928
|
+
if (typeof bends === "string") {
|
|
1929
|
+
points =
|
|
1930
|
+
s === t ? nodeOffsetsToPoints(source, size(s), bends, true) : bendsToPoints(source, target, bends, true);
|
|
1931
|
+
}
|
|
1932
|
+
const port = (property: string, k: number, center: BendPoint): BendPoint | null => {
|
|
1933
|
+
const value = get(property);
|
|
1934
|
+
return typeof value === "string" ? (nodeOffsetsToPoints(center, size(k), value, true)?.[0] ?? null) : null;
|
|
1935
|
+
};
|
|
1936
|
+
const from = port("sourcePort", s, source);
|
|
1937
|
+
const to = port("targetPort", t, target);
|
|
1938
|
+
if (points === null && from === null && to === null) {
|
|
1197
1939
|
return null;
|
|
1198
1940
|
}
|
|
1199
|
-
return { point: [source, ...points, target].map(([x, y]) => ({ x, y })) };
|
|
1941
|
+
return { point: [from ?? source, ...(points ?? []), to ?? target].map(([x, y]) => ({ x, y })) };
|
|
1200
1942
|
}
|
|
1201
1943
|
|
|
1202
1944
|
/**
|