@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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - Cytoscape.js: the stylesheet (an array or a CSS-like text) over the raw element JSON, through graph-io's
|
|
6
6
|
* Cytoscape.js engine, with Cytoscape.js's defaults beneath it; an element's own `style` is a bypass. Cytoscape
|
|
7
7
|
* desktop's export without a stylesheet gets Cytoscape desktop's default style instead.
|
|
8
|
-
* - graphology: the keys sigma.js draws (`color`, `size
|
|
8
|
+
* - graphology: the keys sigma.js draws (`color`, `size` (a radius), `label`), with sigma's defaults. Gephi's JSON
|
|
9
9
|
* export for sigma.js is read the same way.
|
|
10
10
|
* - vis.js: the keys vis-network draws, `options.nodes` and `options.edges` as declared defaults and
|
|
11
11
|
* `options.groups` as group rules, with vis-network's defaults, its palette for groups the file does not define,
|
|
@@ -33,11 +33,14 @@ import { cyjsHoldsExactly, CYTOSCAPE_IMPORT_KEY, toCytoscapeStylesheet, } from "
|
|
|
33
33
|
import { CYJS_EDGE_PROPERTIES, CYJS_NODE_PROPERTIES } from "../../styles/cyjs/mappers.js";
|
|
34
34
|
import { toolDefaults } from "../../styles/defaults.js";
|
|
35
35
|
import { StyleApproximations } from "../../styles/io/approximations.js";
|
|
36
|
-
import {
|
|
36
|
+
import { parseStyleBlock, STYLE_BLOCK_NAME } from "../../styles/io/block.js";
|
|
37
|
+
import { takeFromBlock } from "../../styles/io/round-trip.js";
|
|
37
38
|
import { StyleNotes } from "../../styles/notes.js";
|
|
38
39
|
import { styleFingerprint } from "../../styles/plan/features.js";
|
|
39
|
-
import { plainCell
|
|
40
|
+
import { plainCell } from "../../styles/plan/resolve.js";
|
|
40
41
|
import { isJsonObject } from "./dialect.js";
|
|
42
|
+
/** sigma.js's own settings. */
|
|
43
|
+
export const SIGMA_DEFAULT_SETTINGS = { renderEdgeLabels: false, edgeLabelAnchors: "nodeLabels" };
|
|
41
44
|
const TARGETS = ["node", "edge"];
|
|
42
45
|
// ============================================================ reading
|
|
43
46
|
/**
|
|
@@ -236,14 +239,17 @@ function graphologyStyles(snapshot, src, context) {
|
|
|
236
239
|
const notes = new StyleNotes();
|
|
237
240
|
const out = { node: {}, edge: {} };
|
|
238
241
|
const readFrom = { node: [], edge: [] };
|
|
242
|
+
const edgeLabel = sigmaEdgeLabels(snapshot, src.sigma ?? SIGMA_DEFAULT_SETTINGS);
|
|
239
243
|
for (const target of TARGETS) {
|
|
240
244
|
const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
|
|
241
245
|
const { values, put } = valuesOf(n);
|
|
242
246
|
const keys = {
|
|
243
247
|
color: cells(snapshot, target, "color"),
|
|
244
248
|
size: cells(snapshot, target, "size"),
|
|
249
|
+
// sigma 4's default styles bind nothing else: it draws an element marked `hidden: true` (sigma 3's key)
|
|
250
|
+
// or `visibility: "hidden"` (its own display-data name) like any other (specimen sheets
|
|
251
|
+
// graphology-specimen-node-sizes-opacity, -edges-lines)
|
|
245
252
|
label: cells(snapshot, target, "label"),
|
|
246
|
-
hidden: cells(snapshot, target, "hidden"),
|
|
247
253
|
};
|
|
248
254
|
readFrom[target].push(...Object.entries(keys).flatMap(([name, c]) => (c === null ? [] : [name])));
|
|
249
255
|
for (let i = 0; i < n; i++) {
|
|
@@ -254,7 +260,7 @@ function graphologyStyles(snapshot, src, context) {
|
|
|
254
260
|
element: i,
|
|
255
261
|
budget: context.budget,
|
|
256
262
|
});
|
|
257
|
-
readSigmaRow(keys, target, i, put, site);
|
|
263
|
+
readSigmaRow(keys, target, target === "node" || edgeLabel(i), i, put, site);
|
|
258
264
|
}
|
|
259
265
|
out[target] = values;
|
|
260
266
|
}
|
|
@@ -271,36 +277,82 @@ function graphologyStyles(snapshot, src, context) {
|
|
|
271
277
|
readFrom,
|
|
272
278
|
};
|
|
273
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Which edges sigma.js draws the label of: none unless `renderEdgeLabels` is on; then, under `edgeLabelAnchors`
|
|
282
|
+
* "nodeLabels", only an edge whose two end nodes show a label.
|
|
283
|
+
* @param snapshot - the graph
|
|
284
|
+
* @param settings - sigma's settings
|
|
285
|
+
* @returns whether edge `i`'s label is drawn
|
|
286
|
+
*/
|
|
287
|
+
function sigmaEdgeLabels(snapshot, settings) {
|
|
288
|
+
if (!settings.renderEdgeLabels) {
|
|
289
|
+
return () => false;
|
|
290
|
+
}
|
|
291
|
+
if (settings.edgeLabelAnchors === "allNodes") {
|
|
292
|
+
return () => true;
|
|
293
|
+
}
|
|
294
|
+
// ponytail: a node "shows" its label when it has one; sigma also hides labels by its label grid and
|
|
295
|
+
// labelRenderedSizeThreshold, which depend on its framing. Model those once graphty frames a sigma file as sigma does.
|
|
296
|
+
const label = cells(snapshot, "node", "label");
|
|
297
|
+
const shows = (node) => {
|
|
298
|
+
const text = label?.(node);
|
|
299
|
+
return typeof text === "string" ? text !== "" : text !== null && text !== undefined;
|
|
300
|
+
};
|
|
301
|
+
return (i) => shows(snapshot.edgeSource(i)) && shows(snapshot.edgeTarget(i));
|
|
302
|
+
}
|
|
274
303
|
/**
|
|
275
304
|
* Reads one element's sigma.js keys.
|
|
276
305
|
* @param keys - the cell readers
|
|
277
306
|
* @param target - nodes or edges
|
|
307
|
+
* @param labeled - whether sigma draws the element's label
|
|
278
308
|
* @param i - the element
|
|
279
309
|
* @param put - the setter
|
|
280
310
|
* @param site - where a value of a property was read
|
|
281
311
|
*/
|
|
282
|
-
function readSigmaRow(keys, target, i, put, site) {
|
|
283
|
-
const { color, size, label
|
|
312
|
+
function readSigmaRow(keys, target, labeled, i, put, site) {
|
|
313
|
+
const { color, size, label } = keys;
|
|
284
314
|
if (color !== null) {
|
|
285
315
|
const [fill, opacity] = target === "node" ? ["fill", "fillOpacity"] : ["color", "opacity"];
|
|
286
316
|
putColor(put, fill, opacity, i, color(i), site(fill));
|
|
287
317
|
}
|
|
288
318
|
if (size !== null) {
|
|
289
|
-
|
|
290
|
-
put("width", i, v);
|
|
291
|
-
if (target === "node") {
|
|
292
|
-
put("height", i, v);
|
|
293
|
-
}
|
|
319
|
+
putSigmaSize(put, target, i, size(i), site);
|
|
294
320
|
}
|
|
295
|
-
|
|
296
|
-
|
|
321
|
+
// an edge's label is drawn only under sigma's renderEdgeLabels setting, which a document never carries (the
|
|
322
|
+
// `sigmaSettings` import option); otherwise it is data (graphology-specimen-edge-labels, -p2, -p3)
|
|
323
|
+
if (label !== null && labeled) {
|
|
324
|
+
const text = canonText(label(i), site("label"));
|
|
325
|
+
// sigma draws a label with the canvas's fillText, which draws each ASCII whitespace as a space: one line
|
|
326
|
+
// (graphology-specimen-labels, label-multiline)
|
|
327
|
+
put("label", i, text === null ? null : text.replaceAll(/[\t\n\f\r]/g, " "));
|
|
297
328
|
}
|
|
298
|
-
|
|
299
|
-
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* One element's sigma.js size, as its width (and a node's height).
|
|
332
|
+
* @param put - the setter
|
|
333
|
+
* @param target - nodes or edges
|
|
334
|
+
* @param i - the element
|
|
335
|
+
* @param raw - the size value
|
|
336
|
+
* @param site - where a value of a property was read
|
|
337
|
+
*/
|
|
338
|
+
function putSigmaSize(put, target, i, raw, site) {
|
|
339
|
+
if (target === "node") {
|
|
340
|
+
// sigma draws a node's size as its radius, and a node of negative size not at all
|
|
341
|
+
// (graphology-specimen-node-sizes-opacity, size-negative)
|
|
342
|
+
const v = Number(raw) < 0 ? 0 : canonNumber(raw, site("width"));
|
|
343
|
+
const d = v === null ? null : v * 2;
|
|
344
|
+
put("width", i, d);
|
|
345
|
+
put("height", i, d);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
// sigma's edge shader draws an edge half as wide as a node of the same size is in radius: size 6 draws
|
|
349
|
+
// 3 px where a node of size 6 is 5.8 px in radius (graphology-specimen-arrows edge-size-6)
|
|
350
|
+
const w = canonNumber(raw, site("width"));
|
|
351
|
+
put("width", i, w === null ? null : w / 2);
|
|
300
352
|
}
|
|
301
353
|
}
|
|
302
354
|
/**
|
|
303
|
-
* A
|
|
355
|
+
* A color as sigma.js reads it, into a property and its opacity.
|
|
304
356
|
* @param put - the setter
|
|
305
357
|
* @param property - the color property
|
|
306
358
|
* @param opacity - its opacity property, or null when it has none
|
|
@@ -309,10 +361,11 @@ function readSigmaRow(keys, target, i, put, site) {
|
|
|
309
361
|
* @param site - where it was read
|
|
310
362
|
*/
|
|
311
363
|
function putColor(put, property, opacity, i, raw, site) {
|
|
312
|
-
|
|
364
|
+
// sigma draws a missing color with its default, and any string it cannot read ("" too) black
|
|
365
|
+
if (raw === null || raw === undefined) {
|
|
313
366
|
return;
|
|
314
367
|
}
|
|
315
|
-
const c = canonColor(raw, "
|
|
368
|
+
const c = canonColor(raw, "graphology", site);
|
|
316
369
|
if (c === null) {
|
|
317
370
|
return;
|
|
318
371
|
}
|
|
@@ -354,59 +407,27 @@ function blockText(snapshot, src) {
|
|
|
354
407
|
* nothing about the block's (the writer never wrote it), so the block's value is the truth
|
|
355
408
|
*/
|
|
356
409
|
function exactFromBlock(snapshot, text, values, written, holdsExactly) {
|
|
357
|
-
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const parsed = parseStyleBlock(text, styleFingerprint(snapshot));
|
|
361
|
-
const ex = parsed.ok ? blockStyles(parsed.block, snapshot, "json") : null;
|
|
362
|
-
if (ex === null) {
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
for (const target of TARGETS) {
|
|
410
|
+
takeFromBlock(snapshot, text, "json", (target, block, ex) => {
|
|
366
411
|
const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
|
|
367
|
-
const cache = new Map();
|
|
368
|
-
const resolved = (p) => {
|
|
369
|
-
let r = cache.get(p);
|
|
370
|
-
if (r === undefined) {
|
|
371
|
-
r = resolveStyles(ex, target, p, snapshot);
|
|
372
|
-
cache.set(p, r);
|
|
373
|
-
}
|
|
374
|
-
return r;
|
|
375
|
-
};
|
|
376
412
|
if (holdsExactly !== undefined) {
|
|
377
413
|
const properties = new Set(ex.styles.rules
|
|
378
414
|
.filter((r) => r.target === target)
|
|
379
415
|
.flatMap((r) => [...Object.keys(r.set ?? {}), ...Object.keys(r.map ?? {})]));
|
|
380
416
|
for (const property of properties) {
|
|
381
417
|
const held = (w) => holdsExactly(target, property, w);
|
|
382
|
-
takeUnheld(values[target], property,
|
|
418
|
+
takeUnheld(values[target], property, block(target, property), n, held);
|
|
383
419
|
}
|
|
384
420
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
* @param resolved - the block's values of a property
|
|
396
|
-
* @param write - what the writer writes for an element, from the block's values
|
|
397
|
-
* @param snapshot - the graph
|
|
398
|
-
*/
|
|
399
|
-
function takeWritten(column, property, n, resolved, write, snapshot) {
|
|
400
|
-
const want = resolved(property);
|
|
401
|
-
for (let i = 0; i < n; i++) {
|
|
402
|
-
const w = want[i];
|
|
403
|
-
const native = column[property]?.[i] ?? null;
|
|
404
|
-
// a native value the writer made from the block's other values (vis.js's one size for a width) is the
|
|
405
|
-
// block's own value, none included
|
|
406
|
-
if (native !== w && native !== null && native === write((p) => resolved(p)[i], i, snapshot)) {
|
|
407
|
-
setValue(column, property, n, i, w);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
421
|
+
const writes = written[target] ?? {};
|
|
422
|
+
return {
|
|
423
|
+
values: values[target],
|
|
424
|
+
properties: Object.keys(writes),
|
|
425
|
+
// a native value the writer made from the block's other values (vis.js's one size for a width) is the
|
|
426
|
+
// block's own value, none included
|
|
427
|
+
readBack: (i, want) => (property) => writes[property](want, i, snapshot),
|
|
428
|
+
take: (_property, _wanted, native) => native !== null,
|
|
429
|
+
};
|
|
430
|
+
});
|
|
410
431
|
}
|
|
411
432
|
/**
|
|
412
433
|
* Takes the block's value of a property wherever the dialect does not hold it exactly.
|
|
@@ -449,6 +470,17 @@ const VIS_SIZED = new Set([
|
|
|
449
470
|
"square",
|
|
450
471
|
"icon",
|
|
451
472
|
]);
|
|
473
|
+
/** vis-network's node shapes sized by their label, as the `labelFit` facts of each (vis-network 9.1.9 `resize`). */
|
|
474
|
+
const VIS_LABEL_FIT = new Map([
|
|
475
|
+
["box", { square: false, ellipse: false }],
|
|
476
|
+
["text", { square: false, ellipse: false }],
|
|
477
|
+
["circle", { square: true, ellipse: false }],
|
|
478
|
+
["database", { square: true, ellipse: false }],
|
|
479
|
+
["ellipse", { square: false, ellipse: true }],
|
|
480
|
+
]);
|
|
481
|
+
/** vis-network's default `margin` and font size (vis-network.esm.js NodesHandler defaults). */
|
|
482
|
+
const VIS_MARGIN = 5;
|
|
483
|
+
const VIS_FONT_SIZE = 14;
|
|
452
484
|
/**
|
|
453
485
|
* vis-network's palette for a group the file does not define, in the order groups first appear (vis-network
|
|
454
486
|
* 10.1.2, `Groups._defaultGroups`): border, background.
|
|
@@ -475,6 +507,9 @@ const VIS_GROUP_PALETTE = [
|
|
|
475
507
|
["#C2FABC", "#74D66A"],
|
|
476
508
|
["#EE0000", "#990000"],
|
|
477
509
|
];
|
|
510
|
+
/** vis-network's node colors where nothing sets them (vis-network 9.1.9 `NodesHandler` defaults `color`). */
|
|
511
|
+
const VIS_NODE_FILL = "#97C2FC";
|
|
512
|
+
const VIS_NODE_BORDER = "#2B7CE9";
|
|
478
513
|
/** vis-network's edge color when an edge inherits none (vis-network 10.1.2, `color.color`). */
|
|
479
514
|
const VIS_EDGE_COLOR = "#848484";
|
|
480
515
|
/**
|
|
@@ -563,7 +598,14 @@ function visLayer(o, target) {
|
|
|
563
598
|
set("hidden", o.hidden);
|
|
564
599
|
set("label", o.label);
|
|
565
600
|
set("title", o.title);
|
|
601
|
+
set("wrapWidth", isJsonObject(o.widthConstraint) ? o.widthConstraint.maximum : o.widthConstraint);
|
|
602
|
+
if (isJsonObject(o.scaling)) {
|
|
603
|
+
set("scalingLabel", o.scaling.label);
|
|
604
|
+
}
|
|
566
605
|
visFontLayer(o.font, set);
|
|
606
|
+
if (target === "edge" && o.font !== undefined && o.font !== null) {
|
|
607
|
+
set("fontText", typeof o.font === "string");
|
|
608
|
+
}
|
|
567
609
|
return layer;
|
|
568
610
|
}
|
|
569
611
|
/**
|
|
@@ -584,8 +626,143 @@ function visNodeLayer(o, set) {
|
|
|
584
626
|
}
|
|
585
627
|
set("shape", o.shape);
|
|
586
628
|
set("size", o.size);
|
|
629
|
+
set("value", o.value);
|
|
630
|
+
if (isJsonObject(o.scaling)) {
|
|
631
|
+
set("scalingMin", o.scaling.min);
|
|
632
|
+
set("scalingMax", o.scaling.max);
|
|
633
|
+
}
|
|
587
634
|
set("borderWidth", o.borderWidth);
|
|
588
635
|
set("opacity", o.opacity);
|
|
636
|
+
set("margin", o.margin);
|
|
637
|
+
set("widthMin", isJsonObject(o.widthConstraint) ? o.widthConstraint.minimum : o.widthConstraint);
|
|
638
|
+
set("heightMin", isJsonObject(o.heightConstraint) ? o.heightConstraint.minimum : o.heightConstraint);
|
|
639
|
+
set("valign", isJsonObject(o.heightConstraint) ? o.heightConstraint.valign : undefined);
|
|
640
|
+
set("image", isJsonObject(o.image) ? o.image.unselected : o.image);
|
|
641
|
+
set("brokenImage", o.brokenImage);
|
|
642
|
+
set("imagePadding", o.imagePadding);
|
|
643
|
+
if (isJsonObject(o.icon)) {
|
|
644
|
+
set("iconCode", o.icon.code);
|
|
645
|
+
set("iconFace", o.icon.face);
|
|
646
|
+
set("iconSize", o.icon.size);
|
|
647
|
+
set("iconColor", o.icon.color);
|
|
648
|
+
set("iconWeight", o.icon.weight);
|
|
649
|
+
}
|
|
650
|
+
if (isJsonObject(o.shapeProperties)) {
|
|
651
|
+
set("useImageSize", o.shapeProperties.useImageSize);
|
|
652
|
+
set("useBorderWithImage", o.shapeProperties.useBorderWithImage);
|
|
653
|
+
set("interpolation", o.shapeProperties.interpolation);
|
|
654
|
+
set("coordinateOrigin", o.shapeProperties.coordinateOrigin);
|
|
655
|
+
set("borderDashes", o.shapeProperties.borderDashes);
|
|
656
|
+
set("borderRadius", o.shapeProperties.borderRadius);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* What a vis-network `image` or `circularImage` node draws (vis-network 9.1.9 `Image`, `CircularImage`,
|
|
661
|
+
* `CircleImageBase._resizeImage`): its picture, the broken image in its place when it has none, with its shorter
|
|
662
|
+
* side across the node's `2 * size` square (or at its own pixels with `shapeProperties.useImageSize`); an `image`
|
|
663
|
+
* node is the picture alone, reaching past that square, or over a box of its background with
|
|
664
|
+
* `shapeProperties.useBorderWithImage`; a `circularImage` is cut to a circle and ringed by its border. Its
|
|
665
|
+
* `imagePadding` keeps the picture that far inside the node's box on each side, stretched to what is left
|
|
666
|
+
* (`_drawImageAtPosition`); the node's `opacity` fades the picture too; with `shapeProperties.coordinateOrigin`
|
|
667
|
+
* "top-left" an `image` node with no box of its own draws its picture with its top left corner at the node's position.
|
|
668
|
+
* @param layer - the node's merged layer
|
|
669
|
+
* @param shape - its vis-network shape
|
|
670
|
+
* @param box - the node's width and height, in the file's units
|
|
671
|
+
* @param box.width - its width
|
|
672
|
+
* @param box.height - its height
|
|
673
|
+
* @returns the values, none for another shape or a node with no picture
|
|
674
|
+
*/
|
|
675
|
+
function visImage(layer, shape, box) {
|
|
676
|
+
const text = (v) => (typeof v === "string" && v !== "" ? v : null);
|
|
677
|
+
const [own, broken] = [text(layer.image), text(layer.brokenImage)];
|
|
678
|
+
const src = own ?? broken;
|
|
679
|
+
if ((shape !== "image" && shape !== "circularImage") || src === null) {
|
|
680
|
+
return {};
|
|
681
|
+
}
|
|
682
|
+
const circular = shape === "circularImage";
|
|
683
|
+
const row = {
|
|
684
|
+
image: src,
|
|
685
|
+
...(own !== null && broken !== null ? { imageFallback: broken } : {}),
|
|
686
|
+
imageFit: layer.useImageSize === true ? "none" : "cover",
|
|
687
|
+
imageClip: circular ? "node" : "none",
|
|
688
|
+
shape: "ellipse",
|
|
689
|
+
};
|
|
690
|
+
if (!circular) {
|
|
691
|
+
row.shape = layer.useBorderWithImage === true ? "rectangle" : "none";
|
|
692
|
+
}
|
|
693
|
+
if (layer.interpolation === false) {
|
|
694
|
+
row.imageSmoothing = false;
|
|
695
|
+
}
|
|
696
|
+
const opacity = typeof layer.opacity === "number" && layer.opacity >= 0 && layer.opacity < 1 ? layer.opacity : null;
|
|
697
|
+
if (opacity !== null) {
|
|
698
|
+
row.imageOpacity = opacity;
|
|
699
|
+
}
|
|
700
|
+
visImagePlace(row, layer, box);
|
|
701
|
+
return row;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Where a vis-network picture sits in its node's box: inside its `imagePadding`, and from the top left corner under
|
|
705
|
+
* `shapeProperties.coordinateOrigin` "top-left" (visImage).
|
|
706
|
+
* @param row - the picture's values so far, written in place
|
|
707
|
+
* @param layer - the node's merged layer
|
|
708
|
+
* @param box - the node's width and height, in the file's units
|
|
709
|
+
* @param box.width - its width
|
|
710
|
+
* @param box.height - its height
|
|
711
|
+
*/
|
|
712
|
+
function visImagePlace(row, layer, box) {
|
|
713
|
+
const pad = visImagePadding(layer.imagePadding);
|
|
714
|
+
if (pad !== null && layer.useImageSize !== true) {
|
|
715
|
+
row.imageFit = "none";
|
|
716
|
+
row.imageWidth = Math.max(0, box.width - pad.left - pad.right);
|
|
717
|
+
row.imageHeight = Math.max(0, box.height - pad.top - pad.bottom);
|
|
718
|
+
row.imageOffsetX = (pad.left - pad.right) / 2;
|
|
719
|
+
row.imageOffsetY = (pad.top - pad.bottom) / 2;
|
|
720
|
+
}
|
|
721
|
+
if (layer.coordinateOrigin === "top-left" && row.shape === "none") {
|
|
722
|
+
row.imageOffsetX = box.width / 2 + (typeof row.imageOffsetX === "number" ? row.imageOffsetX : 0);
|
|
723
|
+
row.imageOffsetY = box.height / 2 + (typeof row.imageOffsetY === "number" ? row.imageOffsetY : 0);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* What a vis-network `icon` node draws (vis-network 9.1.9 `Icon`): its `icon.code` in `icon.face` (FontAwesome
|
|
728
|
+
* unless given) at `icon.size` (50) pixels and `icon.weight`, in `icon.color` (#2B7CE9), centered on the node; no
|
|
729
|
+
* code draws nothing.
|
|
730
|
+
* @param layer - the node's merged layer
|
|
731
|
+
* @param site - where a bad color is noted
|
|
732
|
+
* @returns the values, none for an icon with no code
|
|
733
|
+
*/
|
|
734
|
+
function visIcon(layer, site) {
|
|
735
|
+
const code = typeof layer.iconCode === "string" && layer.iconCode !== "" ? layer.iconCode : null;
|
|
736
|
+
if (code === null) {
|
|
737
|
+
return {};
|
|
738
|
+
}
|
|
739
|
+
const size = typeof layer.iconSize === "number" && layer.iconSize >= 0 ? layer.iconSize : 50;
|
|
740
|
+
const color = typeof layer.iconColor === "string" ? canonColor(layer.iconColor, "vis", site) : null;
|
|
741
|
+
const weight = layer.iconWeight;
|
|
742
|
+
const bold = weight === "bold" || weight === "bolder" || (typeof weight === "number" && weight >= 600);
|
|
743
|
+
return {
|
|
744
|
+
glyph: code,
|
|
745
|
+
glyphFont: typeof layer.iconFace === "string" && layer.iconFace !== "" ? layer.iconFace : "FontAwesome",
|
|
746
|
+
glyphSize: size,
|
|
747
|
+
glyphColor: color?.color ?? "#2b7ce9",
|
|
748
|
+
glyphWeight: bold ? "bold" : "normal",
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* vis-network's `imagePadding`: one number for every side, or `{ top, right, bottom, left }` (each 0 unless given).
|
|
753
|
+
* @param raw - the option
|
|
754
|
+
* @returns the four sides, or null for no padding
|
|
755
|
+
*/
|
|
756
|
+
function visImagePadding(raw) {
|
|
757
|
+
const side = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0);
|
|
758
|
+
let pad = null;
|
|
759
|
+
if (typeof raw === "number") {
|
|
760
|
+
pad = { top: side(raw), right: side(raw), bottom: side(raw), left: side(raw) };
|
|
761
|
+
}
|
|
762
|
+
else if (isJsonObject(raw)) {
|
|
763
|
+
pad = { top: side(raw.top), right: side(raw.right), bottom: side(raw.bottom), left: side(raw.left) };
|
|
764
|
+
}
|
|
765
|
+
return pad === null || pad.top + pad.right + pad.bottom + pad.left === 0 ? null : pad;
|
|
589
766
|
}
|
|
590
767
|
/**
|
|
591
768
|
* The edge keys of a vis.js options object: a color of its own stops the edge inheriting its node's.
|
|
@@ -605,8 +782,19 @@ function visEdgeLayer(o, set) {
|
|
|
605
782
|
set("inherit", own ? false : color.inherit);
|
|
606
783
|
}
|
|
607
784
|
set("width", o.width);
|
|
785
|
+
set("value", o.value);
|
|
786
|
+
if (isJsonObject(o.scaling)) {
|
|
787
|
+
set("scalingMin", o.scaling.min);
|
|
788
|
+
set("scalingMax", o.scaling.max);
|
|
789
|
+
}
|
|
608
790
|
set("dashes", o.dashes);
|
|
609
791
|
set("arrows", o.arrows);
|
|
792
|
+
set("background", o.background);
|
|
793
|
+
set("smooth", o.smooth);
|
|
794
|
+
set("selfReference", o.selfReference);
|
|
795
|
+
set("selfReferenceSize", o.selfReferenceSize);
|
|
796
|
+
set("physics", o.physics);
|
|
797
|
+
set("length", o.length);
|
|
610
798
|
}
|
|
611
799
|
/**
|
|
612
800
|
* The font keys of a vis.js options object: an object, or text like `"14px arial red"`.
|
|
@@ -615,9 +803,19 @@ function visEdgeLayer(o, set) {
|
|
|
615
803
|
*/
|
|
616
804
|
function visFontLayer(font, set) {
|
|
617
805
|
if (isJsonObject(font)) {
|
|
806
|
+
set("fontMulti", font.multi);
|
|
807
|
+
set("fontStrokeWidth", font.strokeWidth);
|
|
808
|
+
set("fontStrokeColor", font.strokeColor);
|
|
809
|
+
set("fontBackground", font.background);
|
|
810
|
+
set("fontAlign", font.align);
|
|
618
811
|
set("fontColor", font.color);
|
|
619
812
|
set("fontSize", font.size);
|
|
620
813
|
set("fontFace", font.face);
|
|
814
|
+
set("fontVadjust", font.vadjust);
|
|
815
|
+
const mods = ["bold", "ital", "boldital", "mono"].filter((m) => font[m] !== undefined && font[m] !== null);
|
|
816
|
+
if (mods.length > 0) {
|
|
817
|
+
set("fontMods", Object.fromEntries(mods.map((m) => [m, font[m]])));
|
|
818
|
+
}
|
|
621
819
|
}
|
|
622
820
|
else if (typeof font === "string") {
|
|
623
821
|
const m = /^\s*(\d+(?:\.\d+)?)(?:px)?\s+(\S+)(?:\s+(\S+))?/.exec(font.slice(0, 256));
|
|
@@ -657,30 +855,460 @@ function visValues(layer, target, site, sized) {
|
|
|
657
855
|
};
|
|
658
856
|
if (target === "node") {
|
|
659
857
|
color("fill", "fillOpacity", layer.fill);
|
|
660
|
-
color("borderColor",
|
|
858
|
+
color("borderColor", "borderOpacity", layer.border);
|
|
661
859
|
visNodeValues(layer, sized, put, site);
|
|
662
860
|
}
|
|
663
861
|
else {
|
|
664
862
|
color("color", "opacity", layer.edgeColor);
|
|
665
863
|
visEdgeValues(layer, put, site);
|
|
864
|
+
visEdgeBackground(layer, put, color);
|
|
666
865
|
}
|
|
667
866
|
if (layer.hidden !== undefined && canonBoolean(layer.hidden, site("visible")) === true) {
|
|
668
867
|
put("visible", false);
|
|
669
868
|
}
|
|
670
|
-
|
|
671
|
-
|
|
869
|
+
visLabelValues(layer, target, put, color, site);
|
|
870
|
+
return out;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* An edge's background: a band `size` wide under the line, its outline beyond the line. vis-network draws it only
|
|
874
|
+
* under a smooth (bezier) edge (BezierEdgeBase._bezierCurve calls drawBackground; StraightEdge never does).
|
|
875
|
+
* @param layer - the layer
|
|
876
|
+
* @param put - the row's setter
|
|
877
|
+
* @param color - the row's color setter
|
|
878
|
+
*/
|
|
879
|
+
function visEdgeBackground(layer, put, color) {
|
|
880
|
+
// ponytail: `smooth` is read from this layer alone, else vis-network's default (smooth)
|
|
881
|
+
if (!isJsonObject(layer.background)) {
|
|
882
|
+
return;
|
|
672
883
|
}
|
|
673
|
-
|
|
674
|
-
|
|
884
|
+
const bg = layer.background;
|
|
885
|
+
const straight = layer.smooth === false || (isJsonObject(layer.smooth) && layer.smooth.enabled === false);
|
|
886
|
+
color("outlineColor", "outlineOpacity", bg.color);
|
|
887
|
+
if (bg.enabled !== undefined) {
|
|
888
|
+
const size = typeof bg.size === "number" ? bg.size : 10;
|
|
889
|
+
const width = typeof layer.width === "number" ? layer.width : 1;
|
|
890
|
+
put("outlineWidth", bg.enabled === true && !straight ? Math.max(0, size - width) : 0);
|
|
675
891
|
}
|
|
676
|
-
|
|
892
|
+
const band = visBandStyle(bg, false);
|
|
893
|
+
if (band !== null) {
|
|
894
|
+
put("outlineStyle", band);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* The label values of a vis.js layer: its text (markup read), wrap width, font, outline, background and, for an edge,
|
|
899
|
+
* where on the line it sits.
|
|
900
|
+
* @param layer - the layer
|
|
901
|
+
* @param target - nodes or edges
|
|
902
|
+
* @param put - the row's setter
|
|
903
|
+
* @param color - the row's color setter
|
|
904
|
+
* @param site - where its values are read
|
|
905
|
+
*/
|
|
906
|
+
function visLabelValues(layer, target, put, color, site) {
|
|
677
907
|
if (layer.fontSize !== undefined) {
|
|
678
908
|
put("labelSize", canonNumber(layer.fontSize, site("labelSize")));
|
|
679
909
|
}
|
|
680
910
|
if (layer.fontFace !== undefined) {
|
|
681
911
|
put("labelFont", canonText(layer.fontFace, site("labelFont")));
|
|
682
912
|
}
|
|
683
|
-
|
|
913
|
+
if (layer.label !== undefined) {
|
|
914
|
+
visLabelText(layer, target, put, site);
|
|
915
|
+
}
|
|
916
|
+
// vis-network wraps a label's lines at `widthConstraint.maximum` (or a number), margins not taken off:
|
|
917
|
+
// Label.adjustSizes subtracts them only under constrainWidth, which nothing sets. LabelSplitter keeps each blank
|
|
918
|
+
// as a word of its own and joins words with a blank (getLongestFit), so a blank between two words counts three.
|
|
919
|
+
if (typeof layer.wrapWidth === "number" && Number.isFinite(layer.wrapWidth) && layer.wrapWidth > 0) {
|
|
920
|
+
put("labelWrapWidth", layer.wrapWidth);
|
|
921
|
+
put("labelWrapSpaceCount", 3);
|
|
922
|
+
}
|
|
923
|
+
if (layer.title !== undefined) {
|
|
924
|
+
put("tooltip", canonText(layer.title, site("tooltip")));
|
|
925
|
+
}
|
|
926
|
+
// a label drawn under `scaling.label.drawThreshold - 1` px on screen is not drawn (Label.draw)
|
|
927
|
+
const threshold = isJsonObject(layer.scalingLabel) ? layer.scalingLabel.drawThreshold : undefined;
|
|
928
|
+
if (typeof threshold === "number" && Number.isFinite(threshold)) {
|
|
929
|
+
put("labelMinScreenSize", Math.max(0, threshold - 1));
|
|
930
|
+
}
|
|
931
|
+
visLabelLook(layer, target, put, color, site);
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* The text of a vis.js layer's label, its markup read into runs.
|
|
935
|
+
* @param layer - the layer
|
|
936
|
+
* @param target - nodes or edges
|
|
937
|
+
* @param put - the row's setter
|
|
938
|
+
* @param site - where its values are read
|
|
939
|
+
*/
|
|
940
|
+
function visLabelText(layer, target, put, site) {
|
|
941
|
+
// vis-network 9.1.9 draws no edge label whose `font` is text: Edge.drawLabel reads `options.font.align` (undefined
|
|
942
|
+
// on a string) as not horizontal and moves the context to the label point, while the label module, which
|
|
943
|
+
// parsed the text, still draws at the absolute point, so the label lands off the canvas
|
|
944
|
+
const text = target === "edge" && layer.fontText === true ? "" : canonText(layer.label, site("label"));
|
|
945
|
+
const marked = typeof text === "string" ? visMarkup(text, layer.fontMulti) : null;
|
|
946
|
+
put("label", marked?.text ?? text);
|
|
947
|
+
if (marked !== null) {
|
|
948
|
+
const runs = visRuns(marked.runs, layer, site("labelRuns"));
|
|
949
|
+
put("labelRuns", runs);
|
|
950
|
+
if (runs !== null) {
|
|
951
|
+
put("labelRunAlign", visRunAlign(layer, target));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
// after the face: a label all in mono is drawn in the mono face
|
|
955
|
+
for (const [p, v] of Object.entries(VIS_MOD_VALUES[marked?.mod ?? "normal"] ?? {})) {
|
|
956
|
+
put(p, v);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* The look of a vis.js layer's label: its color, background box, letter outline and, for an edge, where on the line
|
|
961
|
+
* it sits.
|
|
962
|
+
* @param layer - the layer
|
|
963
|
+
* @param target - nodes or edges
|
|
964
|
+
* @param put - the row's setter
|
|
965
|
+
* @param color - the row's color setter
|
|
966
|
+
* @param site - where its values are read
|
|
967
|
+
*/
|
|
968
|
+
function visLabelLook(layer, target, put, color, site) {
|
|
969
|
+
color("labelColor", null, layer.fontColor);
|
|
970
|
+
// font.background "none" (vis-network's default) draws no box
|
|
971
|
+
color("labelBackground", null, layer.fontBackground === "none" ? undefined : layer.fontBackground);
|
|
972
|
+
// the box is the words' own: as wide as the text and one font size tall a line (Label._drawBackground: getSize)
|
|
973
|
+
if (typeof layer.fontBackground === "string" && layer.fontBackground !== "none") {
|
|
974
|
+
put("labelPadding", 0);
|
|
975
|
+
put("labelLineHeight", 1);
|
|
976
|
+
}
|
|
977
|
+
// a stroke with no color of its own is vis-network's default, white (font.strokeColor "#ffffff")
|
|
978
|
+
const stroked = typeof layer.fontStrokeWidth === "number" && layer.fontStrokeWidth > 0;
|
|
979
|
+
color("labelOutlineColor", null, layer.fontStrokeColor ?? (stroked ? "#ffffff" : undefined));
|
|
980
|
+
if (layer.fontStrokeWidth !== undefined) {
|
|
981
|
+
put("labelOutlineWidth", canonNumber(layer.fontStrokeWidth, site("labelOutlineWidth")));
|
|
982
|
+
}
|
|
983
|
+
if (target === "edge" && typeof layer.fontAlign === "string") {
|
|
984
|
+
// vis-network draws an edge label on the line ("horizontal", "middle"), above it ("top") or below it ("bottom")
|
|
985
|
+
put("labelPosition", layer.fontAlign === "top" || layer.fontAlign === "bottom" ? layer.fontAlign : "center");
|
|
986
|
+
}
|
|
987
|
+
else if (target === "node" && typeof layer.fontAlign === "string") {
|
|
988
|
+
// a node label's lines are centered on one another, or flush left or right in the label's box (Label._drawText:
|
|
989
|
+
// any align but center and right draws flush left); an edge label's lines are always centered
|
|
990
|
+
put("labelTextAlign", { center: "center", right: "right" }[layer.fontAlign] ?? "left");
|
|
991
|
+
}
|
|
992
|
+
// the text is drawn `font.vadjust` lower than its place (Label._drawText: y + block.vadjust)
|
|
993
|
+
if (typeof layer.fontVadjust === "number" && Number.isFinite(layer.fontVadjust) && layer.fontVadjust !== 0) {
|
|
994
|
+
put("labelOffsetY", layer.fontVadjust);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
/** The label values of a label whose every word has one vis.js font modifier; vis-network's mono face is monospace. */
|
|
998
|
+
const VIS_MOD_VALUES = {
|
|
999
|
+
bold: { labelWeight: "bold" },
|
|
1000
|
+
ital: { labelItalic: true },
|
|
1001
|
+
boldital: { labelWeight: "bold", labelItalic: true },
|
|
1002
|
+
mono: { labelFont: "monospace" },
|
|
1003
|
+
};
|
|
1004
|
+
/**
|
|
1005
|
+
* Whether vis-network's node `opacity` replaces a color's alpha: a hex color of 3 or 6 digits or an `rgb()` color, and
|
|
1006
|
+
* vis-network's own default (overrideOpacity; hexToRGB reads no other text).
|
|
1007
|
+
* @param color - the color as the file gives it, or undefined for vis-network's default
|
|
1008
|
+
* @returns true when the opacity applies
|
|
1009
|
+
*/
|
|
1010
|
+
function visTakesOpacity(color) {
|
|
1011
|
+
if (color === undefined) {
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
return (typeof color === "string" &&
|
|
1015
|
+
!color.includes("rgba") &&
|
|
1016
|
+
(color.includes("rgb") || /^#?(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(color.trim())));
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* The line style of an edge's background band: stroked `size` wide (10 by default) in its own dashes, [5, 5] for true
|
|
1020
|
+
* (EdgeBase.drawBackground, setStrokeDashed), with the canvas's line caps.
|
|
1021
|
+
* @param bg - the edge's `background`
|
|
1022
|
+
* @param round - whether the canvas draws round caps by then (see visStyles' edge loop)
|
|
1023
|
+
* @returns the line style, or null for a band with no dashes
|
|
1024
|
+
*/
|
|
1025
|
+
function visBandStyle(bg, round) {
|
|
1026
|
+
if (bg.dashes === undefined || bg.dashes === false) {
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
return visDashStyle(bg.dashes === true ? [5, 5] : bg.dashes, typeof bg.size === "number" ? bg.size : 10, round);
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* vis.js dashes (`dashes`, `shapeProperties.borderDashes`) as the line style they draw at a width, read as a dash
|
|
1033
|
+
* array is read anywhere: with round caps each dash a width longer and each gap a width shorter; then gaps under a
|
|
1034
|
+
* tenth of the pattern are solid, dashes no longer than 1.5 widths are dotted, dashes of which the longest is twice
|
|
1035
|
+
* the shortest dash-dot, others dashed; false is solid.
|
|
1036
|
+
* @param dashes - false, or the dash array (dash, gap, dash, gap ...; an odd one repeats)
|
|
1037
|
+
* @param width - the line's width
|
|
1038
|
+
* @param round - whether the dashes have round caps
|
|
1039
|
+
* @returns the line style
|
|
1040
|
+
*/
|
|
1041
|
+
function visDashStyle(dashes, width, round) {
|
|
1042
|
+
if (!Array.isArray(dashes)) {
|
|
1043
|
+
return dashes === false ? "solid" : "dashed";
|
|
1044
|
+
}
|
|
1045
|
+
const lengths = dashes.filter((d) => typeof d === "number" && Number.isFinite(d));
|
|
1046
|
+
const pairs = lengths.length % 2 === 1 ? [...lengths, ...lengths] : lengths;
|
|
1047
|
+
const grow = round ? width : 0;
|
|
1048
|
+
const on = pairs.filter((_d, i) => i % 2 === 0).map((d) => Math.max(0, d) + grow);
|
|
1049
|
+
const off = pairs.filter((_d, i) => i % 2 === 1).map((d) => Math.max(0, d - grow));
|
|
1050
|
+
const sum = (xs) => xs.reduce((a, b) => a + b, 0);
|
|
1051
|
+
// a gap of exactly a tenth still shows (mappings-bypass edge-default: [5, 5] at width 4)
|
|
1052
|
+
if (on.length === 0 || sum(off) < 0.1 * (sum(on) + sum(off))) {
|
|
1053
|
+
return "solid";
|
|
1054
|
+
}
|
|
1055
|
+
if (on.every((d) => d <= 1.5 * Math.max(1, width))) {
|
|
1056
|
+
return "dotted";
|
|
1057
|
+
}
|
|
1058
|
+
return Math.max(...on) >= 2 * Math.min(...on) ? "dash-dot" : "dashed";
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* vis.js label markup (`font.multi`): true or "html" reads `<b>`, `<i>` and `<code>` and the entities `<` and
|
|
1062
|
+
* `&`; "markdown" or "md" reads `*bold*`, `_ital_` and `` `mono` `` at word edges, with `\` escaping a mark
|
|
1063
|
+
* (vis-network 9.1.9 `LabelSplitter.splitHtmlBlocks`, `splitMarkdownBlocks`). The text without its markup, and the
|
|
1064
|
+
* one font modifier every word has when they share one.
|
|
1065
|
+
* @param text - the label
|
|
1066
|
+
* @param multi - `font.multi`
|
|
1067
|
+
* @returns the plain text and the shared modifier, or null when the label is not read as markup
|
|
1068
|
+
*/
|
|
1069
|
+
function visMarkup(text, multi) {
|
|
1070
|
+
const system = VIS_MARKUP_SYSTEM.get(multi);
|
|
1071
|
+
if (system === undefined) {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
const state = { bold: false, ital: false, mono: false };
|
|
1075
|
+
const runs = [];
|
|
1076
|
+
const source = text.slice(0, 4096);
|
|
1077
|
+
let i = 0;
|
|
1078
|
+
while (i < source.length) {
|
|
1079
|
+
const tagged = visMarkTag(source, i, system, state);
|
|
1080
|
+
if (tagged > 0) {
|
|
1081
|
+
i += tagged;
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
const [ch, next] = visMarkChar(source, i, system);
|
|
1085
|
+
runs.push({ text: ch, mod: visMarkMod(state) });
|
|
1086
|
+
i = next;
|
|
1087
|
+
}
|
|
1088
|
+
const mods = new Set(runs.filter((r) => /\S/.test(r.text)).map((r) => r.mod));
|
|
1089
|
+
const only = mods.size === 1 ? [...mods][0] : null;
|
|
1090
|
+
// the characters in runs of one modifier each, as vis-network draws its blocks
|
|
1091
|
+
const merged = [];
|
|
1092
|
+
for (const r of runs) {
|
|
1093
|
+
const last = merged.at(-1);
|
|
1094
|
+
if (last?.mod === r.mod) {
|
|
1095
|
+
last.text += r.text;
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
merged.push({ ...r });
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
return { text: runs.map((r) => r.text).join(""), mod: only === "normal" ? null : only, runs: merged };
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* vis-network's own options of each font modifier (vis-network 9.1.9 `NodesHandler` defaults, `font.mono`); an option
|
|
1105
|
+
* a modifier does not set is the label's own (Label.propagateFonts).
|
|
1106
|
+
*/
|
|
1107
|
+
const VIS_MOD_DEFAULTS = {
|
|
1108
|
+
bold: { mod: "bold" },
|
|
1109
|
+
ital: { mod: "italic" },
|
|
1110
|
+
boldital: { mod: "bold italic" },
|
|
1111
|
+
mono: { mod: "", face: "monospace", size: 15, vadjust: 2 },
|
|
1112
|
+
};
|
|
1113
|
+
/**
|
|
1114
|
+
* vis.js multi-font runs as the neutral `labelRuns`: each run's font as vis-network draws it (its modifier's own
|
|
1115
|
+
* `font.bold`, `font.ital`, `font.boldital` or `font.mono` options, an object or text like "16px arial black", over
|
|
1116
|
+
* the label's font; mono is 15 px monospace unless given), each field only where it differs from the label's own, and
|
|
1117
|
+
* `shift`: how much lower than the label's own words its modifier's `vadjust` draws the run (mono's is 2 unless given;
|
|
1118
|
+
* the others' are the label's).
|
|
1119
|
+
* @param runs - the runs, each with its modifier
|
|
1120
|
+
* @param layer - the element's layer
|
|
1121
|
+
* @param site - where a bad color is noted
|
|
1122
|
+
* @returns the runs as JSON, or null when every run is drawn in the label's own font
|
|
1123
|
+
*/
|
|
1124
|
+
function visRuns(runs, layer, site) {
|
|
1125
|
+
const own = {
|
|
1126
|
+
face: typeof layer.fontFace === "string" ? layer.fontFace : "arial",
|
|
1127
|
+
size: typeof layer.fontSize === "number" ? layer.fontSize : VIS_FONT_SIZE,
|
|
1128
|
+
color: typeof layer.fontColor === "string" ? layer.fontColor : null,
|
|
1129
|
+
vadjust: typeof layer.fontVadjust === "number" && Number.isFinite(layer.fontVadjust) ? layer.fontVadjust : 0,
|
|
1130
|
+
};
|
|
1131
|
+
const mods = isJsonObject(layer.fontMods) ? layer.fontMods : {};
|
|
1132
|
+
let differs = false;
|
|
1133
|
+
const out = runs.map((r) => {
|
|
1134
|
+
const run = { text: r.text };
|
|
1135
|
+
const base = VIS_MOD_DEFAULTS[r.mod];
|
|
1136
|
+
if (base === undefined) {
|
|
1137
|
+
return run;
|
|
1138
|
+
}
|
|
1139
|
+
const given = visModOptions(mods[r.mod]);
|
|
1140
|
+
const modText = typeof given.mod === "string" ? given.mod : base.mod;
|
|
1141
|
+
const face = typeof given.face === "string" ? given.face : (base.face ?? own.face);
|
|
1142
|
+
const size = typeof given.size === "number" ? given.size : (base.size ?? own.size);
|
|
1143
|
+
const vadjust = typeof given.vadjust === "number" ? given.vadjust : (base.vadjust ?? own.vadjust);
|
|
1144
|
+
const color = typeof given.color === "string" ? canonColor(given.color, "vis", site)?.color : null;
|
|
1145
|
+
if (modText.includes("bold")) {
|
|
1146
|
+
run.weight = "bold";
|
|
1147
|
+
}
|
|
1148
|
+
if (modText.includes("italic")) {
|
|
1149
|
+
run.italic = true;
|
|
1150
|
+
}
|
|
1151
|
+
if (face !== null && face !== own.face) {
|
|
1152
|
+
run.font = face;
|
|
1153
|
+
}
|
|
1154
|
+
if (size !== own.size) {
|
|
1155
|
+
run.size = size;
|
|
1156
|
+
}
|
|
1157
|
+
if (Number.isFinite(vadjust) && vadjust !== own.vadjust) {
|
|
1158
|
+
run.shift = vadjust - own.vadjust;
|
|
1159
|
+
}
|
|
1160
|
+
if (typeof color === "string") {
|
|
1161
|
+
run.color = color;
|
|
1162
|
+
}
|
|
1163
|
+
differs || (differs = Object.keys(run).length > 1);
|
|
1164
|
+
return run;
|
|
1165
|
+
});
|
|
1166
|
+
return differs ? JSON.stringify(out) : null;
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Where vis-network sets the runs of a label on a line: each block is drawn at the line's place with the label's
|
|
1170
|
+
* `textBaseline` (Label._drawText): "hanging" below a node sized by `size` (ShapeBase._drawShape), "middle" inside
|
|
1171
|
+
* one sized by its label and on an edge's line, "alphabetic" above an edge (`font.align` top) and "hanging" below it.
|
|
1172
|
+
* @param layer - the element's merged layer
|
|
1173
|
+
* @param target - nodes or edges
|
|
1174
|
+
* @returns the neutral run alignment
|
|
1175
|
+
*/
|
|
1176
|
+
function visRunAlign(layer, target) {
|
|
1177
|
+
if (target === "edge") {
|
|
1178
|
+
return ({ top: "baseline", bottom: "hanging" }[String(layer.fontAlign)] ?? "middle");
|
|
1179
|
+
}
|
|
1180
|
+
const shape = typeof layer.shape === "string" ? layer.shape : "ellipse";
|
|
1181
|
+
return VIS_SIZED.has(shape) ? "hanging" : "middle";
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* How much lower than a neutral `bottom` label vis-network draws the label of a node sized by `size`: it hangs the
|
|
1185
|
+
* text's top 4 + size / 2 below the outline, whatever the line count (ShapeBase._drawShape puts the label half its
|
|
1186
|
+
* height below the node and Label.calculateLabelSize moves its first line up by that again, then down 4 and half a
|
|
1187
|
+
* font size more), where a `bottom` label's words start about 2 + size / 3 below it (a small gap, then the space
|
|
1188
|
+
* above the capitals in a line's box). Measured on the vis-network labels sheet, font sizes 6 to 40.
|
|
1189
|
+
* @param size - the label's font size
|
|
1190
|
+
* @returns the extra distance down
|
|
1191
|
+
*/
|
|
1192
|
+
function visBelowGap(size) {
|
|
1193
|
+
// ponytail: a fixed fit of the renderer's gap and line box; a neutral "hang the text's top here" anchor would remove it
|
|
1194
|
+
return 2 + size / 6;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* How much farther below a node sized by `size` vis-network draws a label in runs than one in its own font alone:
|
|
1198
|
+
* half of what its lines are taller (each line as tall as its largest run, Label: line.height) than one font size each
|
|
1199
|
+
* (ShapeBase._drawShape centers the label half its height below the node, then draws its first line at the top).
|
|
1200
|
+
* @param runs - the label's runs, as `visRuns` writes them
|
|
1201
|
+
* @param size - the label's font size
|
|
1202
|
+
* @returns the distance, 0 for runs no larger than the label's font
|
|
1203
|
+
*/
|
|
1204
|
+
function visRunsDrop(runs, size) {
|
|
1205
|
+
let parsed;
|
|
1206
|
+
try {
|
|
1207
|
+
parsed = JSON.parse(runs);
|
|
1208
|
+
}
|
|
1209
|
+
catch {
|
|
1210
|
+
return 0;
|
|
1211
|
+
}
|
|
1212
|
+
if (!Array.isArray(parsed)) {
|
|
1213
|
+
return 0;
|
|
1214
|
+
}
|
|
1215
|
+
const lines = [size];
|
|
1216
|
+
for (const run of parsed) {
|
|
1217
|
+
const runSize = typeof run.size === "number" ? run.size : size;
|
|
1218
|
+
(typeof run.text === "string" ? run.text : "").split("\n").forEach((piece, i) => {
|
|
1219
|
+
if (i > 0) {
|
|
1220
|
+
lines.push(size);
|
|
1221
|
+
}
|
|
1222
|
+
if (piece !== "") {
|
|
1223
|
+
lines[lines.length - 1] = Math.max(lines.at(-1) ?? size, runSize);
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
return lines.reduce((sum, h) => sum + h - size, 0) / 2;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* A vis.js font modifier's options: an object, or text like "16px arial black" (size, face, color).
|
|
1231
|
+
* @param raw - `font.bold`, `font.ital`, `font.boldital` or `font.mono`
|
|
1232
|
+
* @returns the options
|
|
1233
|
+
*/
|
|
1234
|
+
function visModOptions(raw) {
|
|
1235
|
+
if (isJsonObject(raw)) {
|
|
1236
|
+
return raw;
|
|
1237
|
+
}
|
|
1238
|
+
if (typeof raw === "string") {
|
|
1239
|
+
const m = /^\s*(\d+(?:\.\d+)?)(?:px)?\s+(\S+)(?:\s+(\S+))?/.exec(raw.slice(0, 256));
|
|
1240
|
+
if (m !== null) {
|
|
1241
|
+
return { size: Number(m[1]), face: m[2], ...(m[3] === undefined ? {} : { color: m[3] }) };
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
return {};
|
|
1245
|
+
}
|
|
1246
|
+
/** vis-network's markup systems by `font.multi`. */
|
|
1247
|
+
const VIS_MARKUP_SYSTEM = new Map([
|
|
1248
|
+
[true, "html"],
|
|
1249
|
+
["html", "html"],
|
|
1250
|
+
["markdown", "md"],
|
|
1251
|
+
["md", "md"],
|
|
1252
|
+
]);
|
|
1253
|
+
/** Each system's marks and the modifier each turns on or off. */
|
|
1254
|
+
const VIS_MARKS = {
|
|
1255
|
+
html: { "<b>": "bold", "</b>": "bold", "<i>": "ital", "</i>": "ital", "<code>": "mono", "</code>": "mono" },
|
|
1256
|
+
md: { "*": "bold", _: "ital", "`": "mono" },
|
|
1257
|
+
};
|
|
1258
|
+
/**
|
|
1259
|
+
* Reads a mark at a place in a label, turning its modifier on or off: an html tag opens a modifier that is off and
|
|
1260
|
+
* closes one that is on; a markdown mark opens at the start of a word and closes a modifier that is on.
|
|
1261
|
+
* @param source - the label
|
|
1262
|
+
* @param i - the place
|
|
1263
|
+
* @param system - the markup system
|
|
1264
|
+
* @param state - the modifiers on, changed in place
|
|
1265
|
+
* @returns how many characters the mark takes, 0 for no mark
|
|
1266
|
+
*/
|
|
1267
|
+
function visMarkTag(source, i, system, state) {
|
|
1268
|
+
const marks = VIS_MARKS[system];
|
|
1269
|
+
const tag = Object.keys(marks).find((m) => source.startsWith(m, i));
|
|
1270
|
+
if (tag === undefined) {
|
|
1271
|
+
return 0;
|
|
1272
|
+
}
|
|
1273
|
+
const key = marks[tag];
|
|
1274
|
+
const toggles = system === "html" ? state[key] === tag.startsWith("</") : state[key] || i === 0 || /\s/.test(source[i - 1]);
|
|
1275
|
+
if (!toggles) {
|
|
1276
|
+
return 0;
|
|
1277
|
+
}
|
|
1278
|
+
state[key] = !state[key];
|
|
1279
|
+
return tag.length;
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* The character at a place in a label, an html entity (`<`, `&`) or a markdown escape read as the character it
|
|
1283
|
+
* stands for.
|
|
1284
|
+
* @param source - the label
|
|
1285
|
+
* @param i - the place
|
|
1286
|
+
* @param system - the markup system
|
|
1287
|
+
* @returns the character and the place after it
|
|
1288
|
+
*/
|
|
1289
|
+
function visMarkChar(source, i, system) {
|
|
1290
|
+
if (system === "md") {
|
|
1291
|
+
return source[i] === "\\" && i + 1 < source.length ? [source[i + 1], i + 2] : [source[i], i + 1];
|
|
1292
|
+
}
|
|
1293
|
+
const entity = [
|
|
1294
|
+
["<", "<"],
|
|
1295
|
+
["&", "&"],
|
|
1296
|
+
].find(([e]) => source.startsWith(e, i));
|
|
1297
|
+
return entity === undefined ? [source[i], i + 1] : [entity[1], i + entity[0].length];
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* The font modifier the modifiers on make: mono over the others, bold and italic together boldital.
|
|
1301
|
+
* @param state - the modifiers on
|
|
1302
|
+
* @returns the modifier
|
|
1303
|
+
*/
|
|
1304
|
+
function visMarkMod(state) {
|
|
1305
|
+
if (state.mono) {
|
|
1306
|
+
return "mono";
|
|
1307
|
+
}
|
|
1308
|
+
if (state.bold) {
|
|
1309
|
+
return state.ital ? "boldital" : "bold";
|
|
1310
|
+
}
|
|
1311
|
+
return state.ital ? "ital" : "normal";
|
|
684
1312
|
}
|
|
685
1313
|
/**
|
|
686
1314
|
* The node values of a vis.js layer besides its colors.
|
|
@@ -692,17 +1320,40 @@ function visValues(layer, target, site, sized) {
|
|
|
692
1320
|
function visNodeValues(layer, sized, put, site) {
|
|
693
1321
|
if (layer.shape !== undefined) {
|
|
694
1322
|
put("shape", canonEnum("shape", layer.shape, "vis", site("shape")));
|
|
1323
|
+
// vis-network draws a label inside the shapes sized by their label and below the shapes sized by `size`
|
|
1324
|
+
if (typeof layer.shape === "string") {
|
|
1325
|
+
put("labelPosition", VIS_SIZED.has(layer.shape) ? "bottom" : "center");
|
|
1326
|
+
}
|
|
695
1327
|
}
|
|
696
1328
|
if (sized && layer.size !== undefined) {
|
|
1329
|
+
// vis-network draws `size` as a radius: a dot of size 25 is 50 across
|
|
697
1330
|
const v = canonNumber(layer.size, site("width"));
|
|
698
|
-
put("width", v);
|
|
699
|
-
put("height", v);
|
|
1331
|
+
put("width", v === null ? null : 2 * v);
|
|
1332
|
+
put("height", v === null ? null : 2 * v);
|
|
700
1333
|
}
|
|
701
1334
|
if (layer.borderWidth !== undefined) {
|
|
702
1335
|
put("borderWidth", canonNumber(layer.borderWidth, site("borderWidth")));
|
|
703
1336
|
}
|
|
1337
|
+
visNodeOpacityAndDashes(layer, put, site);
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* A vis.js node's opacity and border dashes (visNodeValues).
|
|
1341
|
+
* @param layer - the layer
|
|
1342
|
+
* @param put - the row's setter
|
|
1343
|
+
* @param site - where its values are read
|
|
1344
|
+
*/
|
|
1345
|
+
function visNodeOpacityAndDashes(layer, put, site) {
|
|
704
1346
|
if (layer.opacity !== undefined) {
|
|
705
|
-
|
|
1347
|
+
// vis-network's node opacity replaces the alpha of its border color and its background color alike, but only of
|
|
1348
|
+
// a hex or rgb() color: overrideOpacity leaves rgba() and any other text (a name) as it is (Node.getFormattingValues)
|
|
1349
|
+
const opacity = canonOpacity(layer.opacity, "vis", site("opacity"));
|
|
1350
|
+
put("opacity", visTakesOpacity(layer.fill) ? opacity : null);
|
|
1351
|
+
put("borderOpacity", visTakesOpacity(layer.border) ? opacity : null);
|
|
1352
|
+
}
|
|
1353
|
+
if (layer.borderDashes !== undefined) {
|
|
1354
|
+
// vis-network's default border dashes are [5, 15]
|
|
1355
|
+
const width = typeof layer.borderWidth === "number" ? layer.borderWidth : 1;
|
|
1356
|
+
put("borderStyle", visDashStyle(layer.borderDashes === true ? [5, 15] : layer.borderDashes, width, false));
|
|
706
1357
|
}
|
|
707
1358
|
}
|
|
708
1359
|
/**
|
|
@@ -719,24 +1370,71 @@ function visEdgeValues(layer, put, site) {
|
|
|
719
1370
|
put("width", canonNumber(layer.width, site("width")));
|
|
720
1371
|
}
|
|
721
1372
|
if (layer.dashes !== undefined) {
|
|
722
|
-
|
|
1373
|
+
// vis-network's default dashes are [5, 5]
|
|
1374
|
+
const width = typeof layer.width === "number" ? layer.width : 1;
|
|
1375
|
+
const dashes = layer.dashes === true ? [5, 5] : layer.dashes;
|
|
1376
|
+
// vis-network draws an edge with round caps (EdgeBase: lineCap "round"), each dash a width longer
|
|
1377
|
+
put("lineStyle", layer.dashes === "false" ? "solid" : visDashStyle(dashes, width, true));
|
|
723
1378
|
}
|
|
724
1379
|
if (layer.arrows !== undefined) {
|
|
725
|
-
const [to, from] = visArrows(layer.arrows, site);
|
|
1380
|
+
const [to, from, middle] = visArrows(layer.arrows, site);
|
|
726
1381
|
put("targetArrow", to);
|
|
727
1382
|
put("sourceArrow", from);
|
|
1383
|
+
put("midTargetArrow", middle);
|
|
1384
|
+
// One neutral size for every end: the target's scaleFactor, else the source's, else the middle's.
|
|
1385
|
+
const scaled = (name) => {
|
|
1386
|
+
const a = isJsonObject(layer.arrows) ? layer.arrows[name] : undefined;
|
|
1387
|
+
return isJsonObject(a) ? a.scaleFactor : undefined;
|
|
1388
|
+
};
|
|
1389
|
+
const scale = scaled("to") ?? scaled("from") ?? scaled("middle");
|
|
1390
|
+
if (scale !== undefined) {
|
|
1391
|
+
put("arrowSize", canonNumber(scale, site("arrowSize")));
|
|
1392
|
+
}
|
|
1393
|
+
visArrowImages(layer.arrows, put, site);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* The pictures of vis.js `image` arrows (`{ to: { type: "image", src, imageWidth, imageHeight } }`), each with the
|
|
1398
|
+
* size it is drawn at where the file gives one (vis-network 9.1.9 `Image.draw`: `imageWidth` across the line,
|
|
1399
|
+
* `imageHeight` along it, else the picture's own).
|
|
1400
|
+
* @param arrows - the `arrows` value
|
|
1401
|
+
* @param put - the row's setter
|
|
1402
|
+
* @param site - where its values are read
|
|
1403
|
+
*/
|
|
1404
|
+
function visArrowImages(arrows, put, site) {
|
|
1405
|
+
if (!isJsonObject(arrows)) {
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
for (const [name, property] of [
|
|
1409
|
+
["to", "targetArrow"],
|
|
1410
|
+
["from", "sourceArrow"],
|
|
1411
|
+
["middle", "midTargetArrow"],
|
|
1412
|
+
]) {
|
|
1413
|
+
const a = arrows[name];
|
|
1414
|
+
if (!isJsonObject(a) || a.type !== "image" || typeof a.src !== "string" || a.src === "") {
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
put(`${property}Image`, a.src);
|
|
1418
|
+
if (a.imageWidth !== undefined) {
|
|
1419
|
+
put(`${property}ImageWidth`, canonNumber(a.imageWidth, site(`${property}ImageWidth`)));
|
|
1420
|
+
}
|
|
1421
|
+
if (a.imageHeight !== undefined) {
|
|
1422
|
+
put(`${property}ImageHeight`, canonNumber(a.imageHeight, site(`${property}ImageHeight`)));
|
|
1423
|
+
}
|
|
728
1424
|
}
|
|
729
1425
|
}
|
|
730
1426
|
/**
|
|
731
|
-
* vis.js `arrows` as the target and
|
|
1427
|
+
* vis.js `arrows` as the target, source and middle arrows: `"to"`, `"to, from"`, `"middle"`, or
|
|
1428
|
+
* `{ to: true | { enabled, type } }`. The middle one points toward the target.
|
|
732
1429
|
* @param arrows - the value
|
|
733
1430
|
* @param site - where it is read
|
|
734
|
-
* @returns the target arrow and the
|
|
1431
|
+
* @returns the target arrow, the source arrow and the middle arrow
|
|
735
1432
|
*/
|
|
736
1433
|
function visArrows(arrows, site) {
|
|
737
|
-
|
|
1434
|
+
// a middle arrow the file does not mention is no rule at all, so a file that names only its ends carries none
|
|
1435
|
+
const end = (name, property, absent = "none") => {
|
|
738
1436
|
if (typeof arrows === "string") {
|
|
739
|
-
return new RegExp(String.raw `\b${name}\b`).test(arrows.slice(0, 256)) ? "triangle" :
|
|
1437
|
+
return new RegExp(String.raw `\b${name}\b`).test(arrows.slice(0, 256)) ? "triangle" : absent;
|
|
740
1438
|
}
|
|
741
1439
|
if (!isJsonObject(arrows)) {
|
|
742
1440
|
return null;
|
|
@@ -745,12 +1443,15 @@ function visArrows(arrows, site) {
|
|
|
745
1443
|
if (a === true) {
|
|
746
1444
|
return "triangle";
|
|
747
1445
|
}
|
|
1446
|
+
if (a === undefined) {
|
|
1447
|
+
return absent;
|
|
1448
|
+
}
|
|
748
1449
|
if (!isJsonObject(a) || a.enabled === false) {
|
|
749
1450
|
return "none";
|
|
750
1451
|
}
|
|
751
1452
|
return canonEnum("arrow", a.type ?? "arrow", "vis", site(property));
|
|
752
1453
|
};
|
|
753
|
-
return [end("to", "targetArrow"), end("from", "sourceArrow")];
|
|
1454
|
+
return [end("to", "targetArrow"), end("from", "sourceArrow"), end("middle", "midTargetArrow", null)];
|
|
754
1455
|
}
|
|
755
1456
|
/**
|
|
756
1457
|
* vis.js: the per-element keys over the file's groups and `options.nodes` / `options.edges`, beneath them
|
|
@@ -780,13 +1481,17 @@ function visStyles(snapshot, src, context) {
|
|
|
780
1481
|
context.budget.spend("node", n);
|
|
781
1482
|
// what vis-network draws per node: its own keys over its group over options.nodes
|
|
782
1483
|
const node = valuesOf(n);
|
|
783
|
-
const
|
|
784
|
-
const
|
|
1484
|
+
const shapes = [];
|
|
1485
|
+
const rawRecords = elementRaw(snapshot, "node");
|
|
1486
|
+
const range = visNodeValueRange(n, groupOf, rawRecords, groupLayers, nodeDefaults);
|
|
1487
|
+
const ownGroup = visOwnGroup(groupLayers, visGroupDefs(src, options));
|
|
785
1488
|
for (let i = 0; i < n; i++) {
|
|
786
1489
|
const g = groupOf(i);
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
const
|
|
1490
|
+
const raw = rawRecords(i);
|
|
1491
|
+
const own = visLayer(raw, "node");
|
|
1492
|
+
const group = g === null ? {} : ownGroup(g, raw);
|
|
1493
|
+
const shape = visShapeOf(own) ?? visShapeOf(group) ?? visShapeOf(nodeDefaults) ?? "ellipse";
|
|
1494
|
+
shapes.push(shape);
|
|
790
1495
|
const site = (property) => ({
|
|
791
1496
|
notes,
|
|
792
1497
|
target: "node",
|
|
@@ -794,8 +1499,8 @@ function visStyles(snapshot, src, context) {
|
|
|
794
1499
|
element: i,
|
|
795
1500
|
budget: context.budget,
|
|
796
1501
|
});
|
|
797
|
-
const merged =
|
|
798
|
-
for (const [p, v] of Object.entries(
|
|
1502
|
+
const merged = visMergedNode(nodeDefaults, group, own, g !== null && raw.color !== undefined);
|
|
1503
|
+
for (const [p, v] of Object.entries(visNodeRow(merged, shape, range, site))) {
|
|
799
1504
|
node.put(p, i, v);
|
|
800
1505
|
}
|
|
801
1506
|
}
|
|
@@ -803,23 +1508,15 @@ function visStyles(snapshot, src, context) {
|
|
|
803
1508
|
if (Object.keys(declaredNode).length > 0) {
|
|
804
1509
|
nodeRules.push(rule("node.vis.defaults", "node", "defaults", "declared", { all: true }, declaredNode));
|
|
805
1510
|
}
|
|
806
|
-
|
|
807
|
-
for (const [g, layer] of groupLayers) {
|
|
808
|
-
const set = paletteRows.get(g) ?? visValues(layer, "node", ruleSite("node"), true);
|
|
809
|
-
if (Object.keys(set).length === 0) {
|
|
810
|
-
continue;
|
|
811
|
-
}
|
|
812
|
-
const key = `group.${String(k)}`;
|
|
813
|
-
nodeColumns.set(key, Array.from({ length: n }, (_v, i) => (groupOf(i) === g ? true : null)));
|
|
814
|
-
const origin = paletteRows.has(g) ? "tool" : "declared";
|
|
815
|
-
const r = rule(`node.vis.group.${String(k++)}`, "node", "group", origin, { has: { style: key } }, set);
|
|
816
|
-
nodeRules.push({ ...r, facts: { names: [[key, g]] } });
|
|
817
|
-
}
|
|
1511
|
+
visGroupRules(n, groupOf, groupLayers, paletteRows, ruleSite("node"), nodeRules, nodeColumns);
|
|
818
1512
|
const toolNode = toolDefaults("vis-network")?.node;
|
|
819
1513
|
const borderOf = (i) => node.values.borderColor?.[i] ?? toolNode?.borderColor?.value ?? "#2b7ce9";
|
|
820
|
-
const
|
|
1514
|
+
const geometry = visGeometry(snapshot, node.values, shapes);
|
|
1515
|
+
const support = visSupport(options.physics, options.layout);
|
|
1516
|
+
const edges = visLayer(options.edges, "edge");
|
|
1517
|
+
const edge = visEdges(snapshot, edges, borderOf, { ...geometry, support }, notes, context);
|
|
821
1518
|
const values = { node: node.values, edge: edge.values };
|
|
822
|
-
exactFromBlock(snapshot, src.block, values,
|
|
1519
|
+
exactFromBlock(snapshot, src.block, values, visWritten(snapshot, values.edge));
|
|
823
1520
|
return {
|
|
824
1521
|
canonical: true,
|
|
825
1522
|
dialect: "vis",
|
|
@@ -836,6 +1533,343 @@ function visStyles(snapshot, src, context) {
|
|
|
836
1533
|
},
|
|
837
1534
|
};
|
|
838
1535
|
}
|
|
1536
|
+
/**
|
|
1537
|
+
* vis.js: a layer's own shape.
|
|
1538
|
+
* @param layer - the layer
|
|
1539
|
+
* @returns its `shape`, or null when it gives none
|
|
1540
|
+
*/
|
|
1541
|
+
function visShapeOf(layer) {
|
|
1542
|
+
return typeof layer.shape === "string" ? layer.shape : null;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* vis.js: the smallest and largest `value` of the nodes, each its own over its group's over `options.nodes`.
|
|
1546
|
+
* @param n - the node count
|
|
1547
|
+
* @param groupOf - each node's group
|
|
1548
|
+
* @param rawRecords - each node's own keys
|
|
1549
|
+
* @param groupLayers - each group's layer (visGroupLayers)
|
|
1550
|
+
* @param nodeDefaults - `options.nodes`
|
|
1551
|
+
* @returns the range, or null when no node has a value
|
|
1552
|
+
*/
|
|
1553
|
+
function visNodeValueRange(n, groupOf, rawRecords, groupLayers, nodeDefaults) {
|
|
1554
|
+
return visValueRange(n, (i) => {
|
|
1555
|
+
const g = groupOf(i);
|
|
1556
|
+
const own = visLayer(rawRecords(i), "node").value;
|
|
1557
|
+
const value = own ?? (g === null ? undefined : groupLayers.get(g)?.value) ?? nodeDefaults.value;
|
|
1558
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* vis.js: one node's layer, its own keys over its group's over `options.nodes`.
|
|
1563
|
+
* @param nodeDefaults - `options.nodes`
|
|
1564
|
+
* @param group - its group's layer
|
|
1565
|
+
* @param own - its own layer
|
|
1566
|
+
* @param ownColorInGroup - whether it is in a group and gives a color of its own
|
|
1567
|
+
* @returns the merged layer
|
|
1568
|
+
*/
|
|
1569
|
+
function visMergedNode(nodeDefaults, group, own, ownColorInGroup) {
|
|
1570
|
+
const merged = { ...nodeDefaults, ...group, ...own };
|
|
1571
|
+
if (ownColorInGroup) {
|
|
1572
|
+
// a color of the node's own fills its gaps from options.nodes and vis-network's defaults, never its group's
|
|
1573
|
+
merged.fill ?? (merged.fill = VIS_NODE_FILL);
|
|
1574
|
+
merged.border ?? (merged.border = VIS_NODE_BORDER);
|
|
1575
|
+
}
|
|
1576
|
+
return merged;
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* vis.js: the values one node draws, from its merged layer: its colors and label, then its shape's own.
|
|
1580
|
+
* @param merged - the node's merged layer
|
|
1581
|
+
* @param shape - its vis-network shape
|
|
1582
|
+
* @param range - the smallest and largest `value` of the graph's nodes (visValueRange)
|
|
1583
|
+
* @param site - where its values are read
|
|
1584
|
+
* @returns the values
|
|
1585
|
+
*/
|
|
1586
|
+
function visNodeRow(merged, shape, range, site) {
|
|
1587
|
+
const sized = VIS_SIZED.has(shape);
|
|
1588
|
+
const row = visValues(merged, "node", site, sized);
|
|
1589
|
+
const shaped = sized
|
|
1590
|
+
? visSizedShape(merged, shape, visRadius(merged, range, site), site)
|
|
1591
|
+
: visFitShape(merged, shape);
|
|
1592
|
+
if (sized && merged.label !== undefined) {
|
|
1593
|
+
const size = visLabelScaled(merged, range) ?? (typeof merged.fontSize === "number" ? merged.fontSize : VIS_FONT_SIZE);
|
|
1594
|
+
// a label in runs below the node hangs lower by half of what its larger runs make its lines taller
|
|
1595
|
+
const drop = typeof row.labelRuns === "string" ? visRunsDrop(row.labelRuns, size) : 0;
|
|
1596
|
+
const below = typeof row.labelOffsetY === "number" ? row.labelOffsetY : 0;
|
|
1597
|
+
row.labelOffsetY = below + drop + visBelowGap(size);
|
|
1598
|
+
}
|
|
1599
|
+
// the shape's own label place, then font.vadjust below it
|
|
1600
|
+
for (const p of ["labelOffsetX", "labelOffsetY"]) {
|
|
1601
|
+
const [a, b] = [row[p], shaped[p]];
|
|
1602
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
1603
|
+
shaped[p] = a + b;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
Object.assign(row, shaped);
|
|
1607
|
+
const labelSize = visLabelScaled(merged, range);
|
|
1608
|
+
if (labelSize !== null) {
|
|
1609
|
+
row.labelSize = labelSize;
|
|
1610
|
+
}
|
|
1611
|
+
return row;
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* vis.js: one rule per node group with values, over a column marking the group's nodes; a group the file does not
|
|
1615
|
+
* define is the tool's palette.
|
|
1616
|
+
* @param n - the node count
|
|
1617
|
+
* @param groupOf - each node's group
|
|
1618
|
+
* @param groupLayers - each group's layer (visGroupLayers)
|
|
1619
|
+
* @param paletteRows - the palette values of the groups the file does not define
|
|
1620
|
+
* @param site - where a group's values are read
|
|
1621
|
+
* @param rules - the node rules, appended to
|
|
1622
|
+
* @param columns - the node columns, written to
|
|
1623
|
+
*/
|
|
1624
|
+
function visGroupRules(n, groupOf, groupLayers, paletteRows, site, rules, columns) {
|
|
1625
|
+
let k = 0;
|
|
1626
|
+
for (const [g, layer] of groupLayers) {
|
|
1627
|
+
const set = paletteRows.get(g) ?? visValues(layer, "node", site, true);
|
|
1628
|
+
if (Object.keys(set).length === 0) {
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
const key = `group.${String(k)}`;
|
|
1632
|
+
columns.set(key, Array.from({ length: n }, (_v, i) => (groupOf(i) === g ? true : null)));
|
|
1633
|
+
const origin = paletteRows.has(g) ? "tool" : "declared";
|
|
1634
|
+
const r = rule(`node.vis.group.${String(k++)}`, "node", "group", origin, { has: { style: key } }, set);
|
|
1635
|
+
rules.push({ ...r, facts: { names: [[key, g]] } });
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* vis.js: what a node of a shape sized by `size` draws besides its colors: its box from its radius, its picture or
|
|
1640
|
+
* glyph, and triangleDown turned over.
|
|
1641
|
+
* @param merged - the node's merged layer
|
|
1642
|
+
* @param shape - its vis-network shape
|
|
1643
|
+
* @param radius - its radius (visRadius)
|
|
1644
|
+
* @param site - where its values are read
|
|
1645
|
+
* @returns the values
|
|
1646
|
+
*/
|
|
1647
|
+
function visSizedShape(merged, shape, radius, site) {
|
|
1648
|
+
// vis-network's hexagon is regular, its corners `size` from its center on the left and right (drawHexagon): it is
|
|
1649
|
+
// 2 * size wide and sqrt(3) * size tall, where the neutral hexagon fills its box
|
|
1650
|
+
const row = { width: 2 * radius, height: 2 * radius };
|
|
1651
|
+
if (shape === "hexagon") {
|
|
1652
|
+
// its label still hangs below the 2 * size square (ShapeBase._drawShape: y + 0.5 * height + half the label)
|
|
1653
|
+
row.height = Math.sqrt(3) * radius;
|
|
1654
|
+
row.labelOffsetY = ((2 - Math.sqrt(3)) * radius) / 2;
|
|
1655
|
+
}
|
|
1656
|
+
Object.assign(row, visImage(merged, shape, { width: 2 * radius, height: 2 * radius }));
|
|
1657
|
+
if (shape === "icon") {
|
|
1658
|
+
// an icon node is its glyph alone: vis-network draws no body, no fill and no border for it (Icon.draw)
|
|
1659
|
+
Object.assign(row, { shape: "none" }, visIcon(merged, site("glyphColor")));
|
|
1660
|
+
}
|
|
1661
|
+
// vis-network's triangleDown: its triangle turned over, a corner down
|
|
1662
|
+
if (shape === "triangleDown") {
|
|
1663
|
+
row.shape = "polygon";
|
|
1664
|
+
row.shapePoints = "-1,1 0,-1 1,1";
|
|
1665
|
+
}
|
|
1666
|
+
return row;
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* vis.js: what a node of a shape sized by its label draws besides its colors: its growth round its label, nothing at
|
|
1670
|
+
* all with a margin of 0, and its label at the top or bottom under `heightConstraint.valign`.
|
|
1671
|
+
* @param merged - the node's merged layer
|
|
1672
|
+
* @param shape - its vis-network shape
|
|
1673
|
+
* @returns the values
|
|
1674
|
+
*/
|
|
1675
|
+
function visFitShape(merged, shape) {
|
|
1676
|
+
const row = visLabelFit(merged, shape);
|
|
1677
|
+
// a margin of 0 leaves vis-network's margins undefined (`_setMargins` tests `if (options.margin)`), so the node's
|
|
1678
|
+
// size is NaN and neither it nor its label is drawn
|
|
1679
|
+
if (merged.margin === 0 && (shape === "box" || shape === "circle" || shape === "database")) {
|
|
1680
|
+
row.visible = false;
|
|
1681
|
+
}
|
|
1682
|
+
// heightConstraint.valign moves the label to the top or the bottom of a node taller than it (Label._drawText)
|
|
1683
|
+
if (merged.valign === "top" || merged.valign === "bottom") {
|
|
1684
|
+
row.labelPosition = `inside-${merged.valign}`;
|
|
1685
|
+
}
|
|
1686
|
+
else if (shape === "database") {
|
|
1687
|
+
// a database draws its label at its top, its top margin down (Database.draw: this.top + margin.top), not in
|
|
1688
|
+
// the middle of the square it grows to
|
|
1689
|
+
const m = merged.margin;
|
|
1690
|
+
const top = isJsonObject(m) ? m.top : m;
|
|
1691
|
+
row.labelPosition = "inside-top";
|
|
1692
|
+
row.labelOffsetY = typeof top === "number" && Number.isFinite(top) && top >= 0 ? top : VIS_MARGIN;
|
|
1693
|
+
}
|
|
1694
|
+
// a box's corners are rounded at `shapeProperties.borderRadius`, at most half its shorter side (drawRoundRect)
|
|
1695
|
+
if (shape === "box" && typeof merged.borderRadius === "number" && Number.isFinite(merged.borderRadius)) {
|
|
1696
|
+
row.cornerRadius = Math.max(0, merged.borderRadius);
|
|
1697
|
+
}
|
|
1698
|
+
return row;
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* vis.js: how a node of a shape sized by its label grows to hold it (each shape's `resize`): its label's box, at
|
|
1702
|
+
* least `widthConstraint.minimum` by `heightConstraint.minimum`, plus the `margin` on each side is the node's width
|
|
1703
|
+
* and height; a circle and a database stay as wide as they are tall; an ellipse is the box's height taller and
|
|
1704
|
+
* wider instead of the margin (`width + height` by twice the height).
|
|
1705
|
+
* @param layer - the node's merged layer
|
|
1706
|
+
* @param shape - its vis-network shape
|
|
1707
|
+
* @returns the values, none for a shape not sized by its label
|
|
1708
|
+
*/
|
|
1709
|
+
function visLabelFit(layer, shape) {
|
|
1710
|
+
const fit = VIS_LABEL_FIT.get(shape);
|
|
1711
|
+
if (fit === undefined) {
|
|
1712
|
+
return {};
|
|
1713
|
+
}
|
|
1714
|
+
const length = (v) => (typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null);
|
|
1715
|
+
const fontSize = length(layer.fontSize) ?? VIS_FONT_SIZE;
|
|
1716
|
+
const lines = typeof layer.label === "string" ? Math.max(1, layer.label.split("\n").length) : 1;
|
|
1717
|
+
const minW = length(layer.widthMin) ?? 0;
|
|
1718
|
+
const minH = length(layer.heightMin) ?? 0;
|
|
1719
|
+
let padX;
|
|
1720
|
+
let padY;
|
|
1721
|
+
let offset = {};
|
|
1722
|
+
if (fit.ellipse) {
|
|
1723
|
+
padX = Math.max(lines * fontSize, minH) / 2;
|
|
1724
|
+
padY = padX;
|
|
1725
|
+
}
|
|
1726
|
+
else {
|
|
1727
|
+
({ padX, padY, offset } = visMarginPads(layer.margin, fit.square));
|
|
1728
|
+
}
|
|
1729
|
+
// a node with no label text is sized as if its label were 14 x 14 (NodeBase.getDimensionsFromLabel)
|
|
1730
|
+
const empty = typeof layer.label !== "string" || layer.label === "";
|
|
1731
|
+
const [boxW, boxH] = empty ? [Math.max(minW, 14), Math.max(minH, 14)] : [minW, minH];
|
|
1732
|
+
if (empty && fit.ellipse) {
|
|
1733
|
+
padX = boxH / 2;
|
|
1734
|
+
padY = padX;
|
|
1735
|
+
}
|
|
1736
|
+
const row = {
|
|
1737
|
+
...offset,
|
|
1738
|
+
labelFit: "bounds",
|
|
1739
|
+
labelPaddingX: padX,
|
|
1740
|
+
labelPaddingY: padY,
|
|
1741
|
+
width: boxW > 0 ? boxW + 2 * padX : 0,
|
|
1742
|
+
height: boxH > 0 ? boxH + 2 * padY : 0,
|
|
1743
|
+
};
|
|
1744
|
+
if (fit.square) {
|
|
1745
|
+
row.labelFitSquare = true;
|
|
1746
|
+
}
|
|
1747
|
+
return row;
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* vis.js: the padding a node's `margin` puts round its label (visLabelFit). A box or text node draws its label its
|
|
1751
|
+
* left and top margins in from its corner (Box.draw, Text.draw), off center by half the difference of opposite
|
|
1752
|
+
* margins.
|
|
1753
|
+
* @param m - the `margin`: one number for every side, or `{ top, right, bottom, left }`
|
|
1754
|
+
* @param square - whether the shape stays as wide as it is tall
|
|
1755
|
+
* @returns the horizontal and vertical padding, and the label's offset
|
|
1756
|
+
*/
|
|
1757
|
+
function visMarginPads(m, square) {
|
|
1758
|
+
const length = (v) => (typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null);
|
|
1759
|
+
const side = (key) => (isJsonObject(m) ? length(m[key]) : length(m)) ?? VIS_MARGIN;
|
|
1760
|
+
const offset = {};
|
|
1761
|
+
if (!square && side("left") !== side("right")) {
|
|
1762
|
+
offset.labelOffsetX = (side("left") - side("right")) / 2;
|
|
1763
|
+
}
|
|
1764
|
+
if (!square && side("top") !== side("bottom")) {
|
|
1765
|
+
offset.labelOffsetY = (side("top") - side("bottom")) / 2;
|
|
1766
|
+
}
|
|
1767
|
+
return { padX: (side("left") + side("right")) / 2, padY: (side("top") + side("bottom")) / 2, offset };
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* vis.js `scaling.label`: with `enabled`, the font size of an element with a `value`, mapped as its size is from the
|
|
1771
|
+
* value range onto `scaling.label.min` .. `max` (14 .. 30) (vis-network 9.1.9 `Node.setValueRange`, `Edge.setValueRange`).
|
|
1772
|
+
* @param layer - the element's merged layer
|
|
1773
|
+
* @param range - the value range, or null
|
|
1774
|
+
* @returns the font size, or null when the label is not scaled
|
|
1775
|
+
*/
|
|
1776
|
+
function visLabelScaled(layer, range) {
|
|
1777
|
+
const value = finiteValue(layer.value);
|
|
1778
|
+
const label = layer.scalingLabel;
|
|
1779
|
+
if (value === null || range === null || !isJsonObject(label) || label.enabled !== true) {
|
|
1780
|
+
return null;
|
|
1781
|
+
}
|
|
1782
|
+
const min = typeof label.min === "number" ? label.min : 14;
|
|
1783
|
+
const max = typeof label.max === "number" ? label.max : 30;
|
|
1784
|
+
return visScaled(value, range, min, max);
|
|
1785
|
+
}
|
|
1786
|
+
/** vis-network's default node size, a radius (`size: 25`, vis-network.esm.js NodesHandler defaults). */
|
|
1787
|
+
const VIS_SIZE = 25;
|
|
1788
|
+
/** vis-network's default `scaling.min` and `scaling.max`, the radii `value` is mapped onto. */
|
|
1789
|
+
const VIS_SCALING = [10, 30];
|
|
1790
|
+
/**
|
|
1791
|
+
* vis.js: the smallest and largest `value` of the nodes that have one, as vis-network's
|
|
1792
|
+
* `_updateValueRange` takes them; null when no node has one.
|
|
1793
|
+
* @param n - the node count
|
|
1794
|
+
* @param valueOf - each node's value
|
|
1795
|
+
* @returns the range
|
|
1796
|
+
*/
|
|
1797
|
+
function visValueRange(n, valueOf) {
|
|
1798
|
+
let min = Infinity;
|
|
1799
|
+
let max = -Infinity;
|
|
1800
|
+
for (let i = 0; i < n; i++) {
|
|
1801
|
+
const v = valueOf(i);
|
|
1802
|
+
if (v !== null) {
|
|
1803
|
+
min = Math.min(min, v);
|
|
1804
|
+
max = Math.max(max, v);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
return min <= max ? [min, max] : null;
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* A `value` that is a finite number, else null.
|
|
1811
|
+
* @param value - the value
|
|
1812
|
+
* @returns the number, or null
|
|
1813
|
+
*/
|
|
1814
|
+
const finiteValue = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1815
|
+
/**
|
|
1816
|
+
* vis.js: `value` mapped linearly from the value range onto `min` .. `max`, the middle when every value is the same
|
|
1817
|
+
* (vis-network's default `customScalingFunction`).
|
|
1818
|
+
* @param value - the element's value
|
|
1819
|
+
* @param range - the value range
|
|
1820
|
+
* @param min - `scaling.min`
|
|
1821
|
+
* @param max - `scaling.max`
|
|
1822
|
+
* @returns the scaled value
|
|
1823
|
+
*/
|
|
1824
|
+
function visScaled(value, range, min, max) {
|
|
1825
|
+
const [lo, hi] = range;
|
|
1826
|
+
const scale = hi === lo ? 0.5 : Math.max(0, (value - lo) / (hi - lo));
|
|
1827
|
+
return min + scale * (max - min);
|
|
1828
|
+
}
|
|
1829
|
+
/** vis-network's default edge `scaling.min` and `scaling.max`, the widths `value` is mapped onto. */
|
|
1830
|
+
const VIS_EDGE_SCALING = [1, 15];
|
|
1831
|
+
/**
|
|
1832
|
+
* vis.js: the width vis-network draws an edge at once any edge has a `value`. Its `_updateValueRange` then sets every
|
|
1833
|
+
* edge's width: one with a value to the value mapped onto `scaling`, one without to the width it was created with,
|
|
1834
|
+
* which is `options.edges.width` -- an edge's own `width` is dropped (`Edge.baseWidth` is read before the edge's own
|
|
1835
|
+
* options are applied, and nothing updates it).
|
|
1836
|
+
* @param layer - the edge's merged layer
|
|
1837
|
+
* @param defaults - `options.edges`
|
|
1838
|
+
* @param range - the value range over the edges, or null when no edge has a value
|
|
1839
|
+
* @param site - where its values are read
|
|
1840
|
+
* @returns the width, or nothing when no edge has a value (the edge's own width holds)
|
|
1841
|
+
*/
|
|
1842
|
+
function visEdgeWidth(layer, defaults, range, site) {
|
|
1843
|
+
if (range === null) {
|
|
1844
|
+
return {};
|
|
1845
|
+
}
|
|
1846
|
+
const value = finiteValue(layer.value);
|
|
1847
|
+
if (value !== null) {
|
|
1848
|
+
const min = typeof layer.scalingMin === "number" ? layer.scalingMin : VIS_EDGE_SCALING[0];
|
|
1849
|
+
const max = typeof layer.scalingMax === "number" ? layer.scalingMax : VIS_EDGE_SCALING[1];
|
|
1850
|
+
return { width: visScaled(value, range, min, max) };
|
|
1851
|
+
}
|
|
1852
|
+
return { width: (defaults.width === undefined ? null : canonNumber(defaults.width, site("width"))) ?? 1 };
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* vis.js: the radius vis-network draws a node of a sized shape at: its `value` mapped linearly from the value range
|
|
1856
|
+
* onto `scaling.min` .. `scaling.max` (the middle when every value is the same), else its `size`, else 25
|
|
1857
|
+
* (vis-network's `setValueRange` and `customScalingFunction`).
|
|
1858
|
+
* @param layer - the node's merged layer
|
|
1859
|
+
* @param range - the value range over the nodes
|
|
1860
|
+
* @param site - where its values are read
|
|
1861
|
+
* @returns the radius
|
|
1862
|
+
*/
|
|
1863
|
+
function visRadius(layer, range, site) {
|
|
1864
|
+
const value = finiteValue(layer.value);
|
|
1865
|
+
if (value !== null && range !== null) {
|
|
1866
|
+
const min = typeof layer.scalingMin === "number" ? layer.scalingMin : VIS_SCALING[0];
|
|
1867
|
+
const max = typeof layer.scalingMax === "number" ? layer.scalingMax : VIS_SCALING[1];
|
|
1868
|
+
return visScaled(value, range, min, max);
|
|
1869
|
+
}
|
|
1870
|
+
const size = layer.size === undefined ? null : canonNumber(layer.size, site("width"));
|
|
1871
|
+
return size ?? VIS_SIZE;
|
|
1872
|
+
}
|
|
839
1873
|
/**
|
|
840
1874
|
* vis.js: the group definitions, from `options.groups` or a top-level `groups`.
|
|
841
1875
|
* @param src - what the import kept
|
|
@@ -876,25 +1910,64 @@ function visGroupLayers(n, groupOf, groupDefs) {
|
|
|
876
1910
|
}
|
|
877
1911
|
return { groupLayers, paletteRows };
|
|
878
1912
|
}
|
|
1913
|
+
/**
|
|
1914
|
+
* vis.js: the group layer one node takes. vis-network merges a group's options into a node only for the keys the node
|
|
1915
|
+
* does not set itself, each key whole (vis-network 9.1.9 `Node.updateGroupOptions`: every option of the node's own is
|
|
1916
|
+
* skipped, `font` always), so a node with a `color` of its own takes none of its group's colors, not even a border it
|
|
1917
|
+
* leaves out.
|
|
1918
|
+
* @param groupLayers - each group's whole layer (visGroupLayers)
|
|
1919
|
+
* @param groupDefs - the group definitions the file gives
|
|
1920
|
+
* @returns the layer of a group for a node of the given own keys
|
|
1921
|
+
*/
|
|
1922
|
+
function visOwnGroup(groupLayers, groupDefs) {
|
|
1923
|
+
const cache = new Map();
|
|
1924
|
+
return (g, own) => {
|
|
1925
|
+
const whole = groupLayers.get(g) ?? {};
|
|
1926
|
+
const def = Object.hasOwn(groupDefs, g) ? groupDefs[g] : null;
|
|
1927
|
+
// a group the file does not define is vis-network's palette color, which a color of the node's own replaces
|
|
1928
|
+
const skip = Object.keys(own).filter((k) => k !== "font" && (isJsonObject(def) ? Object.hasOwn(def, k) : k === "color"));
|
|
1929
|
+
if (skip.length === 0) {
|
|
1930
|
+
return whole;
|
|
1931
|
+
}
|
|
1932
|
+
const key = `${g}\u0000${skip.join("\u0000")}`;
|
|
1933
|
+
let layer = cache.get(key);
|
|
1934
|
+
if (layer === undefined) {
|
|
1935
|
+
layer = isJsonObject(def)
|
|
1936
|
+
? visLayer(Object.fromEntries(Object.entries(def).filter(([k]) => !skip.includes(k))), "node")
|
|
1937
|
+
: {};
|
|
1938
|
+
cache.set(key, layer);
|
|
1939
|
+
}
|
|
1940
|
+
return layer;
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
879
1943
|
/**
|
|
880
1944
|
* vis.js edges: their own keys over `options.edges`; an edge without a color of its own takes its source node's
|
|
881
1945
|
* border color (its target's under `inherit: "to"`).
|
|
882
1946
|
* @param snapshot - the graph
|
|
883
1947
|
* @param edgeDefaults - `options.edges`
|
|
884
1948
|
* @param borderOf - each node's drawn border color
|
|
1949
|
+
* @param geometry - the nodes' positions and boxes, for the edges' routes
|
|
885
1950
|
* @param notes - where problems are noted
|
|
886
1951
|
* @param context - the work budget
|
|
887
1952
|
* @returns the edge values, rules and style columns
|
|
888
1953
|
*/
|
|
889
|
-
function visEdges(snapshot, edgeDefaults, borderOf, notes, context) {
|
|
1954
|
+
function visEdges(snapshot, edgeDefaults, borderOf, geometry, notes, context) {
|
|
890
1955
|
const m = snapshot.edgeCount;
|
|
891
1956
|
const edge = valuesOf(m);
|
|
892
1957
|
const edgeRecords = elementRecords(snapshot, "edge");
|
|
893
1958
|
const inherited = new Array(m).fill(null);
|
|
894
1959
|
let inheriting = 0;
|
|
1960
|
+
const range = visValueRange(m, (e) => finiteValue({ ...edgeDefaults, ...edgeRecords(e) }.value));
|
|
1961
|
+
// vis-network sets round caps to draw a dashed line (EdgeBase._drawDashedLine) and never sets them back, so every
|
|
1962
|
+
// band drawn with or after a dashed edge, in the file's order, has round caps, which close gaps narrower than the
|
|
1963
|
+
// band
|
|
1964
|
+
let roundCaps = false;
|
|
895
1965
|
for (let e = 0; e < m; e++) {
|
|
896
1966
|
const own = edgeRecords(e);
|
|
897
|
-
|
|
1967
|
+
// an array of `options.edges.dashes` reaches the edge through bridgeObject as a plain object, no longer an
|
|
1968
|
+
// array, so vis-network draws its default [5, 5] (EdgeBase.setStrokeDashed)
|
|
1969
|
+
const bridged = own.dashes === undefined && Array.isArray(edgeDefaults.dashes) ? { dashes: true } : {};
|
|
1970
|
+
const merged = { ...edgeDefaults, ...own, ...bridged };
|
|
898
1971
|
const site = (property) => ({
|
|
899
1972
|
notes,
|
|
900
1973
|
target: "edge",
|
|
@@ -902,24 +1975,70 @@ function visEdges(snapshot, edgeDefaults, borderOf, notes, context) {
|
|
|
902
1975
|
element: e,
|
|
903
1976
|
budget: context.budget,
|
|
904
1977
|
});
|
|
905
|
-
const
|
|
1978
|
+
const [s, t] = [snapshot.edgeSource(e), snapshot.edgeTarget(e)];
|
|
1979
|
+
const vals = {
|
|
1980
|
+
...visValues(merged, "edge", site, true),
|
|
1981
|
+
...visEdgeWidth(merged, edgeDefaults, range, site),
|
|
1982
|
+
...visRoute(edgeDefaults, own, s, t, geometry),
|
|
1983
|
+
...visEdgeLabel(merged, range, geometry.at(s), geometry.at(t)),
|
|
1984
|
+
};
|
|
1985
|
+
// a dashed edge sets them before it draws its own band (BezierEdgeBase._bezierCurve draws the band first)
|
|
1986
|
+
roundCaps || (roundCaps = merged.hidden !== true && ![undefined, false, "false"].includes(merged.dashes));
|
|
1987
|
+
const band = roundCaps && isJsonObject(merged.background) ? visBandStyle(merged.background, true) : null;
|
|
1988
|
+
if (band !== null) {
|
|
1989
|
+
vals.outlineStyle = band;
|
|
1990
|
+
}
|
|
1991
|
+
const from = visEdgeInherit(vals, own.inherit ?? edgeDefaults.inherit ?? "from", borderOf, s, t);
|
|
906
1992
|
for (const [p, v] of Object.entries(vals)) {
|
|
907
1993
|
edge.put(p, e, v);
|
|
908
1994
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
edge.put("color", e, VIS_EDGE_COLOR);
|
|
913
|
-
}
|
|
914
|
-
continue;
|
|
915
|
-
}
|
|
916
|
-
if (inherit === "both") {
|
|
917
|
-
notes.add(STYLE_ISSUE.GRADIENT, "edge", "color", { sample: "inherit both", element: e });
|
|
1995
|
+
if (from !== null) {
|
|
1996
|
+
inherited[e] = from;
|
|
1997
|
+
inheriting++;
|
|
918
1998
|
}
|
|
919
|
-
inherited[e] = borderOf(inherit === "to" ? snapshot.edgeTarget(e) : snapshot.edgeSource(e));
|
|
920
|
-
inheriting++;
|
|
921
1999
|
}
|
|
922
2000
|
context.budget.spend("edge", m);
|
|
2001
|
+
const { rules, columns } = visEdgeRules(edgeDefaults, notes, inherited, inheriting);
|
|
2002
|
+
return { values: edge.values, rules, columns };
|
|
2003
|
+
}
|
|
2004
|
+
/**
|
|
2005
|
+
* vis.js: an edge's `inherit`, applied to its values: an edge that inherits draws its node's border color, not a
|
|
2006
|
+
* color `options.edges` gives (that only stops inheriting there), and under "both" a gradient between its nodes'; one
|
|
2007
|
+
* that does not inherit and has no color draws vis-network's default.
|
|
2008
|
+
* @param vals - the edge's values, written in place
|
|
2009
|
+
* @param inherit - its `inherit`
|
|
2010
|
+
* @param borderOf - each node's drawn border color
|
|
2011
|
+
* @param s - its source node
|
|
2012
|
+
* @param t - its target node
|
|
2013
|
+
* @returns the color it inherits, or null for none
|
|
2014
|
+
*/
|
|
2015
|
+
function visEdgeInherit(vals, inherit, borderOf, s, t) {
|
|
2016
|
+
const inherits = inherit !== false && inherit !== "false";
|
|
2017
|
+
if (inherits) {
|
|
2018
|
+
delete vals.color;
|
|
2019
|
+
}
|
|
2020
|
+
if (inherit === "both") {
|
|
2021
|
+
// vis-network: a linear gradient from the source node's border color to the target's
|
|
2022
|
+
vals.gradientKind = "linear";
|
|
2023
|
+
vals.gradientColors = `${String(borderOf(s))} ${String(borderOf(t))}`;
|
|
2024
|
+
}
|
|
2025
|
+
if (!inherits) {
|
|
2026
|
+
if (vals.color === undefined) {
|
|
2027
|
+
vals.color = VIS_EDGE_COLOR;
|
|
2028
|
+
}
|
|
2029
|
+
return null;
|
|
2030
|
+
}
|
|
2031
|
+
return borderOf(inherit === "to" ? t : s);
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* vis.js: the edge rules: `options.edges` as declared, and the colors edges inherit from their nodes.
|
|
2035
|
+
* @param edgeDefaults - `options.edges`
|
|
2036
|
+
* @param notes - where problems are noted
|
|
2037
|
+
* @param inherited - each edge's inherited color, or null
|
|
2038
|
+
* @param inheriting - how many edges inherit one
|
|
2039
|
+
* @returns the rules and their style columns
|
|
2040
|
+
*/
|
|
2041
|
+
function visEdgeRules(edgeDefaults, notes, inherited, inheriting) {
|
|
923
2042
|
const edgeRules = [];
|
|
924
2043
|
const edgeColumns = new Map();
|
|
925
2044
|
const declaredEdge = visValues(edgeDefaults, "edge", (property) => ({ notes, target: "edge", property }), true);
|
|
@@ -941,21 +2060,479 @@ function visEdges(snapshot, edgeDefaults, borderOf, notes, context) {
|
|
|
941
2060
|
facts: { elements: inheriting, reason: "SOURCE_NODE_COLOR" },
|
|
942
2061
|
});
|
|
943
2062
|
}
|
|
944
|
-
return {
|
|
2063
|
+
return { rules: edgeRules, columns: edgeColumns };
|
|
945
2064
|
}
|
|
946
|
-
/** The vis.js keys the reader reads, per target. */
|
|
947
|
-
const VIS_KEYS = {
|
|
948
|
-
node: ["color", "shape", "size", "borderWidth", "opacity", "hidden", "label", "title", "font"],
|
|
949
|
-
edge: ["color", "width", "dashes", "arrows", "hidden", "label", "title", "font"],
|
|
950
|
-
};
|
|
951
2065
|
/**
|
|
952
|
-
*
|
|
2066
|
+
* vis.js: the positions and boxes of the nodes, for the routes of their edges.
|
|
953
2067
|
* @param snapshot - the graph
|
|
954
|
-
* @
|
|
2068
|
+
* @param nodeValues - the node values read so far (sizes and border widths)
|
|
2069
|
+
* @param shapes - each node's vis-network shape
|
|
2070
|
+
* @returns the geometry
|
|
955
2071
|
*/
|
|
956
|
-
function
|
|
957
|
-
|
|
2072
|
+
function visGeometry(snapshot, nodeValues, shapes) {
|
|
2073
|
+
const whole = (v) => {
|
|
2074
|
+
if (typeof v === "string") {
|
|
2075
|
+
return Number.parseInt(v, 10);
|
|
2076
|
+
}
|
|
2077
|
+
return typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : Number.NaN;
|
|
2078
|
+
};
|
|
2079
|
+
const [xs, ys] = [cells(snapshot, "node", "x"), cells(snapshot, "node", "y")];
|
|
2080
|
+
const num = (v) => (typeof v === "number" && v > 0 ? v : null);
|
|
2081
|
+
return {
|
|
2082
|
+
at: (i) => {
|
|
2083
|
+
const p = [whole(xs?.(i)), whole(ys?.(i))];
|
|
2084
|
+
return Number.isNaN(p[0]) || Number.isNaN(p[1]) ? null : p;
|
|
2085
|
+
},
|
|
2086
|
+
box: (i) => {
|
|
2087
|
+
const [w, h] = [num(nodeValues.width?.[i]), num(nodeValues.height?.[i])];
|
|
2088
|
+
const bw = nodeValues.borderWidth?.[i];
|
|
2089
|
+
return w === null || h === null ? null : [w / 2, h / 2, typeof bw === "number" ? bw : 1, shapes[i]];
|
|
2090
|
+
},
|
|
2091
|
+
};
|
|
958
2092
|
}
|
|
2093
|
+
/**
|
|
2094
|
+
* vis.js: an edge's `smooth` over `options.edges.smooth` over vis-network's defaults (dynamic, roundness 0.5).
|
|
2095
|
+
* @param layers - the layers, the lowest first
|
|
2096
|
+
* @returns the smooth options
|
|
2097
|
+
*/
|
|
2098
|
+
function visSmooth(...layers) {
|
|
2099
|
+
const s = { enabled: true, type: "dynamic", roundness: 0.5, forceDirection: "none" };
|
|
2100
|
+
for (const l of layers) {
|
|
2101
|
+
if (typeof l === "boolean") {
|
|
2102
|
+
s.enabled = l;
|
|
2103
|
+
}
|
|
2104
|
+
else if (isJsonObject(l)) {
|
|
2105
|
+
s.enabled = typeof l.enabled === "boolean" ? l.enabled : s.enabled;
|
|
2106
|
+
s.type = typeof l.type === "string" ? l.type : s.type;
|
|
2107
|
+
s.roundness = typeof l.roundness === "number" && Number.isFinite(l.roundness) ? l.roundness : s.roundness;
|
|
2108
|
+
s.forceDirection = l.forceDirection ?? s.forceDirection;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
return s;
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* vis.js: an edge label's size under `scaling.label` and its turn along the line.
|
|
2115
|
+
* @param merged - the edge's merged layer
|
|
2116
|
+
* @param range - the edges' value range
|
|
2117
|
+
* @param a - the source node's center, or null
|
|
2118
|
+
* @param b - the target node's center, or null
|
|
2119
|
+
* @returns the values
|
|
2120
|
+
*/
|
|
2121
|
+
function visEdgeLabel(merged, range, a, b) {
|
|
2122
|
+
const row = {};
|
|
2123
|
+
const size = visLabelScaled(merged, range);
|
|
2124
|
+
if (size !== null) {
|
|
2125
|
+
row.labelSize = size;
|
|
2126
|
+
}
|
|
2127
|
+
const turn = visLabelTurn(merged.fontAlign, a, b);
|
|
2128
|
+
if (turn !== null) {
|
|
2129
|
+
row.labelRotation = turn;
|
|
2130
|
+
}
|
|
2131
|
+
return row;
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* vis.js: how far an edge label whose `font.align` is not "horizontal" is turned to run along its edge, kept readable
|
|
2135
|
+
* (vis-network 9.1.9 `Edge._getRotation`), in degrees clockwise on screen.
|
|
2136
|
+
* @param align - `font.align`
|
|
2137
|
+
* @param a - the source node's center, or null
|
|
2138
|
+
* @param b - the target node's center, or null
|
|
2139
|
+
* @returns the turn, or null for a level label
|
|
2140
|
+
*/
|
|
2141
|
+
function visLabelTurn(align, a, b) {
|
|
2142
|
+
if (typeof align !== "string" || align === "horizontal" || a === null || b === null) {
|
|
2143
|
+
return null;
|
|
2144
|
+
}
|
|
2145
|
+
const [dx, dy] = [a[0] - b[0], a[1] - b[1]];
|
|
2146
|
+
let angle = Math.atan2(dy, dx);
|
|
2147
|
+
if ((angle < -1 && dx < 0) || (angle > 0 && dx < 0)) {
|
|
2148
|
+
angle += Math.PI;
|
|
2149
|
+
}
|
|
2150
|
+
const degrees = Math.round(((angle * 180) / Math.PI) * 1e4) / 1e4;
|
|
2151
|
+
return degrees < 0 ? degrees + 360 : degrees;
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* vis-network's control point of a static smooth curve (9.1.9 `BezierEdgeStatic._getViaCoordinates`), from the two
|
|
2155
|
+
* node centers.
|
|
2156
|
+
* @param from - the source node's center
|
|
2157
|
+
* @param to - the target node's center
|
|
2158
|
+
* @param smooth - the smooth options
|
|
2159
|
+
* @returns the control point
|
|
2160
|
+
*/
|
|
2161
|
+
function visStaticVia(from, to, smooth) {
|
|
2162
|
+
const f = smooth.roundness;
|
|
2163
|
+
const [fx, fy] = from;
|
|
2164
|
+
const [tx, ty] = to;
|
|
2165
|
+
const dx = Math.abs(fx - tx);
|
|
2166
|
+
const dy = Math.abs(fy - ty);
|
|
2167
|
+
// the step a discrete, diagonalCross or continuous curve takes from its source, toward its target in x and in y
|
|
2168
|
+
// (away from it in y when level: `from.y >= to.y` steps up)
|
|
2169
|
+
const step = f * Math.max(dx, dy);
|
|
2170
|
+
const sx = step * (Math.sign(tx - fx) || 1);
|
|
2171
|
+
const sy = step * (Math.sign(ty - fy) || -1);
|
|
2172
|
+
// which way a cross, horizontal or vertical curve's point lies from the target, back toward the source
|
|
2173
|
+
const backX = (1 - f) * dx * (Math.sign(fx - tx) || 1);
|
|
2174
|
+
const backY = (1 - f) * dy * (Math.sign(fy - ty) || 1);
|
|
2175
|
+
switch (smooth.type) {
|
|
2176
|
+
case "discrete":
|
|
2177
|
+
return visDiscreteVia(from, [fx + sx, fy + sy], dx, dy, f);
|
|
2178
|
+
case "diagonalCross":
|
|
2179
|
+
return [fx + sx, fy + sy];
|
|
2180
|
+
case "straightCross":
|
|
2181
|
+
return dx <= dy ? [tx, ty + backY] : [tx + backX, ty];
|
|
2182
|
+
case "horizontal":
|
|
2183
|
+
return [tx + backX, fy];
|
|
2184
|
+
case "vertical":
|
|
2185
|
+
return [fx, ty + backY];
|
|
2186
|
+
case "curvedCW":
|
|
2187
|
+
return visCurvedVia(from, to, f, f * 0.5 + 0.5);
|
|
2188
|
+
case "curvedCCW":
|
|
2189
|
+
return visCurvedVia(from, to, f, -f * 0.5 + 0.5);
|
|
2190
|
+
default:
|
|
2191
|
+
// continuous: the diagonal step, no farther than the target along the longer axis
|
|
2192
|
+
return dx <= dy
|
|
2193
|
+
? [fx + Math.sign(sx) * Math.min(step, dx), fy + sy]
|
|
2194
|
+
: [fx + sx, fy + Math.sign(sy) * Math.min(step, dy)];
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
/**
|
|
2198
|
+
* vis-network's discrete curve: the diagonal step, kept at the source's x (or y) when the nodes are nearly in line.
|
|
2199
|
+
* @param from - the source node's center
|
|
2200
|
+
* @param diagonal - the diagonal step's point
|
|
2201
|
+
* @param dx - the distance between the nodes in x
|
|
2202
|
+
* @param dy - the distance in y
|
|
2203
|
+
* @param f - the roundness
|
|
2204
|
+
* @returns the control point
|
|
2205
|
+
*/
|
|
2206
|
+
function visDiscreteVia(from, diagonal, dx, dy, f) {
|
|
2207
|
+
if (dx <= dy) {
|
|
2208
|
+
return [dx < f * dy ? from[0] : diagonal[0], diagonal[1]];
|
|
2209
|
+
}
|
|
2210
|
+
return [diagonal[0], dy < f * dx ? from[1] : diagonal[1]];
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* vis-network's curvedCW and curvedCCW: a point `(roundness / 2 + 1 / 2)` of the distance from the source, turned
|
|
2214
|
+
* `turn` half turns from the line to the target (vis-network's sin / cos convention, y down).
|
|
2215
|
+
* @param from - the source node's center
|
|
2216
|
+
* @param to - the target node's center
|
|
2217
|
+
* @param f - the roundness
|
|
2218
|
+
* @param turn - the turn, in half turns
|
|
2219
|
+
* @returns the control point
|
|
2220
|
+
*/
|
|
2221
|
+
function visCurvedVia(from, to, f, turn) {
|
|
2222
|
+
const ex = to[0] - from[0];
|
|
2223
|
+
const ey = from[1] - to[1];
|
|
2224
|
+
const r = (f * 0.5 + 0.5) * Math.hypot(ex, ey);
|
|
2225
|
+
const angle = (Math.atan2(ey, ex) + turn * Math.PI) % (2 * Math.PI);
|
|
2226
|
+
return [from[0] + r * Math.sin(angle), from[1] + r * Math.cos(angle)];
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* vis-network's two control points of a cubicBezier curve (9.1.9 `CubicBezierEdge._getViaCoordinates`): each
|
|
2230
|
+
* `roundness` of the way across from its own end, horizontally when the nodes are farther apart in x or
|
|
2231
|
+
* `forceDirection` is horizontal (or true), else vertically.
|
|
2232
|
+
* @param from - the source node's center
|
|
2233
|
+
* @param to - the target node's center
|
|
2234
|
+
* @param smooth - the smooth options
|
|
2235
|
+
* @returns the two control points
|
|
2236
|
+
*/
|
|
2237
|
+
function visCubicVia(from, to, smooth) {
|
|
2238
|
+
const [dx, dy] = [from[0] - to[0], from[1] - to[1]];
|
|
2239
|
+
const r = smooth.roundness;
|
|
2240
|
+
const force = smooth.forceDirection;
|
|
2241
|
+
const across = (Math.abs(dx) > Math.abs(dy) || force === true || force === "horizontal") && force !== "vertical";
|
|
2242
|
+
return across
|
|
2243
|
+
? [
|
|
2244
|
+
[from[0] - r * dx, from[1]],
|
|
2245
|
+
[to[0] + r * dx, to[1]],
|
|
2246
|
+
]
|
|
2247
|
+
: [
|
|
2248
|
+
[from[0], from[1] - r * dy],
|
|
2249
|
+
[to[0], to[1] + r * dy],
|
|
2250
|
+
];
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* A point as the neutral `bends` spell it: `along,across` relative to the line between two centers, y down in, y up
|
|
2254
|
+
* out.
|
|
2255
|
+
* @param a - the source center
|
|
2256
|
+
* @param b - the target center
|
|
2257
|
+
* @param p - the point
|
|
2258
|
+
* @returns `along,across`
|
|
2259
|
+
*/
|
|
2260
|
+
function alongAcross(a, b, p) {
|
|
2261
|
+
const [dx, dy] = [b[0] - a[0], b[1] - a[1]];
|
|
2262
|
+
const [qx, qy] = [p[0] - a[0], p[1] - a[1]];
|
|
2263
|
+
const l2 = dx * dx + dy * dy;
|
|
2264
|
+
return `${fraction((qx * dx + qy * dy) / l2)},${fraction((dy * qx - dx * qy) / l2)}`;
|
|
2265
|
+
}
|
|
2266
|
+
/**
|
|
2267
|
+
* A number rounded to four places for a bend, with no negative zero.
|
|
2268
|
+
* @param v - the number
|
|
2269
|
+
* @returns its text
|
|
2270
|
+
*/
|
|
2271
|
+
const fraction = (v) => String(Math.round(v * 1e4) / 1e4 || 0);
|
|
2272
|
+
/**
|
|
2273
|
+
* vis.js: the route vis-network draws an edge along. A smooth edge is one Bezier curve through its control points
|
|
2274
|
+
* (`bendCurve: "bezier"`): a static type's one point, cubicBezier's two. A dynamic one (vis-network's default) is
|
|
2275
|
+
* drawn through a support node the physics simulation moves; where it never moves (physics off for the network or
|
|
2276
|
+
* the edge) it stays at the middle and the edge is straight. A self-loop is vis-network's circle of
|
|
2277
|
+
* `selfReference.size` (20) centered on its node's outline at `selfReference.angle` (pi / 4).
|
|
2278
|
+
* @param defaults - `options.edges`
|
|
2279
|
+
* @param own - the edge's own keys
|
|
2280
|
+
* @param s - its source node
|
|
2281
|
+
* @param t - its target node
|
|
2282
|
+
* @param g - the nodes' geometry
|
|
2283
|
+
* @returns the route's values, none for a straight edge or a route the file does not decide
|
|
2284
|
+
*/
|
|
2285
|
+
function visRoute(defaults, own, s, t, g) {
|
|
2286
|
+
if (s === t) {
|
|
2287
|
+
return visLoop(defaults, own, g.box(s));
|
|
2288
|
+
}
|
|
2289
|
+
const smooth = visSmooth(defaults.smooth, own.smooth);
|
|
2290
|
+
if (smooth.enabled && smooth.type === "dynamic") {
|
|
2291
|
+
return visDynamic(defaults, own, g.support ?? null);
|
|
2292
|
+
}
|
|
2293
|
+
const [a, b] = [g.at(s), g.at(t)];
|
|
2294
|
+
if (!smooth.enabled || a === null || b === null || (a[0] === b[0] && a[1] === b[1])) {
|
|
2295
|
+
return {};
|
|
2296
|
+
}
|
|
2297
|
+
const controls = smooth.type === "cubicBezier" ? visCubicVia(a, b, smooth) : [visStaticVia(a, b, smooth)];
|
|
2298
|
+
return { curved: true, bendCurve: "bezier", bends: controls.map((p) => alongAcross(a, b, p)).join(" ") };
|
|
2299
|
+
}
|
|
2300
|
+
/**
|
|
2301
|
+
* vis.js: a dynamic smooth edge, drawn through the support node vis-network's physics places, wherever its nodes
|
|
2302
|
+
* are (they need no position in the file): its forces, with the edge's own `length` as its springs' rest length.
|
|
2303
|
+
* Straight where physics does not run for it (`physics: false` on the network or the edge), as the support node
|
|
2304
|
+
* then stays at the middle.
|
|
2305
|
+
* @param defaults - `options.edges`
|
|
2306
|
+
* @param own - the edge's own keys
|
|
2307
|
+
* @param support - the network's forces, or null
|
|
2308
|
+
* @returns the route's values
|
|
2309
|
+
*/
|
|
2310
|
+
function visDynamic(defaults, own, support) {
|
|
2311
|
+
if (support === null || own.physics === false || (own.physics === undefined && defaults.physics === false)) {
|
|
2312
|
+
return {};
|
|
2313
|
+
}
|
|
2314
|
+
const length = [own.length, defaults.length].find((v) => typeof v === "number" && Number.isFinite(v) && v >= 0);
|
|
2315
|
+
return { curved: true, ...support, ...(length === undefined ? {} : { supportSpringLength: length }) };
|
|
2316
|
+
}
|
|
2317
|
+
/** vis-network 9.1.9's physics defaults for the two solvers whose forces a support node is drawn under. */
|
|
2318
|
+
const VIS_SOLVERS = {
|
|
2319
|
+
barnesHut: { model: "barnesHut", g: -2000, cg: 0.3, length: 95, k: 0.04 },
|
|
2320
|
+
forceAtlas2Based: { model: "forceAtlas2", g: -50, cg: 0.01, length: 100, k: 0.08 },
|
|
2321
|
+
};
|
|
2322
|
+
/**
|
|
2323
|
+
* vis.js: the forces a dynamic edge's support node settles under (9.1.9 BarnesHutSolver,
|
|
2324
|
+
* ForceAtlas2BasedRepulsionSolver, their central gravity solvers and SpringSolver). None where physics is off, where
|
|
2325
|
+
* a hierarchical layout turns every dynamic curve static (`_forceDisableDynamicCurves`), or under the repulsion and
|
|
2326
|
+
* hierarchicalRepulsion solvers (ponytail: drawn straight; their force laws when a file uses them).
|
|
2327
|
+
* @param physics - `options.physics`
|
|
2328
|
+
* @param layout - `options.layout`
|
|
2329
|
+
* @returns the neutral support values, or null
|
|
2330
|
+
*/
|
|
2331
|
+
function visSupport(physics, layout) {
|
|
2332
|
+
const hierarchical = isJsonObject(layout) ? layout.hierarchical : undefined;
|
|
2333
|
+
if (hierarchical === true || (isJsonObject(hierarchical) && hierarchical.enabled !== false)) {
|
|
2334
|
+
return null;
|
|
2335
|
+
}
|
|
2336
|
+
if (physics === false || (isJsonObject(physics) && physics.enabled === false)) {
|
|
2337
|
+
return null;
|
|
2338
|
+
}
|
|
2339
|
+
const o = isJsonObject(physics) ? physics : {};
|
|
2340
|
+
const solver = typeof o.solver === "string" ? o.solver : "barnesHut";
|
|
2341
|
+
const base = VIS_SOLVERS[solver];
|
|
2342
|
+
if (base === undefined) {
|
|
2343
|
+
return null;
|
|
2344
|
+
}
|
|
2345
|
+
const given = isJsonObject(o[solver]) ? o[solver] : {};
|
|
2346
|
+
const num = (v, fallback) => (typeof v === "number" && Number.isFinite(v) ? v : fallback);
|
|
2347
|
+
return {
|
|
2348
|
+
supportModel: base.model,
|
|
2349
|
+
supportSpringLength: Math.max(0, num(given.springLength, base.length)),
|
|
2350
|
+
supportSpringConstant: Math.max(0, num(given.springConstant, base.k)),
|
|
2351
|
+
supportRepulsion: Math.max(0, -num(given.gravitationalConstant, base.g)),
|
|
2352
|
+
supportCentralGravity: Math.max(0, num(given.centralGravity, base.cg)),
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* vis.js: a self-loop, vis-network's circle (9.1.9 `EdgeBase._getCircleData`, `getSelfRefCoordinates`) of radius
|
|
2357
|
+
* `selfReference.size` (or the older `selfReferenceSize`; 20) centered on the node's outline (visToBorder) along
|
|
2358
|
+
* the ray at `selfReference.angle` (pi / 4, counterclockwise from the right). Written as bends at the corners of the
|
|
2359
|
+
* square round the circle, drawn through their midpoints, which run along the circle.
|
|
2360
|
+
* @param defaults - `options.edges`
|
|
2361
|
+
* @param own - the edge's own keys
|
|
2362
|
+
* @param box - the node's half width, half height, border width and shape, or null when unknown
|
|
2363
|
+
* @returns the loop's values, none for a node whose size is not known here
|
|
2364
|
+
*/
|
|
2365
|
+
function visLoop(defaults, own, box) {
|
|
2366
|
+
if (box === null) {
|
|
2367
|
+
return {};
|
|
2368
|
+
}
|
|
2369
|
+
const field = (layer, key) => isJsonObject(layer.selfReference) ? layer.selfReference[key] : undefined;
|
|
2370
|
+
const pick = (...vs) => vs.find((v) => typeof v === "number" && Number.isFinite(v)) ?? null;
|
|
2371
|
+
const radius = pick(field(own, "size"), own.selfReferenceSize, field(defaults, "size"), defaults.selfReferenceSize) ?? 20;
|
|
2372
|
+
const angle = pick(field(own, "angle"), field(defaults, "angle")) ?? Math.PI / 4;
|
|
2373
|
+
const [hw, hh] = box;
|
|
2374
|
+
const reach = visToBorder(box, angle);
|
|
2375
|
+
// the circle's center relative to the node's, y up
|
|
2376
|
+
const [cx, cy] = [Math.cos(angle) * reach, Math.sin(angle) * reach];
|
|
2377
|
+
const behind = [field(own, "renderBehindTheNode"), field(defaults, "renderBehindTheNode")].find((v) => typeof v === "boolean");
|
|
2378
|
+
if (behind === false) {
|
|
2379
|
+
return visFrontArc(cx, cy, radius, angle, box);
|
|
2380
|
+
}
|
|
2381
|
+
// the node's farthest reach from its center: its radius when round, its corner otherwise
|
|
2382
|
+
const round = box[3] === "dot" || box[3] === "circle" || box[3] === "circularImage";
|
|
2383
|
+
if (reach + (round ? hw : Math.hypot(hw, hh)) < radius) {
|
|
2384
|
+
return visRing(cx, cy, radius, angle, box);
|
|
2385
|
+
}
|
|
2386
|
+
// the corners of the square round the circle, the first the one facing the node and again last, so the curve
|
|
2387
|
+
// through their midpoints runs the whole circle and its ends, out of and back into the node, bulge as it does
|
|
2388
|
+
const corners = [];
|
|
2389
|
+
for (let k = 0; k <= 4; k++) {
|
|
2390
|
+
const a = angle + Math.PI + ((k % 4) * Math.PI) / 2;
|
|
2391
|
+
const [x, y] = [cx + Math.SQRT2 * radius * Math.cos(a), cy + Math.SQRT2 * radius * Math.sin(a)];
|
|
2392
|
+
corners.push(`${fraction(x / hw)},${fraction(y / hh)}`);
|
|
2393
|
+
}
|
|
2394
|
+
return { curved: true, bendCurve: "midpoints", bends: corners.join(" ") };
|
|
2395
|
+
}
|
|
2396
|
+
/** How many points a vis-network self-loop's circle is written through where it does not meet its node. */
|
|
2397
|
+
const VIS_RING_STEPS = 96;
|
|
2398
|
+
/**
|
|
2399
|
+
* vis.js: a self-loop whose circle holds its whole node, so meets it nowhere (a small node, or a large
|
|
2400
|
+
* `selfReference.size`): the circle alone, written as points round it from the side facing the node and back to it,
|
|
2401
|
+
* the first and the last the same point, which graphty draws as that closed ring with no line to the node.
|
|
2402
|
+
* @param cx - the circle's center across from the node's, y up
|
|
2403
|
+
* @param cy - and up from it
|
|
2404
|
+
* @param radius - its radius
|
|
2405
|
+
* @param angle - the ray its center is on, counterclockwise from the right
|
|
2406
|
+
* @param box - the node's half width and half height
|
|
2407
|
+
* @returns the loop's values
|
|
2408
|
+
*/
|
|
2409
|
+
function visRing(cx, cy, radius, angle, box) {
|
|
2410
|
+
const [hw, hh] = box;
|
|
2411
|
+
const points = [];
|
|
2412
|
+
for (let k = 0; k <= VIS_RING_STEPS; k++) {
|
|
2413
|
+
const a = angle + Math.PI + (2 * Math.PI * (k % VIS_RING_STEPS)) / VIS_RING_STEPS;
|
|
2414
|
+
points.push(`${fraction((cx + radius * Math.cos(a)) / hw)},${fraction((cy + radius * Math.sin(a)) / hh)}`);
|
|
2415
|
+
}
|
|
2416
|
+
return { curved: false, bends: points.join(" ") };
|
|
2417
|
+
}
|
|
2418
|
+
/**
|
|
2419
|
+
* vis.js: a self-loop drawn in front of its node (`selfReference.renderBehindTheNode: false`): only the arc of its
|
|
2420
|
+
* circle between the two points vis-network 9.1.9 searches for (Edge._circle, `_findBorderPositionCircle`: ten
|
|
2421
|
+
* halvings of a "position" between the angle and the angle plus pi, read as a fraction of a turn), clockwise on screen
|
|
2422
|
+
* from the first to the second, written as points along it.
|
|
2423
|
+
* @param cx - the circle's center across from the node's, y up
|
|
2424
|
+
* @param cy - and up from it
|
|
2425
|
+
* @param radius - its radius
|
|
2426
|
+
* @param angle - `selfReference.angle`
|
|
2427
|
+
* @param box - the node's half width, half height, border width and shape
|
|
2428
|
+
* @returns the loop's values
|
|
2429
|
+
*/
|
|
2430
|
+
function visFrontArc(cx, cy, radius, angle, box) {
|
|
2431
|
+
// on screen, y down, the node's center at the origin
|
|
2432
|
+
const [x, y] = [cx, -cy];
|
|
2433
|
+
const search = (direction) => {
|
|
2434
|
+
let [low, high] = [angle, angle + Math.PI];
|
|
2435
|
+
let pos = [x, y];
|
|
2436
|
+
for (let iteration = 0; low <= high && iteration < 10; iteration++) {
|
|
2437
|
+
const middle = (low + high) * 0.5;
|
|
2438
|
+
const turn = middle * 2 * Math.PI;
|
|
2439
|
+
pos = [x + radius * Math.cos(turn), y - radius * Math.sin(turn)];
|
|
2440
|
+
const difference = visToBorder(box, Math.atan2(-pos[1], -pos[0])) - Math.hypot(pos[0], pos[1]);
|
|
2441
|
+
if (Math.abs(difference) < 0.05) {
|
|
2442
|
+
break;
|
|
2443
|
+
}
|
|
2444
|
+
if (difference > 0 === direction > 0) {
|
|
2445
|
+
low = middle;
|
|
2446
|
+
}
|
|
2447
|
+
else {
|
|
2448
|
+
high = middle;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
return pos;
|
|
2452
|
+
};
|
|
2453
|
+
const [from, to] = [search(-1), search(1)];
|
|
2454
|
+
const start = Math.atan2(from[1] - y, from[0] - x);
|
|
2455
|
+
let end = Math.atan2(to[1] - y, to[0] - x);
|
|
2456
|
+
if (end < start) {
|
|
2457
|
+
end += 2 * Math.PI;
|
|
2458
|
+
}
|
|
2459
|
+
const [hw, hh] = box;
|
|
2460
|
+
const steps = Math.max(2, Math.ceil(((end - start) / (2 * Math.PI)) * VIS_RING_STEPS));
|
|
2461
|
+
const points = [];
|
|
2462
|
+
for (let k = 0; k <= steps; k++) {
|
|
2463
|
+
const a = start + ((end - start) * k) / steps;
|
|
2464
|
+
points.push(`${fraction((x + radius * Math.cos(a)) / hw)},${fraction(-(y + radius * Math.sin(a)) / hh)}`);
|
|
2465
|
+
}
|
|
2466
|
+
// the arc's two ends are where vis-network starts and stops it, off the node as often as on it: ports, so no line
|
|
2467
|
+
// is drawn from the node out to them
|
|
2468
|
+
return {
|
|
2469
|
+
curved: false,
|
|
2470
|
+
sourcePort: points[0],
|
|
2471
|
+
bends: points.slice(1, -1).join(" "),
|
|
2472
|
+
targetPort: points.at(-1) ?? points[0],
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* vis-network 9.1.9's `distanceToBorder` of a node, how far from its center its outline is along a ray: a dot its
|
|
2477
|
+
* size, a circle or circularImage half its width, an ellipse its ellipse, all with no border; every other shape its
|
|
2478
|
+
* box plus its border width (NodeBase._distanceToBorder, Box).
|
|
2479
|
+
* @param box - the node's half width, half height, border width and shape
|
|
2480
|
+
* @param angle - the ray, counterclockwise from the right
|
|
2481
|
+
* @returns the distance
|
|
2482
|
+
*/
|
|
2483
|
+
function visToBorder(box, angle) {
|
|
2484
|
+
const [hw, hh, bw, shape] = box;
|
|
2485
|
+
if (shape === "dot" || shape === "circle" || shape === "circularImage") {
|
|
2486
|
+
return hw;
|
|
2487
|
+
}
|
|
2488
|
+
if (shape === "ellipse") {
|
|
2489
|
+
return (hw * hh) / Math.hypot(Math.sin(angle) * hw, Math.cos(angle) * hh);
|
|
2490
|
+
}
|
|
2491
|
+
return Math.min(Math.abs(hw / Math.cos(angle)), Math.abs(hh / Math.sin(angle))) + bw;
|
|
2492
|
+
}
|
|
2493
|
+
/** The vis.js keys the reader reads, per target. */
|
|
2494
|
+
const VIS_KEYS = {
|
|
2495
|
+
node: [
|
|
2496
|
+
"color",
|
|
2497
|
+
"shape",
|
|
2498
|
+
"size",
|
|
2499
|
+
"value",
|
|
2500
|
+
"scaling",
|
|
2501
|
+
"borderWidth",
|
|
2502
|
+
"opacity",
|
|
2503
|
+
"hidden",
|
|
2504
|
+
"label",
|
|
2505
|
+
"title",
|
|
2506
|
+
"font",
|
|
2507
|
+
"margin",
|
|
2508
|
+
"widthConstraint",
|
|
2509
|
+
"heightConstraint",
|
|
2510
|
+
"image",
|
|
2511
|
+
"brokenImage",
|
|
2512
|
+
"imagePadding",
|
|
2513
|
+
"icon",
|
|
2514
|
+
"shapeProperties",
|
|
2515
|
+
],
|
|
2516
|
+
edge: [
|
|
2517
|
+
"color",
|
|
2518
|
+
"width",
|
|
2519
|
+
"value",
|
|
2520
|
+
"scaling",
|
|
2521
|
+
"dashes",
|
|
2522
|
+
"arrows",
|
|
2523
|
+
"hidden",
|
|
2524
|
+
"label",
|
|
2525
|
+
"title",
|
|
2526
|
+
"font",
|
|
2527
|
+
"background",
|
|
2528
|
+
"smooth",
|
|
2529
|
+
"selfReference",
|
|
2530
|
+
"selfReferenceSize",
|
|
2531
|
+
"physics",
|
|
2532
|
+
"length",
|
|
2533
|
+
"widthConstraint",
|
|
2534
|
+
],
|
|
2535
|
+
};
|
|
959
2536
|
/**
|
|
960
2537
|
* Each element's own vis.js keys as a layer, read from the graph's columns.
|
|
961
2538
|
* @param snapshot - the graph
|
|
@@ -963,6 +2540,16 @@ function nodeRecords(snapshot) {
|
|
|
963
2540
|
* @returns the layer of an element
|
|
964
2541
|
*/
|
|
965
2542
|
function elementRecords(snapshot, target) {
|
|
2543
|
+
const raw = elementRaw(snapshot, target);
|
|
2544
|
+
return (i) => visLayer(raw(i), target);
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Each element's own vis.js keys as the file gives them, read from the graph's columns.
|
|
2548
|
+
* @param snapshot - the graph
|
|
2549
|
+
* @param target - nodes or edges
|
|
2550
|
+
* @returns the keys of an element
|
|
2551
|
+
*/
|
|
2552
|
+
function elementRaw(snapshot, target) {
|
|
966
2553
|
const readers = VIS_KEYS[target]
|
|
967
2554
|
.map((name) => [name, cells(snapshot, target, name)])
|
|
968
2555
|
.filter((x) => x[1] !== null);
|
|
@@ -974,7 +2561,7 @@ function elementRecords(snapshot, target) {
|
|
|
974
2561
|
record[name] = v;
|
|
975
2562
|
}
|
|
976
2563
|
}
|
|
977
|
-
return
|
|
2564
|
+
return record;
|
|
978
2565
|
};
|
|
979
2566
|
}
|
|
980
2567
|
/**
|
|
@@ -1067,10 +2654,88 @@ const visSize = (want) => {
|
|
|
1067
2654
|
const shape = typeof s === "string" ? visShape(s)[0] : "ellipse";
|
|
1068
2655
|
return VIS_SIZED.has(shape) ? oneSize(want) : null;
|
|
1069
2656
|
};
|
|
2657
|
+
/**
|
|
2658
|
+
* The vis.js shape a written node is read back as.
|
|
2659
|
+
* @param want - the block's values of the element
|
|
2660
|
+
* @returns the shape
|
|
2661
|
+
*/
|
|
2662
|
+
const visShapeBack = (want) => {
|
|
2663
|
+
const s = want("shape");
|
|
2664
|
+
return typeof s === "string" ? visShape(s)[0] : "ellipse";
|
|
2665
|
+
};
|
|
2666
|
+
/**
|
|
2667
|
+
* What a written vis.js node of a shape that changes what its size gives back reads as (visSizedShape, visFitShape):
|
|
2668
|
+
* a hexagon's height and how far below its square its label hangs, a database's label at its top.
|
|
2669
|
+
* @param property - the fact
|
|
2670
|
+
* @returns the reading back of the element's values, null where the shape changes nothing
|
|
2671
|
+
*/
|
|
2672
|
+
const visShapeFactBack = (property) => (want) => {
|
|
2673
|
+
const shape = visShapeBack(want);
|
|
2674
|
+
const size = visSize(want);
|
|
2675
|
+
if (shape === "hexagon" && typeof size === "number") {
|
|
2676
|
+
return property === "height" ? (Math.sqrt(3) * size) / 2 : ((2 - Math.sqrt(3)) * size) / 4;
|
|
2677
|
+
}
|
|
2678
|
+
return property === "labelOffsetY" && shape === "database" ? VIS_MARGIN : null;
|
|
2679
|
+
};
|
|
2680
|
+
/**
|
|
2681
|
+
* What the reader reads back for a fact of a node the writer wrote in a shape vis-network sizes by its label: the
|
|
2682
|
+
* shape's own growth, from the label and font size written.
|
|
2683
|
+
* @param property - the fact
|
|
2684
|
+
* @returns the reading back of the element's values
|
|
2685
|
+
*/
|
|
2686
|
+
const visFitBack = (property) => (want) => {
|
|
2687
|
+
const s = want("shape");
|
|
2688
|
+
const shape = typeof s === "string" ? visShape(s)[0] : "ellipse";
|
|
2689
|
+
return visLabelFit({ fontSize: want("labelSize"), label: want("label") }, shape)[property] ?? null;
|
|
2690
|
+
};
|
|
2691
|
+
/**
|
|
2692
|
+
* What reading a file graphty wrote in vis.js gives back: VIS_WRITTEN, and for a self-loop the route the reader draws
|
|
2693
|
+
* it along (vis-network's circle, made from vis-network's own defaults and the node's size, never from the block).
|
|
2694
|
+
* @param snapshot - the graph
|
|
2695
|
+
* @param edgeValues - the edge values the reader read
|
|
2696
|
+
* @returns the readings back
|
|
2697
|
+
*/
|
|
2698
|
+
function visWritten(snapshot, edgeValues) {
|
|
2699
|
+
const isLoop = (i) => snapshot.edgeSource(i) === snapshot.edgeTarget(i);
|
|
2700
|
+
const loop = (property) => (want, i) => isLoop(i) ? (edgeValues[property]?.[i] ?? null) : want(property);
|
|
2701
|
+
// the writer writes no `smooth` and no `options.physics`, so every other edge reads back as vis-network's default,
|
|
2702
|
+
// a dynamic curve under the default barnesHut physics
|
|
2703
|
+
const dynamic = visSupport(undefined, undefined) ?? {};
|
|
2704
|
+
const support = (property) => (_want, i) => isLoop(i) ? (edgeValues[property]?.[i] ?? null) : (dynamic[property] ?? null);
|
|
2705
|
+
const supports = Object.fromEntries(Object.keys(dynamic).map((property) => [property, support(property)]));
|
|
2706
|
+
// an edge's own `length`, kept as data, is its springs' rest length again
|
|
2707
|
+
const lengths = cells(snapshot, "edge", "length");
|
|
2708
|
+
supports.supportSpringLength = (want, i) => {
|
|
2709
|
+
const own = lengths?.(i);
|
|
2710
|
+
return typeof own === "number" && Number.isFinite(own) && own >= 0 && !isLoop(i)
|
|
2711
|
+
? own
|
|
2712
|
+
: support("supportSpringLength")(want, i);
|
|
2713
|
+
};
|
|
2714
|
+
return {
|
|
2715
|
+
node: VIS_WRITTEN.node,
|
|
2716
|
+
edge: {
|
|
2717
|
+
...VIS_WRITTEN.edge,
|
|
2718
|
+
...supports,
|
|
2719
|
+
bends: loop("bends"),
|
|
2720
|
+
bendCurve: loop("bendCurve"),
|
|
2721
|
+
curved: (_want, i) => (isLoop(i) ? (edgeValues.curved?.[i] ?? null) : true),
|
|
2722
|
+
},
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
1070
2725
|
const VIS_WRITTEN = {
|
|
1071
2726
|
node: {
|
|
1072
|
-
width: visSize,
|
|
1073
|
-
height: visSize,
|
|
2727
|
+
width: (want) => visSize(want) ?? visFitBack("width")(want),
|
|
2728
|
+
height: (want) => visShapeFactBack("height")(want) ?? visSize(want) ?? visFitBack("height")(want),
|
|
2729
|
+
labelOffsetY: visShapeFactBack("labelOffsetY"),
|
|
2730
|
+
labelFit: visFitBack("labelFit"),
|
|
2731
|
+
labelPaddingX: visFitBack("labelPaddingX"),
|
|
2732
|
+
labelPaddingY: visFitBack("labelPaddingY"),
|
|
2733
|
+
labelFitSquare: visFitBack("labelFitSquare"),
|
|
2734
|
+
// the writer writes hex colors, whose alpha vis-network's node opacity replaces, the border's too
|
|
2735
|
+
borderOpacity: (want) => {
|
|
2736
|
+
const opacity = want("opacity");
|
|
2737
|
+
return typeof opacity === "number" ? opacity : want("borderOpacity");
|
|
2738
|
+
},
|
|
1074
2739
|
shape: (want) => {
|
|
1075
2740
|
const s = want("shape");
|
|
1076
2741
|
if (typeof s !== "string") {
|
|
@@ -1079,6 +2744,17 @@ const VIS_WRITTEN = {
|
|
|
1079
2744
|
const site = { notes: new StyleNotes(), target: "node", property: "shape" };
|
|
1080
2745
|
return canonEnum("shape", visShape(s)[0], "vis", site);
|
|
1081
2746
|
},
|
|
2747
|
+
labelPosition: (want) => {
|
|
2748
|
+
const s = want("shape");
|
|
2749
|
+
if (typeof s !== "string") {
|
|
2750
|
+
return null;
|
|
2751
|
+
}
|
|
2752
|
+
const shape = visShape(s)[0];
|
|
2753
|
+
if (shape === "database") {
|
|
2754
|
+
return "inside-top";
|
|
2755
|
+
}
|
|
2756
|
+
return VIS_SIZED.has(shape) ? "bottom" : "center";
|
|
2757
|
+
},
|
|
1082
2758
|
},
|
|
1083
2759
|
edge: {
|
|
1084
2760
|
lineStyle: (want) => {
|
|
@@ -1086,7 +2762,12 @@ const VIS_WRITTEN = {
|
|
|
1086
2762
|
if (s === null) {
|
|
1087
2763
|
return null;
|
|
1088
2764
|
}
|
|
1089
|
-
|
|
2765
|
+
// written as `dashes: true`, vis-network's [5, 5], which a wide edge's round caps close up
|
|
2766
|
+
if (s === "solid") {
|
|
2767
|
+
return "solid";
|
|
2768
|
+
}
|
|
2769
|
+
const width = want("width");
|
|
2770
|
+
return visDashStyle([5, 5], typeof width === "number" ? width : 1, true);
|
|
1090
2771
|
},
|
|
1091
2772
|
targetArrow: (want) => arrowBack(want("targetArrow")),
|
|
1092
2773
|
sourceArrow: (want) => arrowBack(want("sourceArrow")),
|
|
@@ -1134,8 +2815,8 @@ export const NATIVE = {
|
|
|
1134
2815
|
edge: cyjsProperties(CYJS_EDGE_PROPERTIES, ["curved", "labelItalic", "visible"]),
|
|
1135
2816
|
},
|
|
1136
2817
|
graphology: {
|
|
1137
|
-
node: ["fill", "fillOpacity", "width", "height", "label"
|
|
1138
|
-
edge: ["color", "opacity", "width", "label"
|
|
2818
|
+
node: ["fill", "fillOpacity", "width", "height", "label"],
|
|
2819
|
+
edge: ["color", "opacity", "width", "label"],
|
|
1139
2820
|
},
|
|
1140
2821
|
vis: {
|
|
1141
2822
|
node: [
|
|
@@ -1639,9 +3320,11 @@ function writeGraphology(out, input, approximated, replaced) {
|
|
|
1639
3320
|
const colors = p(target, fill);
|
|
1640
3321
|
const alphas = p(target, opacity);
|
|
1641
3322
|
writeKey(table, "color", colors.map((c, i) => cssColor(c, alphas[i])), replaced);
|
|
1642
|
-
writeKey(table, "size", target === "node"
|
|
3323
|
+
writeKey(table, "size", target === "node"
|
|
3324
|
+
? nodeSizes(p("node", "width"), p("node", "height"), approximated).map((d) => d === null ? null : d / 2)
|
|
3325
|
+
: // sigma draws an edge half as wide as its size (readSigmaRow)
|
|
3326
|
+
p("edge", "width").map((w) => (typeof w === "number" ? w * 2 : w)), replaced);
|
|
1643
3327
|
writeKey(table, "label", p(target, "label"), replaced);
|
|
1644
|
-
writeKey(table, "hidden", p(target, "visible").map((v) => (v === false ? true : null)), replaced);
|
|
1645
3328
|
}
|
|
1646
3329
|
}
|
|
1647
3330
|
/**
|
|
@@ -1651,6 +3334,21 @@ function writeGraphology(out, input, approximated, replaced) {
|
|
|
1651
3334
|
* @param approximated - where approximations are counted
|
|
1652
3335
|
* @param replaced - where replaced data values are counted
|
|
1653
3336
|
*/
|
|
3337
|
+
/**
|
|
3338
|
+
* vis.js: a scaling range of each written size for the elements with a `value`, which vis-network would otherwise
|
|
3339
|
+
* map onto `scaling` instead of drawing the size written (a node's radius, an edge's width).
|
|
3340
|
+
* @param table - the node or edge table being written
|
|
3341
|
+
* @param sizes - each element's written size, or null
|
|
3342
|
+
* @param replaced - the data values replaced so far
|
|
3343
|
+
*/
|
|
3344
|
+
function keepScaled(table, sizes, replaced) {
|
|
3345
|
+
const value = table.get("value");
|
|
3346
|
+
if (value === null) {
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
const scaling = sizes.map((v, i) => typeof v !== "number" || typeof plainCell(value.value(i)) !== "number" ? null : { min: v, max: v });
|
|
3350
|
+
writeKey(table, "scaling", scaling, replaced);
|
|
3351
|
+
}
|
|
1654
3352
|
function writeVis(out, input, approximated, replaced) {
|
|
1655
3353
|
const p = (target, property) => input.picture(target, property);
|
|
1656
3354
|
const font = (target) => {
|
|
@@ -1693,7 +3391,9 @@ function writeVis(out, input, approximated, replaced) {
|
|
|
1693
3391
|
}), replaced);
|
|
1694
3392
|
const sizes = nodeSizes(p("node", "width"), p("node", "height"), approximated);
|
|
1695
3393
|
const shapes = p("node", "shape").map((s, i) => {
|
|
1696
|
-
const [
|
|
3394
|
+
const [written, exact] = typeof s === "string" ? visShape(s) : [null, true];
|
|
3395
|
+
// an ellipse with no size of its own is vis-network's label-sized `ellipse`, one with a size its `dot`
|
|
3396
|
+
const shape = written === "dot" && s === "ellipse" && sizes[i] === null ? "ellipse" : written;
|
|
1697
3397
|
if (!exact) {
|
|
1698
3398
|
approximated.add("node", "shape", s);
|
|
1699
3399
|
}
|
|
@@ -1703,7 +3403,11 @@ function writeVis(out, input, approximated, replaced) {
|
|
|
1703
3403
|
return shape;
|
|
1704
3404
|
});
|
|
1705
3405
|
writeKey(out.nodes, "shape", shapes, replaced);
|
|
1706
|
-
|
|
3406
|
+
// vis-network draws `size` as a radius
|
|
3407
|
+
const radii = sizes.map((d) => (d === null ? null : d / 2));
|
|
3408
|
+
writeKey(out.nodes, "size", radii, replaced);
|
|
3409
|
+
// a node's `value` would scale its size again on reading: a scaling range of its own radius keeps it
|
|
3410
|
+
keepScaled(out.nodes, radii, replaced);
|
|
1707
3411
|
writeKey(out.nodes, "borderWidth", p("node", "borderWidth"), replaced);
|
|
1708
3412
|
writeKey(out.nodes, "opacity", p("node", "opacity"), replaced);
|
|
1709
3413
|
label(out.nodes, "node");
|
|
@@ -1719,7 +3423,10 @@ function writeVis(out, input, approximated, replaced) {
|
|
|
1719
3423
|
}
|
|
1720
3424
|
return Object.keys(c).length === 0 ? null : c;
|
|
1721
3425
|
}), replaced);
|
|
1722
|
-
|
|
3426
|
+
const widths = p("edge", "width");
|
|
3427
|
+
writeKey(out.edges, "width", widths, replaced);
|
|
3428
|
+
// an edge's `value` would scale its width again on reading: a scaling range of its own width keeps it
|
|
3429
|
+
keepScaled(out.edges, widths, replaced);
|
|
1723
3430
|
writeKey(out.edges, "dashes", p("edge", "lineStyle").map((s) => {
|
|
1724
3431
|
if (typeof s !== "string") {
|
|
1725
3432
|
return null;
|