@graphty/graph-io 0.3.48 → 0.3.49
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-Bzn3L-je.js} +2 -2
- package/dist/chunks/{approximations-C2riR4NT.js.map → approximations-Bzn3L-je.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-iptrlMBb.js} +906 -164
- package/dist/chunks/block-iptrlMBb.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-C3p0RQxR.js} +5 -4
- package/dist/chunks/{context-cgmmOxZ2.js.map → context-C3p0RQxR.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-DXHx7nwR.js} +16 -14
- package/dist/chunks/credentials-DXHx7nwR.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-DLA5kINx.js} +7 -2
- package/dist/chunks/descriptor-DLA5kINx.js.map +1 -0
- package/dist/chunks/{emit-CD8xnY8s.js → emit-BwHQkZle.js} +1660 -133
- package/dist/chunks/emit-BwHQkZle.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-Dcw7Ldtq.js → exporter-BP6otMO6.js} +6 -6
- package/dist/chunks/{exporter-Dcw7Ldtq.js.map → exporter-BP6otMO6.js.map} +1 -1
- package/dist/chunks/{exporter-bxxjOrDZ.js → exporter-D2CETOMK.js} +1222 -156
- package/dist/chunks/exporter-D2CETOMK.js.map +1 -0
- package/dist/chunks/{exporter-BIdaoIgh.js → exporter-D9yMtocA.js} +7 -7
- package/dist/chunks/{exporter-BIdaoIgh.js.map → exporter-D9yMtocA.js.map} +1 -1
- package/dist/chunks/{exporter-DtVU80Fa.js → exporter-_jrBTnos.js} +9 -8
- package/dist/chunks/{exporter-DtVU80Fa.js.map → exporter-_jrBTnos.js.map} +1 -1
- package/dist/chunks/{exporter-ChjL5fmP.js → exporter-nDfxIK5D.js} +9 -8
- package/dist/chunks/{exporter-ChjL5fmP.js.map → exporter-nDfxIK5D.js.map} +1 -1
- 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-ymWCETAJ.js} +387 -31
- package/dist/chunks/generic-ymWCETAJ.js.map +1 -0
- package/dist/chunks/{importer-e3huMZbm.js → importer-B92x1xY5.js} +14 -10
- package/dist/chunks/importer-B92x1xY5.js.map +1 -0
- package/dist/chunks/{importer-B87_fmVx.js → importer-CT9mJ1nn.js} +326 -84
- package/dist/chunks/importer-CT9mJ1nn.js.map +1 -0
- package/dist/chunks/{importer-CB_zV5_L.js → importer-DaLTjbds.js} +116 -1102
- package/dist/chunks/importer-DaLTjbds.js.map +1 -0
- package/dist/chunks/{importer-DPZDN40-.js → importer-GREKMdIf.js} +25 -17
- package/dist/chunks/importer-GREKMdIf.js.map +1 -0
- package/dist/chunks/{importer-Bji2MeWr.js → importer-PqaaKKnY.js} +13 -10
- package/dist/chunks/{importer-Bji2MeWr.js.map → importer-PqaaKKnY.js.map} +1 -1
- package/dist/chunks/{importer-Cyy6UseH.js → importer-Vn5QvmmJ.js} +66 -13
- package/dist/chunks/importer-Vn5QvmmJ.js.map +1 -0
- package/dist/chunks/{importer-CVT-JyDE.js → importer-vRq4h3j5.js} +1469 -356
- package/dist/chunks/importer-vRq4h3j5.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-Clf4QIyl.js} +282 -74
- package/dist/chunks/lexicon-Clf4QIyl.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-Zm3_ivgq.js +79 -0
- package/dist/chunks/round-trip-Zm3_ivgq.js.map +1 -0
- package/dist/chunks/{sigv4-LkAzsLAu.js → sigv4-EokFic1V.js} +5 -5
- package/dist/chunks/sigv4-EokFic1V.js.map +1 -0
- package/dist/chunks/{styles-CaABMoO1.js → styles-CCs214_3.js} +1237 -138
- package/dist/chunks/styles-CCs214_3.js.map +1 -0
- package/dist/chunks/styles-Cm62yyQX.js +2812 -0
- package/dist/chunks/styles-Cm62yyQX.js.map +1 -0
- package/dist/chunks/styles-DPQQIig4.js +1928 -0
- package/dist/chunks/styles-DPQQIig4.js.map +1 -0
- package/dist/chunks/{styles-yed-Cs1nmrjn.js → styles-yed-UMFdNqze.js} +503 -172
- package/dist/chunks/styles-yed-UMFdNqze.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 +5 -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 +847 -193
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +232 -220
- 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 +138 -23
- package/dist/sources-cypher.js.map +1 -1
- package/dist/sources-ndex.js +2 -2
- package/dist/sources-rest.js +16 -6
- package/dist/sources-rest.js.map +1 -1
- package/dist/sources-sparql.d.ts +1 -0
- package/dist/sources-sparql.js +405 -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/importer.d.ts.map +1 -1
- package/dist/src/formats/dot/importer.js +108 -31
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/dot/label-places.d.ts +34 -0
- package/dist/src/formats/dot/label-places.d.ts.map +1 -0
- package/dist/src/formats/dot/label-places.js +179 -0
- package/dist/src/formats/dot/label-places.js.map +1 -0
- 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 +62 -2
- package/dist/src/formats/dot/styles.d.ts.map +1 -1
- package/dist/src/formats/dot/styles.js +1454 -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 +753 -101
- 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 +692 -161
- 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 +429 -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 +1755 -136
- package/dist/src/formats/json/styles.js.map +1 -1
- package/dist/src/formats/pajek/draw.d.ts +96 -0
- package/dist/src/formats/pajek/draw.d.ts.map +1 -0
- package/dist/src/formats/pajek/draw.js +372 -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 +1278 -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 +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -3
- 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/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 +124 -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 +5 -0
- package/dist/src/sources/rest/descriptor.js.map +1 -1
- package/dist/src/sources/rest/source.d.ts.map +1 -1
- package/dist/src/sources/rest/source.js +15 -0
- package/dist/src/sources/rest/source.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 +314 -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 +72 -0
- package/dist/src/sources/sparql/query.d.ts.map +1 -0
- package/dist/src/sources/sparql/query.js +113 -0
- package/dist/src/sources/sparql/query.js.map +1 -0
- package/dist/src/sources/types.d.ts +31 -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 +81 -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 +1015 -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 +3 -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 +202 -9
- 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 +461 -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 +622 -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 +203 -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/importer.ts +120 -35
- package/src/formats/dot/label-places.ts +228 -0
- package/src/formats/dot/record.ts +336 -0
- package/src/formats/dot/styles.ts +1654 -153
- 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 +851 -109
- package/src/formats/graphml/styles-plain.ts +6 -15
- package/src/formats/graphml/styles-yed.ts +762 -173
- package/src/formats/graphml/styles-yfiles3.ts +485 -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 +2041 -185
- package/src/formats/pajek/draw.ts +458 -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 +1381 -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 +5 -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/cypher/descriptor.ts +7 -1
- package/src/sources/cypher/index.ts +148 -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 +5 -0
- package/src/sources/rest/source.ts +16 -0
- package/src/sources/sigv4.ts +6 -4
- package/src/sources/sparql/descriptor.ts +67 -0
- package/src/sources/sparql/index.ts +356 -0
- package/src/sources/sparql/ntriples.ts +219 -0
- package/src/sources/sparql/params.ts +28 -0
- package/src/sources/sparql/query.ts +162 -0
- package/src/sources/types.ts +35 -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 +98 -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 +1160 -84
- package/src/styles/cyjs/defaults-table.ts +3 -0
- package/src/styles/cyjs/emit.ts +233 -13
- package/src/styles/cyjs/face.ts +372 -0
- package/src/styles/cyjs/mappers.ts +492 -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 +633 -5
- package/src/styles/vocabulary.ts +211 -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
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pajek styles: each vertex's shape keyword and drawing parameters (`ic`, `bc`, `bw`, `x_fact`, `y_fact`, `lc`, `fos`)
|
|
3
|
-
* and each line's `c`, `w` and `p`, read the way Pajek draws them
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* and each line's `c`, `w` and `p`, read the way Pajek 6.02 draws them (parameters carried from line to line, `CARRIED`),
|
|
4
|
+
* with Pajek's own defaults beneath (a #ffd9bc ellipse with a #660000 border, grey arcs with an arrow at the target,
|
|
5
|
+
* teal edges). Colors are Pajek's: the dvips names and the ten Pajek adds, `GrayNN`, `RGBrrggbb`, `RGB(r,g,b)` and the
|
|
6
|
+
* CMYK forms. Lengths are in PostScript points, the unit of Pajek's EPS and SVG export, where the drawing's [0,1] square
|
|
7
|
+
* is [0,500] (`PAJEK_POINTS_PER_UNIT`): a vertex is Pajek's normal size 20 times its `x_fact` and `y_fact`,
|
|
8
|
+
* `bw` and `fos` are points as written, an arc is `w` wide and an edge twice `w`.
|
|
7
9
|
*
|
|
8
10
|
* Writing: the same parameters from what graphty draws, leaving out what Pajek draws by default; a color that is a
|
|
9
11
|
* dvips name is written by name, any other as `RGBrrggbb`, so colors are exact. The `graphty.styles` block, with the
|
|
@@ -13,18 +15,21 @@
|
|
|
13
15
|
|
|
14
16
|
import type { GraphSnapshot } from "@graphty/graph-format";
|
|
15
17
|
|
|
18
|
+
import { explicitWeights } from "../../common/weights.js";
|
|
16
19
|
import { canonColor, colorOpacity } from "../../styles/canon/color.js";
|
|
17
20
|
import { DVIPS_COLORS } from "../../styles/canon/color-tables.js";
|
|
18
21
|
import { canonEnum } from "../../styles/canon/enums.js";
|
|
19
22
|
import { canonNumber } from "../../styles/canon/number.js";
|
|
20
|
-
import type
|
|
23
|
+
import { type CanonSite, noteAt } from "../../styles/canon/site.js";
|
|
21
24
|
import { canonText } from "../../styles/canon/text.js";
|
|
25
|
+
import { STYLE_ISSUE } from "../../styles/codes.js";
|
|
26
|
+
import { toolDefaults } from "../../styles/defaults.js";
|
|
22
27
|
import { StyleApproximations } from "../../styles/io/approximations.js";
|
|
23
|
-
import {
|
|
28
|
+
import { styleBlockLosses, writeStyleBlock } from "../../styles/io/block.js";
|
|
29
|
+
import { elementValues, takeFromBlock } from "../../styles/io/round-trip.js";
|
|
24
30
|
import type { ExporterStyleInput } from "../../styles/io/writer.js";
|
|
25
31
|
import { StyleNotes } from "../../styles/notes.js";
|
|
26
|
-
import {
|
|
27
|
-
import { plainCell, resolveStyles } from "../../styles/plan/resolve.js";
|
|
32
|
+
import { plainCell } from "../../styles/plan/resolve.js";
|
|
28
33
|
import type {
|
|
29
34
|
ImporterStyles,
|
|
30
35
|
StyleColumn,
|
|
@@ -35,11 +40,14 @@ import type {
|
|
|
35
40
|
StyleWriteCapabilities,
|
|
36
41
|
} from "../../styles/types.js";
|
|
37
42
|
import type { LossNote } from "../../types.js";
|
|
43
|
+
import { graphvizFontName, postscriptFont } from "../dot/drawing.js";
|
|
38
44
|
import { type ValueColumns, valueColumns } from "../graphml/styles-yed.js";
|
|
39
|
-
import {
|
|
45
|
+
import { drawLines, drawsLineLabels, drawVertices, type PajekDraw } from "./draw.js";
|
|
46
|
+
import { LABEL_COLUMN, PAJEK_STYLE_COMMENT, SHAPE_COLUMN, VALUE_COLUMN } from "./syntax.js";
|
|
40
47
|
|
|
41
48
|
/**
|
|
42
|
-
* What the Pajek writer holds natively: the vertex label, the vertex shape keyword
|
|
49
|
+
* What the Pajek writer holds natively: the vertex label, the vertex shape keyword (a polygon only as Pajek's diamond,
|
|
50
|
+
* `shapePoints` with its `q`) and drawing parameters, the line
|
|
43
51
|
* parameters, and the arrows Pajek draws on arcs. The `graphty.styles` block is a comment line the writer places itself.
|
|
44
52
|
* @category Built-in formats
|
|
45
53
|
*/
|
|
@@ -53,26 +61,139 @@ export const PAJEK_STYLE_CAPABILITIES: StyleWriteCapabilities = Object.freeze({
|
|
|
53
61
|
"borderColor",
|
|
54
62
|
"borderWidth",
|
|
55
63
|
"shape",
|
|
64
|
+
"shapePoints",
|
|
56
65
|
"width",
|
|
57
66
|
"height",
|
|
58
67
|
"labelColor",
|
|
59
68
|
"labelSize",
|
|
69
|
+
"labelFont",
|
|
70
|
+
"labelPosition",
|
|
71
|
+
"labelOffsetX",
|
|
72
|
+
"labelOffsetY",
|
|
73
|
+
"labelShiftX",
|
|
74
|
+
] as const),
|
|
75
|
+
edge: Object.freeze([
|
|
76
|
+
"color",
|
|
77
|
+
"opacity",
|
|
78
|
+
"width",
|
|
79
|
+
"lineStyle",
|
|
80
|
+
"sourceArrow",
|
|
81
|
+
"targetArrow",
|
|
82
|
+
"arrowPosition",
|
|
83
|
+
"label",
|
|
84
|
+
"labelColor",
|
|
85
|
+
"labelSize",
|
|
86
|
+
"labelFont",
|
|
60
87
|
] as const),
|
|
61
|
-
edge: Object.freeze(["color", "opacity", "width", "lineStyle", "sourceArrow", "targetArrow"] as const),
|
|
62
88
|
block: "native",
|
|
63
89
|
});
|
|
64
90
|
|
|
65
91
|
/**
|
|
66
|
-
* Pajek's
|
|
67
|
-
*
|
|
92
|
+
* Pajek's normal vertex size, in points: what `x_fact` and `y_fact` multiply (the manual, p. 89, names `x_fact` and
|
|
93
|
+
* `y_fact` "magnification" without stating the value).
|
|
94
|
+
* Measured in Pajek 6.02's own SVG export (`test/fidelity/oracles/pajek-*.svg`): a vertex with no size parameter is
|
|
95
|
+
* drawn 20 across, and `x_fact 1.54` 30.8, on the drawing's [0,500] square.
|
|
96
|
+
*/
|
|
97
|
+
const PAJEK_VERTEX_SIZE = 20;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* How far past its vertex size Pajek draws its man and woman figures, across and up: Pajek 6.02's SVG export draws
|
|
101
|
+
* them as paths that reach beyond the vertex's own square, a man 2.5 times its size across and 6.49 times its size
|
|
102
|
+
* tall, a woman (her skirt and arms wider) 3 times across and 6.48 tall (`pajek-specimen-node-shapes`, vertices 9 and
|
|
103
|
+
* 10 at `x_fact 0.5`: the man's path spans x -12.51..12.51 and y -32.74..32.13, the woman's -15.02..15.02 and
|
|
104
|
+
* -32.99..31.79, around a size of 10).
|
|
68
105
|
*/
|
|
69
|
-
const
|
|
106
|
+
const PAJEK_FIGURES: ReadonlyMap<string, readonly [number, number]> = new Map([
|
|
107
|
+
["other:man", [2.502, 6.487]],
|
|
108
|
+
["other:woman", [3.003, 6.478]],
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Points per coordinate unit: "When Postscript picture is obtained, the square [0,1]x[0,1] is transformed to
|
|
113
|
+
* [0,500]x[0,500]" (Pajek, "Pajek to EPS", http://mrvar.fdv.uni-lj.si/pajek/DrawEPS.htm).
|
|
114
|
+
*/
|
|
115
|
+
export const PAJEK_POINTS_PER_UNIT = 500;
|
|
70
116
|
|
|
71
117
|
/** The vertex parameters read as styles. */
|
|
72
|
-
const NODE_KEYS: readonly string[] = [
|
|
118
|
+
const NODE_KEYS: readonly string[] = [
|
|
119
|
+
"ic",
|
|
120
|
+
"bc",
|
|
121
|
+
"bw",
|
|
122
|
+
"s_size",
|
|
123
|
+
"x_fact",
|
|
124
|
+
"y_fact",
|
|
125
|
+
"phi",
|
|
126
|
+
"q",
|
|
127
|
+
"lc",
|
|
128
|
+
"fos",
|
|
129
|
+
"font",
|
|
130
|
+
"lr",
|
|
131
|
+
"lphi",
|
|
132
|
+
"la",
|
|
133
|
+
];
|
|
73
134
|
|
|
74
135
|
/** The line parameters read as styles. */
|
|
75
|
-
const EDGE_KEYS: readonly string[] = ["c", "w", "p"];
|
|
136
|
+
const EDGE_KEYS: readonly string[] = ["c", "w", "p", "s", "ap", "l", "lc", "fos", "font"];
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* What Pajek carries from one line of a section to the next. A line (arc or edge) that does not set one of these
|
|
140
|
+
* parameters is drawn with the value the line before it set. A vertex line that sets no parameter and names no shape
|
|
141
|
+
* is drawn exactly as the vertex line before it; a vertex line that sets any starts again from Pajek's defaults.
|
|
142
|
+
* Measured in Pajek 6.02's own SVG export: `test/fidelity/oracles/pajek-kdorola.svg` (vertices 2 to 59, which set
|
|
143
|
+
* nothing, drawn with vertex 1's `lr 11 lphi 285 fos 9`, and 61 on as vertex 60's `box`), `pajek-1crn.svg` (vertex 3
|
|
144
|
+
* drawn as vertex 2), `pajek-synth-lines.svg` (arc 3, which sets `w 3` but no `c`, drawn in arc 2's `c Blue`), and
|
|
145
|
+
* the generated `shapes-and-lines.net` (vertex 5 at the default size after vertex 4's `x_fact 8`). A line's label `l`
|
|
146
|
+
* is its own; its label's color, size and font are not carried here, as they only show with a label.
|
|
147
|
+
*/
|
|
148
|
+
const CARRIED: Readonly<Record<StyleTarget, readonly string[]>> = {
|
|
149
|
+
node: [SHAPE_COLUMN, ...NODE_KEYS],
|
|
150
|
+
edge: ["c", "w", "p", "s", "ap"],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* What Pajek 6.02 carries from line to line as it reads: not the pattern `p`, which the writer still restates
|
|
155
|
+
* (`pajek-synth-lines`: edge `3 10` after `1 8 ... p Dots` and arc `5 6 -1` after a `p Solid` are drawn as if no line
|
|
156
|
+
* before set one).
|
|
157
|
+
*/
|
|
158
|
+
const CARRIED_LINE: readonly string[] = CARRIED.edge.filter((key) => key !== "p");
|
|
159
|
+
|
|
160
|
+
/** The width Pajek draws an edge (undirected) at for each unit of `w`; an arc is drawn `w` wide. */
|
|
161
|
+
const EDGE_WIDTH_PER_W = 2;
|
|
162
|
+
|
|
163
|
+
/** The color and width Pajek draws an arc with when it sets no `c` or `w` (an edge's are the tool defaults). */
|
|
164
|
+
const ARC_DEFAULTS = { color: "#999999", width: 1 } as const;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* A getter that gives an element the parameters an element before it set, as Pajek reads them: for a line each
|
|
168
|
+
* parameter it leaves out, for a vertex all of them when it sets none (`CARRIED`).
|
|
169
|
+
* @param keys - the parameters carried
|
|
170
|
+
* @param whole - true for vertices: carried only to an element that sets none, and then all together
|
|
171
|
+
* @returns for each element in order, its getter
|
|
172
|
+
*/
|
|
173
|
+
function carriedParams(keys: readonly string[], whole: boolean): (get: Param) => Param {
|
|
174
|
+
let last = new Map<string, unknown>();
|
|
175
|
+
return (get) => {
|
|
176
|
+
const own = new Map<string, unknown>();
|
|
177
|
+
for (const key of keys) {
|
|
178
|
+
const v = get(key);
|
|
179
|
+
if (v !== null) {
|
|
180
|
+
own.set(key, v);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (whole) {
|
|
184
|
+
if (own.size > 0) {
|
|
185
|
+
last = own;
|
|
186
|
+
return get;
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
for (const [key, v] of own) {
|
|
190
|
+
last.set(key, v);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const carried = last;
|
|
194
|
+
return (key) => get(key) ?? (keys.includes(key) ? (carried.get(key) ?? null) : null);
|
|
195
|
+
};
|
|
196
|
+
}
|
|
76
197
|
|
|
77
198
|
/** The style column marking arcs, which Pajek draws with an arrow at the target. */
|
|
78
199
|
const DIRECTED_KEY = "directed";
|
|
@@ -82,6 +203,8 @@ const READ: Readonly<Record<StyleTarget, readonly string[]>> = {
|
|
|
82
203
|
node: [
|
|
83
204
|
"label",
|
|
84
205
|
"shape",
|
|
206
|
+
"shapePoints",
|
|
207
|
+
"shapePointsStretch",
|
|
85
208
|
"fill",
|
|
86
209
|
"fillOpacity",
|
|
87
210
|
"borderColor",
|
|
@@ -90,8 +213,25 @@ const READ: Readonly<Record<StyleTarget, readonly string[]>> = {
|
|
|
90
213
|
"height",
|
|
91
214
|
"labelColor",
|
|
92
215
|
"labelSize",
|
|
216
|
+
"labelFont",
|
|
217
|
+
"labelPosition",
|
|
218
|
+
"labelOffsetX",
|
|
219
|
+
"labelOffsetY",
|
|
220
|
+
"labelShiftX",
|
|
221
|
+
"labelRotation",
|
|
222
|
+
],
|
|
223
|
+
edge: [
|
|
224
|
+
"color",
|
|
225
|
+
"opacity",
|
|
226
|
+
"width",
|
|
227
|
+
"lineStyle",
|
|
228
|
+
"arrowSize",
|
|
229
|
+
"arrowPosition",
|
|
230
|
+
"label",
|
|
231
|
+
"labelColor",
|
|
232
|
+
"labelSize",
|
|
233
|
+
"labelFont",
|
|
93
234
|
],
|
|
94
|
-
edge: ["color", "opacity", "width", "lineStyle"],
|
|
95
235
|
};
|
|
96
236
|
|
|
97
237
|
/** A parameter's value on one element, or null. */
|
|
@@ -117,6 +257,13 @@ function readColor(raw: unknown, property: string, opacity: string | null, put:
|
|
|
117
257
|
}
|
|
118
258
|
const c = canonColor(raw, "dvips", at(property));
|
|
119
259
|
if (c === null) {
|
|
260
|
+
// a name Pajek does not know, or a known one in another case (`red`), fills a vertex white and draws a line
|
|
261
|
+
// black (`pajek-specimen-node-fills-borders-p3` `ic-unknown-name`, `ic-lowercase-name`); ponytail: measured
|
|
262
|
+
// on names in `ic` and `c` only, so a border or label color and an unreadable RGB / CMYK form keep the default
|
|
263
|
+
const unknown = UNKNOWN_COLORS.get(property);
|
|
264
|
+
if (unknown !== undefined && typeof raw === "string" && /^[A-Za-z]\w*$/.test(raw.trim())) {
|
|
265
|
+
put(property, unknown);
|
|
266
|
+
}
|
|
120
267
|
return;
|
|
121
268
|
}
|
|
122
269
|
put(property, c.color);
|
|
@@ -126,6 +273,12 @@ function readColor(raw: unknown, property: string, opacity: string | null, put:
|
|
|
126
273
|
}
|
|
127
274
|
}
|
|
128
275
|
|
|
276
|
+
/** What Pajek draws for a color it cannot read, by property. */
|
|
277
|
+
const UNKNOWN_COLORS: ReadonlyMap<string, StyleValue> = new Map([
|
|
278
|
+
["fill", "#ffffff"],
|
|
279
|
+
["color", "#000000"],
|
|
280
|
+
]);
|
|
281
|
+
|
|
129
282
|
/**
|
|
130
283
|
* Reads a length or factor.
|
|
131
284
|
* @param raw - the parameter's value, or null
|
|
@@ -144,42 +297,742 @@ function readNumber(raw: unknown, property: string, put: Put, at: At): void {
|
|
|
144
297
|
* @param get - the vertex's parameters
|
|
145
298
|
* @param put - receives the values
|
|
146
299
|
* @param at - the sites
|
|
300
|
+
* @param place - the vertex's place in the file's coordinates, or null when it has none
|
|
147
301
|
*/
|
|
148
|
-
function readVertex(get: Param, put: Put, at: At): void {
|
|
302
|
+
function readVertex(get: Param, put: Put, at: At, place: Place | null = null): void {
|
|
149
303
|
// an empty label draws nothing
|
|
150
304
|
const label = get(LABEL_COLUMN);
|
|
151
305
|
if (label !== null && label !== "") {
|
|
152
306
|
put("label", canonText(label, at("label")));
|
|
153
307
|
}
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
308
|
+
const raw = get(SHAPE_COLUMN);
|
|
309
|
+
const shape = raw === null ? null : canonEnum("shape", raw, "dvips", at("shape"));
|
|
310
|
+
if (raw !== null) {
|
|
311
|
+
put("shape", shape);
|
|
157
312
|
}
|
|
158
313
|
readColor(get("ic"), "fill", "fillOpacity", put, at);
|
|
159
314
|
readColor(get("bc"), "borderColor", null, put, at);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
put("borderWidth", width === null ? null : width / VERTEX_SIZE);
|
|
164
|
-
}
|
|
165
|
-
readNumber(get("x_fact"), "width", put, at);
|
|
166
|
-
readNumber(get("y_fact"), "height", put, at);
|
|
315
|
+
readNumber(get("bw"), "borderWidth", put, at);
|
|
316
|
+
readSize(get, put, at, PAJEK_FIGURES.get(String(shape)));
|
|
317
|
+
readOutline(get, put, shape);
|
|
167
318
|
readColor(get("lc"), "labelColor", null, put, at);
|
|
168
319
|
readNumber(get("fos"), "labelSize", put, at);
|
|
320
|
+
readFont(get("font"), put, at);
|
|
321
|
+
readLabelPosition(get, put, at);
|
|
322
|
+
readLabelAngle(get("la"), put, at, place);
|
|
323
|
+
if (shape === "none") {
|
|
324
|
+
// an `empty` vertex's label is drawn white at size 1 on its center, whatever the line sets
|
|
325
|
+
// (`pajek-synth-vertices` `empty *`, `pajek-lin-rel4` v226 to v233)
|
|
326
|
+
for (const [property, value] of EMPTY_LABEL) {
|
|
327
|
+
put(property, value);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* The corners Pajek 6.02's SVG export draws a box, a triangle and a house with, as `shapePoints` (fractions of the half
|
|
334
|
+
* width and height, y up): `pajek-specimen-node-shapes`, `shape-box`, `shape-triangle`, `shape-house`.
|
|
335
|
+
*/
|
|
336
|
+
const PAJEK_OUTLINES: ReadonlyMap<string, readonly (readonly [number, number])[]> = new Map([
|
|
337
|
+
[
|
|
338
|
+
"rectangle",
|
|
339
|
+
[
|
|
340
|
+
[-1, 1],
|
|
341
|
+
[1, 1],
|
|
342
|
+
[1, -1],
|
|
343
|
+
[-1, -1],
|
|
344
|
+
],
|
|
345
|
+
],
|
|
346
|
+
[
|
|
347
|
+
"triangle",
|
|
348
|
+
[
|
|
349
|
+
[1, -1],
|
|
350
|
+
[0, 1],
|
|
351
|
+
[-1, -1],
|
|
352
|
+
],
|
|
353
|
+
],
|
|
354
|
+
[
|
|
355
|
+
"other:house",
|
|
356
|
+
[
|
|
357
|
+
[-0.7, -1],
|
|
358
|
+
[0.7, -1],
|
|
359
|
+
[0.7, 0.3],
|
|
360
|
+
[1, 0.3],
|
|
361
|
+
[1, 0.4],
|
|
362
|
+
[0, 1],
|
|
363
|
+
[-1, 0.4],
|
|
364
|
+
[-1, 0.3],
|
|
365
|
+
[-0.7, 0.3],
|
|
366
|
+
],
|
|
367
|
+
],
|
|
368
|
+
]);
|
|
369
|
+
|
|
370
|
+
/** A diamond's `q` where the line sets none (Pajek's shipped SHAPES.CFG). */
|
|
371
|
+
const PAJEK_DIAMOND_Q = 0.001;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* A vertex's outline where Pajek draws one no named shape is: a diamond is Pajek's six-cornered one, its top and bottom
|
|
375
|
+
* cut `q` of its half width wide (`pajek-specimen-node-shapes`: `q 0.001` by default, `q 0.5` a hexagon, `q 1` a
|
|
376
|
+
* square, `q 2` a bow tie twice as wide), and `phi` turns a box, a triangle, a house or a diamond `phi` degrees
|
|
377
|
+
* counterclockwise about its center (`phi-30-box`, `phi-90-triangle`: Pajek draws the shape at its size, then turns
|
|
378
|
+
* it). ponytail: the corners are fractions of the size the line gives (`x_fact`, `y_fact`); a turned shape whose width
|
|
379
|
+
* and height the Draw window's Size of Vertices or vectors scale apart is turned in the wrong box. A turned ellipse,
|
|
380
|
+
* man or woman is drawn unturned: an outline of corners would draw an ellipse as a polygon.
|
|
381
|
+
* @param get - the vertex's parameters
|
|
382
|
+
* @param put - receives the values
|
|
383
|
+
* @param shape - its shape
|
|
384
|
+
*/
|
|
385
|
+
function readOutline(get: Param, put: Put, shape: StyleValue | null): void {
|
|
386
|
+
const phi = numberOrNull(get("phi")) ?? 0;
|
|
387
|
+
const q = numberOrNull(get("q")) ?? PAJEK_DIAMOND_Q;
|
|
388
|
+
const diamond: readonly (readonly [number, number])[] = [
|
|
389
|
+
[-1, 0],
|
|
390
|
+
[-q, 1],
|
|
391
|
+
[q, 1],
|
|
392
|
+
[1, 0],
|
|
393
|
+
[q, -1],
|
|
394
|
+
[-q, -1],
|
|
395
|
+
];
|
|
396
|
+
const corners = shape === "diamond" ? diamond : PAJEK_OUTLINES.get(String(shape));
|
|
397
|
+
if (corners === undefined || (shape !== "diamond" && phi % 360 === 0)) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
// turned in the drawn box, x_fact by y_fact, and back to fractions of it
|
|
401
|
+
const [w, h] = [numberOrNull(get("x_fact")) || 1, numberOrNull(get("y_fact")) || 1];
|
|
402
|
+
const [c, s] = [Math.cos((phi * Math.PI) / 180), Math.sin((phi * Math.PI) / 180)];
|
|
403
|
+
const round = (v: number): string => String(Number(v.toFixed(4)) + 0);
|
|
404
|
+
const points = corners.map(([x, y]) => {
|
|
405
|
+
const [px, py] = [x * w, y * h];
|
|
406
|
+
return `${round((px * c - py * s) / w)},${round((px * s + py * c) / h)}`;
|
|
407
|
+
});
|
|
408
|
+
put("shape", "polygon");
|
|
409
|
+
put("shapePoints", points.join(" "));
|
|
410
|
+
put("shapePointsStretch", false);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** How Pajek 6.02's SVG export draws an `empty` vertex's label. */
|
|
414
|
+
const EMPTY_LABEL: readonly (readonly [string, StyleValue])[] = [
|
|
415
|
+
["labelSize", 1],
|
|
416
|
+
["labelColor", "#ffffff"],
|
|
417
|
+
["labelPosition", "center"],
|
|
418
|
+
["labelOffsetX", 0],
|
|
419
|
+
["labelOffsetY", 0],
|
|
420
|
+
["labelShiftX", 0],
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
/** A vertex's place in the file's own coordinates, 0..1 across and down. */
|
|
424
|
+
type Place = readonly [x: number, y: number];
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* A label's `font`: Pajek writes it into its SVG export as the family, as given, so a PostScript name (`Courier-Bold`,
|
|
428
|
+
* `Helvetica-Oblique`) is drawn in its family, with a generic one after it, and upright, as the pinned fonts draw it
|
|
429
|
+
* (`pajek-specimen-labels` `font-courier-bold` monospace, `font-helvetica-oblique` sans-serif and upright); any other
|
|
430
|
+
* name is the family as written. Pajek draws every label bold whatever the name says.
|
|
431
|
+
* @param raw - the parameter's value, or null
|
|
432
|
+
* @param put - receives the values
|
|
433
|
+
* @param at - the sites
|
|
434
|
+
*/
|
|
435
|
+
function readFont(raw: unknown, put: Put, at: At): void {
|
|
436
|
+
if (raw === null || raw === "") {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const ps = typeof raw === "string" ? postscriptFont(raw.trim()) : null;
|
|
440
|
+
if (ps === null) {
|
|
441
|
+
readText(raw, "labelFont", put, at);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
put("labelFont", ps.family);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* A vertex label's angle `la` (manual p. 90), degrees counterclockwise, as a clockwise rotation: Pajek 6.02's SVG export
|
|
449
|
+
* turns the label by `rotate(-la)` (`pajek-specimen-labels`, `la-30` .. `la-270`). An angle of 360 or more turns the
|
|
450
|
+
* label away from the middle of the file's square, (0.5, 0.5) in its own coordinates, and then `la - 360` further
|
|
451
|
+
* (`la-360` at (0.4533, 0.3706) is drawn at -109.83, the direction from the middle; `la-400` at (0.5467, 0.3706) at
|
|
452
|
+
* -110.17, that direction, 70.17, and 40 more); without the vertex's place it is left unread.
|
|
453
|
+
* @param raw - the parameter's value, or null
|
|
454
|
+
* @param put - receives the value
|
|
455
|
+
* @param at - the sites
|
|
456
|
+
* @param place - the vertex's place in the file's coordinates, or null
|
|
457
|
+
*/
|
|
458
|
+
function readLabelAngle(raw: unknown, put: Put, at: At, place: Place | null): void {
|
|
459
|
+
let a = raw === null ? null : canonNumber(raw, at("labelRotation"));
|
|
460
|
+
if (a !== null && a >= 360) {
|
|
461
|
+
a = place === null ? null : (Math.atan2(0.5 - place[1], place[0] - 0.5) * 180) / Math.PI + a - 360;
|
|
462
|
+
}
|
|
463
|
+
if (a !== null) {
|
|
464
|
+
put("labelRotation", Number(((((360 - a) % 360) + 360) % 360).toPrecision(12)));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* A vertex's width and height in points: the normal size times its `x_fact` and `y_fact`, each 1 when unset; a vertex
|
|
470
|
+
* that sets neither is left to the default beneath. The manual (p. 89) calls `s_size` "default size", but Pajek 6.02
|
|
471
|
+
* draws a vertex the same size whatever its `s_size` (`pajek-specimen-node-sizes-opacity`: `s_size` 0.5, 2 and 3 all
|
|
472
|
+
* drawn 36 across at `x_fact 1.8`), so it is not read. A man or a woman is the box of the figure Pajek draws for
|
|
473
|
+
* that size (`PAJEK_FIGURES`), set or not.
|
|
474
|
+
* @param get - the vertex's parameters
|
|
475
|
+
* @param put - receives the values
|
|
476
|
+
* @param at - the sites
|
|
477
|
+
* @param figure - a figure's width and height over its size, or undefined for another shape
|
|
478
|
+
*/
|
|
479
|
+
function readSize(get: Param, put: Put, at: At, figure?: readonly [number, number]): void {
|
|
480
|
+
for (const [key, property, k] of [
|
|
481
|
+
["x_fact", "width", 0],
|
|
482
|
+
["y_fact", "height", 1],
|
|
483
|
+
] as const) {
|
|
484
|
+
const f = get(key);
|
|
485
|
+
if (f === null && figure === undefined) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const factor = f === null ? 1 : canonNumber(f, at(property));
|
|
489
|
+
const over = figure?.[k] ?? 1;
|
|
490
|
+
put(property, factor === null ? null : Number((PAJEK_VERTEX_SIZE * factor * over).toPrecision(15)));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* A text parameter (a PostScript font name).
|
|
496
|
+
* @param raw - the parameter's value, or null
|
|
497
|
+
* @param property - the property
|
|
498
|
+
* @param put - receives the value
|
|
499
|
+
* @param at - the sites
|
|
500
|
+
*/
|
|
501
|
+
function readText(raw: unknown, property: string, put: Put, at: At): void {
|
|
502
|
+
if (raw !== null && raw !== "") {
|
|
503
|
+
put(property, canonText(raw, at(property)));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** The `lphi` written for each side graphty puts a label on, and the side written for each Pajek has no angle for. */
|
|
508
|
+
const SIDE_ANGLES: ReadonlyMap<string, number> = new Map([
|
|
509
|
+
["right", 0],
|
|
510
|
+
["top-right", 90],
|
|
511
|
+
["left", 180],
|
|
512
|
+
["bottom-right", 285],
|
|
513
|
+
]);
|
|
514
|
+
const NEAREST_SIDES: ReadonlyMap<string, string> = new Map([
|
|
515
|
+
["top", "top-right"],
|
|
516
|
+
["top-left", "top-right"],
|
|
517
|
+
["bottom", "bottom-right"],
|
|
518
|
+
["bottom-left", "bottom-right"],
|
|
519
|
+
]);
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* A vertex's label place from Pajek's polar label parameters (manual p. 90): `lr` is the distance of the label from
|
|
523
|
+
* the vertex center, 0 centering it, and `lphi` its angle in degrees counterclockwise. Pajek 6.02's SVG export starts
|
|
524
|
+
* the text at that point, its baseline through it (`text-anchor` start at y = the point), so the label's left edge is
|
|
525
|
+
* at the point's x (`labelShiftX` 0.5) and its middle `PAJEK_BASELINE_TO_MIDDLE` of its size above the point's y
|
|
526
|
+
* (`pajek-specimen-labels` `lr-*`, `lphi-*`; every corpus label). A vertex that sets none of `lr`, `lphi` and `fos`
|
|
527
|
+
* is left to the default beneath, the same place at the default size.
|
|
528
|
+
* @param get - the vertex's parameters
|
|
529
|
+
* @param put - receives the values
|
|
530
|
+
* @param at - the sites
|
|
531
|
+
*/
|
|
532
|
+
function readLabelPosition(get: Param, put: Put, at: At): void {
|
|
533
|
+
const [lr, lphi, fos] = [get("lr"), get("lphi"), get("fos")];
|
|
534
|
+
if (lr === null && lphi === null && fos === null) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const radius = lr === null ? PAJEK_LABEL_RADIUS : canonNumber(lr, at("labelPosition"));
|
|
538
|
+
const angle = lphi === null ? PAJEK_LABEL_ANGLE : canonNumber(lphi, at("labelPosition"));
|
|
539
|
+
const size = fos === null ? PAJEK_LABEL_SIZE : canonNumber(fos, at("labelSize"));
|
|
540
|
+
if (radius === null || angle === null || size === null) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const [x, y] = labelOffset(radius, angle, size);
|
|
544
|
+
put("labelPosition", "center");
|
|
545
|
+
put("labelOffsetX", x);
|
|
546
|
+
put("labelOffsetY", y);
|
|
547
|
+
put("labelShiftX", radius === 0 ? 0 : 0.5);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Where Pajek's polar label parameters put a label, from the vertex center, y down: the middle of its left edge, or
|
|
552
|
+
* its middle when `lr` is 0, which centers it.
|
|
553
|
+
* @param radius - `lr`
|
|
554
|
+
* @param angle - `lphi`, degrees counterclockwise
|
|
555
|
+
* @param size - the label's size
|
|
556
|
+
* @returns the offset across and down
|
|
557
|
+
*/
|
|
558
|
+
function labelOffset(radius: number, angle: number, size: number): [number, number] {
|
|
559
|
+
if (radius === 0) {
|
|
560
|
+
return [0, 0];
|
|
561
|
+
}
|
|
562
|
+
const t = (angle * Math.PI) / 180;
|
|
563
|
+
const round = (v: number): number => Number(v.toFixed(9)) + 0;
|
|
564
|
+
return [round(radius * Math.cos(t)), round(-radius * Math.sin(t) - PAJEK_BASELINE_TO_MIDDLE * size)];
|
|
169
565
|
}
|
|
170
566
|
|
|
171
567
|
/**
|
|
172
|
-
*
|
|
568
|
+
* The label radius and angle Pajek uses when a vertex sets none: 20 and 285, below and to the right of the vertex.
|
|
569
|
+
* Measured in Pajek 6.02's own SVG export (`test/fidelity/oracles/pajek-football.svg`, `pajek-specimen-labels`
|
|
570
|
+
* `default`: every such label starts 20 from its vertex at 285 degrees); the size is the default label size.
|
|
571
|
+
*/
|
|
572
|
+
const PAJEK_LABEL_RADIUS = 20;
|
|
573
|
+
const PAJEK_LABEL_ANGLE = 285;
|
|
574
|
+
const PAJEK_LABEL_SIZE = 15;
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* How far above its baseline a label's middle is, per unit of its size: half the ascent less the descent of Arial
|
|
578
|
+
* (Liberation Sans: 0.905 and 0.212), the face Pajek draws its labels in by default.
|
|
579
|
+
*/
|
|
580
|
+
const PAJEK_BASELINE_TO_MIDDLE = 0.3465;
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* One line's style: its color, width and pattern. Pajek draws an edge (undirected) twice as wide as an arc of the
|
|
584
|
+
* same `w`.
|
|
173
585
|
* @param get - the line's parameters
|
|
174
586
|
* @param put - receives the values
|
|
175
587
|
* @param at - the sites
|
|
588
|
+
* @param directed - whether the line is an arc
|
|
589
|
+
* @param labels - whether its label is drawn (`drawsLineLabels`)
|
|
590
|
+
* @param run - the line from its source's center to its target's, in drawn units, y down, or null when unplaced
|
|
591
|
+
* @param loop - for a loop on a placed node, receives its control points; null for any other line
|
|
176
592
|
*/
|
|
177
|
-
function readLine(
|
|
593
|
+
function readLine(
|
|
594
|
+
get: Param,
|
|
595
|
+
put: Put,
|
|
596
|
+
at: At,
|
|
597
|
+
directed: boolean,
|
|
598
|
+
labels = true,
|
|
599
|
+
run: Run | null = null,
|
|
600
|
+
loop: ((controls: LoopControls) => void) | null = null,
|
|
601
|
+
): void {
|
|
178
602
|
readColor(get("c"), "color", "opacity", put, at);
|
|
179
|
-
readNumber(
|
|
603
|
+
readNumber(
|
|
604
|
+
get("w"),
|
|
605
|
+
"width",
|
|
606
|
+
(property, value) => {
|
|
607
|
+
put(property, typeof value === "number" && !directed ? value * EDGE_WIDTH_PER_W : value);
|
|
608
|
+
},
|
|
609
|
+
at,
|
|
610
|
+
);
|
|
180
611
|
const p = get("p");
|
|
181
|
-
if (p
|
|
182
|
-
put("lineStyle",
|
|
612
|
+
if (p === "Dots") {
|
|
613
|
+
put("lineStyle", pajekDash(get, directed));
|
|
614
|
+
} else if (p === "Solid") {
|
|
615
|
+
put("lineStyle", "solid");
|
|
616
|
+
} else if (p !== null) {
|
|
617
|
+
// Pajek knows Solid and Dots, case for case; any other pattern (`Dashed`, `dots`) draws a solid line
|
|
618
|
+
noteAt(at("lineStyle"), STYLE_ISSUE.UNKNOWN_VALUE, typeof p === "string" ? p : JSON.stringify(p));
|
|
619
|
+
put("lineStyle", "solid");
|
|
620
|
+
}
|
|
621
|
+
// an arrowhead is 6 x `s` long and 5 x `s` wide; `s` 1, Pajek's normal size, is 6 long (the pajek tool arrowhead)
|
|
622
|
+
readNumber(get("s"), "arrowSize", put, at);
|
|
623
|
+
readArrowPosition(get("ap"), put, at);
|
|
624
|
+
// a loop is drawn as the Bezier route of a line PAJEK_POINTS_PER_UNIT long to the right, its two ends on the node
|
|
625
|
+
const line = loop === null ? get : loopParam(get);
|
|
626
|
+
const controls = loop === null ? null : readLoop(line, put, loop);
|
|
627
|
+
if (labels) {
|
|
628
|
+
readText(get("l"), "label", put, at);
|
|
629
|
+
if (controls === null) {
|
|
630
|
+
readLineLabelPlace(get, put, at, run);
|
|
631
|
+
} else {
|
|
632
|
+
readLineLabelPlace(line, put, at, [PAJEK_POINTS_PER_UNIT, 0], loopTop(controls));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (loop === null) {
|
|
636
|
+
readBezier(get, put, run);
|
|
637
|
+
}
|
|
638
|
+
readColor(get("lc"), "labelColor", null, put, at);
|
|
639
|
+
readNumber(get("fos"), "labelSize", put, at);
|
|
640
|
+
readFont(get("font"), put, at);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** A line from its source's center to its target's, in drawn units, y down. */
|
|
644
|
+
type Run = readonly [dx: number, dy: number];
|
|
645
|
+
|
|
646
|
+
/** A loop's two Bezier control points, each from its node's center in drawn units, y down. */
|
|
647
|
+
type LoopControls = readonly [readonly [number, number], readonly [number, number]];
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* The route parameters Pajek 6.02's SVG export draws a loop with where the line sets none (`pajek-specimen-edges-lines-p2`
|
|
651
|
+
* `loop` and `loop-arc`, `pajek-specimen-edge-labels` `on-loop`: a cubic from the node's center up and back, its control
|
|
652
|
+
* points 74.9 out at 70 and 110 degrees up from the right).
|
|
653
|
+
*/
|
|
654
|
+
const LOOP_DEFAULTS: Readonly<Record<string, number>> = { a1: -70, k1: 0.3, a2: -70, k2: 0.3 };
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* A loop's route parameters: the line's own, Pajek's loop defaults for any it leaves out.
|
|
658
|
+
* @param get - the line's parameters
|
|
659
|
+
* @returns the parameters with the defaults
|
|
660
|
+
*/
|
|
661
|
+
function loopParam(get: Param): Param {
|
|
662
|
+
return (key) => get(key) ?? (key in LOOP_DEFAULTS ? LOOP_DEFAULTS[key] : null);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* A loop's route (manual p. 90: "if arc is a loop (arc to itself) bezier loop will be drawn"), as Pajek 6.02's SVG
|
|
667
|
+
* export draws it: the Bezier route of a line PAJEK_POINTS_PER_UNIT long running right, both its ends on the node
|
|
668
|
+
* (`a1 -130 k1 0.6 a2 -130 k2 0.6`: control points 150 out at 130 and 50 degrees up from the right, measured on
|
|
669
|
+
* `pajek-specimen-edges-lines-p2` `loop-bezier`). Its bends are written once the nodes' sizes are known
|
|
670
|
+
* (`layLoops`). ponytail: measured in one Draw window (1200 x 900) and one vertex size; whether Pajek scales a loop
|
|
671
|
+
* with either was not measured.
|
|
672
|
+
* @param get - the line's parameters, with the loop defaults
|
|
673
|
+
* @param put - receives the values
|
|
674
|
+
* @param loop - receives the control points
|
|
675
|
+
* @returns the control points, or null when the line's parameters give none
|
|
676
|
+
*/
|
|
677
|
+
function readLoop(get: Param, put: Put, loop: (controls: LoopControls) => void): LoopControls | null {
|
|
678
|
+
const run: Run = [PAJEK_POINTS_PER_UNIT, 0];
|
|
679
|
+
const controls = bezierControls(get, run);
|
|
680
|
+
if (controls === null) {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
const [[a1, l1], [a2, l2]] = controls;
|
|
684
|
+
const L = PAJEK_POINTS_PER_UNIT;
|
|
685
|
+
// along the line is right, left of it up: the second point is measured back from the line's far end, the node
|
|
686
|
+
const [c1, c2]: LoopControls = [
|
|
687
|
+
[a1 * L, -l1 * L],
|
|
688
|
+
[(a2 - 1) * L, -l2 * L],
|
|
689
|
+
];
|
|
690
|
+
loop([c1, c2]);
|
|
691
|
+
put("curved", true);
|
|
692
|
+
put("bendCurve", "bezier");
|
|
693
|
+
// an arc's head is drawn where the loop comes back to its node's center, under the node
|
|
694
|
+
// (`pajek-specimen-edges-lines-p2` `loop-arc`, `s 1 a A`: no head shows)
|
|
695
|
+
put("targetArrow", "none");
|
|
696
|
+
return [c1, c2];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* The way a loop runs at its top, where its label is drawn (`lp` 0.5): from its first control point to its second
|
|
701
|
+
* (`pajek-specimen-edge-labels` `on-loop`: 179.97). ponytail: a label elsewhere on the loop (`lp`) is turned as at
|
|
702
|
+
* its top.
|
|
703
|
+
* @param controls - the loop's control points
|
|
704
|
+
* @returns the direction, in degrees clockwise on screen
|
|
705
|
+
*/
|
|
706
|
+
function loopTop(controls: LoopControls): number {
|
|
707
|
+
const [c1, c2] = controls;
|
|
708
|
+
return (Math.atan2(c2[1] - c1[1], c2[0] - c1[0]) * 180) / Math.PI;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Writes each loop's control points as its bends, fractions of its node's half width and half height, y up, once the
|
|
713
|
+
* nodes are sized, and draws it round whatever the Draw window does to the other lines.
|
|
714
|
+
* @param snapshot - the graph
|
|
715
|
+
* @param loops - each loop's control points by edge
|
|
716
|
+
* @param node - the node values, sized
|
|
717
|
+
* @param edge - the edge values; changed in place
|
|
718
|
+
*/
|
|
719
|
+
function layLoops(
|
|
720
|
+
snapshot: GraphSnapshot,
|
|
721
|
+
loops: ReadonlyMap<number, LoopControls>,
|
|
722
|
+
node: ValueColumns,
|
|
723
|
+
edge: ValueColumns,
|
|
724
|
+
): void {
|
|
725
|
+
const fraction = (x: number): string => String(Math.round(x * 1e4) / 1e4 || 0);
|
|
726
|
+
for (const [e, controls] of loops) {
|
|
727
|
+
const v = snapshot.edgeSource(e);
|
|
728
|
+
const half = (property: string): number => {
|
|
729
|
+
const size = node[property]?.[v];
|
|
730
|
+
return (typeof size === "number" && size > 0 ? size : PAJEK_VERTEX_SIZE) / 2;
|
|
731
|
+
};
|
|
732
|
+
const [hw, hh] = [half("width"), half("height")];
|
|
733
|
+
const column = (property: string): (StyleValue | null)[] =>
|
|
734
|
+
(edge[property] ??= new Array<StyleValue | null>(snapshot.edgeCount).fill(null));
|
|
735
|
+
column("bends")[e] = controls.map(([x, y]) => `${fraction(x / hw)},${fraction(-y / hh)}`).join(" ");
|
|
736
|
+
// Export Line Default / Straight Lines straightens every other line; a loop is still drawn round
|
|
737
|
+
// (`pajek-specimen-edges-lines-p4`)
|
|
738
|
+
column("curved")[e] = true;
|
|
739
|
+
column("bendCurve")[e] = "bezier";
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Where Pajek 6.02's SVG export draws a line's label (`pajek-specimen-edge-labels`): its middle at `lp` from the
|
|
745
|
+
* terminal vertex, a fraction of the line up to 1 (0.5 by default, 0.2 near the target) and a distance beyond it
|
|
746
|
+
* (`lp 20` 20 from the target), moved by `lr` at `lphi` degrees counterclockwise (`lr 12 lphi 90` 12 above), its
|
|
747
|
+
* baseline through that point, and turned along the line and `la` degrees counterclockwise from it (`la 45` drawn
|
|
748
|
+
* rotate(-45) on a level line; `la 405` as `la 45`). graphty hangs a line's label on the line's middle, so the place is
|
|
749
|
+
* an offset from there, the words' middle above the baseline in the label's own turned frame.
|
|
750
|
+
* @param get - the line's parameters
|
|
751
|
+
* @param put - receives the values
|
|
752
|
+
* @param at - the sites
|
|
753
|
+
* @param run - the line, or null when unplaced
|
|
754
|
+
* @param direction - the line's direction at the label, in degrees clockwise on screen, where `run` does not give it
|
|
755
|
+
* (a loop)
|
|
756
|
+
*/
|
|
757
|
+
function readLineLabelPlace(get: Param, put: Put, at: At, run: Run | null, direction?: number): void {
|
|
758
|
+
const length = run === null ? 0 : Math.hypot(run[0], run[1]);
|
|
759
|
+
if (run === null || length === 0) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const n = (key: string, fallback: number, property: string): number | null => {
|
|
763
|
+
const v = get(key);
|
|
764
|
+
return v === null ? fallback : canonNumber(v, at(property));
|
|
765
|
+
};
|
|
766
|
+
const [lp, lr, lphi, la] = [
|
|
767
|
+
n("lp", 0.5, "labelOffsetX"),
|
|
768
|
+
n("lr", 0, "labelOffsetX"),
|
|
769
|
+
n("lphi", 0, "labelOffsetX"),
|
|
770
|
+
n("la", 0, "labelRotation"),
|
|
771
|
+
];
|
|
772
|
+
const size = n("fos", PAJEK_LABEL_SIZE, "labelSize");
|
|
773
|
+
if (lp === null || lr === null || lphi === null || la === null || size === null) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
// from the line's middle to the point lp from its target, then out by lr at lphi
|
|
777
|
+
const along = 0.5 - (lp <= 1 ? lp : lp / length);
|
|
778
|
+
const t = (lphi * Math.PI) / 180;
|
|
779
|
+
const round = (v: number): number => Number(v.toFixed(9)) + 0;
|
|
780
|
+
const turn = (((direction ?? lineDirection(get, run, along + 0.5)) - la) % 360) + 360;
|
|
781
|
+
// the baseline runs through the point; the words' middle is above it in the label's own turned frame
|
|
782
|
+
const up = (turn * Math.PI) / 180;
|
|
783
|
+
const rise = PAJEK_BASELINE_TO_MIDDLE * size;
|
|
784
|
+
put("labelPosition", "center");
|
|
785
|
+
put("labelOffsetX", round(along * run[0] + lr * Math.cos(t) + rise * Math.sin(up)));
|
|
786
|
+
put("labelOffsetY", round(along * run[1] - lr * Math.sin(t) - rise * Math.cos(up)));
|
|
787
|
+
put("labelRotation", round(turn % 360));
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* A line's Bezier route (manual p. 90: `a1`, `k1` at its source, `a2`, `k2` at its target), as Pajek 6.02's SVG export
|
|
792
|
+
* draws it (`pajek-specimen-edges-lines-p2`, `pajek-specimen-edge-labels`): one cubic Bezier curve whose first control
|
|
793
|
+
* point is `k1` / 2 of the line's length from the source, turned `a1` degrees clockwise on screen from the line, and
|
|
794
|
+
* whose second is `k2` / 2 of it back from the target, turned `a2` degrees counterclockwise (`a1 40 k1 1.5 a2 -40 k2
|
|
795
|
+
* 1.5` an S, `a2 40` a bow). A negative `k1` is one of Pajek's oval, circular arc or double routes, which its SVG export
|
|
796
|
+
* draws straight. A loop is `readLoop`'s. ponytail: a line that sets only some of the four reads the
|
|
797
|
+
* others as 0 and 1; Pajek's values for them were not measured.
|
|
798
|
+
* @param get - the line's parameters
|
|
799
|
+
* @param put - receives the values
|
|
800
|
+
* @param run - the line, or null when unplaced
|
|
801
|
+
*/
|
|
802
|
+
function readBezier(get: Param, put: Put, run: Run | null): void {
|
|
803
|
+
const controls = bezierControls(get, run);
|
|
804
|
+
if (controls === null) {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const fraction = (x: number): string => String(Math.round(x * 1e4) / 1e4 || 0);
|
|
808
|
+
put("curved", true);
|
|
809
|
+
put("bendCurve", "bezier");
|
|
810
|
+
put("bends", controls.map(([along, across]) => `${fraction(along)},${fraction(across)}`).join(" "));
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Where Pajek 6.02's SVG export ends an arc's Bezier route (`readBezier`): at its arrowhead's back, not at its target's
|
|
815
|
+
* center. It places the arrowhead as on a straight arc, its tip 1.5 short of where the straight line meets the
|
|
816
|
+
* target's outline, turns it `-a2` degrees about that point, and runs the curve from the source's center to the
|
|
817
|
+
* arrowhead's back, its control points measured on that shorter line (`pajek-specimen-edges-lines-p2`
|
|
818
|
+
* `route-bezier-opposite-arc`: the arrowhead's back at (347.02, 124.32), 6.5 back from the outline at (352, 128.5),
|
|
819
|
+
* turned 40 degrees; `route-bezier-same-arc` at (571.02, 132.68), turned -40). graphty draws the route to the target's
|
|
820
|
+
* center and stops it at the outline, so the curve's control points are written in the frame of that line.
|
|
821
|
+
* ponytail: the outline is the ellipse of the vertex's width and height, as the file sizes it (before the Draw
|
|
822
|
+
* window's Size of Vertices or vectors), whatever its shape.
|
|
823
|
+
* @param get - the line's parameters
|
|
824
|
+
* @param put - receives the values
|
|
825
|
+
* @param run - the line from its source's center to its target's, in drawn units, y down
|
|
826
|
+
* @param target - the target vertex's width and height
|
|
827
|
+
*/
|
|
828
|
+
function readArcEnd(get: Param, put: Put, run: Run, target: readonly [number, number]): void {
|
|
829
|
+
const ap = numberOrNull(get("ap")) ?? 0;
|
|
830
|
+
const length = Math.hypot(run[0], run[1]);
|
|
831
|
+
if (ap !== 0 || length === 0 || bezierControls(get, run) === null) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const [ux, uy] = [run[0] / length, run[1] / length];
|
|
835
|
+
// the straight line meets the outline r from the target's center; the arrowhead's back is 1.5 + 5 s before it
|
|
836
|
+
const r = 1 / Math.hypot(ux / (target[0] / 2), uy / (target[1] / 2));
|
|
837
|
+
const back = 1.5 + 5 * (numberOrNull(get("s")) ?? 1);
|
|
838
|
+
const turn = (-(numberOrNull(get("a2")) ?? 0) * Math.PI) / 180;
|
|
839
|
+
const [c, s] = [Math.cos(turn), Math.sin(turn)];
|
|
840
|
+
// the arrowhead's back, from the source's center
|
|
841
|
+
const end: Run = [run[0] - r * ux - back * (ux * c - uy * s), run[1] - r * uy - back * (ux * s + uy * c)];
|
|
842
|
+
const controls = bezierControls(get, end);
|
|
843
|
+
if (controls === null) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
// each control point on screen from the source's center, then along and to the left of the line to the target
|
|
847
|
+
const L2 = length * length;
|
|
848
|
+
const fraction = (x: number): string => String(Math.round(x * 1e4) / 1e4 || 0);
|
|
849
|
+
const bends = controls.map(([along, left]) => {
|
|
850
|
+
const [x, y] = [along * end[0] + left * end[1], along * end[1] - left * end[0]];
|
|
851
|
+
return `${fraction((x * run[0] + y * run[1]) / L2)},${fraction((x * run[1] - y * run[0]) / L2)}`;
|
|
852
|
+
});
|
|
853
|
+
put("bends", bends.join(" "));
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* A line's two Bezier control points (`readBezier`), each `[along, across]` as fractions of the line from its source:
|
|
858
|
+
* along it, and to its left on screen.
|
|
859
|
+
* @param get - the line's parameters
|
|
860
|
+
* @param run - the line, or null when unplaced
|
|
861
|
+
* @returns the points, or null for a line Pajek draws straight or a loop
|
|
862
|
+
*/
|
|
863
|
+
function bezierControls(get: Param, run: Run | null): [[number, number], [number, number]] | null {
|
|
864
|
+
const [a1, k1, a2, k2] = ["a1", "k1", "a2", "k2"].map((key) => numberOrNull(get(key)));
|
|
865
|
+
if ((a1 === null && k1 === null && a2 === null && k2 === null) || run === null || (run[0] === 0 && run[1] === 0)) {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
const [s, t] = [k1 ?? 1, k2 ?? 1];
|
|
869
|
+
if (s < 0 || t < 0) {
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
const rad = Math.PI / 180;
|
|
873
|
+
const [u, v] = [(a1 ?? 0) * rad, (a2 ?? 0) * rad];
|
|
874
|
+
return [
|
|
875
|
+
[(s / 2) * Math.cos(u), (-s / 2) * Math.sin(u)],
|
|
876
|
+
[1 - (t / 2) * Math.cos(v), (-t / 2) * Math.sin(v)],
|
|
877
|
+
];
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* A line's direction on screen, in degrees clockwise, where its label is drawn: along a straight line, or along its
|
|
882
|
+
* Bezier curve (`bezierControls`) at the label's place, `along` of the way from the source.
|
|
883
|
+
* @param get - the line's parameters
|
|
884
|
+
* @param run - the line
|
|
885
|
+
* @param along - the label's place, 0 at the source and 1 at the target
|
|
886
|
+
* @returns the direction
|
|
887
|
+
*/
|
|
888
|
+
function lineDirection(get: Param, run: Run, along: number): number {
|
|
889
|
+
const controls = bezierControls(get, run);
|
|
890
|
+
let [dx, dy] = run;
|
|
891
|
+
if (controls !== null) {
|
|
892
|
+
// the cubic's tangent at `along`, in the line's own frame (along, left), then on screen
|
|
893
|
+
const t = Math.min(1, Math.max(0, along));
|
|
894
|
+
const p = [[0, 0], ...controls, [1, 0]];
|
|
895
|
+
const d = (k: 0 | 1): number =>
|
|
896
|
+
3 * (1 - t) ** 2 * (p[1][k] - p[0][k]) +
|
|
897
|
+
6 * (1 - t) * t * (p[2][k] - p[1][k]) +
|
|
898
|
+
3 * t ** 2 * (p[3][k] - p[2][k]);
|
|
899
|
+
const [a, l] = [d(0), d(1)];
|
|
900
|
+
// left of (dx, dy) on screen, y down, is (dy, -dx)
|
|
901
|
+
[dx, dy] = [a * run[0] + l * run[1], a * run[1] - l * run[0]];
|
|
902
|
+
}
|
|
903
|
+
return (Math.atan2(dy, dx) * 180) / Math.PI;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* What a line's value draws (manual 5.3 p. 90): a line of value 0 is not drawn, and a negative one is drawn as Dots
|
|
908
|
+
* unless the line sets a pattern.
|
|
909
|
+
* @param value - the line's value, or null
|
|
910
|
+
* @param get - the line's parameters
|
|
911
|
+
* @param put - receives the values
|
|
912
|
+
* @param directed - whether the line is an arc
|
|
913
|
+
*/
|
|
914
|
+
function readLineValue(value: number | null, get: Param, put: Put, directed = true): void {
|
|
915
|
+
if (value === 0) {
|
|
916
|
+
put("visible", false);
|
|
917
|
+
} else if (value !== null && value < 0 && get("p") === null) {
|
|
918
|
+
// drawn as Dots are (`pajek-specimen-edges-lines` `value-negative`)
|
|
919
|
+
put("lineStyle", pajekDash(get, directed));
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/** The dash and the gap of Pajek 6.02's Dots, in px whatever the line's width (`stroke-dasharray:5`). */
|
|
924
|
+
const PAJEK_DASH = 5;
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* How a line drawn as Pajek's Dots looks: Pajek 6.02's SVG export draws it `stroke-dasharray:5`, dashes and gaps 5 px
|
|
928
|
+
* long whatever the line's width, so a line 10/3 px wide or wider, its dashes no longer than one and a half widths,
|
|
929
|
+
* shows dots and a thinner one dashes (`pajek-specimen-edges-lines` `p-dots` at 1.5: dashes; `pajek-synth-lines`
|
|
930
|
+
* arc `12 13` at w 5 and edge `1 8` at w 3, drawn 6 wide: dots).
|
|
931
|
+
* @param get - the line's parameters
|
|
932
|
+
* @param directed - whether the line is an arc, drawn `w` wide (an edge twice `w`)
|
|
933
|
+
* @returns the line style
|
|
934
|
+
*/
|
|
935
|
+
function pajekDash(get: Param, directed: boolean): "dashed" | "dotted" {
|
|
936
|
+
const w = numberOrNull(get("w"));
|
|
937
|
+
// without a `w`, Pajek's arc is 1 wide and its edge 2
|
|
938
|
+
const width = (w ?? 1) * (directed ? 1 : EDGE_WIDTH_PER_W);
|
|
939
|
+
return PAJEK_DASH <= 1.5 * Math.max(1, width) ? "dotted" : "dashed";
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* A line's hooks `h1` and `h2` (manual p. 90): the points of its source's and its target's outline it runs between,
|
|
944
|
+
* numbered by Pajek's shipped SHAPES.CFG for the vertex's shape. An ellipse's hook k is at k degrees counterclockwise
|
|
945
|
+
* from the right (CIRC `10 1`), a box's runs round its border in 400 steps from the middle of its right side,
|
|
946
|
+
* counterclockwise (CART), and a cross, which Pajek draws as an ellipse, has an ellipse's; `pajek-specimen-edges-lines-p2`:
|
|
947
|
+
* `h1 90 h2 270` runs from the top of the source to the bottom of the target, `h1 1 h2 1` ends on the far side of the
|
|
948
|
+
* target, `h1 1` on a box leaves at its right side. 0 (the center) and -1 (the nearest hook) are where graphty runs a
|
|
949
|
+
* line anyway. ponytail: the hooks of the other shapes (a diamond's 360 polar radii, a triangle's, a figure's) are
|
|
950
|
+
* not read; Pajek's box hook 100 was measured 0.22 of the half width left of the top's middle, where SHAPES.CFG puts
|
|
951
|
+
* it.
|
|
952
|
+
* @param get - the line's parameters
|
|
953
|
+
* @param put - receives the values
|
|
954
|
+
* @param source - the source vertex's shape keyword, or null
|
|
955
|
+
* @param target - the target vertex's shape keyword, or null
|
|
956
|
+
*/
|
|
957
|
+
function readHooks(get: Param, put: Put, source: unknown, target: unknown): void {
|
|
958
|
+
for (const [key, property, shape] of [
|
|
959
|
+
["h1", "sourcePort", source],
|
|
960
|
+
["h2", "targetPort", target],
|
|
961
|
+
] as const) {
|
|
962
|
+
const hook = numberOrNull(get(key));
|
|
963
|
+
const port = hook === null ? null : hookPort(shape, hook);
|
|
964
|
+
if (port !== null) {
|
|
965
|
+
const round = (v: number): string => String(Number(v.toFixed(4)) + 0);
|
|
966
|
+
put(property, `${round(port[0])},${round(port[1])}`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* One hook's point on its vertex (`readHooks`), as fractions of the half width and height, y up.
|
|
973
|
+
* @param shape - the vertex's shape keyword, null for Pajek's default ellipse
|
|
974
|
+
* @param hook - the hook's number
|
|
975
|
+
* @returns the point, or null where Pajek's hook is not read
|
|
976
|
+
*/
|
|
977
|
+
function hookPort(shape: unknown, hook: number): readonly [number, number] | null {
|
|
978
|
+
if (!Number.isInteger(hook) || hook < 1) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
const keyword = typeof shape === "string" ? shape.toLowerCase() : "ellipse";
|
|
982
|
+
// Pajek 6.02 draws a cross as an ellipse, with an ellipse's hooks (`h-cross-1-3`: hook 3 at 3 degrees)
|
|
983
|
+
if (keyword === "ellipse" || keyword === "cross") {
|
|
984
|
+
const a = (hook * Math.PI) / 180;
|
|
985
|
+
return [Math.cos(a), Math.sin(a)];
|
|
986
|
+
}
|
|
987
|
+
if (keyword !== "box" || hook > 400) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
// 0.02 of the half size a step: up the right side's top half, across the top, down the left, across the
|
|
991
|
+
// bottom and up the right side's bottom half
|
|
992
|
+
const d = hook * 0.02;
|
|
993
|
+
if (hook <= 50) {
|
|
994
|
+
return [1, d];
|
|
995
|
+
}
|
|
996
|
+
if (hook <= 150) {
|
|
997
|
+
return [2 - d, 1];
|
|
998
|
+
}
|
|
999
|
+
if (hook <= 250) {
|
|
1000
|
+
return [-1, 4 - d];
|
|
1001
|
+
}
|
|
1002
|
+
return hook <= 350 ? [d - 6, -1] : [1, d - 8];
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* A cell's number: a number as is, a numeric text parsed, anything else null.
|
|
1007
|
+
* @param raw - the cell
|
|
1008
|
+
* @returns the number, or null
|
|
1009
|
+
*/
|
|
1010
|
+
function numberOrNull(raw: unknown): number | null {
|
|
1011
|
+
if (typeof raw === "string" && raw.trim() !== "") {
|
|
1012
|
+
raw = Number(raw);
|
|
1013
|
+
}
|
|
1014
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : null;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Where a line's arrowhead sits, Pajek's `ap` (manual p. 90): a fraction of the line from its end up to 1 (0.5 is the
|
|
1019
|
+
* middle); a value above 1 is a distance from the end, which the vocabulary does not hold, so it is noted and not read.
|
|
1020
|
+
* @param raw - the parameter's value, or null
|
|
1021
|
+
* @param put - receives the value
|
|
1022
|
+
* @param at - the sites
|
|
1023
|
+
*/
|
|
1024
|
+
function readArrowPosition(raw: unknown, put: Put, at: At): void {
|
|
1025
|
+
if (raw === null) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const ap = canonNumber(raw, at("arrowPosition"));
|
|
1029
|
+
if (ap !== null && ap > 1) {
|
|
1030
|
+
noteAt(at("arrowPosition"), STYLE_ISSUE.NOT_CARRIED, `ap ${String(ap)}`);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
// 0 is Pajek's own place for the arrow, at the line's end, which a line restates after one that moved it
|
|
1034
|
+
if (ap !== 0) {
|
|
1035
|
+
put("arrowPosition", ap);
|
|
183
1036
|
}
|
|
184
1037
|
}
|
|
185
1038
|
|
|
@@ -190,9 +1043,21 @@ function readLine(get: Param, put: Put, at: At): void {
|
|
|
190
1043
|
* @returns the getter
|
|
191
1044
|
*/
|
|
192
1045
|
function cells(table: GraphSnapshot["nodes"], i: number): Param {
|
|
193
|
-
return (key) =>
|
|
1046
|
+
return (key) => {
|
|
1047
|
+
const v = table.has(key) ? plainCell(table.value(key, i)) : null;
|
|
1048
|
+
// a color written as numbers (`c 0.700 0.700 0.700`) is no color to Pajek 6.02: the line keeps the color it
|
|
1049
|
+
// carries from the line before, or Pajek's default (`pajek-1crn`, `pajek-specimen-edges-lines`
|
|
1050
|
+
// `c-three-fractions`). ponytail: measured on lines; vertex colors are read the same way
|
|
1051
|
+
return COLOR_PARAMS.has(key) && typeof v === "string" && NUMERIC_COLOR.test(v) ? null : v;
|
|
1052
|
+
};
|
|
194
1053
|
}
|
|
195
1054
|
|
|
1055
|
+
/** The parameters that hold a color. */
|
|
1056
|
+
const COLOR_PARAMS: ReadonlySet<string> = new Set(["ic", "bc", "lc", "c"]);
|
|
1057
|
+
|
|
1058
|
+
/** A color written as numbers rather than a name or an RGB / CMYK form. */
|
|
1059
|
+
const NUMERIC_COLOR = /^[\d.]+(?: [\d.]+)*$/;
|
|
1060
|
+
|
|
196
1061
|
/**
|
|
197
1062
|
* Whether each edge is an arc (directed).
|
|
198
1063
|
* @param snapshot - the graph
|
|
@@ -220,41 +1085,90 @@ function styleColumns(table: GraphSnapshot["nodes"], keys: readonly string[]): s
|
|
|
220
1085
|
}
|
|
221
1086
|
|
|
222
1087
|
/**
|
|
223
|
-
* The styles of a Pajek file, read the way Pajek draws them, with Pajek's
|
|
224
|
-
* with a
|
|
1088
|
+
* The styles of a Pajek file, read the way Pajek draws them, with Pajek's own defaults beneath: a labeled #ffd9bc
|
|
1089
|
+
* ellipse with a #660000 border, teal edges, and grey arcs with an arrow at the target. A file graphty wrote gives back its `graphty.styles` block.
|
|
225
1090
|
* @param snapshot - the graph the import built
|
|
226
1091
|
* @param context - the work budget and the block text the import kept
|
|
227
1092
|
* @returns the styles
|
|
228
1093
|
*/
|
|
229
1094
|
export function pajekStyles(snapshot: GraphSnapshot, context: StyleReadContext): ImporterStyles {
|
|
230
1095
|
const notes = new StyleNotes();
|
|
1096
|
+
const { block, draw, voided } = keptOf(context.kept);
|
|
1097
|
+
const labels = drawsLineLabels(draw);
|
|
1098
|
+
const place = places(snapshot);
|
|
1099
|
+
// Pajek stretches the file's 0..1 to its Draw window one axis at a time, 500 x 500 without one
|
|
1100
|
+
const window = draw ?? { width: PAJEK_POINTS_PER_UNIT, height: PAJEK_POINTS_PER_UNIT };
|
|
1101
|
+
const loops = new Map<number, LoopControls>();
|
|
1102
|
+
const runOf = (e: number): Run | null => {
|
|
1103
|
+
const [s, t] = [place(snapshot.edgeSource(e)), place(snapshot.edgeTarget(e))];
|
|
1104
|
+
return s === null || t === null ? null : [(t[0] - s[0]) * window.width, (t[1] - s[1]) * window.height];
|
|
1105
|
+
};
|
|
231
1106
|
const read = (target: StyleTarget): ValueColumns => {
|
|
232
1107
|
const table = target === "node" ? snapshot.nodes : snapshot.edges;
|
|
233
1108
|
const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
|
|
234
1109
|
const out = valueColumns(n);
|
|
1110
|
+
const carry = carriedParams(target === "node" ? CARRIED.node : CARRIED_LINE, target === "node");
|
|
235
1111
|
for (let i = 0; i < n; i++) {
|
|
236
1112
|
context.budget.spend(target, 1);
|
|
237
1113
|
const at: At = (property) => ({ notes, target, property, element: i, budget: context.budget });
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
1114
|
+
const put: Put = (property, value) => {
|
|
1115
|
+
out.put(i, property, value);
|
|
1116
|
+
};
|
|
1117
|
+
const own = cells(table, i);
|
|
1118
|
+
const get = target === "node" && voided.has(i) ? voidedParams(own, keywords[i - 1] ?? null) : carry(own);
|
|
1119
|
+
if (target === "node") {
|
|
1120
|
+
readVertex(get, put, at, place(i));
|
|
1121
|
+
keywords[i] = get(SHAPE_COLUMN);
|
|
1122
|
+
} else {
|
|
1123
|
+
readEdge(i, get, put, at);
|
|
1124
|
+
}
|
|
245
1125
|
}
|
|
246
1126
|
return out.values;
|
|
247
1127
|
};
|
|
1128
|
+
const readEdge = (i: number, get: Param, put: Put, at: At): void => {
|
|
1129
|
+
// a loop on a placed node: an unplaced one is laid out by graphty, which draws its own loop, and a
|
|
1130
|
+
// file graphty wrote keeps its routes in its block
|
|
1131
|
+
const run = runOf(i);
|
|
1132
|
+
const loop =
|
|
1133
|
+
run !== null && block === null && snapshot.edgeSource(i) === snapshot.edgeTarget(i)
|
|
1134
|
+
? (controls: LoopControls): void => {
|
|
1135
|
+
loops.set(i, controls);
|
|
1136
|
+
}
|
|
1137
|
+
: null;
|
|
1138
|
+
readLine(get, put, at, directed[i], labels, run, loop);
|
|
1139
|
+
if (directed[i] && loop === null && run !== null) {
|
|
1140
|
+
readArcEnd(get, put, run, targetSize(snapshot.edgeTarget(i)));
|
|
1141
|
+
}
|
|
1142
|
+
readLineValue(lineValue(i), get, put, directed[i]);
|
|
1143
|
+
readHooks(get, put, keywords[snapshot.edgeSource(i)], keywords[snapshot.edgeTarget(i)]);
|
|
1144
|
+
};
|
|
1145
|
+
const directed = directedEdges(snapshot);
|
|
1146
|
+
const keywords: unknown[] = [];
|
|
1147
|
+
const weights = explicitWeights(snapshot);
|
|
1148
|
+
// the line's value: its weight, or the `value` column when the import kept the value as an attribute
|
|
1149
|
+
const lineValue = (e: number): number | null =>
|
|
1150
|
+
weights.isExplicit(e) ? weights.value(e) : numberOrNull(cells(snapshot.edges, e)(VALUE_COLUMN));
|
|
248
1151
|
const node = read("node");
|
|
1152
|
+
// a vertex's width and height as the file draws it, for where an arc's arrowhead meets it
|
|
1153
|
+
const targetSize = (v: number): readonly [number, number] => {
|
|
1154
|
+
const size = (property: string): number => {
|
|
1155
|
+
const s = node[property]?.[v];
|
|
1156
|
+
return typeof s === "number" && s > 0 ? s : PAJEK_VERTEX_SIZE;
|
|
1157
|
+
};
|
|
1158
|
+
return [size("width"), size("height")];
|
|
1159
|
+
};
|
|
249
1160
|
const edge = read("edge");
|
|
250
|
-
|
|
1161
|
+
if (draw !== null) {
|
|
1162
|
+
drawVertices(draw, snapshot, node);
|
|
1163
|
+
drawLines(draw, snapshot, node, edge);
|
|
1164
|
+
}
|
|
1165
|
+
layLoops(snapshot, loops, node, edge);
|
|
251
1166
|
if (block !== null) {
|
|
252
|
-
exactFromBlock(snapshot, block, { node, edge });
|
|
1167
|
+
exactFromBlock(snapshot, block, { node, edge }, labels);
|
|
253
1168
|
}
|
|
254
|
-
// Pajek's
|
|
1169
|
+
// Pajek's defaults that are a rule: an arc has an arrow at its target and is drawn grey and thinner, an edge not
|
|
255
1170
|
const rules: StyleRule[] = [];
|
|
256
1171
|
const columns = new Map<string, StyleColumn>();
|
|
257
|
-
const directed = directedEdges(snapshot);
|
|
258
1172
|
if (directed.some(Boolean)) {
|
|
259
1173
|
columns.set(
|
|
260
1174
|
DIRECTED_KEY,
|
|
@@ -266,14 +1180,14 @@ export function pajekStyles(snapshot: GraphSnapshot, context: StyleReadContext):
|
|
|
266
1180
|
role: "group",
|
|
267
1181
|
origin: "tool",
|
|
268
1182
|
where: { has: { style: DIRECTED_KEY } },
|
|
269
|
-
set: { targetArrow: "triangle" },
|
|
1183
|
+
set: { targetArrow: "triangle", color: ARC_DEFAULTS.color, width: ARC_DEFAULTS.width },
|
|
270
1184
|
});
|
|
271
1185
|
}
|
|
272
1186
|
return {
|
|
273
1187
|
canonical: true,
|
|
274
1188
|
dialect: "dvips",
|
|
275
1189
|
tool: "pajek",
|
|
276
|
-
units: "
|
|
1190
|
+
units: "px",
|
|
277
1191
|
node: { values: node },
|
|
278
1192
|
edge: { values: edge, rules, columns },
|
|
279
1193
|
notes: notes.list(),
|
|
@@ -285,6 +1199,67 @@ export function pajekStyles(snapshot: GraphSnapshot, context: StyleReadContext):
|
|
|
285
1199
|
};
|
|
286
1200
|
}
|
|
287
1201
|
|
|
1202
|
+
/**
|
|
1203
|
+
* Each vertex's place in the file's own coordinates, as the position column holds them.
|
|
1204
|
+
* @param snapshot - the graph
|
|
1205
|
+
* @returns a getter of a vertex's place, null when it has none
|
|
1206
|
+
*/
|
|
1207
|
+
function places(snapshot: GraphSnapshot): (i: number) => Place | null {
|
|
1208
|
+
const col = snapshot.nodes.byRole("position");
|
|
1209
|
+
return (i) => {
|
|
1210
|
+
const v: unknown = col === null || col === undefined ? null : col.value(i);
|
|
1211
|
+
if (v === null || typeof v !== "object" || !("length" in v)) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
const [x, y] = v as ArrayLike<unknown> as [unknown, unknown];
|
|
1215
|
+
return typeof x === "number" && typeof y === "number" ? [x, y] : null;
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* What the import kept for the styles: the `graphty.styles` block, the Draw window of the `pajekDraw` option, and the
|
|
1221
|
+
* vertices whose line Pajek draws with none of its parameters.
|
|
1222
|
+
* @param kept - the kept value: the block's text, or `{ block, pajekDraw, voided }`
|
|
1223
|
+
* @returns the block and the draw settings, each null when absent, and the voided vertices
|
|
1224
|
+
*/
|
|
1225
|
+
function keptOf(kept: unknown): {
|
|
1226
|
+
readonly block: string | null;
|
|
1227
|
+
readonly draw: PajekDraw | null;
|
|
1228
|
+
readonly voided: ReadonlySet<number>;
|
|
1229
|
+
} {
|
|
1230
|
+
if (typeof kept === "string") {
|
|
1231
|
+
return { block: kept, draw: null, voided: new Set() };
|
|
1232
|
+
}
|
|
1233
|
+
if (typeof kept === "object" && kept !== null) {
|
|
1234
|
+
const { block, pajekDraw, voided } = kept as { block?: unknown; pajekDraw?: PajekDraw; voided?: number[] };
|
|
1235
|
+
return {
|
|
1236
|
+
block: typeof block === "string" ? block : null,
|
|
1237
|
+
draw: pajekDraw ?? null,
|
|
1238
|
+
voided: new Set(voided ?? []),
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
return { block: null, draw: null, voided: new Set() };
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* The parameters Pajek 6.02 draws a voided vertex line with (`VOIDING_KEYS` in the importer): its label, and none of
|
|
1246
|
+
* its own parameters, so the vertex is Pajek's default size and colors, in the shape of the vertex line before it
|
|
1247
|
+
* (`pajek-specimen-node-sizes-opacity` `s-size-no-shape`, after a box: a box 20 across in #ffd9bc). ponytail: probes
|
|
1248
|
+
* showed the shape is not always the line before's (after `box ic Green` a voided line drew an ellipse in one file and
|
|
1249
|
+
* the triangle of two lines before in another); the rule Pajek follows was not found.
|
|
1250
|
+
* @param own - the line's own parameters
|
|
1251
|
+
* @param before - the shape keyword of the vertex line before it, or null
|
|
1252
|
+
* @returns the getter
|
|
1253
|
+
*/
|
|
1254
|
+
function voidedParams(own: Param, before: unknown): Param {
|
|
1255
|
+
return (key) => {
|
|
1256
|
+
if (key === LABEL_COLUMN) {
|
|
1257
|
+
return own(key);
|
|
1258
|
+
}
|
|
1259
|
+
return key === SHAPE_COLUMN ? before : null;
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
|
|
288
1263
|
// ============================================================ writing
|
|
289
1264
|
|
|
290
1265
|
/** One element's value of a property. */
|
|
@@ -361,13 +1336,62 @@ function shapeKeyword(get: Get, approximate: Approximate): string {
|
|
|
361
1336
|
if (keyword !== undefined) {
|
|
362
1337
|
return keyword;
|
|
363
1338
|
}
|
|
1339
|
+
if (shape === "polygon" && diamondQ(get) !== null) {
|
|
1340
|
+
return "diamond";
|
|
1341
|
+
}
|
|
364
1342
|
approximate("shape", shape);
|
|
1343
|
+
const points = get("shapePoints");
|
|
1344
|
+
if (shape === "polygon" && points !== null) {
|
|
1345
|
+
approximate("shapePoints", points);
|
|
1346
|
+
}
|
|
365
1347
|
return NEAREST_SHAPES.get(shape) ?? "ellipse";
|
|
366
1348
|
}
|
|
367
1349
|
|
|
368
1350
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
1351
|
+
* The `q` of a polygon that is Pajek's unturned diamond (`readOutline`), or null for any other.
|
|
1352
|
+
* @param get - the vertex's values
|
|
1353
|
+
* @returns the diamond's `q`, or null
|
|
1354
|
+
*/
|
|
1355
|
+
function diamondQ(get: Get): number | null {
|
|
1356
|
+
const points = get("shapePoints");
|
|
1357
|
+
const corners =
|
|
1358
|
+
typeof points === "string"
|
|
1359
|
+
? points
|
|
1360
|
+
.trim()
|
|
1361
|
+
.split(/\s+/)
|
|
1362
|
+
.map((p) => p.split(",").map(Number))
|
|
1363
|
+
: [];
|
|
1364
|
+
if (corners.length !== 6) {
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1367
|
+
const q = corners[2][0];
|
|
1368
|
+
const want = [
|
|
1369
|
+
[-1, 0],
|
|
1370
|
+
[-q, 1],
|
|
1371
|
+
[q, 1],
|
|
1372
|
+
[1, 0],
|
|
1373
|
+
[q, -1],
|
|
1374
|
+
[-q, -1],
|
|
1375
|
+
];
|
|
1376
|
+
return want.every(([x, y], k) => corners[k][0] === x && corners[k][1] === y) ? q : null;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* What Pajek draws by default for a property (`styles/defaults.ts`), which the writer leaves out.
|
|
1381
|
+
* @param target - nodes or edges
|
|
1382
|
+
* @param property - the property
|
|
1383
|
+
* @returns the default, or undefined
|
|
1384
|
+
*/
|
|
1385
|
+
function pajekDefault(target: StyleTarget, property: string): StyleValue | undefined {
|
|
1386
|
+
const values: Readonly<Record<string, { readonly value: unknown } | undefined>> | undefined =
|
|
1387
|
+
toolDefaults("pajek")?.[target];
|
|
1388
|
+
return values?.[property]?.value as StyleValue | undefined;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* A vertex's parameters from its values, leaving out the ellipse and the black border 1 wide Pajek draws by default;
|
|
1393
|
+
* the inverse of `readVertex`: `x_fact` and `y_fact` are the width and height over the normal size, the other lengths
|
|
1394
|
+
* points as they are.
|
|
371
1395
|
* @param get - the vertex's values
|
|
372
1396
|
* @param approximate - records approximations
|
|
373
1397
|
* @returns the parameters, the shape keyword under `shape`
|
|
@@ -379,43 +1403,148 @@ function vertexParams(get: Get, approximate: Approximate): Map<string, Written>
|
|
|
379
1403
|
if (keyword !== "ellipse") {
|
|
380
1404
|
out.set(SHAPE_COLUMN, keyword);
|
|
381
1405
|
}
|
|
1406
|
+
const q = diamondQ(get);
|
|
1407
|
+
if (q !== null && q !== PAJEK_DIAMOND_Q) {
|
|
1408
|
+
out.set("q", q);
|
|
1409
|
+
}
|
|
1410
|
+
vertexColorParams(get, out);
|
|
1411
|
+
vertexSizeParams(get, out);
|
|
1412
|
+
const bw = get("borderWidth");
|
|
1413
|
+
if (typeof bw === "number" && bw !== pajekDefault("node", "borderWidth")) {
|
|
1414
|
+
out.set("bw", bw);
|
|
1415
|
+
}
|
|
1416
|
+
fontParam(get, out, pajekDefault("node", "labelFont"));
|
|
1417
|
+
labelPositionParams(get, out, approximate);
|
|
1418
|
+
return out;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* A vertex's colors as parameters (`ic`, `bc`, `lc`), leaving out Pajek's defaults.
|
|
1423
|
+
* @param get - the vertex's values
|
|
1424
|
+
* @param out - receives the parameters
|
|
1425
|
+
*/
|
|
1426
|
+
function vertexColorParams(get: Get, out: Map<string, Written>): void {
|
|
382
1427
|
const colors = [
|
|
383
1428
|
["fill", "ic"],
|
|
384
1429
|
["borderColor", "bc"],
|
|
385
1430
|
["labelColor", "lc"],
|
|
386
1431
|
] as const;
|
|
387
|
-
const numbers = [
|
|
388
|
-
["width", "x_fact"],
|
|
389
|
-
["height", "y_fact"],
|
|
390
|
-
["labelSize", "fos"],
|
|
391
|
-
] as const;
|
|
392
1432
|
for (const [property, key] of colors) {
|
|
393
1433
|
const v = get(property);
|
|
394
|
-
if (typeof v === "string" &&
|
|
1434
|
+
if (typeof v === "string" && v !== pajekDefault("node", property)) {
|
|
395
1435
|
out.set(key, pajekColorText(v));
|
|
396
1436
|
}
|
|
397
1437
|
}
|
|
398
|
-
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* A vertex's sizes as parameters (`x_fact`, `y_fact`, `fos`), leaving out Pajek's defaults.
|
|
1442
|
+
* @param get - the vertex's values
|
|
1443
|
+
* @param out - receives the parameters
|
|
1444
|
+
*/
|
|
1445
|
+
function vertexSizeParams(get: Get, out: Map<string, Written>): void {
|
|
1446
|
+
const numbers = [
|
|
1447
|
+
["width", "x_fact", PAJEK_VERTEX_SIZE],
|
|
1448
|
+
["height", "y_fact", PAJEK_VERTEX_SIZE],
|
|
1449
|
+
["labelSize", "fos", 1],
|
|
1450
|
+
] as const;
|
|
1451
|
+
// a man's or a woman's box is the figure's, which reaches past the size its x_fact and y_fact give (readSize)
|
|
1452
|
+
const figure = PAJEK_FIGURES.get(String(get("shape")));
|
|
1453
|
+
for (const [property, key, per] of numbers) {
|
|
399
1454
|
const v = get(property);
|
|
400
|
-
|
|
401
|
-
|
|
1455
|
+
let over = 1;
|
|
1456
|
+
if (figure !== undefined && key !== "fos") {
|
|
1457
|
+
over = figure[key === "x_fact" ? 0 : 1];
|
|
1458
|
+
}
|
|
1459
|
+
if (typeof v === "number" && (over !== 1 || v !== pajekDefault("node", property))) {
|
|
1460
|
+
out.set(key, Number((v / (per * over)).toPrecision(15)));
|
|
402
1461
|
}
|
|
403
1462
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/**
|
|
1466
|
+
* A label's `font`, the inverse of `readFont`: a PostScript family with a generic one after it is written by its
|
|
1467
|
+
* PostScript name (`Courier, monospace` as `Courier`), any other family as it is; Pajek draws every label bold and
|
|
1468
|
+
* upright, so neither the weight nor the slant is written.
|
|
1469
|
+
* @param get - the element's values
|
|
1470
|
+
* @param out - the parameters, changed in place
|
|
1471
|
+
* @param skip - the font left out, Pajek's default, or undefined
|
|
1472
|
+
*/
|
|
1473
|
+
function fontParam(get: Get, out: Map<string, Written>, skip?: StyleValue): void {
|
|
1474
|
+
const font = get("labelFont");
|
|
1475
|
+
const name = typeof font === "string" && font !== "" ? graphvizFontName(font, false, false) : null;
|
|
1476
|
+
if (name !== null && name !== skip) {
|
|
1477
|
+
out.set("font", name);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
/**
|
|
1482
|
+
* A label position as Pajek's polar label parameters, leaving out the default (20 from the center at 285 degrees): a
|
|
1483
|
+
* label centered with its left edge's middle offset from the center is `lr` and `lphi` (the inverse of
|
|
1484
|
+
* `readLabelPosition`), one centered on the vertex `lr 0`, and a side its angle `lphi`; a side Pajek cannot put a
|
|
1485
|
+
* label on is written as the nearest it can, and noted.
|
|
1486
|
+
* @param get - the vertex's values
|
|
1487
|
+
* @param out - the parameters, changed in place
|
|
1488
|
+
* @param approximate - records approximations
|
|
1489
|
+
*/
|
|
1490
|
+
function labelPositionParams(get: Get, out: Map<string, Written>, approximate: Approximate): void {
|
|
1491
|
+
const position = get("labelPosition");
|
|
1492
|
+
if (typeof position !== "string") {
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
if (position === "center") {
|
|
1496
|
+
const polar = polarOf(get);
|
|
1497
|
+
if (polar === null) {
|
|
1498
|
+
out.set("lr", 0);
|
|
1499
|
+
} else if (polar[0] !== PAJEK_LABEL_RADIUS || polar[1] !== PAJEK_LABEL_ANGLE) {
|
|
1500
|
+
out.set("lr", polar[0]);
|
|
1501
|
+
out.set("lphi", polar[1]);
|
|
411
1502
|
}
|
|
1503
|
+
return;
|
|
412
1504
|
}
|
|
413
|
-
|
|
1505
|
+
const nearest = NEAREST_SIDES.get(position);
|
|
1506
|
+
if (nearest !== undefined) {
|
|
1507
|
+
approximate("labelPosition", position);
|
|
1508
|
+
}
|
|
1509
|
+
const angle = SIDE_ANGLES.get(nearest ?? position);
|
|
1510
|
+
if (angle !== undefined && angle !== PAJEK_LABEL_ANGLE) {
|
|
1511
|
+
out.set("lphi", angle);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* The `lr` and `lphi` that put a centered label where its offsets say (`labelOffset`, inverted), 6 places each.
|
|
1517
|
+
* @param get - the vertex's values
|
|
1518
|
+
* @returns `[lr, lphi]`, or null for a label on the vertex's center (no offset, no shift)
|
|
1519
|
+
*/
|
|
1520
|
+
function polarOf(get: Get): [number, number] | null {
|
|
1521
|
+
const [x, y, shift] = [get("labelOffsetX"), get("labelOffsetY"), get("labelShiftX")];
|
|
1522
|
+
if (typeof x !== "number" || typeof y !== "number" || shift !== 0.5) {
|
|
1523
|
+
return null;
|
|
1524
|
+
}
|
|
1525
|
+
const size = get("labelSize");
|
|
1526
|
+
const up = y + PAJEK_BASELINE_TO_MIDDLE * (typeof size === "number" ? size : PAJEK_LABEL_SIZE);
|
|
1527
|
+
const round = (v: number): number => Number(v.toFixed(6)) + 0;
|
|
1528
|
+
const lr = round(Math.hypot(x, up));
|
|
1529
|
+
return lr === 0 ? null : [lr, round(((((Math.atan2(-up, x) * 180) / Math.PI) % 360) + 360) % 360)];
|
|
414
1530
|
}
|
|
415
1531
|
|
|
416
1532
|
/**
|
|
417
|
-
* A line's
|
|
418
|
-
*
|
|
1533
|
+
* A line's color and width when it sets neither: an arc's, or an edge's (the tool defaults).
|
|
1534
|
+
* @param directed - whether the line is an arc
|
|
1535
|
+
* @returns the color and width
|
|
1536
|
+
*/
|
|
1537
|
+
function lineDefaults(directed: boolean): {
|
|
1538
|
+
readonly color: StyleValue | undefined;
|
|
1539
|
+
readonly width: StyleValue | undefined;
|
|
1540
|
+
} {
|
|
1541
|
+
return directed ? ARC_DEFAULTS : { color: pajekDefault("edge", "color"), width: pajekDefault("edge", "width") };
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/**
|
|
1545
|
+
* A line's parameters from its values, leaving out the solid line Pajek draws by default (grey and 1 wide for an arc,
|
|
1546
|
+
* teal and 2 wide for an edge; `w` is half an edge's width). Arrows are Pajek's own: an arc has one at its target and
|
|
1547
|
+
* an edge none, so any other arrow is noted.
|
|
419
1548
|
* @param get - the line's values
|
|
420
1549
|
* @param directed - whether the line is an arc
|
|
421
1550
|
* @param approximate - records approximations
|
|
@@ -424,18 +1553,24 @@ function vertexParams(get: Get, approximate: Approximate): Map<string, Written>
|
|
|
424
1553
|
function lineParams(get: Get, directed: boolean, approximate: Approximate): Map<string, Written> {
|
|
425
1554
|
const out = new Map<string, Written>();
|
|
426
1555
|
opaque(get, ["opacity"], approximate);
|
|
1556
|
+
const defaults = lineDefaults(directed);
|
|
427
1557
|
const color = get("color");
|
|
428
|
-
if (typeof color === "string" && color !==
|
|
1558
|
+
if (typeof color === "string" && color !== defaults.color) {
|
|
429
1559
|
out.set("c", pajekColorText(color));
|
|
430
1560
|
}
|
|
431
1561
|
const width = get("width");
|
|
432
|
-
if (typeof width === "number") {
|
|
433
|
-
out.set("w", width);
|
|
1562
|
+
if (typeof width === "number" && width !== defaults.width) {
|
|
1563
|
+
out.set("w", directed ? width : Number((width / EDGE_WIDTH_PER_W).toPrecision(15)));
|
|
434
1564
|
}
|
|
435
1565
|
const lineStyle = get("lineStyle");
|
|
436
|
-
if (typeof lineStyle === "string" && lineStyle !== "solid" && pajekDots(lineStyle, approximate)) {
|
|
1566
|
+
if (typeof lineStyle === "string" && lineStyle !== "solid" && pajekDots(lineStyle, out, directed, approximate)) {
|
|
437
1567
|
out.set("p", "Dots");
|
|
438
1568
|
}
|
|
1569
|
+
const position = get("arrowPosition");
|
|
1570
|
+
if (typeof position === "number" && position > 0) {
|
|
1571
|
+
out.set("ap", position);
|
|
1572
|
+
}
|
|
1573
|
+
lineLabelParams(get, out, approximate);
|
|
439
1574
|
const target = get("targetArrow");
|
|
440
1575
|
if (target !== null && target !== (directed ? "triangle" : "none")) {
|
|
441
1576
|
approximate("targetArrow", target);
|
|
@@ -448,84 +1583,100 @@ function lineParams(get: Get, directed: boolean, approximate: Approximate): Map<
|
|
|
448
1583
|
}
|
|
449
1584
|
|
|
450
1585
|
/**
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
* @param
|
|
1586
|
+
* A line's label parameters: its text `l` (a line break or a double quote written as Pajek can hold it, noted), its
|
|
1587
|
+
* color `lc`, size `fos` and font `font`.
|
|
1588
|
+
* @param get - the line's values
|
|
1589
|
+
* @param out - the parameters, changed in place
|
|
454
1590
|
* @param approximate - records approximations
|
|
455
|
-
* @returns true for Dots
|
|
456
1591
|
*/
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
1592
|
+
function lineLabelParams(get: Get, out: Map<string, Written>, approximate: Approximate): void {
|
|
1593
|
+
const label = get("label");
|
|
1594
|
+
if (typeof label === "string" && label !== "") {
|
|
1595
|
+
const text = pajekLabelText(label);
|
|
1596
|
+
if (text !== label) {
|
|
1597
|
+
approximate("label", label);
|
|
1598
|
+
}
|
|
1599
|
+
out.set("l", text);
|
|
460
1600
|
}
|
|
461
|
-
|
|
462
|
-
|
|
1601
|
+
const labelColor = get("labelColor");
|
|
1602
|
+
if (typeof labelColor === "string") {
|
|
1603
|
+
out.set("lc", pajekColorText(labelColor));
|
|
1604
|
+
}
|
|
1605
|
+
const labelSize = get("labelSize");
|
|
1606
|
+
if (typeof labelSize === "number") {
|
|
1607
|
+
out.set("fos", labelSize);
|
|
1608
|
+
}
|
|
1609
|
+
fontParam(get, out);
|
|
463
1610
|
}
|
|
464
1611
|
|
|
465
1612
|
/**
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
* @
|
|
1613
|
+
* Whether a line other than a solid one is written as Pajek's Dots: Pajek draws Solid and Dots only, and its Dots
|
|
1614
|
+
* shows dots on a wide line and dashes on a thin one (`pajekDash`); the other of the two and any other dashed kind is
|
|
1615
|
+
* nearest to Dots, any other line to Solid.
|
|
1616
|
+
* @param lineStyle - the line style
|
|
1617
|
+
* @param params - the line's parameters written so far, its `w` among them
|
|
1618
|
+
* @param directed - whether the line is an arc
|
|
1619
|
+
* @param approximate - records approximations
|
|
1620
|
+
* @returns true for Dots
|
|
470
1621
|
*/
|
|
471
|
-
function
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
1622
|
+
function pajekDots(
|
|
1623
|
+
lineStyle: string,
|
|
1624
|
+
params: ReadonlyMap<string, Written>,
|
|
1625
|
+
directed: boolean,
|
|
1626
|
+
approximate: Approximate,
|
|
1627
|
+
): boolean {
|
|
1628
|
+
if (lineStyle === pajekDash((key) => params.get(key) ?? null, directed)) {
|
|
1629
|
+
return true;
|
|
478
1630
|
}
|
|
479
|
-
|
|
1631
|
+
approximate("lineStyle", lineStyle);
|
|
1632
|
+
return ["dashed", "dotted", "dash-dot", "long-dash"].includes(lineStyle);
|
|
480
1633
|
}
|
|
481
1634
|
|
|
482
1635
|
/**
|
|
483
1636
|
* For a file graphty wrote: where a value read from the parameters is what the writer writes for the block's own
|
|
484
|
-
* value (a hexagon written as an ellipse, a
|
|
1637
|
+
* value (a hexagon written as an ellipse, a dotted line as Dots), the block's exact value is the truth, so a re-import
|
|
485
1638
|
* gives back the same rules; a value another program changed since stays the truth.
|
|
486
1639
|
* @param snapshot - the graph
|
|
487
1640
|
* @param text - the block's text
|
|
488
1641
|
* @param read - the values read, changed in place
|
|
489
1642
|
* @param read.node - the node values
|
|
490
1643
|
* @param read.edge - the edge values
|
|
1644
|
+
* @param labels - whether the lines' labels are drawn
|
|
491
1645
|
*/
|
|
492
1646
|
function exactFromBlock(
|
|
493
1647
|
snapshot: GraphSnapshot,
|
|
494
1648
|
text: string,
|
|
495
1649
|
read: { readonly node: ValueColumns; readonly edge: ValueColumns },
|
|
1650
|
+
labels: boolean,
|
|
496
1651
|
): void {
|
|
497
|
-
const parsed = parseStyleBlock(text, styleFingerprint(snapshot));
|
|
498
|
-
const ex = parsed.ok ? blockStyles(parsed.block, snapshot, "pajek") : null;
|
|
499
|
-
if (ex === null) {
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
1652
|
const notes = new StyleNotes();
|
|
503
1653
|
const directed = directedEdges(snapshot);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
values[property] ??= new Array<StyleValue | null>(n).fill(null);
|
|
516
|
-
values[property][i] = w;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
1654
|
+
takeFromBlock(snapshot, text, "pajek", (target) => ({
|
|
1655
|
+
values: read[target],
|
|
1656
|
+
properties: READ[target],
|
|
1657
|
+
writes: PAJEK_STYLE_CAPABILITIES[target],
|
|
1658
|
+
readBack: (i, want) => {
|
|
1659
|
+
const back = writtenAndReadBack(target, want, directed[i], notes, labels);
|
|
1660
|
+
return (property) => back.get(property) ?? null;
|
|
1661
|
+
},
|
|
1662
|
+
// a label the block places by a side clears the offsets its `lphi` reads back as
|
|
1663
|
+
take: (property, wanted) => wanted !== null || LABEL_PLACE.has(property) || OUTLINE.has(property),
|
|
1664
|
+
}));
|
|
521
1665
|
}
|
|
522
1666
|
|
|
1667
|
+
/** The outline a shape written as `diamond` reads back as, which the block's own shape clears. */
|
|
1668
|
+
const OUTLINE: ReadonlySet<string> = new Set(["shapePoints", "shapePointsStretch"]);
|
|
1669
|
+
|
|
1670
|
+
/** The label place properties a side written as `lphi` reads back as. */
|
|
1671
|
+
const LABEL_PLACE: ReadonlySet<string> = new Set(["labelOffsetX", "labelOffsetY", "labelShiftX"]);
|
|
1672
|
+
|
|
523
1673
|
/**
|
|
524
1674
|
* An element's values written as the writer writes them and read back as the reader reads them.
|
|
525
1675
|
* @param target - nodes or edges
|
|
526
1676
|
* @param want - its values
|
|
527
1677
|
* @param directed - for a line, whether it is an arc
|
|
528
1678
|
* @param notes - a scratch for the notes the round trip makes
|
|
1679
|
+
* @param labels - whether the lines' labels are drawn
|
|
529
1680
|
* @returns the values read back, by property
|
|
530
1681
|
*/
|
|
531
1682
|
function writtenAndReadBack(
|
|
@@ -533,6 +1684,7 @@ function writtenAndReadBack(
|
|
|
533
1684
|
want: Get,
|
|
534
1685
|
directed: boolean,
|
|
535
1686
|
notes: StyleNotes,
|
|
1687
|
+
labels: boolean,
|
|
536
1688
|
): Map<string, StyleValue> {
|
|
537
1689
|
const ignore: Approximate = () => undefined;
|
|
538
1690
|
const params = target === "node" ? vertexParams(want, ignore) : lineParams(want, directed, ignore);
|
|
@@ -541,15 +1693,27 @@ function writtenAndReadBack(
|
|
|
541
1693
|
if (typeof label === "string") {
|
|
542
1694
|
params.set(LABEL_COLUMN, pajekLabelText(label));
|
|
543
1695
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
1696
|
+
// what the writer restates so that nothing is carried to this element (`restateCarried`)
|
|
1697
|
+
if (target === "node" && [...params.keys()].every((key) => key === LABEL_COLUMN)) {
|
|
1698
|
+
params.set(SHAPE_COLUMN, "ellipse");
|
|
1699
|
+
}
|
|
1700
|
+
for (const key of target === "edge" ? CARRIED.edge : []) {
|
|
1701
|
+
if (!params.has(key)) {
|
|
1702
|
+
params.set(key, lineRestated(key, directed));
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
const get: Param = (key) => params.get(key) ?? null;
|
|
1706
|
+
const put: Put = (property, value) => {
|
|
1707
|
+
if (value !== null) {
|
|
1708
|
+
back.set(property, value);
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
const at: At = (property) => ({ notes, target, property });
|
|
1712
|
+
if (target === "node") {
|
|
1713
|
+
readVertex(get, put, at);
|
|
1714
|
+
} else {
|
|
1715
|
+
readLine(get, put, at, directed, labels);
|
|
1716
|
+
}
|
|
553
1717
|
return back;
|
|
554
1718
|
}
|
|
555
1719
|
|
|
@@ -586,15 +1750,16 @@ export function pajekStyledExport(
|
|
|
586
1750
|
const approximate: Approximate = (property, value) => {
|
|
587
1751
|
approximated.add(target, property, value);
|
|
588
1752
|
};
|
|
589
|
-
const get =
|
|
1753
|
+
const get = elementValues((t, p) => input.picture(t, p as never), target, PAJEK_STYLE_CAPABILITIES[target]);
|
|
590
1754
|
const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
|
|
591
1755
|
const params = (i: number): Map<string, Written> =>
|
|
592
1756
|
target === "node" ? vertexParams(get(i), approximate) : lineParams(get(i), directed[i], approximate);
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
);
|
|
1757
|
+
const columns = paramColumns(n, params);
|
|
1758
|
+
if (target === "edge") {
|
|
1759
|
+
keepLineLabels(snapshot, columns);
|
|
1760
|
+
}
|
|
1761
|
+
restateCarried(target, columns, directed);
|
|
1762
|
+
replaceParamColumns(table, target === "node" ? [SHAPE_COLUMN, ...NODE_KEYS] : EDGE_KEYS, columns);
|
|
598
1763
|
}
|
|
599
1764
|
out.nodes.set(
|
|
600
1765
|
LABEL_COLUMN,
|
|
@@ -610,6 +1775,30 @@ export function pajekStyledExport(
|
|
|
610
1775
|
return { snapshot: out, comment: PAJEK_STYLE_COMMENT + block, notes };
|
|
611
1776
|
}
|
|
612
1777
|
|
|
1778
|
+
/**
|
|
1779
|
+
* Keeps each line's own label `l` where graphty draws none: Pajek draws no line label by default (`drawsLineLabels`),
|
|
1780
|
+
* so a label the file holds is not drawn, and writing it back keeps it for a Draw window that marks lines with labels.
|
|
1781
|
+
* @param snapshot - the graph handed to the exporter
|
|
1782
|
+
* @param columns - the written edge columns, by key, changed in place
|
|
1783
|
+
*/
|
|
1784
|
+
function keepLineLabels(snapshot: GraphSnapshot, columns: Map<string, (Written | null)[]>): void {
|
|
1785
|
+
if (!snapshot.edges.has("l")) {
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
const own = (e: number): unknown => plainCell(snapshot.edges.value("l", e));
|
|
1789
|
+
let col = columns.get("l");
|
|
1790
|
+
for (let e = 0; e < snapshot.edgeCount; e++) {
|
|
1791
|
+
const v = own(e);
|
|
1792
|
+
if (typeof v === "string" && v !== "" && (col?.[e] ?? null) === null) {
|
|
1793
|
+
col ??= new Array<Written | null>(snapshot.edgeCount).fill(null);
|
|
1794
|
+
col[e] = pajekLabelText(v);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
if (col !== undefined) {
|
|
1798
|
+
columns.set("l", col);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
613
1802
|
/**
|
|
614
1803
|
* Each vertex's label as graphty draws it; a vertex graphty draws without one gets an empty label, which Pajek draws
|
|
615
1804
|
* as nothing (with no label, Pajek writes and draws the vertex's id).
|
|
@@ -665,6 +1854,65 @@ function replaceParamColumns(
|
|
|
665
1854
|
}
|
|
666
1855
|
}
|
|
667
1856
|
|
|
1857
|
+
/**
|
|
1858
|
+
* Keeps Pajek from drawing an element with the parameters of the one before it (`CARRIED`): a line writes Pajek's own
|
|
1859
|
+
* default for each parameter another line writes and it leaves out, and a vertex that writes no parameter while
|
|
1860
|
+
* another does writes its shape, `ellipse`, which starts it again from Pajek's defaults.
|
|
1861
|
+
* @param target - nodes or edges
|
|
1862
|
+
* @param columns - the written columns, by key, changed in place
|
|
1863
|
+
* @param directed - for each line, whether it is an arc
|
|
1864
|
+
*/
|
|
1865
|
+
function restateCarried(
|
|
1866
|
+
target: StyleTarget,
|
|
1867
|
+
columns: Map<string, (Written | null)[]>,
|
|
1868
|
+
directed: readonly boolean[],
|
|
1869
|
+
): void {
|
|
1870
|
+
if (target === "node") {
|
|
1871
|
+
const all = [...columns.values()];
|
|
1872
|
+
if (all.length === 0) {
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
const n = all[0].length;
|
|
1876
|
+
const shape = columns.get(SHAPE_COLUMN) ?? new Array<Written | null>(n).fill(null);
|
|
1877
|
+
for (let i = 0; i < n; i++) {
|
|
1878
|
+
if (all.every((col) => col[i] === null)) {
|
|
1879
|
+
shape[i] = "ellipse";
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
columns.set(SHAPE_COLUMN, shape);
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
for (const key of CARRIED.edge) {
|
|
1886
|
+
const col = columns.get(key);
|
|
1887
|
+
if (col === undefined) {
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
for (let i = 0; i < col.length; i++) {
|
|
1891
|
+
col[i] ??= lineRestated(key, directed[i]);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
/**
|
|
1897
|
+
* What a line writes for a carried parameter it leaves at Pajek's default.
|
|
1898
|
+
* @param key - the parameter
|
|
1899
|
+
* @param directed - whether the line is an arc
|
|
1900
|
+
* @returns the value written
|
|
1901
|
+
*/
|
|
1902
|
+
function lineRestated(key: string, directed: boolean): Written {
|
|
1903
|
+
const defaults = lineDefaults(directed);
|
|
1904
|
+
switch (key) {
|
|
1905
|
+
case "c":
|
|
1906
|
+
return pajekColorText(String(defaults.color));
|
|
1907
|
+
case "w":
|
|
1908
|
+
return directed ? Number(defaults.width) : Number(defaults.width) / EDGE_WIDTH_PER_W;
|
|
1909
|
+
case "p":
|
|
1910
|
+
return "Solid";
|
|
1911
|
+
default:
|
|
1912
|
+
return 0;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
|
|
668
1916
|
/**
|
|
669
1917
|
* Every element's parameters, as columns.
|
|
670
1918
|
* @param n - the element count
|