@graphty/graph-io 0.3.48 → 0.3.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{approximations-C2riR4NT.js → approximations-Bzn3L-je.js} +2 -2
- package/dist/chunks/{approximations-C2riR4NT.js.map → approximations-Bzn3L-je.js.map} +1 -1
- package/dist/chunks/bends-BGXjv_U4.js +129 -0
- package/dist/chunks/bends-BGXjv_U4.js.map +1 -0
- package/dist/chunks/{block-Cu22ewuS.js → block-iptrlMBb.js} +906 -164
- package/dist/chunks/block-iptrlMBb.js.map +1 -0
- package/dist/chunks/choose-CczZJaX_.js +717 -0
- package/dist/chunks/choose-CczZJaX_.js.map +1 -0
- package/dist/chunks/{codes-CV_Bhcyi.js → codes-BE2xir2k.js} +6 -2
- package/dist/chunks/codes-BE2xir2k.js.map +1 -0
- package/dist/chunks/{constants-D0LqcsLJ.js → constants-CKkYLPtt.js} +2 -2
- package/dist/chunks/{constants-D0LqcsLJ.js.map → constants-CKkYLPtt.js.map} +1 -1
- package/dist/chunks/{context-cgmmOxZ2.js → context-C3p0RQxR.js} +5 -4
- package/dist/chunks/{context-cgmmOxZ2.js.map → context-C3p0RQxR.js.map} +1 -1
- package/dist/chunks/{convert-BcQ8_jMo.js → convert-Ct2zelbQ.js} +7 -2
- package/dist/chunks/convert-Ct2zelbQ.js.map +1 -0
- package/dist/chunks/{credentials-BiQsnMTb.js → credentials-DXHx7nwR.js} +16 -14
- package/dist/chunks/credentials-DXHx7nwR.js.map +1 -0
- package/dist/chunks/{cx-export-BEeF6ymo.js → cx-export-Bt6NoqTQ.js} +3 -3
- package/dist/chunks/{cx-export-BEeF6ymo.js.map → cx-export-Bt6NoqTQ.js.map} +1 -1
- package/dist/chunks/descriptor-B28U25x4.js +60 -0
- package/dist/chunks/descriptor-B28U25x4.js.map +1 -0
- package/dist/chunks/{descriptor-BJ-W0Jd-.js → descriptor-DLA5kINx.js} +7 -2
- package/dist/chunks/descriptor-DLA5kINx.js.map +1 -0
- package/dist/chunks/{emit-CD8xnY8s.js → emit-BwHQkZle.js} +1660 -133
- package/dist/chunks/emit-BwHQkZle.js.map +1 -0
- package/dist/chunks/{escape-DH2Egt-l.js → escape-CuCIzNMB.js} +8 -8
- package/dist/chunks/{escape-DH2Egt-l.js.map → escape-CuCIzNMB.js.map} +1 -1
- package/dist/chunks/{export-CZ7VRm4I.js → export-Cha4dRFz.js} +45 -387
- package/dist/chunks/export-Cha4dRFz.js.map +1 -0
- package/dist/chunks/{exporter-Dcw7Ldtq.js → exporter-BP6otMO6.js} +6 -6
- package/dist/chunks/{exporter-Dcw7Ldtq.js.map → exporter-BP6otMO6.js.map} +1 -1
- package/dist/chunks/{exporter-bxxjOrDZ.js → exporter-D2CETOMK.js} +1222 -156
- package/dist/chunks/exporter-D2CETOMK.js.map +1 -0
- package/dist/chunks/{exporter-BIdaoIgh.js → exporter-D9yMtocA.js} +7 -7
- package/dist/chunks/{exporter-BIdaoIgh.js.map → exporter-D9yMtocA.js.map} +1 -1
- package/dist/chunks/{exporter-DtVU80Fa.js → exporter-_jrBTnos.js} +9 -8
- package/dist/chunks/{exporter-DtVU80Fa.js.map → exporter-_jrBTnos.js.map} +1 -1
- package/dist/chunks/{exporter-ChjL5fmP.js → exporter-nDfxIK5D.js} +9 -8
- package/dist/chunks/{exporter-ChjL5fmP.js.map → exporter-nDfxIK5D.js.map} +1 -1
- package/dist/chunks/format-BXiAb-1h.js +408 -0
- package/dist/chunks/format-BXiAb-1h.js.map +1 -0
- package/dist/chunks/{generic-CP6YD34u.js → generic-ymWCETAJ.js} +387 -31
- package/dist/chunks/generic-ymWCETAJ.js.map +1 -0
- package/dist/chunks/{importer-e3huMZbm.js → importer-B92x1xY5.js} +14 -10
- package/dist/chunks/importer-B92x1xY5.js.map +1 -0
- package/dist/chunks/{importer-B87_fmVx.js → importer-CT9mJ1nn.js} +326 -84
- package/dist/chunks/importer-CT9mJ1nn.js.map +1 -0
- package/dist/chunks/{importer-CB_zV5_L.js → importer-DaLTjbds.js} +116 -1102
- package/dist/chunks/importer-DaLTjbds.js.map +1 -0
- package/dist/chunks/{importer-DPZDN40-.js → importer-GREKMdIf.js} +25 -17
- package/dist/chunks/importer-GREKMdIf.js.map +1 -0
- package/dist/chunks/{importer-Bji2MeWr.js → importer-PqaaKKnY.js} +13 -10
- package/dist/chunks/{importer-Bji2MeWr.js.map → importer-PqaaKKnY.js.map} +1 -1
- package/dist/chunks/{importer-Cyy6UseH.js → importer-Vn5QvmmJ.js} +66 -13
- package/dist/chunks/importer-Vn5QvmmJ.js.map +1 -0
- package/dist/chunks/{importer-CVT-JyDE.js → importer-vRq4h3j5.js} +1469 -356
- package/dist/chunks/importer-vRq4h3j5.js.map +1 -0
- package/dist/chunks/{input-Ccg9I22A.js → input-CAxt_aV-.js} +4 -4
- package/dist/chunks/{input-Ccg9I22A.js.map → input-CAxt_aV-.js.map} +1 -1
- package/dist/chunks/{json-elements-By172SVq.js → json-elements-Cq76WjnZ.js} +4 -4
- package/dist/chunks/{json-elements-By172SVq.js.map → json-elements-Cq76WjnZ.js.map} +1 -1
- package/dist/chunks/{lexicon-u2SI5xcx.js → lexicon-Clf4QIyl.js} +282 -74
- package/dist/chunks/lexicon-Clf4QIyl.js.map +1 -0
- package/dist/chunks/{ontology-export-BwgMyuF0.js → ontology-export-OMo91mb0.js} +4 -4
- package/dist/chunks/{ontology-export-BwgMyuF0.js.map → ontology-export-OMo91mb0.js.map} +1 -1
- package/dist/chunks/{records-Cgvxz-Vx.js → records-NJiYvekn.js} +2 -2
- package/dist/chunks/{records-Cgvxz-Vx.js.map → records-NJiYvekn.js.map} +1 -1
- package/dist/chunks/{report-CgdsL9Hb.js → report-CwMfYa9J.js} +23 -23
- package/dist/chunks/{report-CgdsL9Hb.js.map → report-CwMfYa9J.js.map} +1 -1
- package/dist/chunks/round-trip-Zm3_ivgq.js +79 -0
- package/dist/chunks/round-trip-Zm3_ivgq.js.map +1 -0
- package/dist/chunks/{sigv4-LkAzsLAu.js → sigv4-EokFic1V.js} +5 -5
- package/dist/chunks/sigv4-EokFic1V.js.map +1 -0
- package/dist/chunks/{styles-CaABMoO1.js → styles-CCs214_3.js} +1237 -138
- package/dist/chunks/styles-CCs214_3.js.map +1 -0
- package/dist/chunks/styles-Cm62yyQX.js +2812 -0
- package/dist/chunks/styles-Cm62yyQX.js.map +1 -0
- package/dist/chunks/styles-DPQQIig4.js +1928 -0
- package/dist/chunks/styles-DPQQIig4.js.map +1 -0
- package/dist/chunks/{styles-yed-Cs1nmrjn.js → styles-yed-UMFdNqze.js} +503 -172
- package/dist/chunks/styles-yed-UMFdNqze.js.map +1 -0
- package/dist/chunks/{text-BuKQVMz2.js → text-ystE2bYi.js} +2 -2
- package/dist/chunks/{text-BuKQVMz2.js.map → text-ystE2bYi.js.map} +1 -1
- package/dist/csv.js +7 -7
- package/dist/cx.js +2 -2
- package/dist/cx2.js +2 -2
- package/dist/cys.js +1 -1
- package/dist/dot.js +5 -4
- package/dist/dot.js.map +1 -1
- package/dist/gexf.js +42 -12
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +847 -193
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +232 -220
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +3 -3
- package/dist/json.js +3 -2
- package/dist/neo4j.js +6 -6
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/positions.js +45 -3
- package/dist/positions.js.map +1 -1
- package/dist/sources-cypher.js +138 -23
- package/dist/sources-cypher.js.map +1 -1
- package/dist/sources-ndex.js +2 -2
- package/dist/sources-rest.js +16 -6
- package/dist/sources-rest.js.map +1 -1
- package/dist/sources-sparql.d.ts +1 -0
- package/dist/sources-sparql.js +405 -0
- package/dist/sources-sparql.js.map +1 -0
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +5 -1
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx/styles.d.ts +24 -1
- package/dist/src/formats/cx/styles.d.ts.map +1 -1
- package/dist/src/formats/cx/styles.js +117 -11
- package/dist/src/formats/cx/styles.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +1 -0
- package/dist/src/formats/cx2/importer.js.map +1 -1
- package/dist/src/formats/cx2/styles.d.ts.map +1 -1
- package/dist/src/formats/cx2/styles.js +3 -1
- package/dist/src/formats/cx2/styles.js.map +1 -1
- package/dist/src/formats/cys/importer.d.ts.map +1 -1
- package/dist/src/formats/cys/importer.js +1 -0
- package/dist/src/formats/cys/importer.js.map +1 -1
- package/dist/src/formats/cys/styles.d.ts +2 -0
- package/dist/src/formats/cys/styles.d.ts.map +1 -1
- package/dist/src/formats/cys/styles.js +15 -7
- package/dist/src/formats/cys/styles.js.map +1 -1
- package/dist/src/formats/dot/drawing.d.ts +98 -0
- package/dist/src/formats/dot/drawing.d.ts.map +1 -0
- package/dist/src/formats/dot/drawing.js +734 -0
- package/dist/src/formats/dot/drawing.js.map +1 -0
- package/dist/src/formats/dot/importer.d.ts.map +1 -1
- package/dist/src/formats/dot/importer.js +108 -31
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/dot/label-places.d.ts +34 -0
- package/dist/src/formats/dot/label-places.d.ts.map +1 -0
- package/dist/src/formats/dot/label-places.js +179 -0
- package/dist/src/formats/dot/label-places.js.map +1 -0
- package/dist/src/formats/dot/record.d.ts +50 -0
- package/dist/src/formats/dot/record.d.ts.map +1 -0
- package/dist/src/formats/dot/record.js +275 -0
- package/dist/src/formats/dot/record.js.map +1 -0
- package/dist/src/formats/dot/styles.d.ts +62 -2
- package/dist/src/formats/dot/styles.d.ts.map +1 -1
- package/dist/src/formats/dot/styles.js +1454 -119
- package/dist/src/formats/dot/styles.js.map +1 -1
- package/dist/src/formats/gexf/dialog-metrics.d.ts +15 -0
- package/dist/src/formats/gexf/dialog-metrics.d.ts.map +1 -0
- package/dist/src/formats/gexf/dialog-metrics.js +59 -0
- package/dist/src/formats/gexf/dialog-metrics.js.map +1 -0
- package/dist/src/formats/gexf/importer.d.ts +7 -0
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +41 -5
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/preview.d.ts +77 -0
- package/dist/src/formats/gexf/preview.d.ts.map +1 -0
- package/dist/src/formats/gexf/preview.js +191 -0
- package/dist/src/formats/gexf/preview.js.map +1 -0
- package/dist/src/formats/gexf/schema.d.ts.map +1 -1
- package/dist/src/formats/gexf/schema.js +2 -1
- package/dist/src/formats/gexf/schema.js.map +1 -1
- package/dist/src/formats/gexf/styles.d.ts +58 -31
- package/dist/src/formats/gexf/styles.d.ts.map +1 -1
- package/dist/src/formats/gexf/styles.js +848 -225
- package/dist/src/formats/gexf/styles.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts +5 -2
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +258 -71
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/index.d.ts +1 -1
- package/dist/src/formats/gml/index.d.ts.map +1 -1
- package/dist/src/formats/gml/index.js +1 -1
- package/dist/src/formats/gml/index.js.map +1 -1
- package/dist/src/formats/gml/positions.d.ts +7 -0
- package/dist/src/formats/gml/positions.d.ts.map +1 -1
- package/dist/src/formats/gml/positions.js +29 -7
- package/dist/src/formats/gml/positions.js.map +1 -1
- package/dist/src/formats/gml/styles.d.ts.map +1 -1
- package/dist/src/formats/gml/styles.js +753 -101
- package/dist/src/formats/gml/styles.js.map +1 -1
- package/dist/src/formats/graphml/styles-plain.d.ts.map +1 -1
- package/dist/src/formats/graphml/styles-plain.js +6 -10
- package/dist/src/formats/graphml/styles-plain.js.map +1 -1
- package/dist/src/formats/graphml/styles-yed.d.ts +21 -15
- package/dist/src/formats/graphml/styles-yed.d.ts.map +1 -1
- package/dist/src/formats/graphml/styles-yed.js +692 -161
- package/dist/src/formats/graphml/styles-yed.js.map +1 -1
- package/dist/src/formats/graphml/styles-yfiles3.d.ts +1 -1
- package/dist/src/formats/graphml/styles-yfiles3.d.ts.map +1 -1
- package/dist/src/formats/graphml/styles-yfiles3.js +429 -64
- package/dist/src/formats/graphml/styles-yfiles3.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts +3 -3
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +16 -6
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts +3 -2
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +39 -5
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +13 -2
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +42 -6
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/index.d.ts +1 -1
- package/dist/src/formats/json/index.d.ts.map +1 -1
- package/dist/src/formats/json/index.js +1 -1
- package/dist/src/formats/json/index.js.map +1 -1
- package/dist/src/formats/json/positions.d.ts +5 -2
- package/dist/src/formats/json/positions.d.ts.map +1 -1
- package/dist/src/formats/json/positions.js +20 -4
- package/dist/src/formats/json/positions.js.map +1 -1
- package/dist/src/formats/json/styles.d.ts +12 -1
- package/dist/src/formats/json/styles.d.ts.map +1 -1
- package/dist/src/formats/json/styles.js +1755 -136
- package/dist/src/formats/json/styles.js.map +1 -1
- package/dist/src/formats/pajek/draw.d.ts +96 -0
- package/dist/src/formats/pajek/draw.d.ts.map +1 -0
- package/dist/src/formats/pajek/draw.js +372 -0
- package/dist/src/formats/pajek/draw.js.map +1 -0
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +70 -51
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts +9 -0
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +233 -102
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/index.d.ts +1 -0
- package/dist/src/formats/pajek/index.d.ts.map +1 -1
- package/dist/src/formats/pajek/index.js.map +1 -1
- package/dist/src/formats/pajek/styles.d.ts +15 -7
- package/dist/src/formats/pajek/styles.d.ts.map +1 -1
- package/dist/src/formats/pajek/styles.js +1278 -114
- package/dist/src/formats/pajek/styles.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts +10 -1
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +26 -2
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +1 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +65 -5
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/styles.d.ts +23 -3
- package/dist/src/formats/xgmml/styles.d.ts.map +1 -1
- package/dist/src/formats/xgmml/styles.js +277 -60
- package/dist/src/formats/xgmml/styles.js.map +1 -1
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/positions/convert.d.ts.map +1 -1
- package/dist/src/positions/convert.js +11 -1
- package/dist/src/positions/convert.js.map +1 -1
- package/dist/src/positions/extract.d.ts.map +1 -1
- package/dist/src/positions/extract.js +53 -1
- package/dist/src/positions/extract.js.map +1 -1
- package/dist/src/positions/index.d.ts +1 -1
- package/dist/src/positions/index.d.ts.map +1 -1
- package/dist/src/positions/types.d.ts +59 -0
- package/dist/src/positions/types.d.ts.map +1 -1
- package/dist/src/sources/builtins.d.ts +1 -1
- package/dist/src/sources/builtins.d.ts.map +1 -1
- package/dist/src/sources/builtins.js +8 -2
- package/dist/src/sources/builtins.js.map +1 -1
- package/dist/src/sources/context.d.ts.map +1 -1
- package/dist/src/sources/context.js +1 -0
- package/dist/src/sources/context.js.map +1 -1
- package/dist/src/sources/cypher/descriptor.d.ts.map +1 -1
- package/dist/src/sources/cypher/descriptor.js +5 -0
- package/dist/src/sources/cypher/descriptor.js.map +1 -1
- package/dist/src/sources/cypher/index.d.ts.map +1 -1
- package/dist/src/sources/cypher/index.js +124 -18
- package/dist/src/sources/cypher/index.js.map +1 -1
- package/dist/src/sources/cypher/params.d.ts +1 -1
- package/dist/src/sources/cypher/params.d.ts.map +1 -1
- package/dist/src/sources/cypher/query-builder.d.ts +17 -0
- package/dist/src/sources/cypher/query-builder.d.ts.map +1 -1
- package/dist/src/sources/cypher/query-builder.js +31 -0
- package/dist/src/sources/cypher/query-builder.js.map +1 -1
- package/dist/src/sources/ids.d.ts +5 -0
- package/dist/src/sources/ids.d.ts.map +1 -1
- package/dist/src/sources/ids.js +5 -0
- package/dist/src/sources/ids.js.map +1 -1
- package/dist/src/sources/params.d.ts +3 -0
- package/dist/src/sources/params.d.ts.map +1 -1
- package/dist/src/sources/rest/descriptor.d.ts.map +1 -1
- package/dist/src/sources/rest/descriptor.js +5 -0
- package/dist/src/sources/rest/descriptor.js.map +1 -1
- package/dist/src/sources/rest/source.d.ts.map +1 -1
- package/dist/src/sources/rest/source.js +15 -0
- package/dist/src/sources/rest/source.js.map +1 -1
- package/dist/src/sources/sigv4.d.ts +5 -3
- package/dist/src/sources/sigv4.d.ts.map +1 -1
- package/dist/src/sources/sigv4.js +6 -4
- package/dist/src/sources/sigv4.js.map +1 -1
- package/dist/src/sources/sparql/descriptor.d.ts +10 -0
- package/dist/src/sources/sparql/descriptor.d.ts.map +1 -0
- package/dist/src/sources/sparql/descriptor.js +64 -0
- package/dist/src/sources/sparql/descriptor.js.map +1 -0
- package/dist/src/sources/sparql/index.d.ts +28 -0
- package/dist/src/sources/sparql/index.d.ts.map +1 -0
- package/dist/src/sources/sparql/index.js +314 -0
- package/dist/src/sources/sparql/index.js.map +1 -0
- package/dist/src/sources/sparql/ntriples.d.ts +69 -0
- package/dist/src/sources/sparql/ntriples.d.ts.map +1 -0
- package/dist/src/sources/sparql/ntriples.js +180 -0
- package/dist/src/sources/sparql/ntriples.js.map +1 -0
- package/dist/src/sources/sparql/params.d.ts +27 -0
- package/dist/src/sources/sparql/params.d.ts.map +1 -0
- package/dist/src/sources/sparql/params.js +5 -0
- package/dist/src/sources/sparql/params.js.map +1 -0
- package/dist/src/sources/sparql/query.d.ts +72 -0
- package/dist/src/sources/sparql/query.d.ts.map +1 -0
- package/dist/src/sources/sparql/query.js +113 -0
- package/dist/src/sources/sparql/query.js.map +1 -0
- package/dist/src/sources/types.d.ts +31 -0
- package/dist/src/sources/types.d.ts.map +1 -1
- package/dist/src/styles/annotations.d.ts +61 -5
- package/dist/src/styles/annotations.d.ts.map +1 -1
- package/dist/src/styles/annotations.js +130 -22
- package/dist/src/styles/annotations.js.map +1 -1
- package/dist/src/styles/canon/bends.d.ts +46 -0
- package/dist/src/styles/canon/bends.d.ts.map +1 -1
- package/dist/src/styles/canon/bends.js +123 -0
- package/dist/src/styles/canon/bends.js.map +1 -1
- package/dist/src/styles/canon/color-tables.d.ts +6 -1
- package/dist/src/styles/canon/color-tables.d.ts.map +1 -1
- package/dist/src/styles/canon/color-tables.js +10 -4
- package/dist/src/styles/canon/color-tables.js.map +1 -1
- package/dist/src/styles/canon/color.d.ts +3 -1
- package/dist/src/styles/canon/color.d.ts.map +1 -1
- package/dist/src/styles/canon/color.js +103 -3
- package/dist/src/styles/canon/color.js.map +1 -1
- package/dist/src/styles/canon/enums.d.ts +2 -2
- package/dist/src/styles/canon/enums.d.ts.map +1 -1
- package/dist/src/styles/canon/enums.js +81 -7
- package/dist/src/styles/canon/enums.js.map +1 -1
- package/dist/src/styles/canon/graphviz.d.ts +12 -3
- package/dist/src/styles/canon/graphviz.d.ts.map +1 -1
- package/dist/src/styles/canon/graphviz.js +57 -9
- package/dist/src/styles/canon/graphviz.js.map +1 -1
- package/dist/src/styles/canon/number.d.ts +8 -0
- package/dist/src/styles/canon/number.d.ts.map +1 -1
- package/dist/src/styles/canon/number.js +18 -0
- package/dist/src/styles/canon/number.js.map +1 -1
- package/dist/src/styles/canon/text.d.ts +29 -2
- package/dist/src/styles/canon/text.d.ts.map +1 -1
- package/dist/src/styles/canon/text.js +156 -16
- package/dist/src/styles/canon/text.js.map +1 -1
- package/dist/src/styles/codes.d.ts +2 -0
- package/dist/src/styles/codes.d.ts.map +1 -1
- package/dist/src/styles/codes.js +2 -0
- package/dist/src/styles/codes.js.map +1 -1
- package/dist/src/styles/cyjs/cascade.d.ts.map +1 -1
- package/dist/src/styles/cyjs/cascade.js +1015 -79
- package/dist/src/styles/cyjs/cascade.js.map +1 -1
- package/dist/src/styles/cyjs/defaults-table.d.ts.map +1 -1
- package/dist/src/styles/cyjs/defaults-table.js +3 -0
- package/dist/src/styles/cyjs/defaults-table.js.map +1 -1
- package/dist/src/styles/cyjs/emit.d.ts.map +1 -1
- package/dist/src/styles/cyjs/emit.js +202 -9
- package/dist/src/styles/cyjs/emit.js.map +1 -1
- package/dist/src/styles/cyjs/face.d.ts +20 -0
- package/dist/src/styles/cyjs/face.d.ts.map +1 -0
- package/dist/src/styles/cyjs/face.js +332 -0
- package/dist/src/styles/cyjs/face.js.map +1 -0
- package/dist/src/styles/cyjs/mappers.d.ts +44 -1
- package/dist/src/styles/cyjs/mappers.d.ts.map +1 -1
- package/dist/src/styles/cyjs/mappers.js +461 -26
- package/dist/src/styles/cyjs/mappers.js.map +1 -1
- package/dist/src/styles/cyjs/routes.d.ts +53 -0
- package/dist/src/styles/cyjs/routes.d.ts.map +1 -0
- package/dist/src/styles/cyjs/routes.js +360 -0
- package/dist/src/styles/cyjs/routes.js.map +1 -0
- package/dist/src/styles/cytoscape/charts.d.ts +23 -0
- package/dist/src/styles/cytoscape/charts.d.ts.map +1 -0
- package/dist/src/styles/cytoscape/charts.js +560 -0
- package/dist/src/styles/cytoscape/charts.js.map +1 -0
- package/dist/src/styles/cytoscape/emit.d.ts.map +1 -1
- package/dist/src/styles/cytoscape/emit.js +156 -48
- package/dist/src/styles/cytoscape/emit.js.map +1 -1
- package/dist/src/styles/cytoscape/lexicon.d.ts +91 -3
- package/dist/src/styles/cytoscape/lexicon.d.ts.map +1 -1
- package/dist/src/styles/cytoscape/lexicon.js +387 -77
- package/dist/src/styles/cytoscape/lexicon.js.map +1 -1
- package/dist/src/styles/cytoscape/mappings.d.ts +30 -0
- package/dist/src/styles/cytoscape/mappings.d.ts.map +1 -1
- package/dist/src/styles/cytoscape/mappings.js +92 -0
- package/dist/src/styles/cytoscape/mappings.js.map +1 -1
- package/dist/src/styles/cytoscape/resolve.d.ts +42 -2
- package/dist/src/styles/cytoscape/resolve.d.ts.map +1 -1
- package/dist/src/styles/cytoscape/resolve.js +623 -17
- package/dist/src/styles/cytoscape/resolve.js.map +1 -1
- package/dist/src/styles/defaults.d.ts +46 -26
- package/dist/src/styles/defaults.d.ts.map +1 -1
- package/dist/src/styles/defaults.js +308 -71
- package/dist/src/styles/defaults.js.map +1 -1
- package/dist/src/styles/index.d.ts +4 -3
- package/dist/src/styles/index.d.ts.map +1 -1
- package/dist/src/styles/index.js +3 -2
- package/dist/src/styles/index.js.map +1 -1
- package/dist/src/styles/internal.d.ts +1 -0
- package/dist/src/styles/internal.d.ts.map +1 -1
- package/dist/src/styles/internal.js +1 -0
- package/dist/src/styles/internal.js.map +1 -1
- package/dist/src/styles/io/generic.d.ts.map +1 -1
- package/dist/src/styles/io/generic.js +56 -16
- package/dist/src/styles/io/generic.js.map +1 -1
- package/dist/src/styles/io/round-trip.d.ts +56 -0
- package/dist/src/styles/io/round-trip.d.ts.map +1 -0
- package/dist/src/styles/io/round-trip.js +138 -0
- package/dist/src/styles/io/round-trip.js.map +1 -0
- package/dist/src/styles/io/writer.d.ts.map +1 -1
- package/dist/src/styles/io/writer.js +6 -1
- package/dist/src/styles/io/writer.js.map +1 -1
- package/dist/src/styles/notes.d.ts.map +1 -1
- package/dist/src/styles/notes.js +1 -0
- package/dist/src/styles/notes.js.map +1 -1
- package/dist/src/styles/plan/features.d.ts +1 -1
- package/dist/src/styles/plan/features.d.ts.map +1 -1
- package/dist/src/styles/plan/features.js +22 -2
- package/dist/src/styles/plan/features.js.map +1 -1
- package/dist/src/styles/plan/index.d.ts +1 -1
- package/dist/src/styles/plan/index.d.ts.map +1 -1
- package/dist/src/styles/plan/index.js +1 -1
- package/dist/src/styles/plan/index.js.map +1 -1
- package/dist/src/styles/plan/plan.d.ts.map +1 -1
- package/dist/src/styles/plan/plan.js +61 -4
- package/dist/src/styles/plan/plan.js.map +1 -1
- package/dist/src/styles/plan/resolve.d.ts +11 -0
- package/dist/src/styles/plan/resolve.d.ts.map +1 -1
- package/dist/src/styles/plan/resolve.js +40 -4
- package/dist/src/styles/plan/resolve.js.map +1 -1
- package/dist/src/styles/slots.d.ts +14 -0
- package/dist/src/styles/slots.d.ts.map +1 -0
- package/dist/src/styles/slots.js +20 -0
- package/dist/src/styles/slots.js.map +1 -0
- package/dist/src/styles/types.d.ts +622 -6
- package/dist/src/styles/types.d.ts.map +1 -1
- package/dist/src/styles/vocabulary.d.ts +3 -2
- package/dist/src/styles/vocabulary.d.ts.map +1 -1
- package/dist/src/styles/vocabulary.js +203 -0
- package/dist/src/styles/vocabulary.js.map +1 -1
- package/dist/styles.js +30 -239
- package/dist/styles.js.map +1 -1
- package/dist/xgmml.js +3 -3
- package/package.json +27 -7
- package/src/formats/cx/importer.ts +6 -1
- package/src/formats/cx/styles.ts +143 -9
- package/src/formats/cx2/importer.ts +1 -0
- package/src/formats/cx2/styles.ts +3 -1
- package/src/formats/cys/importer.ts +1 -0
- package/src/formats/cys/styles.ts +27 -5
- package/src/formats/dot/drawing.ts +799 -0
- package/src/formats/dot/importer.ts +120 -35
- package/src/formats/dot/label-places.ts +228 -0
- package/src/formats/dot/record.ts +336 -0
- package/src/formats/dot/styles.ts +1654 -153
- package/src/formats/gexf/dialog-metrics.ts +61 -0
- package/src/formats/gexf/importer.ts +50 -5
- package/src/formats/gexf/preview.ts +217 -0
- package/src/formats/gexf/schema.ts +2 -1
- package/src/formats/gexf/styles.ts +1099 -245
- package/src/formats/gml/importer.ts +291 -76
- package/src/formats/gml/index.ts +1 -1
- package/src/formats/gml/positions.ts +29 -7
- package/src/formats/gml/styles.ts +851 -109
- package/src/formats/graphml/styles-plain.ts +6 -15
- package/src/formats/graphml/styles-yed.ts +762 -173
- package/src/formats/graphml/styles-yfiles3.ts +485 -63
- package/src/formats/graphml/yfiles.ts +19 -6
- package/src/formats/json/dialect.ts +41 -5
- package/src/formats/json/importer.ts +59 -7
- package/src/formats/json/index.ts +1 -0
- package/src/formats/json/positions.ts +22 -5
- package/src/formats/json/styles.ts +2041 -185
- package/src/formats/pajek/draw.ts +458 -0
- package/src/formats/pajek/exporter.ts +81 -49
- package/src/formats/pajek/importer.ts +278 -103
- package/src/formats/pajek/index.ts +1 -0
- package/src/formats/pajek/styles.ts +1381 -133
- package/src/formats/pajek/syntax.ts +27 -2
- package/src/formats/xgmml/emit.ts +73 -5
- package/src/formats/xgmml/styles.ts +330 -71
- package/src/index.ts +5 -1
- package/src/positions/convert.ts +12 -1
- package/src/positions/extract.ts +55 -1
- package/src/positions/index.ts +1 -0
- package/src/positions/types.ts +60 -0
- package/src/sources/builtins.ts +8 -1
- package/src/sources/context.ts +1 -0
- package/src/sources/cypher/descriptor.ts +7 -1
- package/src/sources/cypher/index.ts +148 -18
- package/src/sources/cypher/params.ts +1 -1
- package/src/sources/cypher/query-builder.ts +33 -0
- package/src/sources/ids.ts +6 -0
- package/src/sources/params.ts +3 -0
- package/src/sources/rest/descriptor.ts +5 -0
- package/src/sources/rest/source.ts +16 -0
- package/src/sources/sigv4.ts +6 -4
- package/src/sources/sparql/descriptor.ts +67 -0
- package/src/sources/sparql/index.ts +356 -0
- package/src/sources/sparql/ntriples.ts +219 -0
- package/src/sources/sparql/params.ts +28 -0
- package/src/sources/sparql/query.ts +162 -0
- package/src/sources/types.ts +35 -0
- package/src/styles/annotations.ts +201 -27
- package/src/styles/canon/bends.ts +137 -0
- package/src/styles/canon/color-tables.ts +10 -4
- package/src/styles/canon/color.ts +105 -3
- package/src/styles/canon/enums.ts +98 -8
- package/src/styles/canon/graphviz.ts +71 -10
- package/src/styles/canon/number.ts +19 -0
- package/src/styles/canon/text.ts +187 -16
- package/src/styles/codes.ts +2 -0
- package/src/styles/cyjs/cascade.ts +1160 -84
- package/src/styles/cyjs/defaults-table.ts +3 -0
- package/src/styles/cyjs/emit.ts +233 -13
- package/src/styles/cyjs/face.ts +372 -0
- package/src/styles/cyjs/mappers.ts +492 -27
- package/src/styles/cyjs/routes.ts +432 -0
- package/src/styles/cytoscape/charts.ts +684 -0
- package/src/styles/cytoscape/emit.ts +170 -48
- package/src/styles/cytoscape/lexicon.ts +431 -78
- package/src/styles/cytoscape/mappings.ts +108 -0
- package/src/styles/cytoscape/resolve.ts +741 -17
- package/src/styles/defaults.ts +417 -108
- package/src/styles/index.ts +5 -0
- package/src/styles/internal.ts +1 -0
- package/src/styles/io/generic.ts +79 -23
- package/src/styles/io/round-trip.ts +202 -0
- package/src/styles/io/writer.ts +6 -1
- package/src/styles/notes.ts +1 -0
- package/src/styles/plan/features.ts +23 -2
- package/src/styles/plan/index.ts +1 -1
- package/src/styles/plan/plan.ts +69 -5
- package/src/styles/plan/resolve.ts +47 -4
- package/src/styles/slots.ts +25 -0
- package/src/styles/types.ts +633 -5
- package/src/styles/vocabulary.ts +211 -2
- package/dist/chunks/block-Cu22ewuS.js.map +0 -1
- package/dist/chunks/choose-Cdx6Enl2.js +0 -57
- package/dist/chunks/choose-Cdx6Enl2.js.map +0 -1
- package/dist/chunks/codes-CV_Bhcyi.js.map +0 -1
- package/dist/chunks/convert-BcQ8_jMo.js.map +0 -1
- package/dist/chunks/credentials-BiQsnMTb.js.map +0 -1
- package/dist/chunks/descriptor-BJ-W0Jd-.js.map +0 -1
- package/dist/chunks/emit-CD8xnY8s.js.map +0 -1
- package/dist/chunks/export-CZ7VRm4I.js.map +0 -1
- package/dist/chunks/exporter-bxxjOrDZ.js.map +0 -1
- package/dist/chunks/format-BMS-1Nxb.js +0 -64
- package/dist/chunks/format-BMS-1Nxb.js.map +0 -1
- package/dist/chunks/generic-CP6YD34u.js.map +0 -1
- package/dist/chunks/importer-B87_fmVx.js.map +0 -1
- package/dist/chunks/importer-CB_zV5_L.js.map +0 -1
- package/dist/chunks/importer-CVT-JyDE.js.map +0 -1
- package/dist/chunks/importer-Cyy6UseH.js.map +0 -1
- package/dist/chunks/importer-DPZDN40-.js.map +0 -1
- package/dist/chunks/importer-e3huMZbm.js.map +0 -1
- package/dist/chunks/lexicon-u2SI5xcx.js.map +0 -1
- package/dist/chunks/sigv4-LkAzsLAu.js.map +0 -1
- package/dist/chunks/styles-CaABMoO1.js.map +0 -1
- package/dist/chunks/styles-P_gmFAOi.js +0 -770
- package/dist/chunks/styles-P_gmFAOi.js.map +0 -1
- package/dist/chunks/styles-yed-Cs1nmrjn.js.map +0 -1
- package/dist/chunks/writer-DvHTl36J.js +0 -449
- package/dist/chunks/writer-DvHTl36J.js.map +0 -1
|
@@ -0,0 +1,2812 @@
|
|
|
1
|
+
import { f as isWritableDotText } from "./escape-CuCIzNMB.js";
|
|
2
|
+
import { i as inferTextDtype } from "./text-ystE2bYi.js";
|
|
3
|
+
import { f as flattenCubic, c as canonNodeOffsets, d as canonBends } from "./bends-BGXjv_U4.js";
|
|
4
|
+
import { o as styleFingerprint, b as STYLE_ISSUE, z as styleTag, A as writeStyleBlock, B as styleBlockLosses, r as resolveStyles, S as STYLE_BLOCK_NAME, C as canonicalFor, D as degreeValues, E as dataCell, y as toolDefaults, p as plainCell, i as num6, h as StyleNotes, n as noteAt, F as htmlLabelText, G as dotLabelText, H as DOT_POINTS_PER_INCH, M as MAX_STYLE_VALUE_LENGTH, c as canonNumber, a as canonEnum, d as canonColor, e as colorOpacity, I as recordLabelText, J as dotLabelAlign, K as htmlLabelSpans } from "./block-iptrlMBb.js";
|
|
5
|
+
import { S as StyleApproximations } from "./approximations-Bzn3L-je.js";
|
|
6
|
+
import { t as takeFromBlock, e as elementValues } from "./round-trip-Zm3_ivgq.js";
|
|
7
|
+
import { p as plural, a as agree } from "./report-CwMfYa9J.js";
|
|
8
|
+
import { v as valueColumns } from "./styles-yed-UMFdNqze.js";
|
|
9
|
+
import { g as graphvizFont, G as GRAPHVIZ_GLYPH_SHAPES, a as GRAPHVIZ_POLYGON_SHAPES, b as isGraphvizBox, c as graphvizPolygonCorners, d as graphvizGlyph, e as GRAPHVIZ_DECORATED_BOXES, f as graphvizFontName } from "./choose-CczZJaX_.js";
|
|
10
|
+
const DOT_FORMAT = "dot";
|
|
11
|
+
const DOT_ORIGIN = Object.freeze({ format: DOT_FORMAT });
|
|
12
|
+
const CLUSTER_COLUMN = "graphty.cluster";
|
|
13
|
+
const PARENT_COLUMN = "graphty.parent";
|
|
14
|
+
const SOURCE_PORT_COLUMN = "graphty.sourcePort";
|
|
15
|
+
const TARGET_PORT_COLUMN = "graphty.targetPort";
|
|
16
|
+
const LABEL_ATTRIBUTE = "label";
|
|
17
|
+
const POS_ATTRIBUTE = "pos";
|
|
18
|
+
const POSITION_COLUMN = "graphty.position";
|
|
19
|
+
const KEY_ATTRIBUTE = "key";
|
|
20
|
+
const PIN_ATTRIBUTE = "pin";
|
|
21
|
+
const DOT_META_KEY = "dot";
|
|
22
|
+
const STYLE_ROLES = /* @__PURE__ */ new Set(["color", "size", "shape", "thickness", "hidden"]);
|
|
23
|
+
const TARGETS = ["node", "edge"];
|
|
24
|
+
function prepareStyleExport(snapshot, exporter, given, plain) {
|
|
25
|
+
const fp = styleFingerprint(snapshot);
|
|
26
|
+
const want = given.fingerprint;
|
|
27
|
+
if (fp.nodes !== want.nodes || fp.edges !== want.edges || fp.ids !== want.ids) {
|
|
28
|
+
return {
|
|
29
|
+
snapshot,
|
|
30
|
+
input: null,
|
|
31
|
+
notes: [],
|
|
32
|
+
refusal: Object.freeze({
|
|
33
|
+
code: STYLE_ISSUE.MISMATCH,
|
|
34
|
+
message: `the styles were read from or built for a different graph (${String(want.nodes)} nodes and ${String(want.edges)} edges, this graph has ${String(fp.nodes)} and ${String(fp.edges)}, or other node ids); nothing is written`,
|
|
35
|
+
column: null,
|
|
36
|
+
count: null
|
|
37
|
+
})
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const caps = exporter.styles?.capabilities ?? null;
|
|
41
|
+
let plan = writePlan(snapshot, exporter, given);
|
|
42
|
+
if (keepsStyleBlock(exporter)) {
|
|
43
|
+
let checked;
|
|
44
|
+
try {
|
|
45
|
+
checked = exporter.check(plan.snapshot, plan.input === null ? plain : { ...plain, styles: plan.input });
|
|
46
|
+
} catch {
|
|
47
|
+
checked = [];
|
|
48
|
+
}
|
|
49
|
+
const carried = withLostInputsCarried(given, snapshot, checked);
|
|
50
|
+
if (carried !== given) {
|
|
51
|
+
plan = writePlan(snapshot, exporter, carried);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
snapshot: plan.snapshot,
|
|
56
|
+
input: plan.input,
|
|
57
|
+
notes: lossNotes(plan.extracted, exporter, caps, plan.picture, snapshot),
|
|
58
|
+
refusal: null
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const CALL_WORK = /* @__PURE__ */ new WeakMap();
|
|
62
|
+
function sharedInExportCall(input, key, compute) {
|
|
63
|
+
if (!CALL_WORK.has(input)) {
|
|
64
|
+
return compute();
|
|
65
|
+
}
|
|
66
|
+
const done = CALL_WORK.get(input);
|
|
67
|
+
if (done?.key.length === key.length && done.key.every((k, i) => k === key[i])) {
|
|
68
|
+
return done.value;
|
|
69
|
+
}
|
|
70
|
+
const value = compute();
|
|
71
|
+
CALL_WORK.set(input, { key, value });
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function writePlan(snapshot, exporter, extracted) {
|
|
75
|
+
const caps = exporter.styles?.capabilities ?? null;
|
|
76
|
+
const pictures = /* @__PURE__ */ new Map();
|
|
77
|
+
const picture = (target, property) => {
|
|
78
|
+
const key = `${target}\0${property}`;
|
|
79
|
+
let values = pictures.get(key);
|
|
80
|
+
if (values === void 0) {
|
|
81
|
+
values = resolveStyles(extracted, target, property, snapshot);
|
|
82
|
+
pictures.set(key, values);
|
|
83
|
+
}
|
|
84
|
+
return values;
|
|
85
|
+
};
|
|
86
|
+
const tags = { node: /* @__PURE__ */ new Map(), edge: /* @__PURE__ */ new Map() };
|
|
87
|
+
for (const target of TARGETS) {
|
|
88
|
+
for (const [key, col] of extracted.columns[target]) {
|
|
89
|
+
tags[target].set(styleTag(target, key), col);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const block = writeStyleBlock(extracted, { snapshot });
|
|
93
|
+
const input = exporter.styles === void 0 ? null : { picture, block, tags, rules: extracted };
|
|
94
|
+
if (input !== null) {
|
|
95
|
+
CALL_WORK.set(input, null);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
snapshot: styledSnapshot(snapshot, caps, tags, block, extracted.styles.rules),
|
|
99
|
+
input,
|
|
100
|
+
extracted,
|
|
101
|
+
picture
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const VALUES_KEPT = /NODE_TABLE|NAME_CHANGED|ROLE_ASSUMED|STORAGE_CLASS|NUMBER_TYPE|INTEGRAL|WIDENED/;
|
|
105
|
+
function withLostInputsCarried(extracted, snapshot, notes) {
|
|
106
|
+
const lost = /* @__PURE__ */ new Set();
|
|
107
|
+
let direction = false;
|
|
108
|
+
for (const note of notes) {
|
|
109
|
+
if (!VALUES_KEPT.test(note.code)) {
|
|
110
|
+
for (const name of note.columns ?? (note.column === null ? [] : [note.column])) {
|
|
111
|
+
lost.add(name);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
direction ||= /DIRECTION/.test(note.code);
|
|
115
|
+
}
|
|
116
|
+
const carrier = new LostInputs(snapshot, lost, direction, extracted.columns);
|
|
117
|
+
const rules = extracted.styles.rules.map((rule) => carrier.carry(rule));
|
|
118
|
+
const changed = rules.some((rule, i) => rule !== extracted.styles.rules[i]);
|
|
119
|
+
return changed ? { ...extracted, styles: { ...extracted.styles, rules }, columns: carrier.columns } : extracted;
|
|
120
|
+
}
|
|
121
|
+
class LostInputs {
|
|
122
|
+
/**
|
|
123
|
+
* A carrier for one export.
|
|
124
|
+
* @param snapshot - the graph
|
|
125
|
+
* @param lost - the data columns the export drops or changes
|
|
126
|
+
* @param direction - whether the file loses the edges' direction (and so the in and out degrees)
|
|
127
|
+
* @param columns - the styles' own style columns
|
|
128
|
+
* @param columns.node - the node columns
|
|
129
|
+
* @param columns.edge - the edge columns
|
|
130
|
+
*/
|
|
131
|
+
constructor(snapshot, lost, direction, columns) {
|
|
132
|
+
this.snapshot = snapshot;
|
|
133
|
+
this.lost = lost;
|
|
134
|
+
this.direction = direction;
|
|
135
|
+
this.touched = false;
|
|
136
|
+
this.columns = { node: new Map(columns.node), edge: new Map(columns.edge) };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A rule with every lost input it reads replaced by a copy of its values.
|
|
140
|
+
* @param rule - the rule
|
|
141
|
+
* @returns the rule read from copies, or the rule itself when it reads no lost input
|
|
142
|
+
*/
|
|
143
|
+
carry(rule) {
|
|
144
|
+
this.touched = false;
|
|
145
|
+
const { ext, ...given } = rule;
|
|
146
|
+
const map = this.carryMap(rule);
|
|
147
|
+
const where = this.walk(rule, given.where);
|
|
148
|
+
if (!this.touched) {
|
|
149
|
+
return rule;
|
|
150
|
+
}
|
|
151
|
+
const { map: _given, ...unmapped } = given;
|
|
152
|
+
const plain = Object.keys(map).length === 0 ? { ...unmapped, where } : { ...unmapped, where, map };
|
|
153
|
+
const moved = JSON.parse(JSON.stringify(plain));
|
|
154
|
+
return ext === void 0 ? moved : { ...moved, ext };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A rule's mappings with their lost inputs replaced. A passthrough reads a data column as the property's value: its
|
|
158
|
+
* copy holds those values; one that gives no element a value (a list, text that is not a color) is left out, so
|
|
159
|
+
* no other value the file reads back in the column (a list written as text) is given either.
|
|
160
|
+
* @param rule - the rule
|
|
161
|
+
* @returns the mappings
|
|
162
|
+
*/
|
|
163
|
+
carryMap(rule) {
|
|
164
|
+
const map = /* @__PURE__ */ Object.create(null);
|
|
165
|
+
const { table } = this.tableOf(rule.target);
|
|
166
|
+
for (const [property, mapping] of Object.entries(rule.map ?? {})) {
|
|
167
|
+
if (mapping === void 0) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (mapping.kind !== "passthrough") {
|
|
171
|
+
map[property] = this.walk(rule, mapping);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const name = mapping.by.data;
|
|
175
|
+
const lostColumn = typeof name === "string" && this.lost.has(name) && table.has(name);
|
|
176
|
+
const values = lostColumn ? this.cells(rule.target, name, canonicalFor(rule.target, property)) : null;
|
|
177
|
+
if (values !== null && !values.some((v) => v !== null)) {
|
|
178
|
+
this.touched = true;
|
|
179
|
+
} else if (values !== null && this.isLost(rule.target, name)) {
|
|
180
|
+
this.touched = true;
|
|
181
|
+
map[property] = { ...mapping, by: this.copy(rule.target, `${property}.data.${name}`, () => values) };
|
|
182
|
+
} else {
|
|
183
|
+
map[property] = mapping;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return map;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* A value with every other reference to a lost input reading a copy of its values as they are.
|
|
190
|
+
* @param rule - the rule it belongs to
|
|
191
|
+
* @param value - the value
|
|
192
|
+
* @returns the value, references replaced
|
|
193
|
+
*/
|
|
194
|
+
walk(rule, value) {
|
|
195
|
+
return JSON.parse(JSON.stringify(value, (_k, v) => this.replaced(rule, v)));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* One reference with a lost input replaced by its copy: a data column, or a degree the file does not keep.
|
|
199
|
+
* @param rule - the rule it belongs to
|
|
200
|
+
* @param v - a value of the rule
|
|
201
|
+
* @returns the value, or the reference to the copy
|
|
202
|
+
*/
|
|
203
|
+
replaced(rule, v) {
|
|
204
|
+
if (typeof v !== "object" || v === null || Array.isArray(v) || Object.keys(v).length !== 1) {
|
|
205
|
+
return v;
|
|
206
|
+
}
|
|
207
|
+
const ref = v;
|
|
208
|
+
if (this.isLost(rule.target, ref.data)) {
|
|
209
|
+
const name = ref.data;
|
|
210
|
+
this.touched = true;
|
|
211
|
+
return this.copy(
|
|
212
|
+
rule.target,
|
|
213
|
+
`data.${name}`,
|
|
214
|
+
() => this.cells(rule.target, name, (raw) => isStyleScalar(raw) ? raw : null)
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const which = ref.computed;
|
|
218
|
+
if ((which === "indegree" || which === "outdegree") && this.direction && rule.target === "node") {
|
|
219
|
+
this.touched = true;
|
|
220
|
+
return this.copy(rule.target, `computed.${which}`, () => degreeValues(this.snapshot, which));
|
|
221
|
+
}
|
|
222
|
+
return v;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The reference to a copy of an input, made on first use and shared by every rule that reads it.
|
|
226
|
+
* @param target - nodes or edges
|
|
227
|
+
* @param base - what is copied
|
|
228
|
+
* @param values - its values
|
|
229
|
+
* @returns the reference
|
|
230
|
+
*/
|
|
231
|
+
copy(target, base, values) {
|
|
232
|
+
const key = `input.${base}`;
|
|
233
|
+
const held = this.columns[target];
|
|
234
|
+
if (!held.has(key)) {
|
|
235
|
+
held.set(key, values());
|
|
236
|
+
}
|
|
237
|
+
return { style: key };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Whether a name is a lost input that can be copied: only a column of plain values can, and a list, or a column with
|
|
241
|
+
* no value, stays as it is.
|
|
242
|
+
* @param target - nodes or edges
|
|
243
|
+
* @param name - the name
|
|
244
|
+
* @returns true for a lost input
|
|
245
|
+
*/
|
|
246
|
+
isLost(target, name) {
|
|
247
|
+
const { table, n } = this.tableOf(target);
|
|
248
|
+
return typeof name === "string" && this.lost.has(name) && table.has(name) && carriable(table, n, name);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Every element's value of a data column.
|
|
252
|
+
* @param target - nodes or edges
|
|
253
|
+
* @param name - the column
|
|
254
|
+
* @param read - the value of a cell
|
|
255
|
+
* @returns the values
|
|
256
|
+
*/
|
|
257
|
+
cells(target, name, read) {
|
|
258
|
+
const { table, n } = this.tableOf(target);
|
|
259
|
+
return Array.from({ length: n }, (_u, i) => read(dataCell(table, name, i)));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* A target's table and row count.
|
|
263
|
+
* @param target - nodes or edges
|
|
264
|
+
* @returns the table and its row count
|
|
265
|
+
*/
|
|
266
|
+
tableOf(target) {
|
|
267
|
+
const { snapshot } = this;
|
|
268
|
+
return target === "node" ? { table: snapshot.nodes, n: snapshot.nodeCount } : { table: snapshot.edges, n: snapshot.edgeCount };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function carriable(table, n, name) {
|
|
272
|
+
let some = false;
|
|
273
|
+
for (let i = 0; i < n; i++) {
|
|
274
|
+
const cell = plainCell(table.value(name, i));
|
|
275
|
+
if (cell !== null && cell !== void 0) {
|
|
276
|
+
if (!isStyleScalar(cell)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
some = true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return some;
|
|
283
|
+
}
|
|
284
|
+
function isStyleScalar(v) {
|
|
285
|
+
return typeof v === "string" || typeof v === "boolean" || typeof v === "number" && Number.isFinite(v);
|
|
286
|
+
}
|
|
287
|
+
function keepsStyleBlock(exporter) {
|
|
288
|
+
const caps = exporter.styles?.capabilities;
|
|
289
|
+
return caps !== void 0 && (caps.block === "native" || caps.block === "attributes" && exporter.capabilities.graphAttributes);
|
|
290
|
+
}
|
|
291
|
+
function isKeptGraphtyProperty(note, exporter) {
|
|
292
|
+
return note.style?.property.startsWith("graphty:") === true && keepsStyleBlock(exporter);
|
|
293
|
+
}
|
|
294
|
+
function lossNotes(extracted, exporter, caps, picture, snapshot) {
|
|
295
|
+
const notes = [];
|
|
296
|
+
const kept = keepsStyleBlock(exporter);
|
|
297
|
+
for (const target of TARGETS) {
|
|
298
|
+
const written = new Set(caps?.[target] ?? []);
|
|
299
|
+
for (const property of ruleProperties(extracted.styles.rules, target)) {
|
|
300
|
+
if (written.has(property) || kept && property.startsWith("graphty:")) {
|
|
301
|
+
if (target === "edge") {
|
|
302
|
+
notes.push(...pairNotes(snapshot, exporter, property, picture("edge", property)));
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
const note = notWrittenNote(target, property, exporter, picture);
|
|
306
|
+
if (note.count !== 0) {
|
|
307
|
+
notes.push(note);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
notes.push(...graphLossNotes(extracted, exporter, caps));
|
|
313
|
+
if (kept) {
|
|
314
|
+
notes.push(...styleBlockLosses(writeStyleBlock(extracted, { snapshot }), extracted.styles.rules.length));
|
|
315
|
+
} else if (extracted.styles.rules.length > 0) {
|
|
316
|
+
notes.push(
|
|
317
|
+
Object.freeze({
|
|
318
|
+
code: STYLE_ISSUE.RULES_NOT_KEPT,
|
|
319
|
+
message: `the style rules are not kept: the ${exporter.format} format has no place for them; importing the file infers them again from what it holds`,
|
|
320
|
+
column: null,
|
|
321
|
+
count: extracted.styles.rules.length
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
return notes;
|
|
326
|
+
}
|
|
327
|
+
function graphLossNotes(extracted, exporter, caps) {
|
|
328
|
+
const graphWritten = new Set(caps?.graph ?? []);
|
|
329
|
+
const drawn = (caps?.tool === void 0 ? null : toolDefaults(caps.tool))?.graph ?? {};
|
|
330
|
+
return Object.entries(extracted.styles.graph).filter(([property, value]) => !graphWritten.has(property) && drawn[property]?.value !== value).map(([property]) => property).map(
|
|
331
|
+
(property) => Object.freeze({
|
|
332
|
+
code: STYLE_ISSUE.NOT_WRITTEN,
|
|
333
|
+
message: `the graph's ${property} is not written: the ${exporter.format} format has no place for it`,
|
|
334
|
+
column: null,
|
|
335
|
+
count: 1,
|
|
336
|
+
style: Object.freeze({ target: "graph", property })
|
|
337
|
+
})
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
function ruleProperties(rules, target) {
|
|
341
|
+
return new Set(
|
|
342
|
+
rules.filter((rule) => rule.target === target).flatMap((rule) => [...Object.keys(rule.set ?? {}), ...Object.keys(rule.map ?? {})])
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
function notWrittenNote(target, property, exporter, picture) {
|
|
346
|
+
const count = picture(target, property).filter((v) => v !== null).length;
|
|
347
|
+
return Object.freeze({
|
|
348
|
+
code: STYLE_ISSUE.NOT_WRITTEN,
|
|
349
|
+
message: `${target} ${property} is not written: the ${exporter.format} format has no place for it (${String(count)} ${target}${plural(count)} ${agree(count, "has", "have")} a value)`,
|
|
350
|
+
column: null,
|
|
351
|
+
count,
|
|
352
|
+
style: Object.freeze({ target, property })
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function pairNotes(snapshot, exporter, property, values) {
|
|
356
|
+
const pair = snapshot.edges.byRole("pair");
|
|
357
|
+
let count = 0;
|
|
358
|
+
for (let i = 0; pair !== null && i < values.length; i++) {
|
|
359
|
+
const other = pair.isSet(i) ? pair.value(i) : i;
|
|
360
|
+
if (other < i && values[i] !== values[other]) {
|
|
361
|
+
count++;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (count === 0) {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
return [
|
|
368
|
+
Object.freeze({
|
|
369
|
+
code: STYLE_ISSUE.APPROXIMATED,
|
|
370
|
+
message: `edge ${property}: ${String(count)} undirected edge${plural(count)} in a directed graph ${agree(count, "has", "have")} another value on each half; the ${exporter.format} format writes the edge once, with the first half's value`,
|
|
371
|
+
column: null,
|
|
372
|
+
count,
|
|
373
|
+
style: Object.freeze({ target: "edge", property })
|
|
374
|
+
})
|
|
375
|
+
];
|
|
376
|
+
}
|
|
377
|
+
function styledSnapshot(snapshot, caps, tags, block, rules) {
|
|
378
|
+
const attributes = caps?.block === "attributes";
|
|
379
|
+
const stale = { node: staleTags(snapshot.nodes, rules, "node"), edge: staleTags(snapshot.edges, rules, "edge") };
|
|
380
|
+
const roles = { node: roledNames(snapshot.nodes, caps, "node"), edge: roledNames(snapshot.edges, caps, "edge") };
|
|
381
|
+
const staleBlock = snapshot.graph.has(STYLE_BLOCK_NAME);
|
|
382
|
+
const changes = [stale.node, stale.edge, roles.node, roles.edge].some((names) => names.length > 0);
|
|
383
|
+
if (!attributes && !staleBlock && !changes) {
|
|
384
|
+
return snapshot;
|
|
385
|
+
}
|
|
386
|
+
const out = snapshot.withColumns();
|
|
387
|
+
if (staleBlock) {
|
|
388
|
+
out.graph.remove(STYLE_BLOCK_NAME);
|
|
389
|
+
}
|
|
390
|
+
for (const target of TARGETS) {
|
|
391
|
+
const table = target === "node" ? out.nodes : out.edges;
|
|
392
|
+
for (const name of stale[target]) {
|
|
393
|
+
table.remove(name);
|
|
394
|
+
}
|
|
395
|
+
for (const name of roles[target]) {
|
|
396
|
+
const col = table.get(name);
|
|
397
|
+
if (col !== null) {
|
|
398
|
+
table.set(name, col, { role: void 0 });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (attributes) {
|
|
403
|
+
setTagAttributes(out, tags);
|
|
404
|
+
out.graph.set(STYLE_BLOCK_NAME, [block], { dtype: "string" });
|
|
405
|
+
}
|
|
406
|
+
return out;
|
|
407
|
+
}
|
|
408
|
+
function staleTags(table, rules, target) {
|
|
409
|
+
const read = dataRead$1(rules, target);
|
|
410
|
+
return table.names().filter((name) => isStyleTagName(name) && !read.has(name));
|
|
411
|
+
}
|
|
412
|
+
function roledNames(table, caps, target) {
|
|
413
|
+
const writesLabel = (caps?.[target] ?? []).includes("label");
|
|
414
|
+
return table.names().filter((name) => {
|
|
415
|
+
const role = table.get(name)?.meta.role ?? "";
|
|
416
|
+
return STYLE_ROLES.has(role) || role === "label" && writesLabel;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function setTagAttributes(out, tags) {
|
|
420
|
+
for (const target of TARGETS) {
|
|
421
|
+
const table = target === "node" ? out.nodes : out.edges;
|
|
422
|
+
const prefix = styleTag(target, "");
|
|
423
|
+
for (const name of table.names().filter((c) => c.startsWith(prefix))) {
|
|
424
|
+
table.remove(name);
|
|
425
|
+
}
|
|
426
|
+
for (const [name, col] of tags[target]) {
|
|
427
|
+
const dtype = tagDtype(col);
|
|
428
|
+
table.set(name, dtype === "string" ? col.map((v) => v === null ? null : String(v)) : col, { dtype });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function isStyleTagName(name) {
|
|
433
|
+
return name.startsWith("graphty.style.") || name.startsWith("graphty_style_");
|
|
434
|
+
}
|
|
435
|
+
function dataRead$1(rules, target) {
|
|
436
|
+
const out = /* @__PURE__ */ new Set();
|
|
437
|
+
for (const { ext: _ext, ...rule } of rules.filter((r) => r.target === target)) {
|
|
438
|
+
JSON.stringify(rule, (_k, v) => {
|
|
439
|
+
const data = typeof v === "object" && v !== null && !Array.isArray(v) ? v.data : null;
|
|
440
|
+
if (typeof data === "string" && Object.keys(v).length === 1) {
|
|
441
|
+
out.add(data);
|
|
442
|
+
}
|
|
443
|
+
return v;
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return out;
|
|
447
|
+
}
|
|
448
|
+
function tagDtype(col) {
|
|
449
|
+
let kind = null;
|
|
450
|
+
for (const v of col) {
|
|
451
|
+
if (v === null) {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (kind === null) {
|
|
455
|
+
kind = typeof v;
|
|
456
|
+
} else if (kind !== typeof v) {
|
|
457
|
+
return "string";
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (kind === "number") {
|
|
461
|
+
return "f64";
|
|
462
|
+
}
|
|
463
|
+
return kind === "boolean" ? "bool" : "string";
|
|
464
|
+
}
|
|
465
|
+
const PORT_LABEL_DISTANCE = 10;
|
|
466
|
+
const PORT_LABEL_ANGLE = -25;
|
|
467
|
+
const DEFAULT_SIZE = { w: 0.75, h: 0.5 };
|
|
468
|
+
function readLabelPlaces(snapshot, cx, e, get, put) {
|
|
469
|
+
const s = snapshot.edgeSource(e);
|
|
470
|
+
const t = snapshot.edgeTarget(e);
|
|
471
|
+
const [source, target] = [cx.center(s), cx.center(t)];
|
|
472
|
+
if (source === null || target === null || s === t) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const route = edgeRoute(source, target, get("pos"));
|
|
476
|
+
const lp = route.start === null ? null : point(get("lp"));
|
|
477
|
+
if (lp !== null && get("label") !== null) {
|
|
478
|
+
const mid = routeMiddle(route.points);
|
|
479
|
+
put("labelOffsetX", num6(lp[0] - mid[0]));
|
|
480
|
+
put("labelOffsetY", num6(mid[1] - lp[1]));
|
|
481
|
+
}
|
|
482
|
+
readEndLabel(cx, route, { node: t, at: target, other: source }, "head", get, put);
|
|
483
|
+
readEndLabel(cx, route, { node: s, at: source, other: target }, "tail", get, put);
|
|
484
|
+
}
|
|
485
|
+
function readEndLabel(cx, route, end, which, get, put) {
|
|
486
|
+
if (get(`${which}label`) === null) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const head = which === "head";
|
|
490
|
+
const tip = (head ? route.end : route.start) ?? boundary(cx, end.node, end.at, end.other);
|
|
491
|
+
const back = head ? route.points.slice(0, -1).reverse() : route.points.slice(1);
|
|
492
|
+
const toward = back.find((p) => Math.hypot(p[0] - tip[0], p[1] - tip[1]) > 1e-6) ?? end.other;
|
|
493
|
+
const stored = route.start === null ? null : point(get(`${which}_lp`));
|
|
494
|
+
const at = stored ?? portLabel(get, tip, toward);
|
|
495
|
+
if (at !== null) {
|
|
496
|
+
const prefix = head ? "target" : "source";
|
|
497
|
+
put(`${prefix}LabelOffset`, 0);
|
|
498
|
+
put(`${prefix}LabelOffsetX`, num6(at[0] - tip[0]));
|
|
499
|
+
put(`${prefix}LabelOffsetY`, num6(tip[1] - at[1]));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function portLabel(get, tip, toward) {
|
|
503
|
+
const turn = finite(get("labelangle"), PORT_LABEL_ANGLE, -180);
|
|
504
|
+
const distance = finite(get("labeldistance"), 1, 0);
|
|
505
|
+
if (turn === PORT_LABEL_ANGLE && distance === 1) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
const angle = Math.atan2(toward[1] - tip[1], toward[0] - tip[0]) + turn * Math.PI / 180;
|
|
509
|
+
const dist = PORT_LABEL_DISTANCE * distance;
|
|
510
|
+
return [tip[0] + dist * Math.cos(angle), tip[1] + dist * Math.sin(angle)];
|
|
511
|
+
}
|
|
512
|
+
function finite(raw, fallback, low) {
|
|
513
|
+
const v = raw === null || raw.trim() === "" ? Number.NaN : Number(raw);
|
|
514
|
+
return Number.isFinite(v) ? Math.max(low, v) : fallback;
|
|
515
|
+
}
|
|
516
|
+
function point(raw) {
|
|
517
|
+
const parts = (raw ?? "").replace(/!$/, "").split(",").map(Number);
|
|
518
|
+
return parts.length >= 2 && Number.isFinite(parts[0]) && Number.isFinite(parts[1]) ? [parts[0], parts[1]] : null;
|
|
519
|
+
}
|
|
520
|
+
function edgeRoute(source, target, pos) {
|
|
521
|
+
const tokens = (pos ?? "").split(";")[0].trim().split(/\s+/).filter(Boolean);
|
|
522
|
+
const marked = (flag) => {
|
|
523
|
+
const token = tokens.find((x) => x.startsWith(`${flag},`));
|
|
524
|
+
return token === void 0 ? null : point(token.slice(2));
|
|
525
|
+
};
|
|
526
|
+
const controls = tokens.filter((x) => !/^[es],/.test(x)).map((x) => point(x));
|
|
527
|
+
const flat = controls.every((p) => p !== null) && controls.length >= 4 ? flattenCubic(controls) : null;
|
|
528
|
+
if (flat === null) {
|
|
529
|
+
return { points: [source, target], start: null, end: null };
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
points: [source, ...flat, target],
|
|
533
|
+
start: marked("s") ?? flat[0],
|
|
534
|
+
end: marked("e") ?? flat.at(-1) ?? null
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function routeMiddle(points) {
|
|
538
|
+
const lengths = points.slice(1).map((p, i) => Math.hypot(p[0] - points[i][0], p[1] - points[i][1]));
|
|
539
|
+
let left = lengths.reduce((a, b) => a + b, 0) / 2;
|
|
540
|
+
for (let i = 0; i < lengths.length; i++) {
|
|
541
|
+
if (left <= lengths[i] && lengths[i] > 0) {
|
|
542
|
+
const f = left / lengths[i];
|
|
543
|
+
return [
|
|
544
|
+
points[i][0] + f * (points[i + 1][0] - points[i][0]),
|
|
545
|
+
points[i][1] + f * (points[i + 1][1] - points[i][1])
|
|
546
|
+
];
|
|
547
|
+
}
|
|
548
|
+
left -= lengths[i];
|
|
549
|
+
}
|
|
550
|
+
return points[0];
|
|
551
|
+
}
|
|
552
|
+
function boundary(cx, n, center, toward) {
|
|
553
|
+
const inch = (name, fallback) => {
|
|
554
|
+
const v = Number(cx.node(n, name) ?? Number.NaN);
|
|
555
|
+
return (Number.isFinite(v) && v >= 0 ? v : fallback) * 72;
|
|
556
|
+
};
|
|
557
|
+
const [rx, ry] = [inch("width", DEFAULT_SIZE.w) / 2, inch("height", DEFAULT_SIZE.h) / 2];
|
|
558
|
+
const [dx, dy] = [toward[0] - center[0], toward[1] - center[1]];
|
|
559
|
+
const len = Math.hypot(dx, dy);
|
|
560
|
+
if (len === 0 || rx === 0 || ry === 0) {
|
|
561
|
+
return center;
|
|
562
|
+
}
|
|
563
|
+
const r = 1 / Math.hypot(dx / len / rx, dy / len / ry);
|
|
564
|
+
return [center[0] + dx / len * r, center[1] + dy / len * r];
|
|
565
|
+
}
|
|
566
|
+
const PAD = { x: 16, y: 8 };
|
|
567
|
+
const LINE_SPACING$1 = 1.2;
|
|
568
|
+
const DEJAVU_SANS = [
|
|
569
|
+
651,
|
|
570
|
+
821,
|
|
571
|
+
942,
|
|
572
|
+
1716,
|
|
573
|
+
1303,
|
|
574
|
+
1946,
|
|
575
|
+
1597,
|
|
576
|
+
563,
|
|
577
|
+
799,
|
|
578
|
+
799,
|
|
579
|
+
1024,
|
|
580
|
+
1716,
|
|
581
|
+
651,
|
|
582
|
+
739,
|
|
583
|
+
651,
|
|
584
|
+
690,
|
|
585
|
+
1303,
|
|
586
|
+
1303,
|
|
587
|
+
1303,
|
|
588
|
+
1303,
|
|
589
|
+
1303,
|
|
590
|
+
1303,
|
|
591
|
+
1303,
|
|
592
|
+
1303,
|
|
593
|
+
1303,
|
|
594
|
+
1303,
|
|
595
|
+
690,
|
|
596
|
+
690,
|
|
597
|
+
1716,
|
|
598
|
+
1716,
|
|
599
|
+
1716,
|
|
600
|
+
1087,
|
|
601
|
+
2048,
|
|
602
|
+
1401,
|
|
603
|
+
1405,
|
|
604
|
+
1430,
|
|
605
|
+
1577,
|
|
606
|
+
1294,
|
|
607
|
+
1178,
|
|
608
|
+
1587,
|
|
609
|
+
1540,
|
|
610
|
+
604,
|
|
611
|
+
604,
|
|
612
|
+
1343,
|
|
613
|
+
1141,
|
|
614
|
+
1767,
|
|
615
|
+
1532,
|
|
616
|
+
1612,
|
|
617
|
+
1235,
|
|
618
|
+
1612,
|
|
619
|
+
1423,
|
|
620
|
+
1300,
|
|
621
|
+
1251,
|
|
622
|
+
1499,
|
|
623
|
+
1401,
|
|
624
|
+
2025,
|
|
625
|
+
1403,
|
|
626
|
+
1251,
|
|
627
|
+
1403,
|
|
628
|
+
799,
|
|
629
|
+
690,
|
|
630
|
+
799,
|
|
631
|
+
1716,
|
|
632
|
+
1024,
|
|
633
|
+
1024,
|
|
634
|
+
1255,
|
|
635
|
+
1300,
|
|
636
|
+
1126,
|
|
637
|
+
1300,
|
|
638
|
+
1260,
|
|
639
|
+
721,
|
|
640
|
+
1300,
|
|
641
|
+
1298,
|
|
642
|
+
569,
|
|
643
|
+
569,
|
|
644
|
+
1186,
|
|
645
|
+
569,
|
|
646
|
+
1995,
|
|
647
|
+
1298,
|
|
648
|
+
1253,
|
|
649
|
+
1300,
|
|
650
|
+
1300,
|
|
651
|
+
842,
|
|
652
|
+
1067,
|
|
653
|
+
803,
|
|
654
|
+
1298,
|
|
655
|
+
1212,
|
|
656
|
+
1675,
|
|
657
|
+
1212,
|
|
658
|
+
1212,
|
|
659
|
+
1075,
|
|
660
|
+
1303,
|
|
661
|
+
690,
|
|
662
|
+
1303,
|
|
663
|
+
1716
|
|
664
|
+
];
|
|
665
|
+
function textWidth(text, size) {
|
|
666
|
+
let units = 0;
|
|
667
|
+
for (const ch of text) {
|
|
668
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
669
|
+
units += code >= 32 && code <= 126 ? DEJAVU_SANS[code - 32] : 1229;
|
|
670
|
+
}
|
|
671
|
+
return units / 2048 * size;
|
|
672
|
+
}
|
|
673
|
+
function splitLines(text) {
|
|
674
|
+
const lines = [];
|
|
675
|
+
let line = "";
|
|
676
|
+
let i = 0;
|
|
677
|
+
while (i < text.length) {
|
|
678
|
+
const c = text[i];
|
|
679
|
+
const next = text[i + 1];
|
|
680
|
+
if (c === "\\" && (next === "n" || next === "l" || next === "r")) {
|
|
681
|
+
lines.push({ text: line, justify: next === "n" ? "c" : next });
|
|
682
|
+
line = "";
|
|
683
|
+
i += 2;
|
|
684
|
+
} else if (c === "\\" && next !== void 0) {
|
|
685
|
+
line += c + next;
|
|
686
|
+
i += 2;
|
|
687
|
+
} else {
|
|
688
|
+
line += c;
|
|
689
|
+
i++;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (line !== "" || lines.length === 0) {
|
|
693
|
+
lines.push({ text: line, justify: "c" });
|
|
694
|
+
}
|
|
695
|
+
return lines;
|
|
696
|
+
}
|
|
697
|
+
function parseRecord(label, lr) {
|
|
698
|
+
return parseFields(label, { i: 0 }, lr, true);
|
|
699
|
+
}
|
|
700
|
+
function parentField(children, across) {
|
|
701
|
+
return { lines: null, port: null, children, lr: across, w: 0, h: 0, x: 0, y: 0 };
|
|
702
|
+
}
|
|
703
|
+
function parseFields(label, cursor, across, top) {
|
|
704
|
+
const children = [];
|
|
705
|
+
const pending = { text: "", port: null, sub: null };
|
|
706
|
+
while (cursor.i < label.length) {
|
|
707
|
+
const c = label[cursor.i];
|
|
708
|
+
if (c === "{") {
|
|
709
|
+
cursor.i++;
|
|
710
|
+
pending.sub = parseFields(label, cursor, !across, false);
|
|
711
|
+
} else if (c === "}") {
|
|
712
|
+
cursor.i++;
|
|
713
|
+
if (!top) {
|
|
714
|
+
flushField(children, pending, across);
|
|
715
|
+
return parentField(children, across);
|
|
716
|
+
}
|
|
717
|
+
} else if (c === "|") {
|
|
718
|
+
cursor.i++;
|
|
719
|
+
flushField(children, pending, across);
|
|
720
|
+
} else {
|
|
721
|
+
readFieldText(label, cursor, pending);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
flushField(children, pending, across);
|
|
725
|
+
return parentField(children, across);
|
|
726
|
+
}
|
|
727
|
+
function readFieldText(label, cursor, pending) {
|
|
728
|
+
const c = label[cursor.i];
|
|
729
|
+
const next = label.at(cursor.i + 1);
|
|
730
|
+
if (c === "\\" && next !== void 0) {
|
|
731
|
+
pending.text += "{}|<> ".includes(next) ? next : c + next;
|
|
732
|
+
cursor.i += 2;
|
|
733
|
+
} else if (c === "<") {
|
|
734
|
+
const close = label.indexOf(">", cursor.i + 1);
|
|
735
|
+
const end = close < 0 ? label.length : close;
|
|
736
|
+
pending.port = label.slice(cursor.i + 1, end).trim();
|
|
737
|
+
cursor.i = close < 0 ? label.length : close + 1;
|
|
738
|
+
} else {
|
|
739
|
+
pending.text += c;
|
|
740
|
+
cursor.i++;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function flushField(children, pending, across) {
|
|
744
|
+
const { sub, port } = pending;
|
|
745
|
+
children.push(
|
|
746
|
+
sub ?? { lines: splitLines(pending.text.trim()), port, children: [], lr: !across, w: 0, h: 0, x: 0, y: 0 }
|
|
747
|
+
);
|
|
748
|
+
if (sub !== null && port !== null) {
|
|
749
|
+
sub.port = port;
|
|
750
|
+
}
|
|
751
|
+
pending.text = "";
|
|
752
|
+
pending.port = null;
|
|
753
|
+
pending.sub = null;
|
|
754
|
+
}
|
|
755
|
+
function sizeField(f, size) {
|
|
756
|
+
if (f.lines !== null) {
|
|
757
|
+
const w = Math.max(...f.lines.map((l) => textWidth(l.text, size)));
|
|
758
|
+
const h = f.lines.length * size * LINE_SPACING$1;
|
|
759
|
+
[f.w, f.h] = w > 0 || h > 0 ? [w + PAD.x, h + PAD.y] : [0, 0];
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
f.w = 0;
|
|
763
|
+
f.h = 0;
|
|
764
|
+
for (const c of f.children) {
|
|
765
|
+
sizeField(c, size);
|
|
766
|
+
f.w = f.lr ? f.w + c.w : Math.max(f.w, c.w);
|
|
767
|
+
f.h = f.lr ? Math.max(f.h, c.h) : f.h + c.h;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function resizeField(f, w, h) {
|
|
771
|
+
const inc = (f.lr ? w - f.w : h - f.h) / Math.max(1, f.children.length);
|
|
772
|
+
f.w = w;
|
|
773
|
+
f.h = h;
|
|
774
|
+
f.children.forEach((c, k) => {
|
|
775
|
+
const amt = Math.trunc((k + 1) * inc) - Math.trunc(k * inc);
|
|
776
|
+
resizeField(c, f.lr ? c.w + amt : w, f.lr ? h : c.h + amt);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
function placeField(f, x, y) {
|
|
780
|
+
f.x = x;
|
|
781
|
+
f.y = y;
|
|
782
|
+
let at = f.lr ? x : y;
|
|
783
|
+
for (const c of f.children) {
|
|
784
|
+
placeField(c, f.lr ? at : x, f.lr ? y : at);
|
|
785
|
+
at += f.lr ? c.w : -c.h;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function layoutRecord(label, lr, width, height, fixed, fontSize, text) {
|
|
789
|
+
const root = parseRecord(label, lr);
|
|
790
|
+
const leaves = (f) => {
|
|
791
|
+
if (f.lines !== null) {
|
|
792
|
+
f.lines = f.lines.map((l) => ({ ...l, text: text(l.text) }));
|
|
793
|
+
}
|
|
794
|
+
f.children.forEach(leaves);
|
|
795
|
+
};
|
|
796
|
+
leaves(root);
|
|
797
|
+
sizeField(root, fontSize);
|
|
798
|
+
const w = fixed ? width : Math.max(width, root.w);
|
|
799
|
+
const h = fixed ? height : Math.max(height, root.h);
|
|
800
|
+
resizeField(root, w, h);
|
|
801
|
+
placeField(root, -w / 2, h / 2);
|
|
802
|
+
const dividers = [];
|
|
803
|
+
const texts = [];
|
|
804
|
+
const ports = /* @__PURE__ */ new Map();
|
|
805
|
+
const lineHeight = fontSize * LINE_SPACING$1;
|
|
806
|
+
const visit = (f) => {
|
|
807
|
+
if (f.port !== null && f.port !== "") {
|
|
808
|
+
ports.set(f.port, { x: f.x + f.w / 2, y: f.y - f.h / 2, hw: f.w / 2, hh: f.h / 2 });
|
|
809
|
+
}
|
|
810
|
+
f.children.forEach((c, k) => {
|
|
811
|
+
if (k > 0) {
|
|
812
|
+
dividers.push(f.lr ? [c.x, c.y, c.x, c.y - c.h] : [c.x, c.y, c.x + c.w, c.y]);
|
|
813
|
+
}
|
|
814
|
+
visit(c);
|
|
815
|
+
});
|
|
816
|
+
if (f.lines === null) {
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const n = f.lines.length;
|
|
820
|
+
f.lines.forEach((l, k) => {
|
|
821
|
+
if (l.text === "") {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const half = textWidth(l.text, fontSize) / 2;
|
|
825
|
+
let x = f.x + f.w / 2;
|
|
826
|
+
if (l.justify === "l") {
|
|
827
|
+
x = f.x + PAD.x / 2 + half;
|
|
828
|
+
} else if (l.justify === "r") {
|
|
829
|
+
x = f.x + f.w - PAD.x / 2 - half;
|
|
830
|
+
}
|
|
831
|
+
texts.push({ x, y: f.y - f.h / 2 + ((n - 1) / 2 - k) * lineHeight, text: l.text });
|
|
832
|
+
});
|
|
833
|
+
};
|
|
834
|
+
visit(root);
|
|
835
|
+
return { width: w, height: h, dividers, texts, ports };
|
|
836
|
+
}
|
|
837
|
+
const DOT_STYLE_CAPABILITIES = Object.freeze({
|
|
838
|
+
tool: "graphviz",
|
|
839
|
+
node: Object.freeze([
|
|
840
|
+
"fill",
|
|
841
|
+
"fillOpacity",
|
|
842
|
+
"borderColor",
|
|
843
|
+
"borderWidth",
|
|
844
|
+
"borderStyle",
|
|
845
|
+
"shape",
|
|
846
|
+
"width",
|
|
847
|
+
"height",
|
|
848
|
+
"label",
|
|
849
|
+
"labelColor",
|
|
850
|
+
"labelSize",
|
|
851
|
+
"labelFont",
|
|
852
|
+
"labelWeight",
|
|
853
|
+
"labelItalic",
|
|
854
|
+
"labelPosition",
|
|
855
|
+
"shapePoints",
|
|
856
|
+
"shapePointsStretch",
|
|
857
|
+
"shapeParts",
|
|
858
|
+
"shapeLines",
|
|
859
|
+
"shapeLineWidth",
|
|
860
|
+
"tooltip",
|
|
861
|
+
"visible"
|
|
862
|
+
]),
|
|
863
|
+
edge: Object.freeze([
|
|
864
|
+
"color",
|
|
865
|
+
"opacity",
|
|
866
|
+
"width",
|
|
867
|
+
"lineStyle",
|
|
868
|
+
"sourceArrow",
|
|
869
|
+
"targetArrow",
|
|
870
|
+
"sourceArrowColor",
|
|
871
|
+
"targetArrowColor",
|
|
872
|
+
"arrowSize",
|
|
873
|
+
"label",
|
|
874
|
+
"sourceLabel",
|
|
875
|
+
"targetLabel",
|
|
876
|
+
"labelColor",
|
|
877
|
+
"labelSize",
|
|
878
|
+
"labelFont",
|
|
879
|
+
"labelWeight",
|
|
880
|
+
"labelItalic",
|
|
881
|
+
"tooltip",
|
|
882
|
+
"visible"
|
|
883
|
+
]),
|
|
884
|
+
graph: Object.freeze(["background"]),
|
|
885
|
+
block: "native"
|
|
886
|
+
});
|
|
887
|
+
const NAME_KEY = "name";
|
|
888
|
+
const DIRECTED_KEY = "directed";
|
|
889
|
+
const CLUSTER_KEY = "cluster";
|
|
890
|
+
const NODE_ATTRIBUTES = [
|
|
891
|
+
"colorscheme",
|
|
892
|
+
"color",
|
|
893
|
+
"fillcolor",
|
|
894
|
+
"bgcolor",
|
|
895
|
+
"pencolor",
|
|
896
|
+
"style",
|
|
897
|
+
"shape",
|
|
898
|
+
"width",
|
|
899
|
+
"height",
|
|
900
|
+
"fixedsize",
|
|
901
|
+
"regular",
|
|
902
|
+
"sides",
|
|
903
|
+
"distortion",
|
|
904
|
+
"skew",
|
|
905
|
+
"orientation",
|
|
906
|
+
"penwidth",
|
|
907
|
+
"gradientangle",
|
|
908
|
+
"fontname",
|
|
909
|
+
"fontsize",
|
|
910
|
+
"fontcolor",
|
|
911
|
+
"tooltip"
|
|
912
|
+
];
|
|
913
|
+
const EDGE_ATTRIBUTES = [
|
|
914
|
+
"colorscheme",
|
|
915
|
+
"color",
|
|
916
|
+
"style",
|
|
917
|
+
"penwidth",
|
|
918
|
+
"fontname",
|
|
919
|
+
"fontsize",
|
|
920
|
+
"fontcolor",
|
|
921
|
+
"tooltip",
|
|
922
|
+
"edgetooltip",
|
|
923
|
+
"arrowhead",
|
|
924
|
+
"arrowtail",
|
|
925
|
+
"dir",
|
|
926
|
+
"arrowsize",
|
|
927
|
+
"fillcolor",
|
|
928
|
+
"headlabel",
|
|
929
|
+
"taillabel",
|
|
930
|
+
"xlabel"
|
|
931
|
+
];
|
|
932
|
+
const NOTED_ATTRIBUTES = ["peripheries", "xlabel"];
|
|
933
|
+
const CIRCLE_LIKE = /* @__PURE__ */ new Set(["circle", "doublecircle", "mcircle", "square", "msquare"]);
|
|
934
|
+
const BOX_LIKE = /* @__PURE__ */ new Set(["box", "rect", "rectangle", "square"]);
|
|
935
|
+
const ELLIPSE_LIKE = /* @__PURE__ */ new Set(["", "ellipse", "oval", "circle"]);
|
|
936
|
+
const NO_OUTLINE = /* @__PURE__ */ new Set(["plain", "plaintext", "none"]);
|
|
937
|
+
const RECORD = /* @__PURE__ */ new Set(["record", "mrecord"]);
|
|
938
|
+
const TRUE_TEXTS = /* @__PURE__ */ new Set(["true", "yes", "shape"]);
|
|
939
|
+
const POINT_SIZE = 0.05;
|
|
940
|
+
const REGULAR_SIZE = 0.5;
|
|
941
|
+
function isHtmlString(text) {
|
|
942
|
+
if (text.length < 2 || !text.startsWith("<") || !text.endsWith(">")) {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
let depth = 0;
|
|
946
|
+
for (let i = 0; i < text.length; i++) {
|
|
947
|
+
const c = text.codePointAt(i);
|
|
948
|
+
if (c === 60) {
|
|
949
|
+
depth++;
|
|
950
|
+
} else if (c === 62) {
|
|
951
|
+
depth--;
|
|
952
|
+
if (depth === 0 && i !== text.length - 1) {
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return depth === 0;
|
|
958
|
+
}
|
|
959
|
+
function cellText(table, name, i) {
|
|
960
|
+
if (!table.has(name)) {
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
const v = plainCell(table.value(name, i));
|
|
964
|
+
if (typeof v === "string") {
|
|
965
|
+
return v;
|
|
966
|
+
}
|
|
967
|
+
return typeof v === "number" || typeof v === "boolean" ? String(v) : null;
|
|
968
|
+
}
|
|
969
|
+
function nonEmpty(v) {
|
|
970
|
+
return v === null || v === "" ? null : v;
|
|
971
|
+
}
|
|
972
|
+
function styleTokens(raw, at) {
|
|
973
|
+
if (raw === null) {
|
|
974
|
+
return [];
|
|
975
|
+
}
|
|
976
|
+
if (raw.length > MAX_STYLE_VALUE_LENGTH) {
|
|
977
|
+
noteAt(at, STYLE_ISSUE.VALUE_TOO_LONG, `${String(raw.length)} characters`);
|
|
978
|
+
return [];
|
|
979
|
+
}
|
|
980
|
+
return raw.toLowerCase().split(",").map((t) => t.replace(/^[ \t\r\n]+/, "")).filter((t) => t !== "");
|
|
981
|
+
}
|
|
982
|
+
function setLineWidth(tokens) {
|
|
983
|
+
for (const t of tokens) {
|
|
984
|
+
if (t.startsWith("setlinewidth(") && t.endsWith(")")) {
|
|
985
|
+
return t.slice("setlinewidth(".length, -1).trim();
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
function isTrue(raw) {
|
|
991
|
+
if (raw === null) {
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
const t = raw.trim().toLowerCase();
|
|
995
|
+
return TRUE_TEXTS.has(t) || /^-?\d+$/.test(t) && Number(t) !== 0;
|
|
996
|
+
}
|
|
997
|
+
function labelText(raw, names, html, record, at) {
|
|
998
|
+
if (isHtmlString(raw)) {
|
|
999
|
+
return htmlLabelText(raw, at);
|
|
1000
|
+
}
|
|
1001
|
+
if (html.has(`<${raw}>`)) {
|
|
1002
|
+
return htmlLabelText(`<${raw}>`, at);
|
|
1003
|
+
}
|
|
1004
|
+
return dotLabelText(record ? recordLabelText(raw, at) : raw, names, at);
|
|
1005
|
+
}
|
|
1006
|
+
function readLabelAlign(raw, names, html, record, put) {
|
|
1007
|
+
if (isHtmlString(raw) || html.has(`<${raw}>`)) {
|
|
1008
|
+
const aligns = new Set(
|
|
1009
|
+
[...raw.matchAll(/<br\b([^>]*)>/gi)].map(
|
|
1010
|
+
(m) => /\balign\s*=\s*["']?(left|right|center)/i.exec(m[1])?.[1].toLowerCase() ?? "center"
|
|
1011
|
+
)
|
|
1012
|
+
);
|
|
1013
|
+
const body = isHtmlString(raw) ? raw.slice(1, -1) : raw;
|
|
1014
|
+
const last = /<br\b[^>]*>(?![\s\S]*<br\b)/i.exec(body);
|
|
1015
|
+
const tail = last === null ? "" : body.slice(last.index + last[0].length);
|
|
1016
|
+
if (stripTags(tail).trim() !== "") {
|
|
1017
|
+
aligns.add("center");
|
|
1018
|
+
}
|
|
1019
|
+
const [only] = aligns;
|
|
1020
|
+
if (aligns.size === 1 && only !== "center") {
|
|
1021
|
+
put("labelTextAlign", only);
|
|
1022
|
+
}
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (record) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const align = dotLabelAlign(raw, names);
|
|
1029
|
+
if (align !== null) {
|
|
1030
|
+
put("labelTextAlign", align);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function stripTags(text) {
|
|
1034
|
+
let out = "";
|
|
1035
|
+
let i = 0;
|
|
1036
|
+
while (i < text.length) {
|
|
1037
|
+
const open = text.indexOf("<", i);
|
|
1038
|
+
const close = open < 0 ? -1 : text.indexOf(">", open + 1);
|
|
1039
|
+
if (close < 0) {
|
|
1040
|
+
return out + text.slice(i);
|
|
1041
|
+
}
|
|
1042
|
+
out += text.slice(i, open);
|
|
1043
|
+
i = close + 1;
|
|
1044
|
+
}
|
|
1045
|
+
return out;
|
|
1046
|
+
}
|
|
1047
|
+
function readHtmlRuns(raw, html, scheme, put, at) {
|
|
1048
|
+
if (!isHtmlString(raw) && !html.has(`<${raw}>`)) {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const markup = isHtmlString(raw) ? raw : `<${raw}>`;
|
|
1052
|
+
const runs = [];
|
|
1053
|
+
for (const span of htmlLabelSpans(markup)) {
|
|
1054
|
+
const run = { text: span.text, ...htmlRunFont(span.tags, scheme, at("labelRuns")) };
|
|
1055
|
+
const last = runs.at(-1);
|
|
1056
|
+
if (last !== void 0 && JSON.stringify({ ...last, text: "" }) === JSON.stringify({ ...run, text: "" })) {
|
|
1057
|
+
last.text = `${String(last.text)}${run.text}`;
|
|
1058
|
+
} else {
|
|
1059
|
+
runs.push(run);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
trimBlankRuns(runs);
|
|
1063
|
+
if (runs.every((r) => Object.keys(r).length === 1)) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
if (runs.length === 1) {
|
|
1067
|
+
const { text: _text, ...font } = runs[0];
|
|
1068
|
+
for (const [property, value] of Object.entries(font)) {
|
|
1069
|
+
put(RUN_PROPERTY[property], value);
|
|
1070
|
+
}
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
runs[0].text = String(runs[0].text).trimStart();
|
|
1074
|
+
const end = runs.at(-1);
|
|
1075
|
+
if (end !== void 0) {
|
|
1076
|
+
end.text = String(end.text).trimEnd();
|
|
1077
|
+
}
|
|
1078
|
+
put("labelRuns", JSON.stringify(runs));
|
|
1079
|
+
}
|
|
1080
|
+
function trimBlankRuns(runs) {
|
|
1081
|
+
while (runs.length > 0 && String(runs[0].text).trim() === "") {
|
|
1082
|
+
runs.shift();
|
|
1083
|
+
}
|
|
1084
|
+
while (runs.length > 0 && String(runs.at(-1)?.text).trim() === "") {
|
|
1085
|
+
runs.pop();
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const RUN_PROPERTY = {
|
|
1089
|
+
weight: "labelWeight",
|
|
1090
|
+
italic: "labelItalic",
|
|
1091
|
+
font: "labelFont",
|
|
1092
|
+
size: "labelSize",
|
|
1093
|
+
color: "labelColor"
|
|
1094
|
+
};
|
|
1095
|
+
function htmlRunFont(tags, scheme, site) {
|
|
1096
|
+
const out = {};
|
|
1097
|
+
for (const tag of tags) {
|
|
1098
|
+
const name = /^<(\w+)/.exec(tag)?.[1].toLowerCase();
|
|
1099
|
+
if (name === "b") {
|
|
1100
|
+
out.weight = "bold";
|
|
1101
|
+
} else if (name === "i") {
|
|
1102
|
+
out.italic = true;
|
|
1103
|
+
} else if (name === "font") {
|
|
1104
|
+
for (const [attr, value] of tagAttributes(tag)) {
|
|
1105
|
+
readFontAttribute(attr.toLowerCase(), value, scheme, site, out);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return out;
|
|
1110
|
+
}
|
|
1111
|
+
function readFontAttribute(attr, value, scheme, site, out) {
|
|
1112
|
+
if (attr === "face" && value.trim() !== "" && value.length <= MAX_STYLE_VALUE_LENGTH) {
|
|
1113
|
+
const drawn = graphvizFont(value);
|
|
1114
|
+
out.font = drawn.family;
|
|
1115
|
+
if (drawn.bold) {
|
|
1116
|
+
out.weight = "bold";
|
|
1117
|
+
}
|
|
1118
|
+
if (drawn.italic) {
|
|
1119
|
+
out.italic = true;
|
|
1120
|
+
}
|
|
1121
|
+
} else if (attr === "point-size" && Number(value) > 0) {
|
|
1122
|
+
out.size = Number(value);
|
|
1123
|
+
} else if (attr === "color") {
|
|
1124
|
+
const c = canonColor(value, "graphviz", site, { scheme, use: "text" });
|
|
1125
|
+
if (c !== null) {
|
|
1126
|
+
out.color = c.color;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const ATTRIBUTE_NAME_CHAR = /^[\w-]$/;
|
|
1131
|
+
function tagAttributes(tag) {
|
|
1132
|
+
const attributes = [];
|
|
1133
|
+
let i = 0;
|
|
1134
|
+
while (i < tag.length) {
|
|
1135
|
+
if (!ATTRIBUTE_NAME_CHAR.test(tag[i])) {
|
|
1136
|
+
i++;
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
let end = i;
|
|
1140
|
+
while (end < tag.length && ATTRIBUTE_NAME_CHAR.test(tag[end])) {
|
|
1141
|
+
end++;
|
|
1142
|
+
}
|
|
1143
|
+
const value = quotedValueAt(tag, end);
|
|
1144
|
+
if (value !== null) {
|
|
1145
|
+
attributes.push([tag.slice(i, end), value.text]);
|
|
1146
|
+
}
|
|
1147
|
+
i = value === null ? end : value.next;
|
|
1148
|
+
}
|
|
1149
|
+
return attributes;
|
|
1150
|
+
}
|
|
1151
|
+
function quotedValueAt(tag, at) {
|
|
1152
|
+
let i = at;
|
|
1153
|
+
while (i < tag.length && /\s/.test(tag[i])) {
|
|
1154
|
+
i++;
|
|
1155
|
+
}
|
|
1156
|
+
if (tag[i] !== "=") {
|
|
1157
|
+
return null;
|
|
1158
|
+
}
|
|
1159
|
+
i++;
|
|
1160
|
+
while (i < tag.length && /\s/.test(tag[i])) {
|
|
1161
|
+
i++;
|
|
1162
|
+
}
|
|
1163
|
+
const quote = tag[i];
|
|
1164
|
+
const close = quote === '"' || quote === "'" ? tag.indexOf(quote, i + 1) : -1;
|
|
1165
|
+
return close < 0 ? null : { text: tag.slice(i + 1, close), next: close + 1 };
|
|
1166
|
+
}
|
|
1167
|
+
function readColor(raw, scheme, use, property, opacity, put, at) {
|
|
1168
|
+
if (raw === null) {
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
const c = canonColor(raw, "graphviz", at(property), { scheme, use });
|
|
1172
|
+
if (c === null) {
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
put(property, c.color);
|
|
1176
|
+
if (property === "borderColor") {
|
|
1177
|
+
put("borderOpacity", c.alpha > 0 && c.alpha < 1 ? c.alpha : null);
|
|
1178
|
+
} else if (opacity !== null) {
|
|
1179
|
+
put(opacity, c.alpha);
|
|
1180
|
+
} else {
|
|
1181
|
+
colorOpacity(c, null, at(property), false);
|
|
1182
|
+
}
|
|
1183
|
+
return c.alpha;
|
|
1184
|
+
}
|
|
1185
|
+
function inchesOf(points) {
|
|
1186
|
+
const want = num6(points);
|
|
1187
|
+
for (let digits = 6; digits < 17; digits++) {
|
|
1188
|
+
const inches2 = Number((points / DOT_POINTS_PER_INCH).toPrecision(digits));
|
|
1189
|
+
if (num6(inches2 * DOT_POINTS_PER_INCH) === want) {
|
|
1190
|
+
return inches2 + 0;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return points / DOT_POINTS_PER_INCH;
|
|
1194
|
+
}
|
|
1195
|
+
function inches(raw, at) {
|
|
1196
|
+
if (raw === null) {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
const v = raw.trim() === "" ? Number.NaN : Number(raw);
|
|
1200
|
+
if (!Number.isFinite(v) || v < 0) {
|
|
1201
|
+
noteAt(at, STYLE_ISSUE.BAD_VALUE, raw.slice(0, MAX_STYLE_VALUE_LENGTH));
|
|
1202
|
+
return null;
|
|
1203
|
+
}
|
|
1204
|
+
return v;
|
|
1205
|
+
}
|
|
1206
|
+
function readCommon(get, tokens, scheme, names, put, at) {
|
|
1207
|
+
if (tokens.includes("invis") || tokens.includes("invisible")) {
|
|
1208
|
+
put("visible", false);
|
|
1209
|
+
}
|
|
1210
|
+
const putFont = (property, value) => {
|
|
1211
|
+
put(property, property === "labelOpacity" && value === 1 ? null : value);
|
|
1212
|
+
};
|
|
1213
|
+
readColor(nonEmpty(get("fontcolor")), scheme, "text", "labelColor", "labelOpacity", putFont, at);
|
|
1214
|
+
const size = get("fontsize");
|
|
1215
|
+
put("labelSize", size === null ? null : canonNumber(size, at("labelSize")));
|
|
1216
|
+
const font = get("fontname");
|
|
1217
|
+
if (font !== null && font.length <= MAX_STYLE_VALUE_LENGTH) {
|
|
1218
|
+
const drawn = graphvizFont(font);
|
|
1219
|
+
put("labelFont", drawn.family);
|
|
1220
|
+
put("labelWeight", drawn.bold ? "bold" : null);
|
|
1221
|
+
put("labelItalic", drawn.italic ? true : null);
|
|
1222
|
+
}
|
|
1223
|
+
const tip = get("tooltip") ?? get("edgetooltip");
|
|
1224
|
+
if (tip !== null) {
|
|
1225
|
+
put("tooltip", dotLabelText(tip, names, at("tooltip")));
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
function readNodeLabelAndSize(n, names) {
|
|
1229
|
+
const { get, cx, put, at, shape } = n;
|
|
1230
|
+
const label = n.point ? null : get(LABEL_ATTRIBUTE);
|
|
1231
|
+
const shown = label === null ? cx.name : labelText(label, names, cx.html, RECORD.has(shape), at("label"));
|
|
1232
|
+
readNodeLabel(n, label, shown, names);
|
|
1233
|
+
const htmlLabel = label !== null && (isHtmlString(label) || cx.html.has(`<${label}>`));
|
|
1234
|
+
if (shape === "plain" && shown === "" && !htmlLabel) {
|
|
1235
|
+
put("visible", false);
|
|
1236
|
+
}
|
|
1237
|
+
if (cx.cluster) {
|
|
1238
|
+
readClusterBox(get, put);
|
|
1239
|
+
} else {
|
|
1240
|
+
readNodeSize(n, label, shown, htmlLabel, names);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
function readNodeLabel(n, label, shown, names) {
|
|
1244
|
+
const { get, cx, put, at, shape } = n;
|
|
1245
|
+
if (label !== null && !(cx.cluster && label === "")) {
|
|
1246
|
+
put("label", shown);
|
|
1247
|
+
readLabelAlign(label, names, cx.html, RECORD.has(shape), put);
|
|
1248
|
+
readHtmlRuns(label, cx.html, n.scheme, put, at);
|
|
1249
|
+
if (cx.cluster) {
|
|
1250
|
+
put("labelPosition", clusterLabelPosition(get));
|
|
1251
|
+
readClusterLabelPlace(get, put);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (!cx.cluster && !n.point) {
|
|
1255
|
+
const position = nodeLabelPosition(get);
|
|
1256
|
+
put("labelPosition", position === "center" ? null : position);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
function readNodeSize(n, label, shown, htmlLabel, names) {
|
|
1260
|
+
const { get, put, at, shape } = n;
|
|
1261
|
+
readSize(get, shape, n.point, put, at);
|
|
1262
|
+
if (RECORD.has(shape) && label !== null && !htmlLabel) {
|
|
1263
|
+
readRecord(n, label, names);
|
|
1264
|
+
} else if (!n.point) {
|
|
1265
|
+
readLabelFit(get, shape, label, shown, put);
|
|
1266
|
+
readQuantum(n, label);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const DEFAULT_NODE_SIZE = { w: 54, h: 36 };
|
|
1270
|
+
function readRecord(n, label, names) {
|
|
1271
|
+
const { get, put, at, cx } = n;
|
|
1272
|
+
const pt = (attr, fallback) => {
|
|
1273
|
+
const v = inches(get(attr), at(attr === "width" ? "width" : "height"));
|
|
1274
|
+
return v === null ? fallback : v * DOT_POINTS_PER_INCH;
|
|
1275
|
+
};
|
|
1276
|
+
const size = Number(get("fontsize") ?? 14);
|
|
1277
|
+
const lay = layoutRecord(
|
|
1278
|
+
label,
|
|
1279
|
+
!cx.flip,
|
|
1280
|
+
pt("width", DEFAULT_NODE_SIZE.w),
|
|
1281
|
+
pt("height", DEFAULT_NODE_SIZE.h),
|
|
1282
|
+
isTrue(get("fixedsize")),
|
|
1283
|
+
Number.isFinite(size) && size > 0 ? size : 14,
|
|
1284
|
+
(line) => dotLabelText(line, names)
|
|
1285
|
+
);
|
|
1286
|
+
const hw = lay.width / 2;
|
|
1287
|
+
const hh = lay.height / 2;
|
|
1288
|
+
const fx = (x) => String(num6(hw > 0 ? x / hw : 0));
|
|
1289
|
+
const fy = (y) => String(num6(hh > 0 ? y / hh : 0));
|
|
1290
|
+
put("width", num6(lay.width));
|
|
1291
|
+
put("height", num6(lay.height));
|
|
1292
|
+
put("label", "");
|
|
1293
|
+
put(
|
|
1294
|
+
"shapeLines",
|
|
1295
|
+
lay.dividers.length === 0 ? null : lay.dividers.map(([x0, y0, x1, y1]) => `${fx(x0)},${fy(y0)} ${fx(x1)},${fy(y1)}`).join("; ")
|
|
1296
|
+
);
|
|
1297
|
+
put(
|
|
1298
|
+
"shapeTexts",
|
|
1299
|
+
lay.texts.length === 0 ? null : JSON.stringify(lay.texts.map((t) => [Number(fx(t.x)), Number(fy(t.y)), t.text]))
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
function readClusterBox(get, put) {
|
|
1303
|
+
const parts = (get("bb") ?? "").split(",").map(Number);
|
|
1304
|
+
if (parts.length === 4 && parts.every(Number.isFinite) && parts[2] >= parts[0] && parts[3] >= parts[1]) {
|
|
1305
|
+
put("width", num6(parts[2] - parts[0]));
|
|
1306
|
+
put("height", num6(parts[3] - parts[1]));
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
function readNode(get, cx, put, at) {
|
|
1310
|
+
const tokens = styleTokens(get("style"), at("visible"));
|
|
1311
|
+
const shapeRaw = nonEmpty(get("shape"));
|
|
1312
|
+
const shape = (shapeRaw ?? "").toLowerCase();
|
|
1313
|
+
const n = {
|
|
1314
|
+
get,
|
|
1315
|
+
cx,
|
|
1316
|
+
put,
|
|
1317
|
+
at,
|
|
1318
|
+
tokens,
|
|
1319
|
+
shapeRaw,
|
|
1320
|
+
shape,
|
|
1321
|
+
point: !cx.cluster && shape === "point",
|
|
1322
|
+
scheme: get("colorscheme"),
|
|
1323
|
+
color: nonEmpty(get("color"))
|
|
1324
|
+
};
|
|
1325
|
+
const names = cx.cluster ? { graph: cx.name } : { node: cx.name, graph: cx.graph, edge: "" };
|
|
1326
|
+
readCommon(get, tokens, n.scheme, names, put, at);
|
|
1327
|
+
if (cx.cluster) {
|
|
1328
|
+
readClusterFill(n);
|
|
1329
|
+
} else {
|
|
1330
|
+
readNodeFill(n);
|
|
1331
|
+
}
|
|
1332
|
+
readNodeBands(n);
|
|
1333
|
+
readNodeOutline(n);
|
|
1334
|
+
readNodeShape(n);
|
|
1335
|
+
readNodeLabelAndSize(n, names);
|
|
1336
|
+
if (nonEmpty(get("xlabel")) !== null) {
|
|
1337
|
+
noteAt(at("label"), STYLE_ISSUE.NOT_CARRIED, "xlabel");
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
function nodeLabelPosition(get) {
|
|
1341
|
+
const loc = (get("labelloc") ?? "").trim().toLowerCase();
|
|
1342
|
+
if (loc.startsWith("t")) {
|
|
1343
|
+
return "inside-top";
|
|
1344
|
+
}
|
|
1345
|
+
return loc.startsWith("b") ? "inside-bottom" : "center";
|
|
1346
|
+
}
|
|
1347
|
+
const CLUSTER_LABEL_INSET = 8;
|
|
1348
|
+
function readClusterLabelPlace(get, put) {
|
|
1349
|
+
const lp = (get("lp") ?? "").split(",").map(Number);
|
|
1350
|
+
const bb = (get("bb") ?? "").split(",").map(Number);
|
|
1351
|
+
if (lp.length === 2 && bb.length === 4 && [...lp, ...bb].every(Number.isFinite)) {
|
|
1352
|
+
put("labelPosition", "center");
|
|
1353
|
+
put("labelOffsetX", num6(lp[0] - (bb[0] + bb[2]) / 2));
|
|
1354
|
+
put("labelOffsetY", num6((bb[1] + bb[3]) / 2 - lp[1]));
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const just = (get("labeljust") ?? "").trim().toLowerCase();
|
|
1358
|
+
if (just.startsWith("l")) {
|
|
1359
|
+
put("labelOffsetX", CLUSTER_LABEL_INSET);
|
|
1360
|
+
} else if (just.startsWith("r")) {
|
|
1361
|
+
put("labelOffsetX", -CLUSTER_LABEL_INSET);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
function clusterLabelPosition(get) {
|
|
1365
|
+
const loc = (get("labelloc") ?? "").trim().toLowerCase().startsWith("b") ? "inside-bottom" : "inside-top";
|
|
1366
|
+
const just = (get("labeljust") ?? "").trim().toLowerCase();
|
|
1367
|
+
if (just.startsWith("l")) {
|
|
1368
|
+
return `${loc}-left`;
|
|
1369
|
+
}
|
|
1370
|
+
return just.startsWith("r") ? `${loc}-right` : loc;
|
|
1371
|
+
}
|
|
1372
|
+
function readClusterFill(n) {
|
|
1373
|
+
const { get, tokens } = n;
|
|
1374
|
+
const filled = tokens.includes("filled") || tokens.includes("radial") || tokens.includes("striped");
|
|
1375
|
+
const bg = nonEmpty(get("bgcolor"));
|
|
1376
|
+
const raw = filled ? nonEmpty(get("fillcolor")) ?? n.color ?? bg ?? "lightgrey" : bg;
|
|
1377
|
+
readColor(raw, n.scheme, "fill", "fill", "fillOpacity", n.put, n.at);
|
|
1378
|
+
if (!tokens.includes("striped")) {
|
|
1379
|
+
readFillGradient(n, raw);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
function readFillGradient(n, raw) {
|
|
1383
|
+
const colon = raw === null ? -1 : raw.indexOf(":");
|
|
1384
|
+
if (raw === null || colon < 0) {
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
const radial = n.tokens.includes("radial");
|
|
1388
|
+
if (radial) {
|
|
1389
|
+
n.put("fillGradientKind", "radial");
|
|
1390
|
+
}
|
|
1391
|
+
const frac = Number(raw.slice(0, colon).split(";")[1]);
|
|
1392
|
+
if (frac > 0 && frac < 1) {
|
|
1393
|
+
n.put("fillGradientStops", `${String(num6(frac - 1e-3))} ${String(num6(frac))}`);
|
|
1394
|
+
}
|
|
1395
|
+
const second = raw.slice(colon + 1).split(":")[0].split(";")[0];
|
|
1396
|
+
const c = canonColor(second === "" ? "black" : second, "graphviz", n.at("fillGradient"), {
|
|
1397
|
+
scheme: n.scheme,
|
|
1398
|
+
use: "fill"
|
|
1399
|
+
});
|
|
1400
|
+
if (c === null) {
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
n.put("fillGradient", c.color);
|
|
1404
|
+
if (radial) {
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
const angle = Number(nonEmpty(n.get("gradientangle")) ?? "0");
|
|
1408
|
+
const turned = Number.isFinite(angle) ? (-angle % 360 + 360) % 360 : 0;
|
|
1409
|
+
n.put("fillGradientAngle", num6(turned) + 0);
|
|
1410
|
+
}
|
|
1411
|
+
function readNodeFill(n) {
|
|
1412
|
+
const { point: point2 } = n;
|
|
1413
|
+
const fillcolor = nonEmpty(n.get("fillcolor"));
|
|
1414
|
+
if (isFilled(n)) {
|
|
1415
|
+
const raw = fillcolor ?? n.color ?? (point2 ? "black" : "lightgrey");
|
|
1416
|
+
readColor(raw, n.scheme, "fill", "fill", "fillOpacity", n.put, n.at);
|
|
1417
|
+
const banded = n.tokens.includes("striped") && BOX_LIKE.has(n.shape) || n.tokens.includes("wedged") && ELLIPSE_LIKE.has(n.shape);
|
|
1418
|
+
if ((n.tokens.includes("filled") || n.tokens.includes("radial")) && !banded) {
|
|
1419
|
+
readFillGradient(n, raw);
|
|
1420
|
+
}
|
|
1421
|
+
} else if (fillcolor !== null) {
|
|
1422
|
+
noteAt(n.at("fill"), STYLE_ISSUE.UNFILLED, fillcolor.slice(0, MAX_STYLE_VALUE_LENGTH));
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function readNodeBands(n) {
|
|
1426
|
+
const striped = n.tokens.includes("striped") && (n.cx.cluster || BOX_LIKE.has(n.shape));
|
|
1427
|
+
const wedged = !n.cx.cluster && n.tokens.includes("wedged") && ELLIPSE_LIKE.has(n.shape);
|
|
1428
|
+
const list = nonEmpty(n.get("fillcolor")) ?? n.color;
|
|
1429
|
+
if (!striped && !wedged || !list?.includes(":")) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
const bands = colorBands(list, n.scheme, "fill", n.at(striped ? "stripeColors" : "pieColors"));
|
|
1433
|
+
if (bands.length === 0) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const order = striped ? bands : [...bands].reverse();
|
|
1437
|
+
const prefix = striped ? "stripe" : "pie";
|
|
1438
|
+
n.put(`${prefix}Colors`, order.map((b) => b.color).join(" "));
|
|
1439
|
+
n.put(`${prefix}Sizes`, order.map((b) => String(b.size)).join(" "));
|
|
1440
|
+
if (striped) {
|
|
1441
|
+
n.put("stripeDirection", "right");
|
|
1442
|
+
} else {
|
|
1443
|
+
n.put("pieFitBox", true);
|
|
1444
|
+
n.put("pieRotation", 90);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
function colorBands(list, scheme, use, site) {
|
|
1448
|
+
const parts = list.split(":").map((part) => {
|
|
1449
|
+
const [color, fraction] = part.split(";");
|
|
1450
|
+
const c = canonColor(color.trim(), "graphviz", site, { scheme, use });
|
|
1451
|
+
const f = fraction === void 0 ? Number.NaN : Number(fraction);
|
|
1452
|
+
return { color: c?.color ?? null, fraction: Number.isFinite(f) && f >= 0 ? f : null };
|
|
1453
|
+
});
|
|
1454
|
+
const kept = parts.flatMap((p) => p.color === null ? [] : [{ color: p.color, fraction: p.fraction }]);
|
|
1455
|
+
const given = kept.reduce((sum, p) => sum + (p.fraction ?? 0), 0);
|
|
1456
|
+
const unsized = kept.filter((p) => p.fraction === null).length;
|
|
1457
|
+
const share = unsized > 0 ? Math.max(0, 1 - given) / unsized : 0;
|
|
1458
|
+
let used = 0;
|
|
1459
|
+
const bands = kept.flatMap((p) => {
|
|
1460
|
+
const size = Math.min(p.fraction ?? share, 1 - used);
|
|
1461
|
+
used += Math.max(size, 0);
|
|
1462
|
+
return size > 0 ? [{ color: p.color, size: num6(size) }] : [];
|
|
1463
|
+
});
|
|
1464
|
+
const last = bands.at(-1);
|
|
1465
|
+
if (last !== void 0 && unsized === 0 && used < 1) {
|
|
1466
|
+
last.size = num6(last.size + 1 - used);
|
|
1467
|
+
}
|
|
1468
|
+
return bands;
|
|
1469
|
+
}
|
|
1470
|
+
function isFilled(n) {
|
|
1471
|
+
const { shape, point: point2 } = n;
|
|
1472
|
+
return point2 || n.tokens.some(
|
|
1473
|
+
(t) => t === "filled" || t === "radial" || t === "striped" && BOX_LIKE.has(shape) || t === "wedged" && ELLIPSE_LIKE.has(shape)
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
function readNodeOutline(n) {
|
|
1477
|
+
const { get, cx, put, at, tokens } = n;
|
|
1478
|
+
if (!cx.cluster && NO_OUTLINE.has(n.shape)) {
|
|
1479
|
+
put("borderWidth", 0);
|
|
1480
|
+
} else {
|
|
1481
|
+
const outline = cx.cluster ? nonEmpty(get("pencolor")) ?? n.color : n.color;
|
|
1482
|
+
const alpha = readColor(outline, n.scheme, "line", "borderColor", null, put, at);
|
|
1483
|
+
const width = nonEmpty(get("penwidth")) ?? setLineWidth(tokens);
|
|
1484
|
+
if (alpha === 0 || Number(nonEmpty(get("peripheries")) ?? "1") === 0) {
|
|
1485
|
+
put("borderWidth", 0);
|
|
1486
|
+
} else if (width !== null) {
|
|
1487
|
+
put("borderWidth", penWidth(width, get("penwidth") !== null, at("borderWidth")));
|
|
1488
|
+
} else if (tokens.includes("bold")) {
|
|
1489
|
+
put("borderWidth", 2);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (tokens.includes("dashed")) {
|
|
1493
|
+
put("borderStyle", "dashed");
|
|
1494
|
+
} else if (tokens.includes("dotted")) {
|
|
1495
|
+
put("borderStyle", "dotted");
|
|
1496
|
+
}
|
|
1497
|
+
if (!cx.cluster) {
|
|
1498
|
+
readNodeRings(n);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const SHAPE_PERIPHERIES = /* @__PURE__ */ new Map([
|
|
1502
|
+
["doublecircle", 2],
|
|
1503
|
+
["doubleoctagon", 2],
|
|
1504
|
+
["tripleoctagon", 3]
|
|
1505
|
+
]);
|
|
1506
|
+
const PERIPHERY_GAP = 4;
|
|
1507
|
+
function readNodeRings(n) {
|
|
1508
|
+
const raw = nonEmpty(n.get("peripheries"));
|
|
1509
|
+
const count = raw === null ? SHAPE_PERIPHERIES.get(n.shape) ?? 1 : Math.trunc(Number(raw));
|
|
1510
|
+
if (Number.isNaN(count) || count <= 1 || NO_OUTLINE.has(n.shape)) {
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const c = n.color === null ? null : canonColor(n.color, "graphviz", n.at("outerBorderColor"), {
|
|
1514
|
+
scheme: n.scheme,
|
|
1515
|
+
use: "line"
|
|
1516
|
+
});
|
|
1517
|
+
if (c !== null && c.alpha === 0) {
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
const widthRaw = nonEmpty(n.get("penwidth")) ?? setLineWidth(n.tokens);
|
|
1521
|
+
const parsed = widthRaw === null ? Number.NaN : Number(widthRaw);
|
|
1522
|
+
const unset = n.tokens.includes("bold") ? 2 : 1;
|
|
1523
|
+
const pen = Number.isFinite(parsed) ? Math.max(0, parsed) : unset;
|
|
1524
|
+
n.put("outerBorderColor", c?.color ?? "#000000");
|
|
1525
|
+
n.put("outerBorderOpacity", c !== null && c.alpha < 1 ? c.alpha : null);
|
|
1526
|
+
n.put("outerBorderWidth", num6(pen));
|
|
1527
|
+
n.put("outerBorderOffset", num6(Math.max(0, PERIPHERY_GAP - pen)));
|
|
1528
|
+
if (n.tokens.includes("dashed") || n.tokens.includes("dotted")) {
|
|
1529
|
+
n.put("outerBorderStyle", n.tokens.includes("dashed") ? "dashed" : "dotted");
|
|
1530
|
+
}
|
|
1531
|
+
if (count > 2) {
|
|
1532
|
+
noteAt(n.at("outerBorderWidth"), STYLE_ISSUE.NOT_CARRIED, `peripheries=${String(count)}`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
function readNodeShape(n) {
|
|
1536
|
+
const rounded = n.tokens.includes("rounded") && !n.tokens.includes("striped");
|
|
1537
|
+
const turned = turnedPolygon(n);
|
|
1538
|
+
if (n.cx.cluster) {
|
|
1539
|
+
n.put("shape", rounded ? "round-rectangle" : "rectangle");
|
|
1540
|
+
} else if (n.shape === "polygon") {
|
|
1541
|
+
readPolygon(n, rounded);
|
|
1542
|
+
} else if (turned !== null) {
|
|
1543
|
+
n.put("shape", "polygon");
|
|
1544
|
+
n.put("shapePoints", cornersText(graphvizDrawnCorners(turned)));
|
|
1545
|
+
n.put("shapePointsStretch", false);
|
|
1546
|
+
} else if (NAMED_OUTLINES.has(n.shape)) {
|
|
1547
|
+
n.put("shape", "polygon");
|
|
1548
|
+
n.put("shapePoints", NAMED_OUTLINES.get(n.shape) ?? null);
|
|
1549
|
+
n.put("shapePointsStretch", false);
|
|
1550
|
+
} else if (GRAPHVIZ_GLYPH_SHAPES.has(n.shape)) {
|
|
1551
|
+
const size = (name) => {
|
|
1552
|
+
const v = inches(n.get(name), n.at(name));
|
|
1553
|
+
return v === null ? null : v * DOT_POINTS_PER_INCH;
|
|
1554
|
+
};
|
|
1555
|
+
putGlyph(
|
|
1556
|
+
n.shape,
|
|
1557
|
+
size("width"),
|
|
1558
|
+
size("height"),
|
|
1559
|
+
n.put,
|
|
1560
|
+
() => {
|
|
1561
|
+
const raw = nonEmpty(n.get("penwidth")) ?? setLineWidth(n.tokens);
|
|
1562
|
+
const pen = raw === null ? Number.NaN : Number(raw);
|
|
1563
|
+
if (Number.isFinite(pen)) {
|
|
1564
|
+
return Math.max(0, pen);
|
|
1565
|
+
}
|
|
1566
|
+
return n.tokens.includes("bold") ? 2 : 1;
|
|
1567
|
+
},
|
|
1568
|
+
() => n.color === null ? null : canonColor(n.color, "graphviz", n.at("outerBorderColor"), { scheme: n.scheme, use: "line" })?.color ?? null
|
|
1569
|
+
);
|
|
1570
|
+
} else if (n.shapeRaw !== null) {
|
|
1571
|
+
const shape = rounded && BOX_LIKE.has(n.shape) ? "round-rectangle" : canonEnum("shape", n.shapeRaw, "graphviz", n.at("shape"));
|
|
1572
|
+
n.put("shape", shape === "none" && isFilled(n) ? "rectangle" : shape);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
const TURNABLE_POLYGONS = new Map([
|
|
1576
|
+
...[...BOX_LIKE].map((name) => [name, { sides: 4, orientation: 0 }]),
|
|
1577
|
+
["diamond", { sides: 4, orientation: 45 }],
|
|
1578
|
+
["hexagon", { sides: 6, orientation: 0 }],
|
|
1579
|
+
["octagon", { sides: 8, orientation: 0 }]
|
|
1580
|
+
]);
|
|
1581
|
+
function turnedPolygon(n) {
|
|
1582
|
+
const turn = Number(nonEmpty(n.get("orientation")) ?? "0");
|
|
1583
|
+
const own = TURNABLE_POLYGONS.get(n.shape) ?? GRAPHVIZ_POLYGON_SHAPES.get(n.shape);
|
|
1584
|
+
if (own === void 0 || !Number.isFinite(turn) || turn % 360 === 0) {
|
|
1585
|
+
return null;
|
|
1586
|
+
}
|
|
1587
|
+
const polygon = {
|
|
1588
|
+
distortion: 0,
|
|
1589
|
+
skew: 0,
|
|
1590
|
+
...own,
|
|
1591
|
+
orientation: own.orientation + Math.max(-360, turn)
|
|
1592
|
+
};
|
|
1593
|
+
return isGraphvizBox(polygon) ? null : polygon;
|
|
1594
|
+
}
|
|
1595
|
+
function readPolygon(n, rounded) {
|
|
1596
|
+
const number = (name, fallback, least) => {
|
|
1597
|
+
const raw = nonEmpty(n.get(name));
|
|
1598
|
+
const v = raw === null ? Number.NaN : Number(raw);
|
|
1599
|
+
return Number.isFinite(v) ? Math.max(least, v) : fallback;
|
|
1600
|
+
};
|
|
1601
|
+
const polygon = {
|
|
1602
|
+
sides: Math.trunc(number("sides", 4, 0)),
|
|
1603
|
+
distortion: number("distortion", 0, -100),
|
|
1604
|
+
skew: number("skew", 0, -100),
|
|
1605
|
+
orientation: number("orientation", 0, -360)
|
|
1606
|
+
};
|
|
1607
|
+
if (isGraphvizBox(polygon)) {
|
|
1608
|
+
n.put("shape", rounded ? "round-rectangle" : "rectangle");
|
|
1609
|
+
} else if (polygon.sides < 3) {
|
|
1610
|
+
n.put("shape", "ellipse");
|
|
1611
|
+
} else {
|
|
1612
|
+
n.put("shape", "polygon");
|
|
1613
|
+
n.put("shapePoints", cornersText(graphvizDrawnCorners(polygon)));
|
|
1614
|
+
n.put("shapePointsStretch", false);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
function graphvizDrawnCorners(polygon) {
|
|
1618
|
+
const corners = graphvizPolygonCorners(polygon);
|
|
1619
|
+
const grow = (axis) => Math.max(1, 1 / Math.max(...corners.map((c) => Math.abs(c[axis]))));
|
|
1620
|
+
const [gx, gy] = [grow(0), grow(1)];
|
|
1621
|
+
return corners.map(([x, y]) => [x * gx, y * gy]);
|
|
1622
|
+
}
|
|
1623
|
+
function cornersText(corners) {
|
|
1624
|
+
return corners.map(([x, y]) => `${num6(x)},${num6(y)}`).join(" ");
|
|
1625
|
+
}
|
|
1626
|
+
const NAMED_OUTLINES = new Map(
|
|
1627
|
+
[...GRAPHVIZ_POLYGON_SHAPES].map(([name, polygon]) => [name, cornersText(graphvizDrawnCorners(polygon))])
|
|
1628
|
+
);
|
|
1629
|
+
function putGlyph(shape, width, height, put, pen, color) {
|
|
1630
|
+
const text = glyphTexts(shape, width, height);
|
|
1631
|
+
if (text === null) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
put("shape", "polygon");
|
|
1635
|
+
put("shapePoints", text.outline);
|
|
1636
|
+
put("shapePointsStretch", false);
|
|
1637
|
+
put("shapeParts", text.parts);
|
|
1638
|
+
put("shapeLines", text.lines);
|
|
1639
|
+
if (text.unstroked) {
|
|
1640
|
+
put("shapeLineWidth", pen());
|
|
1641
|
+
put("borderWidth", 0);
|
|
1642
|
+
}
|
|
1643
|
+
if (text.ring !== null) {
|
|
1644
|
+
put("outerBorderColor", color() ?? "#000000");
|
|
1645
|
+
put("outerBorderWidth", num6(pen()));
|
|
1646
|
+
put("outerBorderOffset", num6(Math.max(0, text.ring - pen())));
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
function glyphTexts(shape, width, height) {
|
|
1650
|
+
const glyph = graphvizGlyph(shape, width, height);
|
|
1651
|
+
if (glyph === null) {
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1654
|
+
const half = (v, fallback) => (v !== null && v > 0 ? v : fallback) / 2;
|
|
1655
|
+
const [W, H] = [half(width, 54), half(height, 36)];
|
|
1656
|
+
const list = (points) => cornersText(points.map(([x, y]) => [x / W, y / H]));
|
|
1657
|
+
const lists = (all) => all.length === 0 ? null : all.map(list).join("; ");
|
|
1658
|
+
return {
|
|
1659
|
+
outline: list(glyph.outline),
|
|
1660
|
+
parts: lists(glyph.parts),
|
|
1661
|
+
lines: lists(glyph.lines),
|
|
1662
|
+
unstroked: glyph.unstroked === true,
|
|
1663
|
+
ring: glyph.ring ?? null
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
function graphvizOutline(shape) {
|
|
1667
|
+
return NAMED_OUTLINES.get(shape);
|
|
1668
|
+
}
|
|
1669
|
+
function graphvizHasNoOutline(shape) {
|
|
1670
|
+
return NO_OUTLINE.has(shape);
|
|
1671
|
+
}
|
|
1672
|
+
const OUTLINE_NAMES = new Map([...NAMED_OUTLINES].map(([name, text]) => [text, name]));
|
|
1673
|
+
function glyphOf(get, width, height) {
|
|
1674
|
+
const text = (property) => {
|
|
1675
|
+
const v = get(property);
|
|
1676
|
+
return typeof v === "string" ? v.trim() : null;
|
|
1677
|
+
};
|
|
1678
|
+
const outline = text("shapePoints");
|
|
1679
|
+
if (outline === null) {
|
|
1680
|
+
return void 0;
|
|
1681
|
+
}
|
|
1682
|
+
const corners = outline === "" ? 0 : outline.split(/\s+/).length;
|
|
1683
|
+
for (const name of GRAPHVIZ_GLYPH_SHAPES) {
|
|
1684
|
+
if (graphvizGlyph(name, width, height)?.outline.length !== corners) {
|
|
1685
|
+
continue;
|
|
1686
|
+
}
|
|
1687
|
+
const g = glyphTexts(name, width, height);
|
|
1688
|
+
if (g !== null && g.outline === outline && g.parts === text("shapeParts") && g.lines === text("shapeLines")) {
|
|
1689
|
+
return { name, unstroked: g.unstroked };
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
return void 0;
|
|
1693
|
+
}
|
|
1694
|
+
function penWidth(raw, attribute, site) {
|
|
1695
|
+
const value = Number(raw.trim());
|
|
1696
|
+
if (attribute && Number.isFinite(value) && value < 0) {
|
|
1697
|
+
noteAt(site, STYLE_ISSUE.BAD_VALUE, raw.slice(0, MAX_STYLE_VALUE_LENGTH));
|
|
1698
|
+
return 0;
|
|
1699
|
+
}
|
|
1700
|
+
return canonNumber(raw, site);
|
|
1701
|
+
}
|
|
1702
|
+
function readSize(get, shape, point2, put, at) {
|
|
1703
|
+
let w = inches(get("width"), at("width"));
|
|
1704
|
+
let h = inches(get("height"), at("height"));
|
|
1705
|
+
const same = point2 || CIRCLE_LIKE.has(shape) || isTrue(get("regular"));
|
|
1706
|
+
if (shape === "plain") {
|
|
1707
|
+
w = h = 0;
|
|
1708
|
+
} else if (w === null && h === null) {
|
|
1709
|
+
if (point2) {
|
|
1710
|
+
put("width", num6(POINT_SIZE * DOT_POINTS_PER_INCH));
|
|
1711
|
+
put("height", num6(POINT_SIZE * DOT_POINTS_PER_INCH));
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
if (!same) {
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
w = h = REGULAR_SIZE;
|
|
1718
|
+
}
|
|
1719
|
+
let ww = w;
|
|
1720
|
+
let hh = h;
|
|
1721
|
+
if (point2) {
|
|
1722
|
+
ww = hh = Math.min(w ?? Infinity, h ?? Infinity);
|
|
1723
|
+
} else if (same) {
|
|
1724
|
+
ww = hh = Math.max(w ?? 0, h ?? 0);
|
|
1725
|
+
}
|
|
1726
|
+
put("width", ww === null ? null : canonNumber(ww, at("width"), DOT_POINTS_PER_INCH));
|
|
1727
|
+
put("height", hh === null ? null : canonNumber(hh, at("height"), DOT_POINTS_PER_INCH));
|
|
1728
|
+
}
|
|
1729
|
+
function readQuantum(n, label) {
|
|
1730
|
+
const { get, put, shape } = n;
|
|
1731
|
+
let scale = BOX_LIKE.has(shape) ? 1 : null;
|
|
1732
|
+
if (ELLIPSE_LIKE.has(shape)) {
|
|
1733
|
+
scale = Math.SQRT2;
|
|
1734
|
+
}
|
|
1735
|
+
const w = Number(get("width") ?? Number.NaN);
|
|
1736
|
+
const h = Number(get("height") ?? Number.NaN);
|
|
1737
|
+
if (n.cx.quantum === 0 || scale === null || label === "" || isTrue(get("fixedsize")) || !(w >= 0 && h >= 0)) {
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
const least = n.cx.quantum * scale;
|
|
1741
|
+
put("width", num6(Math.max(w * DOT_POINTS_PER_INCH, least)));
|
|
1742
|
+
put("height", num6(Math.max(h * DOT_POINTS_PER_INCH, least)));
|
|
1743
|
+
}
|
|
1744
|
+
const LABEL_PAD = { x: 8, y: 4 };
|
|
1745
|
+
const LINE_SPACING = 1.2;
|
|
1746
|
+
function readLabelFit(get, shape, label, shown, put) {
|
|
1747
|
+
if (isTrue(get("fixedsize")) || label === "" && !RECORD.has(shape)) {
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
const lines = Math.max(1, shown.replace(/\n$/, "").split("\n").length);
|
|
1751
|
+
const size = Number(get("fontsize") ?? 14);
|
|
1752
|
+
const fontSize = Number.isFinite(size) && size > 0 ? size : 14;
|
|
1753
|
+
const margin = (get("margin") ?? "").split(",").filter((text) => text.trim() !== "").map(Number).filter(isLength);
|
|
1754
|
+
const pad = shape === "plain" ? { x: 0, y: 0 } : LABEL_PAD;
|
|
1755
|
+
const padX = margin.length > 0 ? margin[0] * DOT_POINTS_PER_INCH : pad.x;
|
|
1756
|
+
const padY = margin.length > 0 ? (margin[1] ?? margin[0]) * DOT_POINTS_PER_INCH : pad.y;
|
|
1757
|
+
put("labelFit", GRAPHVIZ_DECORATED_BOXES.has(shape) ? "bounds" : "inside");
|
|
1758
|
+
put("labelPaddingX", num6(padX));
|
|
1759
|
+
put("labelPaddingY", num6(padY + (LINE_SPACING - 1) * fontSize * lines / 2));
|
|
1760
|
+
if (CIRCLE_LIKE.has(shape) || isTrue(get("regular"))) {
|
|
1761
|
+
put("labelFitSquare", true);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function isLength(value) {
|
|
1765
|
+
return Number.isFinite(value) && value >= 0;
|
|
1766
|
+
}
|
|
1767
|
+
function readEdge(get, cx, put, at) {
|
|
1768
|
+
const tokens = styleTokens(get("style"), at("visible"));
|
|
1769
|
+
const scheme = get("colorscheme");
|
|
1770
|
+
readCommon(get, tokens, scheme, cx.names, put, at);
|
|
1771
|
+
readColor(nonEmpty(get("color")), scheme, "edge", "color", "opacity", put, at);
|
|
1772
|
+
readEdgeSegments(nonEmpty(get("color")), scheme, put, at);
|
|
1773
|
+
readEdgeLine(get, tokens, put, at);
|
|
1774
|
+
readEdgeArrows(get, cx, put, at);
|
|
1775
|
+
for (const [attribute, property] of [
|
|
1776
|
+
[LABEL_ATTRIBUTE, "label"],
|
|
1777
|
+
["headlabel", "targetLabel"],
|
|
1778
|
+
["taillabel", "sourceLabel"]
|
|
1779
|
+
]) {
|
|
1780
|
+
const raw = attribute === LABEL_ATTRIBUTE ? get(attribute) ?? nonEmpty(get("xlabel")) : get(attribute);
|
|
1781
|
+
if (raw !== null) {
|
|
1782
|
+
put(property, labelText(raw, cx.names, cx.html, false, at(property)));
|
|
1783
|
+
if (property === "label") {
|
|
1784
|
+
readLabelAlign(raw, cx.names, cx.html, false, put);
|
|
1785
|
+
readHtmlRuns(raw, cx.html, scheme, put, at);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
if (get(LABEL_ATTRIBUTE) !== null && nonEmpty(get("xlabel")) !== null) {
|
|
1790
|
+
noteAt(at("label"), STYLE_ISSUE.NOT_CARRIED, "xlabel");
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
function readEdgeSegments(list, scheme, put, at) {
|
|
1794
|
+
if (list === null || !list.includes(":") || !list.includes(";")) {
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const bands = colorBands(list, scheme, "edge", at("gradientColors"));
|
|
1798
|
+
if (bands.length < 2) {
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
const colors = [];
|
|
1802
|
+
const stops = [];
|
|
1803
|
+
let start = 0;
|
|
1804
|
+
for (const band of bands) {
|
|
1805
|
+
colors.push(band.color, band.color);
|
|
1806
|
+
stops.push(num6(start), num6(Math.min(1, start + band.size)));
|
|
1807
|
+
start += band.size;
|
|
1808
|
+
}
|
|
1809
|
+
put("gradientKind", "linear");
|
|
1810
|
+
put("gradientColors", colors.join(" "));
|
|
1811
|
+
put("gradientStops", stops.join(" "));
|
|
1812
|
+
}
|
|
1813
|
+
function labelPlaceContext(snapshot) {
|
|
1814
|
+
const column = snapshot.nodes.byRole("position");
|
|
1815
|
+
return {
|
|
1816
|
+
center: (i) => {
|
|
1817
|
+
const v = column?.isSet(i) === true ? column.value(i) : null;
|
|
1818
|
+
return v === null ? null : [v[0], v[1]];
|
|
1819
|
+
},
|
|
1820
|
+
node: (i, name) => nonEmpty(cellText(snapshot.nodes, name, i))
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function splineBends(snapshot, e, pos) {
|
|
1824
|
+
if (pos === null) {
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1827
|
+
const column = snapshot.nodes.byRole("position");
|
|
1828
|
+
const center = (i) => {
|
|
1829
|
+
const v = column?.isSet(i) === true ? column.value(i) : null;
|
|
1830
|
+
return v === null ? null : [v[0], v[1]];
|
|
1831
|
+
};
|
|
1832
|
+
const [source, target] = [center(snapshot.edgeSource(e)), center(snapshot.edgeTarget(e))];
|
|
1833
|
+
if (source === null || target === null) {
|
|
1834
|
+
return null;
|
|
1835
|
+
}
|
|
1836
|
+
const tip = (spline, flag) => {
|
|
1837
|
+
const token = (spline ?? "").trim().split(/\s+/).find((t) => t.startsWith(`${flag},`));
|
|
1838
|
+
const [x, y] = (token ?? "").slice(2).split(",").map(Number);
|
|
1839
|
+
return token !== void 0 && Number.isFinite(x) && Number.isFinite(y) ? [x, y] : null;
|
|
1840
|
+
};
|
|
1841
|
+
const parts = pos.split(";");
|
|
1842
|
+
const tips = { start: tip(parts[0], "s"), end: tip(parts.at(-1), "e") };
|
|
1843
|
+
const splines = parts.map(
|
|
1844
|
+
(spline) => spline.trim().split(/\s+/).filter((t) => !/^[es],/.test(t)).map((t) => {
|
|
1845
|
+
const [x, y] = t.split(",").map(Number);
|
|
1846
|
+
return [x, y];
|
|
1847
|
+
})
|
|
1848
|
+
);
|
|
1849
|
+
return graphvizRouteBends(source, target, splines, tips);
|
|
1850
|
+
}
|
|
1851
|
+
const GRAPHVIZ_LOOP_STEP = { x: 18, y: 9 };
|
|
1852
|
+
function graphvizLoops(snapshot) {
|
|
1853
|
+
const portless = (get) => nonEmpty(get("tailport")) === null && nonEmpty(get("headport")) === null && nonEmpty(get(POS_ATTRIBUTE)) === null;
|
|
1854
|
+
const onNode = /* @__PURE__ */ new Map();
|
|
1855
|
+
const splines = (nonEmpty(cellText(snapshot.graph, "splines", 0)) ?? "").toLowerCase();
|
|
1856
|
+
const laid = splines !== "ortho" && splines !== "none" && nonEmpty(cellText(snapshot.graph, STYLE_BLOCK_NAME, 0)) === null;
|
|
1857
|
+
for (let e = 0; e < snapshot.edgeCount && laid; e++) {
|
|
1858
|
+
const n = snapshot.edgeSource(e);
|
|
1859
|
+
if (n === snapshot.edgeTarget(e) && portless((name) => cellText(snapshot.edges, name, e))) {
|
|
1860
|
+
onNode.set(n, [...onNode.get(n) ?? [], e]);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
const inches2 = (n, name, fallback) => {
|
|
1864
|
+
const v = Number(nonEmpty(cellText(snapshot.nodes, name, n)) ?? Number.NaN);
|
|
1865
|
+
return (Number.isFinite(v) && v > 0 ? v : fallback) * DOT_POINTS_PER_INCH;
|
|
1866
|
+
};
|
|
1867
|
+
return {
|
|
1868
|
+
bendsOf: (e, get) => {
|
|
1869
|
+
const n = snapshot.edgeSource(e);
|
|
1870
|
+
const list = onNode.get(n);
|
|
1871
|
+
if (n !== snapshot.edgeTarget(e) || list === void 0 || !portless(get)) {
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1874
|
+
const [w, h] = [inches2(n, "width", 0.75), inches2(n, "height", 0.5)];
|
|
1875
|
+
const k = list.indexOf(e) + 1;
|
|
1876
|
+
const dx = w / 2 + GRAPHVIZ_LOOP_STEP.x * k;
|
|
1877
|
+
const dy = GRAPHVIZ_LOOP_STEP.y * k / list.length;
|
|
1878
|
+
const curve = flattenCubic([
|
|
1879
|
+
[0, 0],
|
|
1880
|
+
[dx / 3, dy],
|
|
1881
|
+
[dx, dy],
|
|
1882
|
+
[dx, 0],
|
|
1883
|
+
[dx, -dy],
|
|
1884
|
+
[dx / 3, -dy],
|
|
1885
|
+
[0, 0]
|
|
1886
|
+
]);
|
|
1887
|
+
return curve === null ? null : canonNodeOffsets([0, 0], [w, h], curve.slice(1, -1), false);
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
function graphvizRouteBends(source, target, splines, tips) {
|
|
1892
|
+
const points = [];
|
|
1893
|
+
for (const controls of splines) {
|
|
1894
|
+
const flat = flattenCubic(controls);
|
|
1895
|
+
if (flat === null || flat.some(([x, y]) => !Number.isFinite(x) || !Number.isFinite(y))) {
|
|
1896
|
+
return null;
|
|
1897
|
+
}
|
|
1898
|
+
points.push(...flat);
|
|
1899
|
+
}
|
|
1900
|
+
const apart = (a, b) => b === void 0 || Math.hypot(a[0] - b[0], a[1] - b[1]) > 1e-6;
|
|
1901
|
+
const start = tips?.start ?? null;
|
|
1902
|
+
const end = tips?.end ?? null;
|
|
1903
|
+
if (start !== null && apart(start, points[0])) {
|
|
1904
|
+
points.unshift(start);
|
|
1905
|
+
}
|
|
1906
|
+
if (end !== null && apart(end, points.at(-1))) {
|
|
1907
|
+
points.push(end);
|
|
1908
|
+
}
|
|
1909
|
+
const [dx, dy] = [target[0] - source[0], target[1] - source[1]];
|
|
1910
|
+
const length = Math.hypot(dx, dy);
|
|
1911
|
+
const off = Math.max(...points.map(([x, y]) => Math.abs((x - source[0]) * dy - (y - source[1]) * dx)));
|
|
1912
|
+
return length > 0 && off / (length * length) > 1e-3 ? canonBends(source, target, points, false) : null;
|
|
1913
|
+
}
|
|
1914
|
+
function readEdgeLine(get, tokens, put, at) {
|
|
1915
|
+
const width = nonEmpty(get("penwidth")) ?? setLineWidth(tokens);
|
|
1916
|
+
if (width !== null) {
|
|
1917
|
+
put("width", penWidth(width, get("penwidth") !== null, at("width")));
|
|
1918
|
+
} else if (tokens.includes("bold")) {
|
|
1919
|
+
put("width", 2);
|
|
1920
|
+
}
|
|
1921
|
+
if (tokens.includes("dashed")) {
|
|
1922
|
+
put("lineStyle", "dashed");
|
|
1923
|
+
} else if (tokens.includes("dotted")) {
|
|
1924
|
+
put("lineStyle", "dotted");
|
|
1925
|
+
} else if (tokens.includes("solid") || tokens.includes("bold")) {
|
|
1926
|
+
put("lineStyle", "solid");
|
|
1927
|
+
} else if (tokens.includes("tapered")) {
|
|
1928
|
+
put("lineStyle", "other:tapered");
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
function readEdgeArrows(get, cx, put, at) {
|
|
1932
|
+
const dirRaw = nonEmpty(get("dir"));
|
|
1933
|
+
const head = nonEmpty(get("arrowhead"));
|
|
1934
|
+
const tail = nonEmpty(get("arrowtail"));
|
|
1935
|
+
if (dirRaw !== null || head !== null || tail !== null) {
|
|
1936
|
+
const dir = (dirRaw ?? (cx.directed ? "forward" : "none")).trim().toLowerCase();
|
|
1937
|
+
readEdgeArrow("target", head, dir === "forward" || dir === "both", put, at);
|
|
1938
|
+
readEdgeArrow("source", tail, dir === "back" || dir === "both", put, at);
|
|
1939
|
+
}
|
|
1940
|
+
const size = get("arrowsize");
|
|
1941
|
+
if (size !== null) {
|
|
1942
|
+
put("arrowSize", canonNumber(size, at("arrowSize")));
|
|
1943
|
+
}
|
|
1944
|
+
const fill = nonEmpty(get("fillcolor"));
|
|
1945
|
+
for (const property of ["targetArrowColor", "sourceArrowColor"]) {
|
|
1946
|
+
readColor(fill, get("colorscheme"), "fill", property, null, put, at);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
function readEdgeArrow(end, raw, on, put, at) {
|
|
1950
|
+
const arrow = on ? graphvizArrowValue(raw ?? "normal", at(`${end}Arrow`)) : null;
|
|
1951
|
+
put(`${end}Arrow`, arrow === null ? "none" : arrow.value);
|
|
1952
|
+
if (arrow?.side !== null && arrow?.side !== void 0) {
|
|
1953
|
+
put(`${end}ArrowSide`, arrow.side);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
const ARROW_SIDES = ["left", "right"];
|
|
1957
|
+
const GRAPHVIZ_OPEN_ARROWS = /* @__PURE__ */ new Set(["normal", "diamond", "dot", "box", "inv"]);
|
|
1958
|
+
const GRAPHVIZ_OTHER_ARROWS = /* @__PURE__ */ new Map([
|
|
1959
|
+
["obox", "other:open-square"],
|
|
1960
|
+
["oinv", "other:open-inverted"]
|
|
1961
|
+
]);
|
|
1962
|
+
function graphvizArrowValue(raw, site) {
|
|
1963
|
+
const first = graphvizArrow(raw);
|
|
1964
|
+
const other = first === null ? void 0 : GRAPHVIZ_OTHER_ARROWS.get(first.name);
|
|
1965
|
+
return {
|
|
1966
|
+
value: other ?? canonEnum("arrow", first?.name ?? raw, "graphviz", site),
|
|
1967
|
+
side: first?.side ?? null
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
function graphvizArrow(raw) {
|
|
1971
|
+
let rest = raw.trim().toLowerCase();
|
|
1972
|
+
if (rest === "invempty") {
|
|
1973
|
+
rest = "oinv";
|
|
1974
|
+
}
|
|
1975
|
+
while (rest.length > 0) {
|
|
1976
|
+
const m = /^([olr]{0,3})(normal|crow|tee|box|diamond|dot|none|inv|vee|icurve|curve)/.exec(rest);
|
|
1977
|
+
if (m === null) {
|
|
1978
|
+
return null;
|
|
1979
|
+
}
|
|
1980
|
+
rest = rest.slice(m[0].length);
|
|
1981
|
+
const [, mods, shape] = m;
|
|
1982
|
+
if (shape !== "none") {
|
|
1983
|
+
const side = ARROW_SIDES.find((s) => mods.includes(s[0])) ?? null;
|
|
1984
|
+
const open = mods.includes("o") && GRAPHVIZ_OPEN_ARROWS.has(shape) ? "o" : "";
|
|
1985
|
+
return { name: open + shape, side };
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
return null;
|
|
1989
|
+
}
|
|
1990
|
+
function htmlStrings(snapshot) {
|
|
1991
|
+
const out = /* @__PURE__ */ new Set();
|
|
1992
|
+
for (let i = 0; i < snapshot.nodeCount; i++) {
|
|
1993
|
+
const id = snapshot.ids.idOf(i);
|
|
1994
|
+
if (typeof id === "string" && isHtmlString(id)) {
|
|
1995
|
+
out.add(id);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
for (const [table, n] of [
|
|
1999
|
+
[snapshot.nodes, snapshot.nodeCount],
|
|
2000
|
+
[snapshot.edges, snapshot.edgeCount]
|
|
2001
|
+
]) {
|
|
2002
|
+
for (const name of [LABEL_ATTRIBUTE, "headlabel", "taillabel"].filter((c) => table.has(c))) {
|
|
2003
|
+
addHtmlCells(table, name, n, out);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
return out;
|
|
2007
|
+
}
|
|
2008
|
+
function addHtmlCells(table, name, n, out) {
|
|
2009
|
+
for (let i = 0; i < n; i++) {
|
|
2010
|
+
const v = cellText(table, name, i);
|
|
2011
|
+
if (v !== null && isHtmlString(v)) {
|
|
2012
|
+
out.add(v);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
function directedEdges(snapshot) {
|
|
2017
|
+
const out = new Array(snapshot.edgeCount).fill(snapshot.directed);
|
|
2018
|
+
const col = snapshot.directed ? snapshot.edges.byRole("directed") : null;
|
|
2019
|
+
if (col !== null) {
|
|
2020
|
+
for (let e = 0; e < snapshot.edgeCount; e++) {
|
|
2021
|
+
const v = plainCell(col.value(e));
|
|
2022
|
+
if (typeof v === "boolean") {
|
|
2023
|
+
out[e] = v;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
return out;
|
|
2028
|
+
}
|
|
2029
|
+
function isCluster(snapshot, i) {
|
|
2030
|
+
return snapshot.nodes.has(CLUSTER_COLUMN) && snapshot.nodes.value(CLUSTER_COLUMN, i) === true;
|
|
2031
|
+
}
|
|
2032
|
+
function contexts(snapshot, written = null) {
|
|
2033
|
+
const html = htmlStrings(snapshot);
|
|
2034
|
+
const graph = snapshot.meta.name ?? "";
|
|
2035
|
+
const rankdir = (cellText(snapshot.graph, "rankdir", 0) ?? "").trim().toUpperCase();
|
|
2036
|
+
const flip = rankdir === "LR" || rankdir === "RL";
|
|
2037
|
+
const q = Number(cellText(snapshot.graph, "quantum", 0) ?? Number.NaN);
|
|
2038
|
+
const quantum = Number.isFinite(q) && q > 0 ? q * DOT_POINTS_PER_INCH : 0;
|
|
2039
|
+
const own = directedEdges(snapshot);
|
|
2040
|
+
const directed = written === null || own.every((d) => d === snapshot.directed) ? own : own.map(() => written);
|
|
2041
|
+
const name = (i) => String(snapshot.ids.idOf(i));
|
|
2042
|
+
return {
|
|
2043
|
+
node: (i) => ({ name: name(i), graph, cluster: isCluster(snapshot, i), html, flip, quantum }),
|
|
2044
|
+
edge: (e) => {
|
|
2045
|
+
const tail = name(snapshot.edgeSource(e));
|
|
2046
|
+
const head = name(snapshot.edgeTarget(e));
|
|
2047
|
+
return {
|
|
2048
|
+
names: { graph, tail, head, edge: `${tail}${directed[e] ? "->" : "--"}${head}` },
|
|
2049
|
+
directed: directed[e],
|
|
2050
|
+
html
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
function defaultLabel(cx, shape, notes) {
|
|
2056
|
+
if (cx.cluster || shape === "point") {
|
|
2057
|
+
return null;
|
|
2058
|
+
}
|
|
2059
|
+
const at = { notes, target: "node", property: "label" };
|
|
2060
|
+
return isHtmlString(cx.name) ? htmlLabelText(cx.name, at) : dotLabelText(String.raw`\N`, { node: cx.name, graph: cx.graph }, at);
|
|
2061
|
+
}
|
|
2062
|
+
function styleColumns(table, attributes) {
|
|
2063
|
+
const own = /* @__PURE__ */ new Set([...attributes, ...NOTED_ATTRIBUTES]);
|
|
2064
|
+
return [...table.names()].filter((c) => own.has(c) || c.startsWith("graphty.style."));
|
|
2065
|
+
}
|
|
2066
|
+
function graphBackground(snapshot, notes) {
|
|
2067
|
+
const graph = {};
|
|
2068
|
+
const bg = nonEmpty(cellText(snapshot.graph, "bgcolor", 0));
|
|
2069
|
+
if (!snapshot.graph.has("bgcolor") || bg === null) {
|
|
2070
|
+
return graph;
|
|
2071
|
+
}
|
|
2072
|
+
readColor(
|
|
2073
|
+
bg,
|
|
2074
|
+
cellText(snapshot.graph, "colorscheme", 0),
|
|
2075
|
+
"fill",
|
|
2076
|
+
"background",
|
|
2077
|
+
null,
|
|
2078
|
+
(p, v) => {
|
|
2079
|
+
if (v !== null) {
|
|
2080
|
+
graph[p] = v;
|
|
2081
|
+
}
|
|
2082
|
+
},
|
|
2083
|
+
(property) => ({ notes, target: "graph", property })
|
|
2084
|
+
);
|
|
2085
|
+
return graph;
|
|
2086
|
+
}
|
|
2087
|
+
function dotStyles(snapshot, context) {
|
|
2088
|
+
const notes = new StyleNotes();
|
|
2089
|
+
const cx = contexts(snapshot);
|
|
2090
|
+
const node = valueColumns(snapshot.nodeCount);
|
|
2091
|
+
const nameColumn = new Array(snapshot.nodeCount).fill(null);
|
|
2092
|
+
const clusterColumn = new Array(snapshot.nodeCount).fill(null);
|
|
2093
|
+
let named = 0;
|
|
2094
|
+
let clusters = 0;
|
|
2095
|
+
for (let i = 0; i < snapshot.nodeCount; i++) {
|
|
2096
|
+
context.budget.spend("node", NODE_ATTRIBUTES.length);
|
|
2097
|
+
const get = (name) => cellText(snapshot.nodes, name, i);
|
|
2098
|
+
const ncx = cx.node(i);
|
|
2099
|
+
const at = (property) => ({ notes, target: "node", property, element: i, budget: context.budget });
|
|
2100
|
+
readNode(
|
|
2101
|
+
get,
|
|
2102
|
+
ncx,
|
|
2103
|
+
(p, v) => {
|
|
2104
|
+
node.put(i, p, v);
|
|
2105
|
+
},
|
|
2106
|
+
at
|
|
2107
|
+
);
|
|
2108
|
+
const shown = defaultLabel(ncx, (get("shape") ?? "").toLowerCase(), notes);
|
|
2109
|
+
if (shown !== null) {
|
|
2110
|
+
nameColumn[i] = shown;
|
|
2111
|
+
named++;
|
|
2112
|
+
}
|
|
2113
|
+
if (ncx.cluster) {
|
|
2114
|
+
clusterColumn[i] = true;
|
|
2115
|
+
clusters++;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
const edge = valueColumns(snapshot.edgeCount);
|
|
2119
|
+
const loops = graphvizLoops(snapshot);
|
|
2120
|
+
const splines = cellText(snapshot.graph, "splines", 0)?.trim().toLowerCase();
|
|
2121
|
+
const noEdges = splines === "" || splines === "none";
|
|
2122
|
+
const places = labelPlaceContext(snapshot);
|
|
2123
|
+
for (let e = 0; e < snapshot.edgeCount; e++) {
|
|
2124
|
+
context.budget.spend("edge", EDGE_ATTRIBUTES.length);
|
|
2125
|
+
const get = (name) => cellText(snapshot.edges, name, e);
|
|
2126
|
+
const at = (property) => ({ notes, target: "edge", property, element: e, budget: context.budget });
|
|
2127
|
+
readEdge(
|
|
2128
|
+
get,
|
|
2129
|
+
cx.edge(e),
|
|
2130
|
+
(p, v) => {
|
|
2131
|
+
edge.put(e, p, v);
|
|
2132
|
+
},
|
|
2133
|
+
at
|
|
2134
|
+
);
|
|
2135
|
+
if (noEdges) {
|
|
2136
|
+
edge.put(e, "visible", false);
|
|
2137
|
+
}
|
|
2138
|
+
const bends = splineBends(snapshot, e, get(POS_ATTRIBUTE)) ?? loops.bendsOf(e, get);
|
|
2139
|
+
if (bends !== null) {
|
|
2140
|
+
edge.put(e, "bends", bends);
|
|
2141
|
+
}
|
|
2142
|
+
readLabelPlaces(snapshot, places, e, get, (p, v) => {
|
|
2143
|
+
edge.put(e, p, v);
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
const graph = graphBackground(snapshot, notes);
|
|
2147
|
+
const blockText = cellText(snapshot.graph, STYLE_BLOCK_NAME, 0);
|
|
2148
|
+
if (blockText !== null) {
|
|
2149
|
+
exactFromBlock(snapshot, blockText, { node: node.values, edge: edge.values }, cx);
|
|
2150
|
+
}
|
|
2151
|
+
const nodeRules = [];
|
|
2152
|
+
const nodeColumns = /* @__PURE__ */ new Map();
|
|
2153
|
+
if (named > 0) {
|
|
2154
|
+
nodeColumns.set(NAME_KEY, nameColumn);
|
|
2155
|
+
nodeRules.push({
|
|
2156
|
+
id: "node.tool.name-label",
|
|
2157
|
+
target: "node",
|
|
2158
|
+
role: "passthrough",
|
|
2159
|
+
origin: "tool",
|
|
2160
|
+
where: { has: { style: NAME_KEY } },
|
|
2161
|
+
map: { label: { kind: "passthrough", by: { style: NAME_KEY } } },
|
|
2162
|
+
facts: { elements: named, reason: "NODE_NAME" }
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
const features = clusters > 0 ? [{ ref: { style: CLUSTER_KEY }, values: clusterColumn }] : [];
|
|
2166
|
+
const edgeRules = [];
|
|
2167
|
+
const edgeColumns = /* @__PURE__ */ new Map();
|
|
2168
|
+
const directed = directedEdges(snapshot);
|
|
2169
|
+
if (directed.some(Boolean)) {
|
|
2170
|
+
edgeColumns.set(
|
|
2171
|
+
DIRECTED_KEY,
|
|
2172
|
+
directed.map((d) => d ? true : null)
|
|
2173
|
+
);
|
|
2174
|
+
edgeRules.push({
|
|
2175
|
+
id: "edge.tool.arrow",
|
|
2176
|
+
target: "edge",
|
|
2177
|
+
role: "group",
|
|
2178
|
+
origin: "tool",
|
|
2179
|
+
where: { has: { style: DIRECTED_KEY } },
|
|
2180
|
+
set: { targetArrow: "triangle" }
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
return {
|
|
2184
|
+
canonical: true,
|
|
2185
|
+
dialect: "graphviz",
|
|
2186
|
+
tool: "graphviz",
|
|
2187
|
+
units: "px",
|
|
2188
|
+
node: { values: node.values, rules: nodeRules, columns: nodeColumns, features },
|
|
2189
|
+
edge: { values: edge.values, rules: edgeRules, columns: edgeColumns },
|
|
2190
|
+
graph,
|
|
2191
|
+
notes: notes.list(),
|
|
2192
|
+
readFrom: {
|
|
2193
|
+
node: styleColumns(snapshot.nodes, NODE_ATTRIBUTES),
|
|
2194
|
+
edge: styleColumns(snapshot.edges, EDGE_ATTRIBUTES)
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
const SHAPES = /* @__PURE__ */ new Map([
|
|
2199
|
+
["ellipse", "ellipse"],
|
|
2200
|
+
["rectangle", "box"],
|
|
2201
|
+
["round-rectangle", "box"],
|
|
2202
|
+
["triangle", "triangle"],
|
|
2203
|
+
["diamond", "diamond"],
|
|
2204
|
+
["pentagon", "pentagon"],
|
|
2205
|
+
["hexagon", "hexagon"],
|
|
2206
|
+
["octagon", "octagon"],
|
|
2207
|
+
["parallelogram", "parallelogram"],
|
|
2208
|
+
["trapezoid", "trapezium"],
|
|
2209
|
+
["star", "star"],
|
|
2210
|
+
["none", "none"]
|
|
2211
|
+
]);
|
|
2212
|
+
const ARROWS = /* @__PURE__ */ new Map([
|
|
2213
|
+
["none", "none"],
|
|
2214
|
+
["triangle", "normal"],
|
|
2215
|
+
["triangle-open", "onormal"],
|
|
2216
|
+
["vee", "vee"],
|
|
2217
|
+
["tee", "tee"],
|
|
2218
|
+
["diamond", "diamond"],
|
|
2219
|
+
["diamond-open", "odiamond"],
|
|
2220
|
+
["circle", "dot"],
|
|
2221
|
+
["circle-open", "odot"],
|
|
2222
|
+
["square", "box"],
|
|
2223
|
+
["crow", "crow"],
|
|
2224
|
+
["inverted", "inv"],
|
|
2225
|
+
["other:open-square", "obox"],
|
|
2226
|
+
["other:open-inverted", "oinv"]
|
|
2227
|
+
]);
|
|
2228
|
+
const STROKES = /* @__PURE__ */ new Map([
|
|
2229
|
+
["dashed", "dashed"],
|
|
2230
|
+
["dotted", "dotted"]
|
|
2231
|
+
]);
|
|
2232
|
+
function graphvizDefaults(target) {
|
|
2233
|
+
const t = toolDefaults("graphviz")?.[target];
|
|
2234
|
+
const out = /* @__PURE__ */ new Map();
|
|
2235
|
+
const add = (attribute, property, scale = 1) => {
|
|
2236
|
+
const v = t[property]?.value;
|
|
2237
|
+
if (typeof v === "number") {
|
|
2238
|
+
out.set(attribute, num6(v / scale));
|
|
2239
|
+
} else if (typeof v === "string") {
|
|
2240
|
+
out.set(attribute, v);
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
add("fontcolor", "labelColor");
|
|
2244
|
+
add("fontsize", "labelSize");
|
|
2245
|
+
const font = t.labelFont?.value;
|
|
2246
|
+
if (typeof font === "string") {
|
|
2247
|
+
out.set("fontname", graphvizFontName(font, false, false));
|
|
2248
|
+
}
|
|
2249
|
+
if (target === "node") {
|
|
2250
|
+
add("color", "borderColor");
|
|
2251
|
+
add("pencolor", "borderColor");
|
|
2252
|
+
add("penwidth", "borderWidth");
|
|
2253
|
+
add("shape", "shape");
|
|
2254
|
+
add("width", "width", DOT_POINTS_PER_INCH);
|
|
2255
|
+
add("height", "height", DOT_POINTS_PER_INCH);
|
|
2256
|
+
} else {
|
|
2257
|
+
add("color", "color");
|
|
2258
|
+
add("penwidth", "width");
|
|
2259
|
+
add("arrowsize", "arrowSize");
|
|
2260
|
+
}
|
|
2261
|
+
return out;
|
|
2262
|
+
}
|
|
2263
|
+
const DEFAULT_ATTRIBUTES = {
|
|
2264
|
+
node: graphvizDefaults("node"),
|
|
2265
|
+
edge: graphvizDefaults("edge")
|
|
2266
|
+
};
|
|
2267
|
+
function withoutDefaults(out, target) {
|
|
2268
|
+
for (const [attribute, value] of DEFAULT_ATTRIBUTES[target]) {
|
|
2269
|
+
if (out.get(attribute) === value) {
|
|
2270
|
+
out.delete(attribute);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
if (!out.has("width") && !out.has("height")) {
|
|
2274
|
+
out.delete("fixedsize");
|
|
2275
|
+
}
|
|
2276
|
+
return out;
|
|
2277
|
+
}
|
|
2278
|
+
function gvColor(color, opacity) {
|
|
2279
|
+
if (typeof opacity !== "number" || opacity >= 1) {
|
|
2280
|
+
return color;
|
|
2281
|
+
}
|
|
2282
|
+
const a = Math.max(0, Math.min(255, Math.round(opacity * 255)));
|
|
2283
|
+
return color + a.toString(16).padStart(2, "0");
|
|
2284
|
+
}
|
|
2285
|
+
function escapeLabel(text) {
|
|
2286
|
+
return text.replaceAll("&", "&").replaceAll("\\", String.raw`\\`).replaceAll("\n", String.raw`\n`);
|
|
2287
|
+
}
|
|
2288
|
+
function spelled(table, value, nearest, property, approximate) {
|
|
2289
|
+
const exact = table.get(value);
|
|
2290
|
+
if (exact !== void 0) {
|
|
2291
|
+
return exact;
|
|
2292
|
+
}
|
|
2293
|
+
if (value.startsWith("other:")) {
|
|
2294
|
+
return value.slice("other:".length);
|
|
2295
|
+
}
|
|
2296
|
+
approximate(property, value);
|
|
2297
|
+
return nearest;
|
|
2298
|
+
}
|
|
2299
|
+
function writeCommon(get, approximate, out, tokens) {
|
|
2300
|
+
const color = get("labelColor");
|
|
2301
|
+
if (typeof color === "string") {
|
|
2302
|
+
out.set("fontcolor", color);
|
|
2303
|
+
}
|
|
2304
|
+
const size = get("labelSize");
|
|
2305
|
+
if (typeof size === "number") {
|
|
2306
|
+
out.set("fontsize", size);
|
|
2307
|
+
}
|
|
2308
|
+
const font = get("labelFont");
|
|
2309
|
+
const bold = get("labelWeight") === "bold";
|
|
2310
|
+
const italic = get("labelItalic") === true;
|
|
2311
|
+
if (typeof font === "string") {
|
|
2312
|
+
const name = graphvizFontName(font, bold, italic);
|
|
2313
|
+
out.set("fontname", name);
|
|
2314
|
+
const drawn = graphvizFont(name);
|
|
2315
|
+
if (drawn.bold !== bold) {
|
|
2316
|
+
approximate("labelWeight", get("labelWeight") ?? "normal");
|
|
2317
|
+
}
|
|
2318
|
+
if (drawn.italic !== italic) {
|
|
2319
|
+
approximate("labelItalic", italic);
|
|
2320
|
+
}
|
|
2321
|
+
} else if (bold || italic) {
|
|
2322
|
+
approximate(bold ? "labelWeight" : "labelItalic", bold ? "bold" : true);
|
|
2323
|
+
}
|
|
2324
|
+
const tip = get("tooltip");
|
|
2325
|
+
if (typeof tip === "string") {
|
|
2326
|
+
out.set("tooltip", escapeLabel(tip));
|
|
2327
|
+
}
|
|
2328
|
+
if (get("visible") === false) {
|
|
2329
|
+
tokens.push("invis");
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
function nodeAttributes(get, cx, raw, approximate, notes) {
|
|
2333
|
+
const out = /* @__PURE__ */ new Map();
|
|
2334
|
+
const tokens = [];
|
|
2335
|
+
writeCommon(get, approximate, out, tokens);
|
|
2336
|
+
writeNodeOutline(get, cx, approximate, out, tokens);
|
|
2337
|
+
const gvShape = writeNodeShape(get, cx, approximate, out, tokens);
|
|
2338
|
+
if (!cx.cluster) {
|
|
2339
|
+
writeNodeSize(get, out);
|
|
2340
|
+
}
|
|
2341
|
+
const label = get("label");
|
|
2342
|
+
const shown = defaultLabel(cx, gvShape, notes);
|
|
2343
|
+
if (typeof label === "string" && label !== shown) {
|
|
2344
|
+
out.set(LABEL_ATTRIBUTE, escapeLabel(label));
|
|
2345
|
+
} else if (label === null && shown !== null) {
|
|
2346
|
+
out.set(LABEL_ATTRIBUTE, "");
|
|
2347
|
+
}
|
|
2348
|
+
noteLabelPosition(get("labelPosition"), cx, raw, approximate);
|
|
2349
|
+
if (tokens.length > 0) {
|
|
2350
|
+
out.set("style", tokens.join(","));
|
|
2351
|
+
}
|
|
2352
|
+
return withoutDefaults(out, "node");
|
|
2353
|
+
}
|
|
2354
|
+
function noteLabelPosition(position, cx, raw, approximate) {
|
|
2355
|
+
if (typeof position === "string" && position !== (cx.cluster ? clusterLabelPosition(raw) : nodeLabelPosition(raw))) {
|
|
2356
|
+
approximate("labelPosition", position);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
function writeNodeOutline(get, cx, approximate, out, tokens) {
|
|
2360
|
+
const fill = get("fill");
|
|
2361
|
+
const fillOpacity = get("fillOpacity");
|
|
2362
|
+
if (typeof fill === "string" && fillOpacity !== 0) {
|
|
2363
|
+
tokens.push("filled");
|
|
2364
|
+
out.set("fillcolor", gvColor(fill, fillOpacity));
|
|
2365
|
+
}
|
|
2366
|
+
const border = get("borderColor");
|
|
2367
|
+
if (typeof border === "string") {
|
|
2368
|
+
out.set(cx.cluster ? "pencolor" : "color", border);
|
|
2369
|
+
}
|
|
2370
|
+
const borderWidth = get("borderWidth");
|
|
2371
|
+
if (typeof borderWidth === "number") {
|
|
2372
|
+
out.set("penwidth", borderWidth);
|
|
2373
|
+
}
|
|
2374
|
+
const borderStyle = get("borderStyle");
|
|
2375
|
+
if (typeof borderStyle === "string" && borderStyle !== "solid") {
|
|
2376
|
+
const nearest = borderStyle.includes("dash") ? "dashed" : "solid";
|
|
2377
|
+
tokens.push(spelled(STROKES, borderStyle, nearest, "borderStyle", approximate));
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
function writeNodeShape(get, cx, approximate, out, tokens) {
|
|
2381
|
+
const shape = get("shape");
|
|
2382
|
+
if (typeof shape !== "string") {
|
|
2383
|
+
return "";
|
|
2384
|
+
}
|
|
2385
|
+
if (shape === "round-rectangle") {
|
|
2386
|
+
tokens.push("rounded");
|
|
2387
|
+
}
|
|
2388
|
+
if (cx.cluster) {
|
|
2389
|
+
if (shape !== "rectangle" && shape !== "round-rectangle") {
|
|
2390
|
+
approximate("shape", shape);
|
|
2391
|
+
}
|
|
2392
|
+
return "";
|
|
2393
|
+
}
|
|
2394
|
+
const size = (name) => {
|
|
2395
|
+
const v = get(name);
|
|
2396
|
+
return typeof v === "number" ? v : null;
|
|
2397
|
+
};
|
|
2398
|
+
const glyph = shape === "polygon" ? glyphOf(get, size("width"), size("height")) : void 0;
|
|
2399
|
+
if (glyph !== void 0) {
|
|
2400
|
+
return writeGlyph(get, glyph, out);
|
|
2401
|
+
}
|
|
2402
|
+
approximateShapeLines(get, approximate);
|
|
2403
|
+
const polygon = shape === "polygon" ? writePolygon(get("shapePoints"), approximate, out) : null;
|
|
2404
|
+
if (polygon !== null) {
|
|
2405
|
+
if (get("shapePointsStretch") !== false) {
|
|
2406
|
+
approximate("shapePointsStretch", true);
|
|
2407
|
+
}
|
|
2408
|
+
return polygon;
|
|
2409
|
+
}
|
|
2410
|
+
const gvShape = spelled(SHAPES, shape, shape === "vee" ? "invtriangle" : "box", "shape", approximate);
|
|
2411
|
+
out.set("shape", gvShape);
|
|
2412
|
+
return gvShape;
|
|
2413
|
+
}
|
|
2414
|
+
function writeGlyph(get, glyph, out) {
|
|
2415
|
+
out.set("shape", glyph.name);
|
|
2416
|
+
const line = get("shapeLineWidth");
|
|
2417
|
+
if (glyph.unstroked && typeof line === "number") {
|
|
2418
|
+
out.set("penwidth", line);
|
|
2419
|
+
}
|
|
2420
|
+
return glyph.name;
|
|
2421
|
+
}
|
|
2422
|
+
function approximateShapeLines(get, approximate) {
|
|
2423
|
+
for (const property of ["shapeParts", "shapeLines", "shapeLineWidth"]) {
|
|
2424
|
+
const v = get(property);
|
|
2425
|
+
if (v !== null && v !== void 0) {
|
|
2426
|
+
approximate(property, v);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
function writePolygon(points, approximate, out) {
|
|
2431
|
+
if (typeof points !== "string") {
|
|
2432
|
+
return null;
|
|
2433
|
+
}
|
|
2434
|
+
const named = OUTLINE_NAMES.get(points.trim());
|
|
2435
|
+
if (named !== void 0) {
|
|
2436
|
+
out.set("shape", named);
|
|
2437
|
+
return named;
|
|
2438
|
+
}
|
|
2439
|
+
const sides = points.trim().split(/\s+/).length;
|
|
2440
|
+
if (sides < 3) {
|
|
2441
|
+
return null;
|
|
2442
|
+
}
|
|
2443
|
+
out.set("shape", "polygon");
|
|
2444
|
+
out.set("sides", sides);
|
|
2445
|
+
approximate("shapePoints", points);
|
|
2446
|
+
return "polygon";
|
|
2447
|
+
}
|
|
2448
|
+
function writeNodeSize(get, out) {
|
|
2449
|
+
const width = get("width");
|
|
2450
|
+
const height = get("height");
|
|
2451
|
+
if ((typeof width === "number" || typeof height === "number") && typeof get("labelFit") !== "string") {
|
|
2452
|
+
out.set("fixedsize", "true");
|
|
2453
|
+
}
|
|
2454
|
+
if (typeof width === "number") {
|
|
2455
|
+
out.set("width", inchesOf(width));
|
|
2456
|
+
}
|
|
2457
|
+
if (typeof height === "number") {
|
|
2458
|
+
out.set("height", inchesOf(height));
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
function edgeAttributes(get, directed, approximate) {
|
|
2462
|
+
const out = /* @__PURE__ */ new Map();
|
|
2463
|
+
const tokens = [];
|
|
2464
|
+
writeCommon(get, approximate, out, tokens);
|
|
2465
|
+
const color = get("color");
|
|
2466
|
+
if (typeof color === "string") {
|
|
2467
|
+
out.set("color", gvColor(color, get("opacity")));
|
|
2468
|
+
}
|
|
2469
|
+
const width = get("width");
|
|
2470
|
+
if (typeof width === "number") {
|
|
2471
|
+
out.set("penwidth", width);
|
|
2472
|
+
}
|
|
2473
|
+
const line = get("lineStyle");
|
|
2474
|
+
if (typeof line === "string" && line !== "solid") {
|
|
2475
|
+
tokens.push(spelled(STROKES, line, line.includes("dash") ? "dashed" : "solid", "lineStyle", approximate));
|
|
2476
|
+
}
|
|
2477
|
+
writeEdgeArrows(get, directed, approximate, out);
|
|
2478
|
+
const head = get("targetArrowColor");
|
|
2479
|
+
const tail = get("sourceArrowColor");
|
|
2480
|
+
const fill = head ?? tail;
|
|
2481
|
+
if (typeof fill === "string") {
|
|
2482
|
+
out.set("fillcolor", fill);
|
|
2483
|
+
if (head !== null && tail !== null && head !== tail) {
|
|
2484
|
+
approximate("sourceArrowColor", tail);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
const arrowSize = get("arrowSize");
|
|
2488
|
+
if (typeof arrowSize === "number") {
|
|
2489
|
+
out.set("arrowsize", arrowSize);
|
|
2490
|
+
}
|
|
2491
|
+
for (const [property, attribute] of [
|
|
2492
|
+
["label", LABEL_ATTRIBUTE],
|
|
2493
|
+
["targetLabel", "headlabel"],
|
|
2494
|
+
["sourceLabel", "taillabel"]
|
|
2495
|
+
]) {
|
|
2496
|
+
const v = get(property);
|
|
2497
|
+
if (typeof v === "string") {
|
|
2498
|
+
out.set(attribute, escapeLabel(v));
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
if (tokens.length > 0) {
|
|
2502
|
+
out.set("style", tokens.join(","));
|
|
2503
|
+
}
|
|
2504
|
+
return withoutDefaults(out, "edge");
|
|
2505
|
+
}
|
|
2506
|
+
function writeEdgeArrows(get, directed, approximate, out) {
|
|
2507
|
+
const source = get("sourceArrow");
|
|
2508
|
+
const target = get("targetArrow");
|
|
2509
|
+
const tail = typeof source === "string" && source !== "none";
|
|
2510
|
+
const head = typeof target === "string" && target !== "none";
|
|
2511
|
+
const plain = directed ? head && !tail && target === "triangle" && get("targetArrowSide") === null : !head && !tail;
|
|
2512
|
+
if (plain || source === null && target === null) {
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
let dir = "none";
|
|
2516
|
+
if (head && tail) {
|
|
2517
|
+
dir = "both";
|
|
2518
|
+
} else if (head) {
|
|
2519
|
+
dir = "forward";
|
|
2520
|
+
} else if (tail) {
|
|
2521
|
+
dir = "back";
|
|
2522
|
+
}
|
|
2523
|
+
out.set("dir", dir);
|
|
2524
|
+
const arrow = (value, side, property) => {
|
|
2525
|
+
if (value === "half") {
|
|
2526
|
+
approximate(property, value);
|
|
2527
|
+
approximate(`${property}Side`, value);
|
|
2528
|
+
return "lnormal";
|
|
2529
|
+
}
|
|
2530
|
+
const name = spelled(ARROWS, value, "normal", property, approximate);
|
|
2531
|
+
return side === "left" || side === "right" ? `${side[0]}${name}` : name;
|
|
2532
|
+
};
|
|
2533
|
+
if (head) {
|
|
2534
|
+
out.set("arrowhead", arrow(target, get("targetArrowSide"), "targetArrow"));
|
|
2535
|
+
}
|
|
2536
|
+
if (tail) {
|
|
2537
|
+
out.set("arrowtail", arrow(source, get("sourceArrowSide"), "sourceArrow"));
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
function exactFromBlock(snapshot, text, read, cx) {
|
|
2541
|
+
const notes = new StyleNotes();
|
|
2542
|
+
takeFromBlock(snapshot, text, "dot", (target) => ({
|
|
2543
|
+
values: read[target],
|
|
2544
|
+
properties: DOT_STYLE_CAPABILITIES[target],
|
|
2545
|
+
writes: DOT_STYLE_CAPABILITIES[target],
|
|
2546
|
+
readBack: (i, want) => {
|
|
2547
|
+
const raw = (name) => cellText(target === "node" ? snapshot.nodes : snapshot.edges, name, i);
|
|
2548
|
+
const back = writtenAndReadBack(target, i, want, { cx, raw }, notes);
|
|
2549
|
+
return (property) => back.get(property) ?? null;
|
|
2550
|
+
}
|
|
2551
|
+
}));
|
|
2552
|
+
}
|
|
2553
|
+
function writtenAndReadBack(target, i, want, data, notes) {
|
|
2554
|
+
const { cx, raw } = data;
|
|
2555
|
+
const ignore = () => void 0;
|
|
2556
|
+
const back = /* @__PURE__ */ new Map();
|
|
2557
|
+
const put = (property, value) => {
|
|
2558
|
+
if (value !== null) {
|
|
2559
|
+
back.set(property, value);
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
const at = (property) => ({ notes, target, property });
|
|
2563
|
+
if (target === "node") {
|
|
2564
|
+
const ncx = cx.node(i);
|
|
2565
|
+
const attrs = nodeAttributes(want, ncx, raw, ignore, notes);
|
|
2566
|
+
const kept = (name) => name === "labelloc" || name === "labeljust" ? raw(name) : null;
|
|
2567
|
+
readNode((name) => attrs.has(name) ? textOf(attrs.get(name)) : kept(name), ncx, put, at);
|
|
2568
|
+
} else {
|
|
2569
|
+
const ecx = cx.edge(i);
|
|
2570
|
+
const attrs = edgeAttributes(want, ecx.directed, ignore);
|
|
2571
|
+
readEdge((name) => textOf(attrs.get(name)), ecx, put, at);
|
|
2572
|
+
}
|
|
2573
|
+
return back;
|
|
2574
|
+
}
|
|
2575
|
+
function textOf(v) {
|
|
2576
|
+
return v === void 0 ? null : String(v);
|
|
2577
|
+
}
|
|
2578
|
+
function readsBack(col) {
|
|
2579
|
+
const vals = col.filter((v) => v !== null);
|
|
2580
|
+
if (vals.every((v) => typeof v === "number")) {
|
|
2581
|
+
return vals.every((v) => Number.isFinite(v));
|
|
2582
|
+
}
|
|
2583
|
+
if (vals.every((v) => typeof v === "boolean")) {
|
|
2584
|
+
return true;
|
|
2585
|
+
}
|
|
2586
|
+
if (vals.every((v) => typeof v === "string")) {
|
|
2587
|
+
return vals.some((v) => inferTextDtype(v) === "string") && vals.every((v) => isWritableDotText(v));
|
|
2588
|
+
}
|
|
2589
|
+
return false;
|
|
2590
|
+
}
|
|
2591
|
+
function dotSafeJson(json) {
|
|
2592
|
+
return json.replaceAll(/\\(["\\])/g, (_m, c) => c === '"' ? String.raw`\u0022` : String.raw`\u005c`);
|
|
2593
|
+
}
|
|
2594
|
+
function dotStyledExport(snapshot, input, onMixedDirection) {
|
|
2595
|
+
if (input === void 0) {
|
|
2596
|
+
return null;
|
|
2597
|
+
}
|
|
2598
|
+
return sharedInExportCall(
|
|
2599
|
+
input,
|
|
2600
|
+
[snapshot, onMixedDirection],
|
|
2601
|
+
() => styledExport(snapshot, input, onMixedDirection)
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
function styledExport(snapshot, input, onMixedDirection) {
|
|
2605
|
+
const approximated = new StyleApproximations();
|
|
2606
|
+
const replaced = [];
|
|
2607
|
+
const out = snapshot.withColumns();
|
|
2608
|
+
const written = onMixedDirection === "directed" || onMixedDirection === "undirected";
|
|
2609
|
+
const cx = contexts(snapshot, written ? onMixedDirection === "directed" : null);
|
|
2610
|
+
const notes = new StyleNotes();
|
|
2611
|
+
for (const target of ["node", "edge"]) {
|
|
2612
|
+
const table = target === "node" ? out.nodes : out.edges;
|
|
2613
|
+
const read = dataRead(input.rules.styles.rules, target);
|
|
2614
|
+
const approximate = (property, value) => {
|
|
2615
|
+
approximated.add(target, property, value);
|
|
2616
|
+
};
|
|
2617
|
+
const columns = attributeColumns(snapshot, input, target, { cx, approximate, notes, read });
|
|
2618
|
+
replaced.push(...removeStyleAttributes(table, target, columns, read));
|
|
2619
|
+
setAttributeColumns(table, columns);
|
|
2620
|
+
}
|
|
2621
|
+
const { background } = input.rules.styles.graph;
|
|
2622
|
+
if (out.graph.has("bgcolor")) {
|
|
2623
|
+
out.graph.remove("bgcolor");
|
|
2624
|
+
}
|
|
2625
|
+
if (typeof background === "string" && background !== toolDefaults("graphviz")?.graph.background?.value) {
|
|
2626
|
+
out.graph.set("bgcolor", [background], { dtype: "string" });
|
|
2627
|
+
}
|
|
2628
|
+
const inline = [...input.tags.node.values(), ...input.tags.edge.values()].some((col) => !readsBack(col));
|
|
2629
|
+
if (!inline) {
|
|
2630
|
+
setTagColumns(out, input);
|
|
2631
|
+
}
|
|
2632
|
+
const block = inline ? writeStyleBlock(input.rules, { inlineColumns: true, snapshot }) : input.block;
|
|
2633
|
+
out.graph.set(STYLE_BLOCK_NAME, [dotSafeJson(block)], { dtype: "string" });
|
|
2634
|
+
const losses = approximated.notes("Graphviz");
|
|
2635
|
+
if (inline) {
|
|
2636
|
+
losses.push(...styleBlockLosses(block, input.rules.styles.rules.length, "size"));
|
|
2637
|
+
}
|
|
2638
|
+
return { snapshot: out, notes: [...replaced, ...losses] };
|
|
2639
|
+
}
|
|
2640
|
+
function attributeColumns(snapshot, input, target, w) {
|
|
2641
|
+
const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
|
|
2642
|
+
const get = elementValues((t, p) => input.picture(t, p), target, DOT_STYLE_CAPABILITIES[target]);
|
|
2643
|
+
const columns = /* @__PURE__ */ new Map();
|
|
2644
|
+
for (let i = 0; i < n; i++) {
|
|
2645
|
+
const attrs = target === "node" ? nodeAttributes(
|
|
2646
|
+
get(i),
|
|
2647
|
+
w.cx.node(i),
|
|
2648
|
+
(name) => cellText(snapshot.nodes, name, i),
|
|
2649
|
+
w.approximate,
|
|
2650
|
+
w.notes
|
|
2651
|
+
) : edgeAttributes(get(i), w.cx.edge(i).directed, w.approximate);
|
|
2652
|
+
if (target === "edge" && snapshot.edgeSource(i) === snapshot.edgeTarget(i)) {
|
|
2653
|
+
w.approximate("bends", "self-loop");
|
|
2654
|
+
}
|
|
2655
|
+
if (w.read.has(LABEL_ATTRIBUTE) && attrs.get(LABEL_ATTRIBUTE) === "" && get(i)("label") === null) {
|
|
2656
|
+
w.approximate("label", "");
|
|
2657
|
+
}
|
|
2658
|
+
setColumnValues(columns, attrs, i, n);
|
|
2659
|
+
}
|
|
2660
|
+
if (target === "node") {
|
|
2661
|
+
stopInheritance(snapshot, w.cx, columns, input.rules.styles.graph.background);
|
|
2662
|
+
}
|
|
2663
|
+
return columns;
|
|
2664
|
+
}
|
|
2665
|
+
function setColumnValues(columns, attrs, i, n) {
|
|
2666
|
+
for (const [name, value] of attrs) {
|
|
2667
|
+
let col = columns.get(name);
|
|
2668
|
+
if (col === void 0) {
|
|
2669
|
+
col = new Array(n).fill(null);
|
|
2670
|
+
columns.set(name, col);
|
|
2671
|
+
}
|
|
2672
|
+
col[i] = value;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
const CLUSTER_UNSET = /* @__PURE__ */ new Map([
|
|
2676
|
+
["style", ""],
|
|
2677
|
+
["penwidth", 1],
|
|
2678
|
+
["color", ""],
|
|
2679
|
+
["pencolor", ""],
|
|
2680
|
+
["fillcolor", ""],
|
|
2681
|
+
["bgcolor", ""],
|
|
2682
|
+
["fontname", "Times-Roman"],
|
|
2683
|
+
["fontsize", 14],
|
|
2684
|
+
["fontcolor", "black"],
|
|
2685
|
+
[LABEL_ATTRIBUTE, ""]
|
|
2686
|
+
]);
|
|
2687
|
+
function stopInheritance(snapshot, cx, columns, background) {
|
|
2688
|
+
const clusters = Array.from({ length: snapshot.nodeCount }, (_, i) => i).filter((i) => cx.node(i).cluster);
|
|
2689
|
+
for (const [name, unset] of CLUSTER_UNSET) {
|
|
2690
|
+
const col = columns.get(name);
|
|
2691
|
+
const enclosing = cellText(snapshot.graph, name, 0) !== null || name === "bgcolor" && typeof background === "string" || clusters.some((i) => (col?.[i] ?? null) !== null);
|
|
2692
|
+
if (!enclosing) {
|
|
2693
|
+
continue;
|
|
2694
|
+
}
|
|
2695
|
+
const filled = col ?? new Array(snapshot.nodeCount).fill(null);
|
|
2696
|
+
for (const i of clusters) {
|
|
2697
|
+
filled[i] ??= unset;
|
|
2698
|
+
}
|
|
2699
|
+
columns.set(name, filled);
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
function removeStyleAttributes(table, target, columns, read) {
|
|
2703
|
+
const attributes = target === "node" ? NODE_ATTRIBUTES : EDGE_ATTRIBUTES;
|
|
2704
|
+
const replaced = [];
|
|
2705
|
+
for (const name of table.names()) {
|
|
2706
|
+
if (!attributes.includes(name) && name !== LABEL_ATTRIBUTE && !name.startsWith("graphty.style.")) {
|
|
2707
|
+
continue;
|
|
2708
|
+
}
|
|
2709
|
+
const differs = read.has(name) ? replacedRows(table, name, columns.get(name)) : 0;
|
|
2710
|
+
if (differs > 0) {
|
|
2711
|
+
replaced.push(
|
|
2712
|
+
Object.freeze({
|
|
2713
|
+
code: DOT_ATTRIBUTE_CLASH_CODE,
|
|
2714
|
+
message: `${target} column "${name}" is not written: the ${name} attribute holds what graphty draws (${String(differs)} ${target}${differs === 1 ? "" : "s"} differ)`,
|
|
2715
|
+
column: name,
|
|
2716
|
+
count: differs
|
|
2717
|
+
})
|
|
2718
|
+
);
|
|
2719
|
+
}
|
|
2720
|
+
table.remove(name);
|
|
2721
|
+
}
|
|
2722
|
+
return replaced;
|
|
2723
|
+
}
|
|
2724
|
+
function setAttributeColumns(table, columns) {
|
|
2725
|
+
for (const [name, col] of columns) {
|
|
2726
|
+
if (name === LABEL_ATTRIBUTE) {
|
|
2727
|
+
table.set(name, col, { dtype: "string", role: "label" }, { replaceRole: true });
|
|
2728
|
+
} else if (col.every((v) => v === null || typeof v === "number")) {
|
|
2729
|
+
table.set(name, col, { dtype: "f64" });
|
|
2730
|
+
} else {
|
|
2731
|
+
table.set(
|
|
2732
|
+
name,
|
|
2733
|
+
col.map((v) => v === null ? null : String(v)),
|
|
2734
|
+
{ dtype: "string" }
|
|
2735
|
+
);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
function setTagColumns(out, input) {
|
|
2740
|
+
for (const target of ["node", "edge"]) {
|
|
2741
|
+
const table = target === "node" ? out.nodes : out.edges;
|
|
2742
|
+
for (const [name, col] of input.tags[target]) {
|
|
2743
|
+
const kinds = new Set(col.filter((v) => v !== null).map((v) => typeof v));
|
|
2744
|
+
let dtype = "string";
|
|
2745
|
+
if (kinds.has("number")) {
|
|
2746
|
+
dtype = "f64";
|
|
2747
|
+
} else if (kinds.has("boolean")) {
|
|
2748
|
+
dtype = "bool";
|
|
2749
|
+
}
|
|
2750
|
+
table.set(name, col, { dtype });
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
function dataRead(rules, target) {
|
|
2755
|
+
const out = /* @__PURE__ */ new Set();
|
|
2756
|
+
for (const { ext: _ext, ...rule } of rules.filter((r) => r.target === target)) {
|
|
2757
|
+
JSON.stringify(rule, (_k, v) => {
|
|
2758
|
+
if (typeof v === "object" && v !== null && !Array.isArray(v) && Object.keys(v).length === 1) {
|
|
2759
|
+
const { data } = v;
|
|
2760
|
+
if (typeof data === "string") {
|
|
2761
|
+
out.add(data);
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
return v;
|
|
2765
|
+
});
|
|
2766
|
+
}
|
|
2767
|
+
return out;
|
|
2768
|
+
}
|
|
2769
|
+
const DOT_ATTRIBUTE_CLASH_CODE = "W_DOT_ATTRIBUTE_CLASH";
|
|
2770
|
+
function replacedRows(table, name, written) {
|
|
2771
|
+
const column = table.get(name);
|
|
2772
|
+
let n = 0;
|
|
2773
|
+
for (let i = 0; column !== null && i < column.length; i++) {
|
|
2774
|
+
const cell = column.isSet(i) ? plainCell(column.value(i)) : null;
|
|
2775
|
+
const w = written?.[i] ?? null;
|
|
2776
|
+
const text = typeof cell === "string" ? cell : JSON.stringify(cell);
|
|
2777
|
+
if (cell !== null && (w === null || String(w) !== text)) {
|
|
2778
|
+
n++;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
return n;
|
|
2782
|
+
}
|
|
2783
|
+
export {
|
|
2784
|
+
CLUSTER_COLUMN as C,
|
|
2785
|
+
DOT_STYLE_CAPABILITIES as D,
|
|
2786
|
+
KEY_ATTRIBUTE as K,
|
|
2787
|
+
LABEL_ATTRIBUTE as L,
|
|
2788
|
+
POS_ATTRIBUTE as P,
|
|
2789
|
+
SOURCE_PORT_COLUMN as S,
|
|
2790
|
+
TARGET_PORT_COLUMN as T,
|
|
2791
|
+
DOT_ATTRIBUTE_CLASH_CODE as a,
|
|
2792
|
+
DOT_FORMAT as b,
|
|
2793
|
+
DOT_META_KEY as c,
|
|
2794
|
+
dotStyledExport as d,
|
|
2795
|
+
PARENT_COLUMN as e,
|
|
2796
|
+
dotStyles as f,
|
|
2797
|
+
DOT_ORIGIN as g,
|
|
2798
|
+
PIN_ATTRIBUTE as h,
|
|
2799
|
+
isHtmlString as i,
|
|
2800
|
+
POSITION_COLUMN as j,
|
|
2801
|
+
dataRead$1 as k,
|
|
2802
|
+
graphvizRouteBends as l,
|
|
2803
|
+
graphvizHasNoOutline as m,
|
|
2804
|
+
PERIPHERY_GAP as n,
|
|
2805
|
+
SHAPE_PERIPHERIES as o,
|
|
2806
|
+
graphvizOutline as p,
|
|
2807
|
+
putGlyph as q,
|
|
2808
|
+
graphvizArrowValue as r,
|
|
2809
|
+
prepareStyleExport as s,
|
|
2810
|
+
isKeptGraphtyProperty as t
|
|
2811
|
+
};
|
|
2812
|
+
//# sourceMappingURL=styles-Cm62yyQX.js.map
|