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