@graphty/graph-io 0.3.42 → 0.3.44
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-3Q4TsdTh.js +40 -0
- package/dist/chunks/approximations-3Q4TsdTh.js.map +1 -0
- package/dist/chunks/block-JQiwViEt.js +2777 -0
- package/dist/chunks/block-JQiwViEt.js.map +1 -0
- package/dist/chunks/{choose-DvpbdRuz.js → choose-BemygfH8.js} +3 -2
- package/dist/chunks/{choose-DvpbdRuz.js.map → choose-BemygfH8.js.map} +1 -1
- package/dist/chunks/{constants-fT3E8JYY.js → constants-BFUR_CtU.js} +6 -2
- package/dist/chunks/constants-BFUR_CtU.js.map +1 -0
- package/dist/chunks/convert-BcQ8_jMo.js +59 -0
- package/dist/chunks/convert-BcQ8_jMo.js.map +1 -0
- package/dist/chunks/emit-DCwFLts5.js +3456 -0
- package/dist/chunks/emit-DCwFLts5.js.map +1 -0
- package/dist/chunks/{escape-B62ZGM_a.js → escape-B1rMftd9.js} +43 -20
- package/dist/chunks/escape-B1rMftd9.js.map +1 -0
- package/dist/chunks/{export-rKLeGQMo.js → export-D7IGzTG4.js} +196 -792
- package/dist/chunks/export-D7IGzTG4.js.map +1 -0
- package/dist/chunks/{exporter-BWwEuOPT.js → exporter-BtnqYuuj.js} +22 -12
- package/dist/chunks/exporter-BtnqYuuj.js.map +1 -0
- package/dist/chunks/{exporter-yLxKBvLE.js → exporter-ChPyhBOv.js} +4 -3
- package/dist/chunks/{exporter-yLxKBvLE.js.map → exporter-ChPyhBOv.js.map} +1 -1
- package/dist/chunks/{exporter-8hLNemFk.js → exporter-KEPxYUXD.js} +133 -74
- package/dist/chunks/exporter-KEPxYUXD.js.map +1 -0
- package/dist/chunks/{exporter-CpRyHZ3H.js → exporter-quPUCBiC.js} +1807 -288
- package/dist/chunks/exporter-quPUCBiC.js.map +1 -0
- package/dist/chunks/format-GDmO6m6Q.js +676 -0
- package/dist/chunks/format-GDmO6m6Q.js.map +1 -0
- package/dist/chunks/generic-BL06jrJf.js +1347 -0
- package/dist/chunks/generic-BL06jrJf.js.map +1 -0
- package/dist/chunks/{importer-WrGRZb0N.js → importer-B7r2owVf.js} +449 -102
- package/dist/chunks/importer-B7r2owVf.js.map +1 -0
- package/dist/chunks/{importer-Ht7LNg8L.js → importer-BFmOP96W.js} +116 -296
- package/dist/chunks/importer-BFmOP96W.js.map +1 -0
- package/dist/chunks/{importer-BbNb7eNg.js → importer-C2nzGhdV.js} +1390 -129
- package/dist/chunks/importer-C2nzGhdV.js.map +1 -0
- package/dist/chunks/{importer-Dz7tI8Sa.js → importer-CbX2_rNF.js} +617 -115
- package/dist/chunks/importer-CbX2_rNF.js.map +1 -0
- package/dist/chunks/{importer-D794XUTe.js → importer-CdzeN6X4.js} +31 -8
- package/dist/chunks/importer-CdzeN6X4.js.map +1 -0
- package/dist/chunks/{importer-xZ-3suY1.js → importer-ChKNC_GN.js} +791 -135
- package/dist/chunks/importer-ChKNC_GN.js.map +1 -0
- package/dist/chunks/{importer-C98dpSH4.js → importer-dwoYpSqb.js} +141 -82
- package/dist/chunks/importer-dwoYpSqb.js.map +1 -0
- package/dist/chunks/index-B9fdNpTA.js +4310 -0
- package/dist/chunks/index-B9fdNpTA.js.map +1 -0
- package/dist/chunks/{json-elements-DKSqpNW1.js → json-elements-C4FwgnH5.js} +123 -32
- package/dist/chunks/json-elements-C4FwgnH5.js.map +1 -0
- package/dist/chunks/lexicon-B-knUi-l.js +508 -0
- package/dist/chunks/lexicon-B-knUi-l.js.map +1 -0
- package/dist/chunks/{ontology-export-CtRXjg_i.js → ontology-export-BSpbY5FL.js} +3 -2
- package/dist/chunks/{ontology-export-CtRXjg_i.js.map → ontology-export-BSpbY5FL.js.map} +1 -1
- package/dist/chunks/{records-Cu4bxYOm.js → records-Nwz_rbNt.js} +2 -2
- package/dist/chunks/{records-Cu4bxYOm.js.map → records-Nwz_rbNt.js.map} +1 -1
- package/dist/chunks/styles-Brt9JMfs.js +690 -0
- package/dist/chunks/styles-Brt9JMfs.js.map +1 -0
- package/dist/chunks/styles-_N-C8S-U.js +2102 -0
- package/dist/chunks/styles-_N-C8S-U.js.map +1 -0
- package/dist/chunks/styles-yed-DAfcIkf7.js +842 -0
- package/dist/chunks/styles-yed-DAfcIkf7.js.map +1 -0
- package/dist/chunks/{text-DHOJXnED.js → text-CgzMqHQb.js} +2 -2
- package/dist/chunks/{text-DHOJXnED.js.map → text-CgzMqHQb.js.map} +1 -1
- package/dist/chunks/units-XMZFIFe1.js +19 -0
- package/dist/chunks/units-XMZFIFe1.js.map +1 -0
- package/dist/csv.js +5 -4
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +1 -1
- package/dist/cx2.js +2 -2
- package/dist/cys.js +1 -1
- package/dist/dot.js +5 -2
- package/dist/dot.js.map +1 -1
- package/dist/gexf.js +295 -414
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +8 -3031
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +354 -240
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +4 -2
- package/dist/graphml.js.map +1 -1
- package/dist/json.js +1 -1
- package/dist/neo4j.js +4 -3
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +4 -2
- package/dist/positions.d.ts +1 -0
- package/dist/positions.js +427 -0
- package/dist/positions.js.map +1 -0
- package/dist/src/common/codes.d.ts +6 -0
- package/dist/src/common/codes.d.ts.map +1 -1
- package/dist/src/common/codes.js +6 -0
- package/dist/src/common/codes.js.map +1 -1
- package/dist/src/common/escape.d.ts.map +1 -1
- package/dist/src/common/escape.js +4 -2
- package/dist/src/common/escape.js.map +1 -1
- package/dist/src/common/export.d.ts +10 -0
- package/dist/src/common/export.d.ts.map +1 -1
- package/dist/src/common/export.js +123 -100
- package/dist/src/common/export.js.map +1 -1
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +8 -1
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/json-elements.d.ts +12 -9
- package/dist/src/common/json-elements.d.ts.map +1 -1
- package/dist/src/common/json-elements.js +160 -37
- package/dist/src/common/json-elements.js.map +1 -1
- package/dist/src/common/notes.d.ts +42 -0
- package/dist/src/common/notes.d.ts.map +1 -0
- package/dist/src/common/notes.js +72 -0
- package/dist/src/common/notes.js.map +1 -0
- package/dist/src/common/report.d.ts +18 -0
- package/dist/src/common/report.d.ts.map +1 -1
- package/dist/src/common/report.js +36 -1
- package/dist/src/common/report.js.map +1 -1
- package/dist/src/common/units.d.ts +24 -0
- package/dist/src/common/units.d.ts.map +1 -0
- package/dist/src/common/units.js +26 -0
- package/dist/src/common/units.js.map +1 -0
- package/dist/src/common/xml.d.ts +18 -0
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +42 -5
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/formats/cx/exporter.d.ts +6 -0
- package/dist/src/formats/cx/exporter.d.ts.map +1 -1
- package/dist/src/formats/cx/exporter.js +71 -8
- package/dist/src/formats/cx/exporter.js.map +1 -1
- package/dist/src/formats/cx/importer.d.ts +5 -2
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +102 -72
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx/styles.d.ts +102 -0
- package/dist/src/formats/cx/styles.d.ts.map +1 -0
- package/dist/src/formats/cx/styles.js +225 -0
- package/dist/src/formats/cx/styles.js.map +1 -0
- package/dist/src/formats/cx2/exporter.d.ts +5 -0
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
- package/dist/src/formats/cx2/exporter.js +181 -71
- package/dist/src/formats/cx2/exporter.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts +4 -1
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +19 -1
- package/dist/src/formats/cx2/importer.js.map +1 -1
- package/dist/src/formats/cx2/styles.d.ts +18 -0
- package/dist/src/formats/cx2/styles.d.ts.map +1 -0
- package/dist/src/formats/cx2/styles.js +24 -0
- package/dist/src/formats/cx2/styles.js.map +1 -0
- package/dist/src/formats/cys/constants.d.ts +2 -0
- package/dist/src/formats/cys/constants.d.ts.map +1 -1
- package/dist/src/formats/cys/constants.js +2 -0
- package/dist/src/formats/cys/constants.js.map +1 -1
- package/dist/src/formats/cys/exporter.d.ts +6 -2
- package/dist/src/formats/cys/exporter.d.ts.map +1 -1
- package/dist/src/formats/cys/exporter.js +215 -53
- package/dist/src/formats/cys/exporter.js.map +1 -1
- package/dist/src/formats/cys/importer.d.ts +2 -1
- package/dist/src/formats/cys/importer.d.ts.map +1 -1
- package/dist/src/formats/cys/importer.js +119 -6
- package/dist/src/formats/cys/importer.js.map +1 -1
- package/dist/src/formats/cys/session.d.ts +2 -0
- package/dist/src/formats/cys/session.d.ts.map +1 -1
- package/dist/src/formats/cys/session.js +5 -0
- package/dist/src/formats/cys/session.js.map +1 -1
- package/dist/src/formats/cys/styles.d.ts +38 -0
- package/dist/src/formats/cys/styles.d.ts.map +1 -0
- package/dist/src/formats/cys/styles.js +213 -0
- package/dist/src/formats/cys/styles.js.map +1 -0
- package/dist/src/formats/cys/tables.d.ts +8 -3
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +88 -34
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/dot/exporter.d.ts +2 -0
- package/dist/src/formats/dot/exporter.d.ts.map +1 -1
- package/dist/src/formats/dot/exporter.js +195 -81
- package/dist/src/formats/dot/exporter.js.map +1 -1
- package/dist/src/formats/dot/importer.d.ts.map +1 -1
- package/dist/src/formats/dot/importer.js +100 -20
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/dot/index.d.ts +1 -0
- package/dist/src/formats/dot/index.d.ts.map +1 -1
- package/dist/src/formats/dot/index.js +1 -0
- package/dist/src/formats/dot/index.js.map +1 -1
- package/dist/src/formats/dot/positions.d.ts +14 -0
- package/dist/src/formats/dot/positions.d.ts.map +1 -0
- package/dist/src/formats/dot/positions.js +70 -0
- package/dist/src/formats/dot/positions.js.map +1 -0
- package/dist/src/formats/dot/styles.d.ts +82 -0
- package/dist/src/formats/dot/styles.d.ts.map +1 -0
- package/dist/src/formats/dot/styles.js +1484 -0
- package/dist/src/formats/dot/styles.js.map +1 -0
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +23 -7
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts +8 -1
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +251 -131
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/index.d.ts +8 -0
- package/dist/src/formats/gexf/index.d.ts.map +1 -1
- package/dist/src/formats/gexf/index.js +10 -2
- package/dist/src/formats/gexf/index.js.map +1 -1
- package/dist/src/formats/gexf/schema.d.ts +5 -0
- package/dist/src/formats/gexf/schema.d.ts.map +1 -1
- package/dist/src/formats/gexf/schema.js +5 -0
- package/dist/src/formats/gexf/schema.js.map +1 -1
- package/dist/src/formats/gexf/styles.d.ts +74 -0
- package/dist/src/formats/gexf/styles.d.ts.map +1 -0
- package/dist/src/formats/gexf/styles.js +604 -0
- package/dist/src/formats/gexf/styles.js.map +1 -0
- package/dist/src/formats/gml/exporter.d.ts +1 -5
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +19 -8
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +4 -0
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/index.d.ts +2 -1
- package/dist/src/formats/gml/index.d.ts.map +1 -1
- package/dist/src/formats/gml/index.js +2 -1
- package/dist/src/formats/gml/index.js.map +1 -1
- package/dist/src/formats/gml/positions.d.ts +14 -0
- package/dist/src/formats/gml/positions.d.ts.map +1 -0
- package/dist/src/formats/gml/positions.js +46 -0
- package/dist/src/formats/gml/positions.js.map +1 -0
- package/dist/src/formats/gml/styles.d.ts +59 -0
- package/dist/src/formats/gml/styles.d.ts.map +1 -0
- package/dist/src/formats/gml/styles.js +1056 -0
- package/dist/src/formats/gml/styles.js.map +1 -0
- package/dist/src/formats/gml/syntax.d.ts +11 -0
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +151 -62
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/constants.d.ts +4 -0
- package/dist/src/formats/graphml/constants.d.ts.map +1 -1
- package/dist/src/formats/graphml/constants.js +5 -1
- package/dist/src/formats/graphml/constants.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +133 -65
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts +4 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +100 -44
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/index.d.ts +1 -0
- package/dist/src/formats/graphml/index.d.ts.map +1 -1
- package/dist/src/formats/graphml/index.js +1 -0
- package/dist/src/formats/graphml/index.js.map +1 -1
- package/dist/src/formats/graphml/styles-plain.d.ts +19 -0
- package/dist/src/formats/graphml/styles-plain.d.ts.map +1 -0
- package/dist/src/formats/graphml/styles-plain.js +187 -0
- package/dist/src/formats/graphml/styles-plain.js.map +1 -0
- package/dist/src/formats/graphml/styles-yed.d.ts +179 -0
- package/dist/src/formats/graphml/styles-yed.d.ts.map +1 -0
- package/dist/src/formats/graphml/styles-yed.js +1086 -0
- package/dist/src/formats/graphml/styles-yed.js.map +1 -0
- package/dist/src/formats/graphml/styles-yfiles3.d.ts +19 -0
- package/dist/src/formats/graphml/styles-yfiles3.d.ts.map +1 -0
- package/dist/src/formats/graphml/styles-yfiles3.js +508 -0
- package/dist/src/formats/graphml/styles-yfiles3.js.map +1 -0
- package/dist/src/formats/graphml/tree.d.ts.map +1 -1
- package/dist/src/formats/graphml/tree.js +34 -19
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts +16 -2
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +83 -12
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts +5 -0
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +68 -25
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +22 -3
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +441 -265
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/positions.d.ts +16 -0
- package/dist/src/formats/json/positions.d.ts.map +1 -0
- package/dist/src/formats/json/positions.js +44 -0
- package/dist/src/formats/json/positions.js.map +1 -0
- package/dist/src/formats/json/styles.d.ts +102 -0
- package/dist/src/formats/json/styles.d.ts.map +1 -0
- package/dist/src/formats/json/styles.js +1753 -0
- package/dist/src/formats/json/styles.js.map +1 -0
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +2 -1
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts +2 -0
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +142 -76
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts +4 -0
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +60 -6
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/index.d.ts +2 -0
- package/dist/src/formats/pajek/index.d.ts.map +1 -1
- package/dist/src/formats/pajek/index.js +2 -0
- package/dist/src/formats/pajek/index.js.map +1 -1
- package/dist/src/formats/pajek/styles.d.ts +50 -0
- package/dist/src/formats/pajek/styles.d.ts.map +1 -0
- package/dist/src/formats/pajek/styles.js +569 -0
- package/dist/src/formats/pajek/styles.js.map +1 -0
- package/dist/src/formats/pajek/syntax.d.ts +6 -0
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +6 -0
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/columns.d.ts.map +1 -1
- package/dist/src/formats/xgmml/columns.js +34 -9
- package/dist/src/formats/xgmml/columns.js.map +1 -1
- package/dist/src/formats/xgmml/constants.d.ts +2 -0
- package/dist/src/formats/xgmml/constants.d.ts.map +1 -1
- package/dist/src/formats/xgmml/constants.js +3 -1
- package/dist/src/formats/xgmml/constants.js.map +1 -1
- package/dist/src/formats/xgmml/document.d.ts +12 -8
- package/dist/src/formats/xgmml/document.d.ts.map +1 -1
- package/dist/src/formats/xgmml/document.js +56 -13
- package/dist/src/formats/xgmml/document.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +12 -6
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts +3 -1
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +22 -9
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts +2 -1
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +31 -24
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/formats/xgmml/styles.d.ts +104 -0
- package/dist/src/formats/xgmml/styles.d.ts.map +1 -0
- package/dist/src/formats/xgmml/styles.js +310 -0
- package/dist/src/formats/xgmml/styles.js.map +1 -0
- package/dist/src/index.d.ts +9 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -7
- package/dist/src/index.js.map +1 -1
- package/dist/src/positions/codes.d.ts +31 -0
- package/dist/src/positions/codes.d.ts.map +1 -0
- package/dist/src/positions/codes.js +26 -0
- package/dist/src/positions/codes.js.map +1 -0
- package/dist/src/positions/convert.d.ts +58 -0
- package/dist/src/positions/convert.d.ts.map +1 -0
- package/dist/src/positions/convert.js +107 -0
- package/dist/src/positions/convert.js.map +1 -0
- package/dist/src/positions/extract.d.ts +27 -0
- package/dist/src/positions/extract.d.ts.map +1 -0
- package/dist/src/positions/extract.js +526 -0
- package/dist/src/positions/extract.js.map +1 -0
- package/dist/src/positions/index.d.ts +10 -0
- package/dist/src/positions/index.d.ts.map +1 -0
- package/dist/src/positions/index.js +9 -0
- package/dist/src/positions/index.js.map +1 -0
- package/dist/src/positions/types.d.ts +208 -0
- package/dist/src/positions/types.d.ts.map +1 -0
- package/dist/src/positions/types.js +10 -0
- package/dist/src/positions/types.js.map +1 -0
- package/dist/src/registry.d.ts +30 -0
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +130 -9
- package/dist/src/registry.js.map +1 -1
- package/dist/src/styles/annotations.d.ts +132 -0
- package/dist/src/styles/annotations.d.ts.map +1 -0
- package/dist/src/styles/annotations.js +320 -0
- package/dist/src/styles/annotations.js.map +1 -0
- package/dist/src/styles/budget.d.ts +45 -0
- package/dist/src/styles/budget.d.ts.map +1 -0
- package/dist/src/styles/budget.js +58 -0
- package/dist/src/styles/budget.js.map +1 -0
- package/dist/src/styles/canon/bends.d.ts +32 -0
- package/dist/src/styles/canon/bends.d.ts.map +1 -0
- package/dist/src/styles/canon/bends.js +74 -0
- package/dist/src/styles/canon/bends.js.map +1 -0
- package/dist/src/styles/canon/color-tables.d.ts +22 -0
- package/dist/src/styles/canon/color-tables.d.ts.map +1 -0
- package/dist/src/styles/canon/color-tables.js +369 -0
- package/dist/src/styles/canon/color-tables.js.map +1 -0
- package/dist/src/styles/canon/color.d.ts +66 -0
- package/dist/src/styles/canon/color.d.ts.map +1 -0
- package/dist/src/styles/canon/color.js +548 -0
- package/dist/src/styles/canon/color.js.map +1 -0
- package/dist/src/styles/canon/enums.d.ts +25 -0
- package/dist/src/styles/canon/enums.d.ts.map +1 -0
- package/dist/src/styles/canon/enums.js +187 -0
- package/dist/src/styles/canon/enums.js.map +1 -0
- package/dist/src/styles/canon/graphviz.d.ts +48 -0
- package/dist/src/styles/canon/graphviz.d.ts.map +1 -0
- package/dist/src/styles/canon/graphviz.js +160 -0
- package/dist/src/styles/canon/graphviz.js.map +1 -0
- package/dist/src/styles/canon/index.d.ts +13 -0
- package/dist/src/styles/canon/index.d.ts.map +1 -0
- package/dist/src/styles/canon/index.js +13 -0
- package/dist/src/styles/canon/index.js.map +1 -0
- package/dist/src/styles/canon/number.d.ts +59 -0
- package/dist/src/styles/canon/number.d.ts.map +1 -0
- package/dist/src/styles/canon/number.js +142 -0
- package/dist/src/styles/canon/number.js.map +1 -0
- package/dist/src/styles/canon/site.d.ts +51 -0
- package/dist/src/styles/canon/site.d.ts.map +1 -0
- package/dist/src/styles/canon/site.js +63 -0
- package/dist/src/styles/canon/site.js.map +1 -0
- package/dist/src/styles/canon/text.d.ts +61 -0
- package/dist/src/styles/canon/text.d.ts.map +1 -0
- package/dist/src/styles/canon/text.js +199 -0
- package/dist/src/styles/canon/text.js.map +1 -0
- package/dist/src/styles/codes.d.ts +95 -0
- package/dist/src/styles/codes.d.ts.map +1 -0
- package/dist/src/styles/codes.js +97 -0
- package/dist/src/styles/codes.js.map +1 -0
- package/dist/src/styles/cyjs/cascade.d.ts +134 -0
- package/dist/src/styles/cyjs/cascade.d.ts.map +1 -0
- package/dist/src/styles/cyjs/cascade.js +2252 -0
- package/dist/src/styles/cyjs/cascade.js.map +1 -0
- package/dist/src/styles/cyjs/defaults-table.d.ts +25 -0
- package/dist/src/styles/cyjs/defaults-table.d.ts.map +1 -0
- package/dist/src/styles/cyjs/defaults-table.js +76 -0
- package/dist/src/styles/cyjs/defaults-table.js.map +1 -0
- package/dist/src/styles/cyjs/emit.d.ts +82 -0
- package/dist/src/styles/cyjs/emit.d.ts.map +1 -0
- package/dist/src/styles/cyjs/emit.js +790 -0
- package/dist/src/styles/cyjs/emit.js.map +1 -0
- package/dist/src/styles/cyjs/index.d.ts +13 -0
- package/dist/src/styles/cyjs/index.d.ts.map +1 -0
- package/dist/src/styles/cyjs/index.js +13 -0
- package/dist/src/styles/cyjs/index.js.map +1 -0
- package/dist/src/styles/cyjs/live.d.ts +46 -0
- package/dist/src/styles/cyjs/live.d.ts.map +1 -0
- package/dist/src/styles/cyjs/live.js +108 -0
- package/dist/src/styles/cyjs/live.js.map +1 -0
- package/dist/src/styles/cyjs/mappers.d.ts +147 -0
- package/dist/src/styles/cyjs/mappers.d.ts.map +1 -0
- package/dist/src/styles/cyjs/mappers.js +744 -0
- package/dist/src/styles/cyjs/mappers.js.map +1 -0
- package/dist/src/styles/cyjs/selector.d.ts +113 -0
- package/dist/src/styles/cyjs/selector.d.ts.map +1 -0
- package/dist/src/styles/cyjs/selector.js +611 -0
- package/dist/src/styles/cyjs/selector.js.map +1 -0
- package/dist/src/styles/cyjs/stylesheet-text.d.ts +37 -0
- package/dist/src/styles/cyjs/stylesheet-text.d.ts.map +1 -0
- package/dist/src/styles/cyjs/stylesheet-text.js +181 -0
- package/dist/src/styles/cyjs/stylesheet-text.js.map +1 -0
- package/dist/src/styles/cytoscape/emit.d.ts +125 -0
- package/dist/src/styles/cytoscape/emit.d.ts.map +1 -0
- package/dist/src/styles/cytoscape/emit.js +1025 -0
- package/dist/src/styles/cytoscape/emit.js.map +1 -0
- package/dist/src/styles/cytoscape/index.d.ts +10 -0
- package/dist/src/styles/cytoscape/index.d.ts.map +1 -0
- package/dist/src/styles/cytoscape/index.js +10 -0
- package/dist/src/styles/cytoscape/index.js.map +1 -0
- package/dist/src/styles/cytoscape/lexicon.d.ts +49 -0
- package/dist/src/styles/cytoscape/lexicon.d.ts.map +1 -0
- package/dist/src/styles/cytoscape/lexicon.js +603 -0
- package/dist/src/styles/cytoscape/lexicon.js.map +1 -0
- package/dist/src/styles/cytoscape/mappings.d.ts +171 -0
- package/dist/src/styles/cytoscape/mappings.d.ts.map +1 -0
- package/dist/src/styles/cytoscape/mappings.js +460 -0
- package/dist/src/styles/cytoscape/mappings.js.map +1 -0
- package/dist/src/styles/cytoscape/resolve.d.ts +137 -0
- package/dist/src/styles/cytoscape/resolve.d.ts.map +1 -0
- package/dist/src/styles/cytoscape/resolve.js +381 -0
- package/dist/src/styles/cytoscape/resolve.js.map +1 -0
- package/dist/src/styles/cytoscape/translate.d.ts +50 -0
- package/dist/src/styles/cytoscape/translate.d.ts.map +1 -0
- package/dist/src/styles/cytoscape/translate.js +156 -0
- package/dist/src/styles/cytoscape/translate.js.map +1 -0
- package/dist/src/styles/defaults.d.ts +90 -0
- package/dist/src/styles/defaults.d.ts.map +1 -0
- package/dist/src/styles/defaults.js +414 -0
- package/dist/src/styles/defaults.js.map +1 -0
- package/dist/src/styles/index.d.ts +15 -0
- package/dist/src/styles/index.d.ts.map +1 -0
- package/dist/src/styles/index.js +14 -0
- package/dist/src/styles/index.js.map +1 -0
- package/dist/src/styles/internal.d.ts +17 -0
- package/dist/src/styles/internal.d.ts.map +1 -0
- package/dist/src/styles/internal.js +16 -0
- package/dist/src/styles/internal.js.map +1 -0
- package/dist/src/styles/io/approximations.d.ts +24 -0
- package/dist/src/styles/io/approximations.d.ts.map +1 -0
- package/dist/src/styles/io/approximations.js +51 -0
- package/dist/src/styles/io/approximations.js.map +1 -0
- package/dist/src/styles/io/block.d.ts +166 -0
- package/dist/src/styles/io/block.d.ts.map +1 -0
- package/dist/src/styles/io/block.js +930 -0
- package/dist/src/styles/io/block.js.map +1 -0
- package/dist/src/styles/io/generic.d.ts +88 -0
- package/dist/src/styles/io/generic.d.ts.map +1 -0
- package/dist/src/styles/io/generic.js +516 -0
- package/dist/src/styles/io/generic.js.map +1 -0
- package/dist/src/styles/io/index.d.ts +8 -0
- package/dist/src/styles/io/index.d.ts.map +1 -0
- package/dist/src/styles/io/index.js +7 -0
- package/dist/src/styles/io/index.js.map +1 -0
- package/dist/src/styles/io/writer.d.ts +80 -0
- package/dist/src/styles/io/writer.d.ts.map +1 -0
- package/dist/src/styles/io/writer.js +596 -0
- package/dist/src/styles/io/writer.js.map +1 -0
- package/dist/src/styles/notes.d.ts +47 -0
- package/dist/src/styles/notes.d.ts.map +1 -0
- package/dist/src/styles/notes.js +140 -0
- package/dist/src/styles/notes.js.map +1 -0
- package/dist/src/styles/plan/classes.d.ts +31 -0
- package/dist/src/styles/plan/classes.d.ts.map +1 -0
- package/dist/src/styles/plan/classes.js +139 -0
- package/dist/src/styles/plan/classes.js.map +1 -0
- package/dist/src/styles/plan/continuous.d.ts +32 -0
- package/dist/src/styles/plan/continuous.d.ts.map +1 -0
- package/dist/src/styles/plan/continuous.js +165 -0
- package/dist/src/styles/plan/continuous.js.map +1 -0
- package/dist/src/styles/plan/discrete.d.ts +39 -0
- package/dist/src/styles/plan/discrete.d.ts.map +1 -0
- package/dist/src/styles/plan/discrete.js +127 -0
- package/dist/src/styles/plan/discrete.js.map +1 -0
- package/dist/src/styles/plan/families.d.ts +31 -0
- package/dist/src/styles/plan/families.d.ts.map +1 -0
- package/dist/src/styles/plan/families.js +95 -0
- package/dist/src/styles/plan/families.js.map +1 -0
- package/dist/src/styles/plan/features.d.ts +58 -0
- package/dist/src/styles/plan/features.d.ts.map +1 -0
- package/dist/src/styles/plan/features.js +185 -0
- package/dist/src/styles/plan/features.js.map +1 -0
- package/dist/src/styles/plan/index.d.ts +7 -0
- package/dist/src/styles/plan/index.d.ts.map +1 -0
- package/dist/src/styles/plan/index.js +7 -0
- package/dist/src/styles/plan/index.js.map +1 -0
- package/dist/src/styles/plan/plan.d.ts +71 -0
- package/dist/src/styles/plan/plan.d.ts.map +1 -0
- package/dist/src/styles/plan/plan.js +893 -0
- package/dist/src/styles/plan/plan.js.map +1 -0
- package/dist/src/styles/plan/resolve.d.ts +137 -0
- package/dist/src/styles/plan/resolve.d.ts.map +1 -0
- package/dist/src/styles/plan/resolve.js +587 -0
- package/dist/src/styles/plan/resolve.js.map +1 -0
- package/dist/src/styles/types.d.ts +702 -0
- package/dist/src/styles/types.d.ts.map +1 -0
- package/dist/src/styles/types.js +10 -0
- package/dist/src/styles/types.js.map +1 -0
- package/dist/src/styles/vocabulary.d.ts +41 -0
- package/dist/src/styles/vocabulary.d.ts.map +1 -0
- package/dist/src/styles/vocabulary.js +140 -0
- package/dist/src/styles/vocabulary.js.map +1 -0
- package/dist/src/types.d.ts +64 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/styles.d.ts +1 -0
- package/dist/styles.js +322 -0
- package/dist/styles.js.map +1 -0
- package/dist/xgmml.js +5 -4
- package/dist/xgmml.js.map +1 -1
- package/package.json +13 -2
- package/src/common/codes.ts +7 -0
- package/src/common/escape.ts +5 -2
- package/src/common/export.ts +179 -140
- package/src/common/input.ts +8 -4
- package/src/common/json-elements.ts +169 -38
- package/src/common/notes.ts +94 -0
- package/src/common/report.ts +41 -1
- package/src/common/units.ts +42 -0
- package/src/common/xml.ts +45 -5
- package/src/formats/cx/exporter.ts +95 -11
- package/src/formats/cx/importer.ts +126 -90
- package/src/formats/cx/styles.ts +304 -0
- package/src/formats/cx2/exporter.ts +234 -77
- package/src/formats/cx2/importer.ts +19 -1
- package/src/formats/cx2/styles.ts +29 -0
- package/src/formats/cys/constants.ts +3 -0
- package/src/formats/cys/exporter.ts +246 -60
- package/src/formats/cys/importer.ts +125 -9
- package/src/formats/cys/session.ts +6 -0
- package/src/formats/cys/styles.ts +254 -0
- package/src/formats/cys/tables.ts +117 -48
- package/src/formats/dot/exporter.ts +262 -126
- package/src/formats/dot/importer.ts +111 -20
- package/src/formats/dot/index.ts +1 -0
- package/src/formats/dot/positions.ts +77 -0
- package/src/formats/dot/styles.ts +1741 -0
- package/src/formats/gexf/exporter.ts +30 -13
- package/src/formats/gexf/importer.ts +308 -158
- package/src/formats/gexf/index.ts +10 -0
- package/src/formats/gexf/schema.ts +5 -0
- package/src/formats/gexf/styles.ts +719 -0
- package/src/formats/gml/exporter.ts +33 -14
- package/src/formats/gml/importer.ts +4 -0
- package/src/formats/gml/index.ts +2 -1
- package/src/formats/gml/positions.ts +52 -0
- package/src/formats/gml/styles.ts +1248 -0
- package/src/formats/gml/syntax.ts +173 -62
- package/src/formats/graphml/constants.ts +5 -0
- package/src/formats/graphml/exporter.ts +163 -71
- package/src/formats/graphml/importer.ts +119 -45
- package/src/formats/graphml/index.ts +1 -0
- package/src/formats/graphml/styles-plain.ts +236 -0
- package/src/formats/graphml/styles-yed.ts +1221 -0
- package/src/formats/graphml/styles-yfiles3.ts +573 -0
- package/src/formats/graphml/tree.ts +36 -20
- package/src/formats/graphml/yfiles.ts +89 -13
- package/src/formats/json/exporter.ts +76 -26
- package/src/formats/json/importer.ts +543 -323
- package/src/formats/json/positions.ts +50 -0
- package/src/formats/json/styles.ts +2082 -0
- package/src/formats/neo4j/exporter.ts +2 -1
- package/src/formats/pajek/exporter.ts +195 -106
- package/src/formats/pajek/importer.ts +78 -9
- package/src/formats/pajek/index.ts +2 -0
- package/src/formats/pajek/styles.ts +685 -0
- package/src/formats/pajek/syntax.ts +7 -0
- package/src/formats/xgmml/columns.ts +39 -9
- package/src/formats/xgmml/constants.ts +3 -0
- package/src/formats/xgmml/document.ts +54 -12
- package/src/formats/xgmml/emit.ts +11 -6
- package/src/formats/xgmml/exporter.ts +32 -13
- package/src/formats/xgmml/importer.ts +43 -29
- package/src/formats/xgmml/styles.ts +400 -0
- package/src/index.ts +16 -0
- package/src/positions/codes.ts +32 -0
- package/src/positions/convert.ts +123 -0
- package/src/positions/extract.ts +619 -0
- package/src/positions/index.ts +23 -0
- package/src/positions/types.ts +221 -0
- package/src/registry.ts +179 -8
- package/src/styles/annotations.ts +478 -0
- package/src/styles/budget.ts +60 -0
- package/src/styles/canon/bends.ts +84 -0
- package/src/styles/canon/color-tables.ts +386 -0
- package/src/styles/canon/color.ts +624 -0
- package/src/styles/canon/enums.ts +211 -0
- package/src/styles/canon/graphviz.ts +189 -0
- package/src/styles/canon/index.ts +13 -0
- package/src/styles/canon/number.ts +152 -0
- package/src/styles/canon/site.ts +87 -0
- package/src/styles/canon/text.ts +221 -0
- package/src/styles/codes.ts +104 -0
- package/src/styles/cyjs/cascade.ts +2670 -0
- package/src/styles/cyjs/defaults-table.ts +82 -0
- package/src/styles/cyjs/emit.ts +951 -0
- package/src/styles/cyjs/index.ts +37 -0
- package/src/styles/cyjs/live.ts +147 -0
- package/src/styles/cyjs/mappers.ts +879 -0
- package/src/styles/cyjs/selector.ts +778 -0
- package/src/styles/cyjs/stylesheet-text.ts +214 -0
- package/src/styles/cytoscape/emit.ts +1264 -0
- package/src/styles/cytoscape/index.ts +40 -0
- package/src/styles/cytoscape/lexicon.ts +656 -0
- package/src/styles/cytoscape/mappings.ts +603 -0
- package/src/styles/cytoscape/resolve.ts +521 -0
- package/src/styles/cytoscape/translate.ts +237 -0
- package/src/styles/defaults.ts +504 -0
- package/src/styles/index.ts +57 -0
- package/src/styles/internal.ts +17 -0
- package/src/styles/io/approximations.ts +47 -0
- package/src/styles/io/block.ts +1138 -0
- package/src/styles/io/generic.ts +687 -0
- package/src/styles/io/index.ts +18 -0
- package/src/styles/io/writer.ts +745 -0
- package/src/styles/notes.ts +165 -0
- package/src/styles/plan/classes.ts +166 -0
- package/src/styles/plan/continuous.ts +218 -0
- package/src/styles/plan/discrete.ts +165 -0
- package/src/styles/plan/families.ts +115 -0
- package/src/styles/plan/features.ts +234 -0
- package/src/styles/plan/index.ts +7 -0
- package/src/styles/plan/plan.ts +1129 -0
- package/src/styles/plan/resolve.ts +685 -0
- package/src/styles/types.ts +814 -0
- package/src/styles/vocabulary.ts +172 -0
- package/src/types.ts +76 -1
- package/dist/chunks/constants-fT3E8JYY.js.map +0 -1
- package/dist/chunks/escape-B62ZGM_a.js.map +0 -1
- package/dist/chunks/export-rKLeGQMo.js.map +0 -1
- package/dist/chunks/exporter-8hLNemFk.js.map +0 -1
- package/dist/chunks/exporter-BWwEuOPT.js.map +0 -1
- package/dist/chunks/exporter-CpRyHZ3H.js.map +0 -1
- package/dist/chunks/importer-BbNb7eNg.js.map +0 -1
- package/dist/chunks/importer-C98dpSH4.js.map +0 -1
- package/dist/chunks/importer-D794XUTe.js.map +0 -1
- package/dist/chunks/importer-Dz7tI8Sa.js.map +0 -1
- package/dist/chunks/importer-Ht7LNg8L.js.map +0 -1
- package/dist/chunks/importer-WrGRZb0N.js.map +0 -1
- package/dist/chunks/importer-xZ-3suY1.js.map +0 -1
- package/dist/chunks/json-elements-DKSqpNW1.js.map +0 -1
|
@@ -0,0 +1,1129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The planner: turns a file's per-element style values into a few readable rules that reproduce them exactly. It
|
|
3
|
+
* groups properties into families, explains each family by a discrete or continuous mapping on one feature, a
|
|
4
|
+
* passthrough on an existing column, style classes or a copy, orders the rules, caps them at 15 per target and
|
|
5
|
+
* replays them: every element whose resolved value differs from the file's goes into one sparse exceptions column
|
|
6
|
+
* per property on top. One counted budget bounds the work; when it runs out the rest is copied.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { StyleBudget } from "../budget.js";
|
|
10
|
+
import { STYLE_ISSUE } from "../codes.js";
|
|
11
|
+
import { toolDefaults } from "../defaults.js";
|
|
12
|
+
import { StyleNotes } from "../notes.js";
|
|
13
|
+
import type {
|
|
14
|
+
ExtractedStyles,
|
|
15
|
+
GraphStyle,
|
|
16
|
+
Mapping,
|
|
17
|
+
ReaderOutput,
|
|
18
|
+
StyleColumn,
|
|
19
|
+
StyleDialect,
|
|
20
|
+
StyleFeature,
|
|
21
|
+
StyleFeatures,
|
|
22
|
+
StyleFingerprint,
|
|
23
|
+
StyleRule,
|
|
24
|
+
StyleRuleFacts,
|
|
25
|
+
StyleTarget,
|
|
26
|
+
StyleTool,
|
|
27
|
+
StyleValue,
|
|
28
|
+
StyleWorkBudget,
|
|
29
|
+
} from "../types.js";
|
|
30
|
+
import { isStyleValue, STYLE_PROPERTIES } from "../vocabulary.js";
|
|
31
|
+
import { CLASS_COLUMN, styleClasses } from "./classes.js";
|
|
32
|
+
import { type ContinuousCandidate, continuousCandidate } from "./continuous.js";
|
|
33
|
+
import { type DiscreteCandidate, discreteCandidate } from "./discrete.js";
|
|
34
|
+
import { familiesOf, type Family } from "./families.js";
|
|
35
|
+
import { describeFeature, type FeatureInfo } from "./features.js";
|
|
36
|
+
import {
|
|
37
|
+
canonicalFor,
|
|
38
|
+
evalContinuous,
|
|
39
|
+
isShortPredicate,
|
|
40
|
+
matches,
|
|
41
|
+
numberOf,
|
|
42
|
+
refSource,
|
|
43
|
+
resolveRules,
|
|
44
|
+
within,
|
|
45
|
+
} from "./resolve.js";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The most rules per target: the planner keeps at most 15 plus the exceptions rule.
|
|
49
|
+
* @category Styles
|
|
50
|
+
*/
|
|
51
|
+
export const MAX_STYLE_RULES = 16;
|
|
52
|
+
/** The most candidate features tested per target, fewest distinct values first. */
|
|
53
|
+
const MAX_FEATURES = 64;
|
|
54
|
+
/** Lengths a tool states in its own unit; they are left out when the file's unit differs. */
|
|
55
|
+
const LENGTHS: ReadonlySet<string> = new Set(["width", "height", "borderWidth", "labelSize"]);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* What the planner is given: the canonical per-element values a style reader produced, the rules the file declares,
|
|
59
|
+
* and the graph they belong to.
|
|
60
|
+
* @category Styles
|
|
61
|
+
*/
|
|
62
|
+
export interface StyleValues {
|
|
63
|
+
/** The graph-io format id. */
|
|
64
|
+
readonly format: string;
|
|
65
|
+
/** How the file spelled its values. */
|
|
66
|
+
readonly dialect?: StyleDialect | null;
|
|
67
|
+
/** The program that wrote the file, whose defaults fill in what the file leaves unset; null when unknown. */
|
|
68
|
+
readonly tool?: StyleTool | null;
|
|
69
|
+
/** The unit of every length; defaults to pixels. */
|
|
70
|
+
readonly units?: "px" | "relative";
|
|
71
|
+
/** The graph's own style. */
|
|
72
|
+
readonly graph?: GraphStyle;
|
|
73
|
+
/** Node values, declared rules, reader columns and extra features. */
|
|
74
|
+
readonly node?: ReaderOutput;
|
|
75
|
+
/** Edge values, declared rules, reader columns and extra features. */
|
|
76
|
+
readonly edge?: ReaderOutput;
|
|
77
|
+
/** The graph the values belong to; its counts give the number of elements. */
|
|
78
|
+
readonly fingerprint: StyleFingerprint;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Options of `planStyles()`.
|
|
83
|
+
* @category Styles
|
|
84
|
+
*/
|
|
85
|
+
export interface PlanOptions {
|
|
86
|
+
/** Whether to add the writing tool's defaults as a rule at the bottom; defaults to true. */
|
|
87
|
+
readonly toolDefaults?: boolean;
|
|
88
|
+
/** The counted work budget; defaults to a fresh `StyleBudget`. */
|
|
89
|
+
readonly budget?: StyleWorkBudget;
|
|
90
|
+
/**
|
|
91
|
+
* Where notes are collected; the reader's notes, when it used the same collector, are kept.
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
94
|
+
readonly notes?: StyleNotes;
|
|
95
|
+
/** Data columns the reader read the values from: they are never offered as explanations of themselves. */
|
|
96
|
+
readonly rawColumns?: {
|
|
97
|
+
/** Node columns. */
|
|
98
|
+
readonly node?: readonly string[];
|
|
99
|
+
/** Edge columns. */
|
|
100
|
+
readonly edge?: readonly string[];
|
|
101
|
+
};
|
|
102
|
+
/** Cancels the planning. */
|
|
103
|
+
readonly signal?: AbortSignal;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A rule being built. */
|
|
107
|
+
interface Draft {
|
|
108
|
+
id: string;
|
|
109
|
+
readonly target: StyleTarget;
|
|
110
|
+
readonly role: string;
|
|
111
|
+
readonly origin: string;
|
|
112
|
+
where: StyleRule["where"];
|
|
113
|
+
set?: Record<string, StyleValue>;
|
|
114
|
+
map?: Record<string, Mapping>;
|
|
115
|
+
facts?: StyleRuleFacts;
|
|
116
|
+
readonly ext?: Readonly<Record<string, unknown>>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The planning state of one target. */
|
|
120
|
+
interface TargetPlan {
|
|
121
|
+
readonly target: StyleTarget;
|
|
122
|
+
readonly n: number;
|
|
123
|
+
readonly notes: StyleNotes;
|
|
124
|
+
readonly budget: StyleWorkBudget;
|
|
125
|
+
readonly spend: (ops: number) => void;
|
|
126
|
+
readonly columns: Map<string, StyleColumn>;
|
|
127
|
+
readonly ids: Set<string>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Plans a file's styles: the rules that reproduce its per-element values, with the columns they read.
|
|
132
|
+
* @param values - the canonical values, the declared rules and the graph they belong to
|
|
133
|
+
* @param features - the candidate explaining columns per target (`featuresOf()` of the snapshot)
|
|
134
|
+
* @param options - tool defaults, the budget, the notes collector, the raw columns and a cancel signal
|
|
135
|
+
* @returns the styles, exact for every element
|
|
136
|
+
* @category Styles
|
|
137
|
+
*/
|
|
138
|
+
export function planStyles(values: StyleValues, features: StyleFeatures, options: PlanOptions = {}): ExtractedStyles {
|
|
139
|
+
const notes = options.notes ?? new StyleNotes();
|
|
140
|
+
const budget = options.budget ?? new StyleBudget();
|
|
141
|
+
const defaults =
|
|
142
|
+
options.toolDefaults !== false && values.tool !== undefined && values.tool !== null
|
|
143
|
+
? toolDefaults(values.tool)
|
|
144
|
+
: null;
|
|
145
|
+
const units = values.units ?? "px";
|
|
146
|
+
const rules: StyleRule[] = [];
|
|
147
|
+
const columns = { node: new Map<string, StyleColumn>(), edge: new Map<string, StyleColumn>() };
|
|
148
|
+
const allFeatures = { node: [] as StyleFeature[], edge: [] as StyleFeature[] };
|
|
149
|
+
for (const target of ["node", "edge"] as const) {
|
|
150
|
+
const given = values[target];
|
|
151
|
+
// a reader's own tool-default rules (Gephi's arrows on directed edges) go with the tool's constants
|
|
152
|
+
const out =
|
|
153
|
+
options.toolDefaults === false && given?.rules !== undefined
|
|
154
|
+
? { ...given, rules: given.rules.filter((r) => r.origin !== "tool") }
|
|
155
|
+
: given;
|
|
156
|
+
allFeatures[target] = [...features[target], ...(out?.features ?? [])];
|
|
157
|
+
const toolSet = defaults === null ? null : toolSetOf(defaults, target, units);
|
|
158
|
+
const plan: TargetPlan = {
|
|
159
|
+
target,
|
|
160
|
+
n: target === "node" ? values.fingerprint.nodes : values.fingerprint.edges,
|
|
161
|
+
notes,
|
|
162
|
+
budget,
|
|
163
|
+
spend: (ops) => {
|
|
164
|
+
budget.spend(target, ops);
|
|
165
|
+
},
|
|
166
|
+
columns: columns[target],
|
|
167
|
+
ids: new Set(),
|
|
168
|
+
};
|
|
169
|
+
rules.push(
|
|
170
|
+
...planTarget(
|
|
171
|
+
plan,
|
|
172
|
+
out,
|
|
173
|
+
allFeatures[target],
|
|
174
|
+
new Set(options.rawColumns?.[target] ?? []),
|
|
175
|
+
toolSet,
|
|
176
|
+
options.signal,
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
const graph = graphStyles(defaults, values.graph ?? {});
|
|
181
|
+
return {
|
|
182
|
+
styles: {
|
|
183
|
+
version: 1,
|
|
184
|
+
source: {
|
|
185
|
+
format: values.format,
|
|
186
|
+
dialect: values.dialect ?? null,
|
|
187
|
+
tool: defaults === null ? null : defaults.tool,
|
|
188
|
+
},
|
|
189
|
+
units: { length: units },
|
|
190
|
+
graph: graph,
|
|
191
|
+
rules,
|
|
192
|
+
notes: notes.list(),
|
|
193
|
+
},
|
|
194
|
+
columns,
|
|
195
|
+
features: allFeatures,
|
|
196
|
+
fingerprint: values.fingerprint,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The tool's default values of a target, as one defaults rule sets them; a length in other units than the file's is
|
|
202
|
+
* left out.
|
|
203
|
+
* @param defaults - the tool's defaults
|
|
204
|
+
* @param target - nodes or edges
|
|
205
|
+
* @param units - the file's length units
|
|
206
|
+
* @returns the values, or null when none applies
|
|
207
|
+
*/
|
|
208
|
+
function toolSetOf(
|
|
209
|
+
defaults: NonNullable<ReturnType<typeof toolDefaults>>,
|
|
210
|
+
target: StyleTarget,
|
|
211
|
+
units: string,
|
|
212
|
+
): Record<string, StyleValue> | null {
|
|
213
|
+
const toolSet: Record<string, StyleValue> = {};
|
|
214
|
+
for (const [p, d] of Object.entries(defaults[target]) as [string, { value: unknown } | undefined][]) {
|
|
215
|
+
const fits = d !== undefined && isStyleValue(target, p, d.value);
|
|
216
|
+
if (fits && !(LENGTHS.has(p) && defaults.units !== units)) {
|
|
217
|
+
toolSet[p] = d.value as StyleValue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return Object.keys(toolSet).length === 0 ? null : toolSet;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The graph's styles: the tool's defaults, then the file's own values over them.
|
|
225
|
+
* @param defaults - the tool's defaults, or null
|
|
226
|
+
* @param given - the file's graph values
|
|
227
|
+
* @returns the values
|
|
228
|
+
*/
|
|
229
|
+
function graphStyles(
|
|
230
|
+
defaults: ReturnType<typeof toolDefaults> | null,
|
|
231
|
+
given: Readonly<Record<string, unknown>>,
|
|
232
|
+
): Record<string, StyleValue> {
|
|
233
|
+
const graph: Record<string, StyleValue> = {};
|
|
234
|
+
const own = Object.entries(defaults?.graph ?? {}) as [string, { value: unknown } | undefined][];
|
|
235
|
+
for (const [p, d] of own) {
|
|
236
|
+
if (d !== undefined && isStyleValue("graph", p, d.value)) {
|
|
237
|
+
graph[p] = d.value as StyleValue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const [p, v] of Object.entries(given)) {
|
|
241
|
+
if (isStyleValue("graph", p, v)) {
|
|
242
|
+
graph[p] = v as StyleValue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return graph;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A rule id not used yet in this target.
|
|
250
|
+
* @param plan - the target's state
|
|
251
|
+
* @param base - the wanted id
|
|
252
|
+
* @returns the id, suffixed when taken
|
|
253
|
+
*/
|
|
254
|
+
function uniqueId(plan: TargetPlan, base: string): string {
|
|
255
|
+
let id = base;
|
|
256
|
+
let k = 2;
|
|
257
|
+
while (plan.ids.has(id)) {
|
|
258
|
+
id = `${base}#${String(k)}`;
|
|
259
|
+
k++;
|
|
260
|
+
}
|
|
261
|
+
plan.ids.add(id);
|
|
262
|
+
return id;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Sets one property's mapping of a rule, making the rule's mappings on first use.
|
|
267
|
+
* @param rule - the rule, changed in place
|
|
268
|
+
* @param property - the property
|
|
269
|
+
* @param mapping - the mapping
|
|
270
|
+
*/
|
|
271
|
+
function setMapping(rule: Draft, property: string, mapping: Mapping): void {
|
|
272
|
+
rule.map ??= {};
|
|
273
|
+
rule.map[property] = mapping;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The type-checked per-element values of a target: a value of the wrong kind is noted and dropped.
|
|
278
|
+
* @param plan - the target's state
|
|
279
|
+
* @param out - the reader's output
|
|
280
|
+
* @returns the values per property that have at least one value
|
|
281
|
+
*/
|
|
282
|
+
function checkedTruth(plan: TargetPlan, out: ReaderOutput | undefined): Map<string, (StyleValue | null)[]> {
|
|
283
|
+
const truth = new Map<string, (StyleValue | null)[]>();
|
|
284
|
+
for (const [p, arr] of Object.entries<StyleColumn | undefined>(out?.values ?? {})) {
|
|
285
|
+
if (arr === undefined) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!STYLE_PROPERTIES[plan.target].has(p) && !p.startsWith("graphty:")) {
|
|
289
|
+
plan.notes.add(STYLE_ISSUE.NOT_CARRIED, plan.target, p, { sample: p });
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const vals = checkedValues(plan, p, arr);
|
|
293
|
+
plan.spend(plan.n);
|
|
294
|
+
if (vals.some((v) => v !== null)) {
|
|
295
|
+
truth.set(p, vals);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return truth;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* One property's values, a value of the wrong kind noted and dropped.
|
|
303
|
+
* @param plan - the target's state
|
|
304
|
+
* @param p - the property
|
|
305
|
+
* @param arr - the reader's values
|
|
306
|
+
* @returns the values, one per element
|
|
307
|
+
*/
|
|
308
|
+
function checkedValues(plan: TargetPlan, p: string, arr: StyleColumn): (StyleValue | null)[] {
|
|
309
|
+
const vals = new Array<StyleValue | null>(plan.n).fill(null);
|
|
310
|
+
for (let i = 0; i < plan.n && i < arr.length; i++) {
|
|
311
|
+
const v = arr[i] ?? null;
|
|
312
|
+
if (v !== null && isStyleValue(plan.target, p, v)) {
|
|
313
|
+
vals[i] = v;
|
|
314
|
+
} else if (v !== null) {
|
|
315
|
+
plan.notes.add(STYLE_ISSUE.BAD_VALUE, plan.target, p, { sample: v, element: i });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return vals;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* A copy of a property record without its undefined entries.
|
|
323
|
+
* @param o - the record
|
|
324
|
+
* @returns the copy, or undefined
|
|
325
|
+
*/
|
|
326
|
+
function defined<T>(o: Readonly<Record<string, T | undefined>> | undefined): Record<string, T> | undefined {
|
|
327
|
+
if (o === undefined) {
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
const out: Record<string, T> = {};
|
|
331
|
+
for (const [k, v] of Object.entries(o)) {
|
|
332
|
+
if (v !== undefined) {
|
|
333
|
+
out[k] = v;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The properties a rule writes.
|
|
341
|
+
* @param r - the rule
|
|
342
|
+
* @returns the property names
|
|
343
|
+
*/
|
|
344
|
+
function writes(r: Draft | StyleRule): string[] {
|
|
345
|
+
return [...Object.keys(r.set ?? {}), ...Object.keys(r.map ?? {})];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* The declared rules, with every predicate longer than a short one evaluated into a membership style column and
|
|
350
|
+
* runs of rules that set values on every element merged into one defaults rule.
|
|
351
|
+
* @param plan - the target's state
|
|
352
|
+
* @param declared - the reader's rules
|
|
353
|
+
* @param features - the features, for evaluating predicates over data columns
|
|
354
|
+
* @returns the rules to keep
|
|
355
|
+
*/
|
|
356
|
+
function prepareDeclared(plan: TargetPlan, declared: readonly StyleRule[], features: readonly StyleFeature[]): Draft[] {
|
|
357
|
+
const src = refSource(plan.target, plan.n, plan.columns, features);
|
|
358
|
+
const drafts: Draft[] = [];
|
|
359
|
+
for (const r of declared.filter((x) => x.target === plan.target)) {
|
|
360
|
+
const d: Draft = { ...r, set: defined(r.set), map: defined(r.map) };
|
|
361
|
+
if (d.set === undefined) {
|
|
362
|
+
delete d.set;
|
|
363
|
+
}
|
|
364
|
+
if (d.map === undefined) {
|
|
365
|
+
delete d.map;
|
|
366
|
+
}
|
|
367
|
+
if (!isShortPredicate(r.where)) {
|
|
368
|
+
asMembership(plan, d, r, src);
|
|
369
|
+
}
|
|
370
|
+
drafts.push(d);
|
|
371
|
+
}
|
|
372
|
+
const merged = mergedDefaults(plan.target, drafts);
|
|
373
|
+
for (const d of merged) {
|
|
374
|
+
d.id = uniqueId(plan, d.id);
|
|
375
|
+
}
|
|
376
|
+
return merged;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Evaluates a long predicate into a membership style column the rule selects instead.
|
|
381
|
+
* @param plan - the target's state
|
|
382
|
+
* @param d - the rule's draft, changed in place
|
|
383
|
+
* @param r - the declared rule
|
|
384
|
+
* @param src - the reference source
|
|
385
|
+
*/
|
|
386
|
+
function asMembership(plan: TargetPlan, d: Draft, r: StyleRule, src: ReturnType<typeof refSource>): void {
|
|
387
|
+
let member = 1;
|
|
388
|
+
while (plan.columns.has(`member.${String(member)}`)) {
|
|
389
|
+
member++;
|
|
390
|
+
}
|
|
391
|
+
const key = `member.${String(member)}`;
|
|
392
|
+
const col = new Array<StyleValue | null>(plan.n).fill(null);
|
|
393
|
+
let count = 0;
|
|
394
|
+
for (let i = 0; i < plan.n; i++) {
|
|
395
|
+
if (matches(r.where, src, i)) {
|
|
396
|
+
col[i] = true;
|
|
397
|
+
count++;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
plan.spend(plan.n * 8);
|
|
401
|
+
plan.columns.set(key, col);
|
|
402
|
+
d.where = { has: { style: key } };
|
|
403
|
+
d.facts = { ...r.facts, elements: count, reason: r.facts?.reason ?? "SELECTOR" };
|
|
404
|
+
plan.notes.add(STYLE_ISSUE.SELECTOR_AS_COLUMN, plan.target, r.id, { count });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Consecutive declared rules that select everything and only set values, as one defaults rule (later wins).
|
|
409
|
+
* @param target - nodes or edges
|
|
410
|
+
* @param drafts - the declared rules
|
|
411
|
+
* @returns the rules, runs merged
|
|
412
|
+
*/
|
|
413
|
+
function mergedDefaults(target: StyleTarget, drafts: readonly Draft[]): Draft[] {
|
|
414
|
+
const merged: Draft[] = [];
|
|
415
|
+
const plain = (x: Draft): boolean => "all" in x.where && x.set !== undefined && x.map === undefined;
|
|
416
|
+
let i = 0;
|
|
417
|
+
while (i < drafts.length) {
|
|
418
|
+
let j = i + 1;
|
|
419
|
+
while (plain(drafts[i]) && j < drafts.length && plain(drafts[j])) {
|
|
420
|
+
j++;
|
|
421
|
+
}
|
|
422
|
+
if (j - i >= 2) {
|
|
423
|
+
merged.push({
|
|
424
|
+
id: drafts[i].id,
|
|
425
|
+
target,
|
|
426
|
+
role: "defaults",
|
|
427
|
+
origin: "declared",
|
|
428
|
+
where: { all: true },
|
|
429
|
+
set: Object.assign({}, ...drafts.slice(i, j).map((x) => x.set)) as Record<string, StyleValue>,
|
|
430
|
+
});
|
|
431
|
+
} else {
|
|
432
|
+
merged.push(drafts[i]);
|
|
433
|
+
}
|
|
434
|
+
i = j;
|
|
435
|
+
}
|
|
436
|
+
return merged;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** A passthrough on existing columns that explains a family. */
|
|
440
|
+
interface PassCandidate {
|
|
441
|
+
readonly type: "passdata";
|
|
442
|
+
readonly cols: ReadonlyMap<string, FeatureInfo>;
|
|
443
|
+
readonly raw: boolean;
|
|
444
|
+
readonly cost: number;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** The floor: classes or a copy. */
|
|
448
|
+
interface OpaqueCandidate {
|
|
449
|
+
readonly type: "opaque";
|
|
450
|
+
readonly cost: number;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
type Candidate = DiscreteCandidate | ContinuousCandidate | PassCandidate | OpaqueCandidate;
|
|
454
|
+
const ORDER = ["passdata", "discrete", "continuous", "opaque"];
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Tests a passthrough on existing data columns: one column per property whose canonical value equals the style on
|
|
458
|
+
* every element.
|
|
459
|
+
* @param plan - the target's state
|
|
460
|
+
* @param fam - the family
|
|
461
|
+
* @param truth - the values per property
|
|
462
|
+
* @param infos - the features
|
|
463
|
+
* @returns the candidate, or null
|
|
464
|
+
*/
|
|
465
|
+
function passCandidate(
|
|
466
|
+
plan: TargetPlan,
|
|
467
|
+
fam: Family,
|
|
468
|
+
truth: ReadonlyMap<string, readonly (StyleValue | null)[]>,
|
|
469
|
+
infos: readonly FeatureInfo[],
|
|
470
|
+
): PassCandidate | null {
|
|
471
|
+
const cols = new Map<string, FeatureInfo>();
|
|
472
|
+
for (const p of fam.props) {
|
|
473
|
+
const canon = canonicalFor(plan.target, p);
|
|
474
|
+
const isColor = STYLE_PROPERTIES[plan.target].get(p) === "color";
|
|
475
|
+
const t = truth.get(p) ?? [];
|
|
476
|
+
const hit = infos.find((f) => {
|
|
477
|
+
if (!("data" in f.feature.ref)) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
plan.spend(plan.n);
|
|
481
|
+
for (let i = 0; i < plan.n; i++) {
|
|
482
|
+
const raw = f.feature.values[i];
|
|
483
|
+
// a color column is reused only when it already holds "#rrggbb": other spellings read differently
|
|
484
|
+
if (isColor && raw !== null && !(typeof raw === "string" && /^#[0-9a-fA-F]{6}$/.test(raw))) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
if ((raw === null ? null : canon(raw)) !== t[i]) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return true;
|
|
492
|
+
});
|
|
493
|
+
if (hit === undefined) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
cols.set(p, hit);
|
|
497
|
+
}
|
|
498
|
+
const raw = Array.from(cols.values()).some((f) => f.raw);
|
|
499
|
+
// a passthrough on the raw style column itself recovers nothing: nearly as opaque as a copy
|
|
500
|
+
return { type: "passdata", cols, raw, cost: raw ? 240 : 50 };
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Plans one target.
|
|
505
|
+
* @param plan - the target's state
|
|
506
|
+
* @param out - the reader's output
|
|
507
|
+
* @param features - the candidate features
|
|
508
|
+
* @param rawNames - the data columns the values were read from
|
|
509
|
+
* @param toolSet - the tool's default values, or null
|
|
510
|
+
* @param signal - cancels the planning
|
|
511
|
+
* @returns the target's rules, bottom to top
|
|
512
|
+
*/
|
|
513
|
+
function planTarget(
|
|
514
|
+
plan: TargetPlan,
|
|
515
|
+
out: ReaderOutput | undefined,
|
|
516
|
+
features: readonly StyleFeature[],
|
|
517
|
+
rawNames: ReadonlySet<string>,
|
|
518
|
+
toolSet: Record<string, StyleValue> | null,
|
|
519
|
+
signal: AbortSignal | undefined,
|
|
520
|
+
): StyleRule[] {
|
|
521
|
+
const { target, n } = plan;
|
|
522
|
+
for (const [k, v] of out?.columns ?? []) {
|
|
523
|
+
plan.columns.set(k, v);
|
|
524
|
+
}
|
|
525
|
+
const truth = checkedTruth(plan, out);
|
|
526
|
+
const toolRule: Draft[] =
|
|
527
|
+
toolSet === null
|
|
528
|
+
? []
|
|
529
|
+
: [
|
|
530
|
+
{
|
|
531
|
+
id: uniqueId(plan, `${target}.tool`),
|
|
532
|
+
target,
|
|
533
|
+
role: "defaults",
|
|
534
|
+
origin: "tool",
|
|
535
|
+
where: { all: true },
|
|
536
|
+
set: toolSet,
|
|
537
|
+
},
|
|
538
|
+
];
|
|
539
|
+
const declared = prepareDeclared(plan, out?.rules ?? [], features);
|
|
540
|
+
const declaredProps = new Set(declared.flatMap(writes));
|
|
541
|
+
|
|
542
|
+
const { defaults, rest } = splitDefaults(n, truth, declaredProps);
|
|
543
|
+
plan.spend(n * truth.size);
|
|
544
|
+
const inferred: Draft[] = [];
|
|
545
|
+
if (Object.keys(defaults).length > 0) {
|
|
546
|
+
inferred.push({
|
|
547
|
+
id: uniqueId(plan, `${target}.defaults`),
|
|
548
|
+
target,
|
|
549
|
+
role: "defaults",
|
|
550
|
+
origin: "inferred",
|
|
551
|
+
where: { all: true },
|
|
552
|
+
set: defaults,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const families = familiesOf(rest);
|
|
557
|
+
plan.spend(n * rest.length);
|
|
558
|
+
const infos = features
|
|
559
|
+
.map((f) => describeFeature(f, "data" in f.ref && rawNames.has(f.ref.data)))
|
|
560
|
+
.sort((a, b) => a.distinct - b.distinct);
|
|
561
|
+
plan.spend(n * features.length);
|
|
562
|
+
const testable = infos.filter((f) => f.categorical || f.numeric).slice(0, MAX_FEATURES);
|
|
563
|
+
|
|
564
|
+
const groups: Draft[] = [];
|
|
565
|
+
const mappings = new Map<string, Draft>();
|
|
566
|
+
const passData: Draft = {
|
|
567
|
+
id: `${target}.passthrough`,
|
|
568
|
+
target,
|
|
569
|
+
role: "passthrough",
|
|
570
|
+
origin: "inferred",
|
|
571
|
+
where: { all: true },
|
|
572
|
+
map: {},
|
|
573
|
+
};
|
|
574
|
+
const passRaw: Draft = {
|
|
575
|
+
id: `${target}.passthrough.raw`,
|
|
576
|
+
target,
|
|
577
|
+
role: "passthrough",
|
|
578
|
+
origin: "inferred",
|
|
579
|
+
where: { all: true },
|
|
580
|
+
map: {},
|
|
581
|
+
facts: { reason: "NO_EXPLANATION" },
|
|
582
|
+
};
|
|
583
|
+
const copy: Draft = {
|
|
584
|
+
id: `${target}.copy`,
|
|
585
|
+
target,
|
|
586
|
+
role: "passthrough",
|
|
587
|
+
origin: "inferred",
|
|
588
|
+
where: { all: true },
|
|
589
|
+
map: {},
|
|
590
|
+
facts: { reason: "NO_EXPLANATION" },
|
|
591
|
+
};
|
|
592
|
+
const opaque: { fam: Family; raw: ReadonlyMap<string, FeatureInfo> | null }[] = [];
|
|
593
|
+
const tolerated = new Map<string, Set<number>>();
|
|
594
|
+
let budgetHit = 0;
|
|
595
|
+
|
|
596
|
+
const st: Inference = { groups, mappings, passData, passRaw, copy, opaque, tolerated };
|
|
597
|
+
for (const fam of families) {
|
|
598
|
+
signal?.throwIfAborted();
|
|
599
|
+
if (plan.budget.exhausted(target)) {
|
|
600
|
+
budgetHit++;
|
|
601
|
+
opaque.push({ fam, raw: null });
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
applyChoice(plan, st, fam, chooseCandidate(plan, fam, testable, infos, truth), truth);
|
|
605
|
+
}
|
|
606
|
+
if (budgetHit > 0) {
|
|
607
|
+
plan.notes.add(STYLE_ISSUE.BUDGET, target, null, { count: budgetHit });
|
|
608
|
+
}
|
|
609
|
+
poolOpaque(plan, st, budgetHit, truth);
|
|
610
|
+
|
|
611
|
+
const nonEmpty = (r: Draft): boolean => Object.keys(r.map ?? {}).length > 0;
|
|
612
|
+
const sortedMappings = Array.from(mappings.values()).sort((a, b) => compareText(a.id, b.id));
|
|
613
|
+
const drafts: Draft[] = [
|
|
614
|
+
...toolRule,
|
|
615
|
+
...declared,
|
|
616
|
+
...inferred,
|
|
617
|
+
...groups,
|
|
618
|
+
...sortedMappings,
|
|
619
|
+
...[passData, passRaw, copy].filter(nonEmpty),
|
|
620
|
+
];
|
|
621
|
+
for (const d of drafts.slice(toolRule.length + declared.length + inferred.length)) {
|
|
622
|
+
d.id = uniqueId(plan, d.id);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const src = refSource(target, n, plan.columns, features);
|
|
626
|
+
enforceCap(plan, drafts, truth, src);
|
|
627
|
+
const exceptions = replay(plan, drafts, truth, toolSet, tolerated, src);
|
|
628
|
+
if (exceptions !== null) {
|
|
629
|
+
drafts.push(exceptions);
|
|
630
|
+
}
|
|
631
|
+
return drafts.map(finish);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* The inferred defaults: each property non-null and equal on every element; the others are left to explain. A
|
|
636
|
+
* property a declared rule writes is the declared rule's.
|
|
637
|
+
* @param n - the element count
|
|
638
|
+
* @param truth - the values per property
|
|
639
|
+
* @param declaredProps - the properties the declared rules write
|
|
640
|
+
* @returns the defaults, and the properties left
|
|
641
|
+
*/
|
|
642
|
+
function splitDefaults(
|
|
643
|
+
n: number,
|
|
644
|
+
truth: ReadonlyMap<string, (StyleValue | null)[]>,
|
|
645
|
+
declaredProps: ReadonlySet<string>,
|
|
646
|
+
): { defaults: Record<string, StyleValue>; rest: [string, (StyleValue | null)[]][] } {
|
|
647
|
+
const defaults: Record<string, StyleValue> = {};
|
|
648
|
+
const rest: [string, (StyleValue | null)[]][] = [];
|
|
649
|
+
for (const [p, a] of truth) {
|
|
650
|
+
const first = a[0];
|
|
651
|
+
if (declaredProps.has(p)) {
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (n > 0 && first !== null && a.every((v) => v === first)) {
|
|
655
|
+
defaults[p] = first;
|
|
656
|
+
} else {
|
|
657
|
+
rest.push([p, a]);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return { defaults, rest };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** What the inference of one target collects. */
|
|
664
|
+
interface Inference {
|
|
665
|
+
readonly groups: Draft[];
|
|
666
|
+
readonly mappings: Map<string, Draft>;
|
|
667
|
+
readonly passData: Draft;
|
|
668
|
+
readonly passRaw: Draft;
|
|
669
|
+
readonly copy: Draft;
|
|
670
|
+
readonly opaque: { fam: Family; raw: ReadonlyMap<string, FeatureInfo> | null }[];
|
|
671
|
+
readonly tolerated: Map<string, Set<number>>;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* The cheapest explanation of one family: a discrete or continuous mapping over a feature, a passthrough over existing
|
|
676
|
+
* columns, or the opaque floor.
|
|
677
|
+
* @param plan - the target's state
|
|
678
|
+
* @param fam - the family
|
|
679
|
+
* @param testable - the features worth testing
|
|
680
|
+
* @param infos - every feature
|
|
681
|
+
* @param truth - the values per property
|
|
682
|
+
* @returns the chosen candidate
|
|
683
|
+
*/
|
|
684
|
+
function chooseCandidate(
|
|
685
|
+
plan: TargetPlan,
|
|
686
|
+
fam: Family,
|
|
687
|
+
testable: readonly FeatureInfo[],
|
|
688
|
+
infos: readonly FeatureInfo[],
|
|
689
|
+
truth: ReadonlyMap<string, readonly (StyleValue | null)[]>,
|
|
690
|
+
): Candidate {
|
|
691
|
+
const { target, n } = plan;
|
|
692
|
+
const cands: Candidate[] = [];
|
|
693
|
+
// text is per element by nature: a label is never explained by another column's table
|
|
694
|
+
const textual = fam.props.some((p) => STYLE_PROPERTIES[target].get(p) === "text");
|
|
695
|
+
if (!textual) {
|
|
696
|
+
for (const f of testable) {
|
|
697
|
+
const d = discreteCandidate(fam, f, n, plan.spend);
|
|
698
|
+
if (d !== null) {
|
|
699
|
+
cands.push(d);
|
|
700
|
+
}
|
|
701
|
+
const c = continuousCandidate(fam, f, truth, plan.spend);
|
|
702
|
+
if (c !== null) {
|
|
703
|
+
cands.push(c);
|
|
704
|
+
}
|
|
705
|
+
if (plan.budget.exhausted(target)) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const pass = passCandidate(plan, fam, truth, infos);
|
|
711
|
+
if (pass !== null) {
|
|
712
|
+
cands.push(pass);
|
|
713
|
+
}
|
|
714
|
+
cands.push({ type: "opaque", cost: 250 });
|
|
715
|
+
cands.sort((a, b) => a.cost - b.cost || ORDER.indexOf(a.type) - ORDER.indexOf(b.type));
|
|
716
|
+
return cands[0];
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Records a family's chosen explanation in the target's inference.
|
|
721
|
+
* @param plan - the target's state
|
|
722
|
+
* @param st - the inference so far; changed in place
|
|
723
|
+
* @param fam - the family
|
|
724
|
+
* @param c - the chosen candidate
|
|
725
|
+
* @param truth - the values per property
|
|
726
|
+
*/
|
|
727
|
+
function applyChoice(
|
|
728
|
+
plan: TargetPlan,
|
|
729
|
+
st: Inference,
|
|
730
|
+
fam: Family,
|
|
731
|
+
c: Candidate,
|
|
732
|
+
truth: ReadonlyMap<string, readonly (StyleValue | null)[]>,
|
|
733
|
+
): void {
|
|
734
|
+
if (c.type === "discrete") {
|
|
735
|
+
applyDiscrete(plan, st, fam, c, truth);
|
|
736
|
+
} else if (c.type === "continuous") {
|
|
737
|
+
applyContinuous(plan, st, fam, c, truth);
|
|
738
|
+
} else if (c.type === "passdata" && !c.raw) {
|
|
739
|
+
for (const p of fam.props) {
|
|
740
|
+
setMapping(st.passData, p, { kind: "passthrough", by: (c.cols.get(p) as FeatureInfo).feature.ref });
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
// the raw style column recovers nothing: the family joins the opaque pool, where it may become part
|
|
744
|
+
// of a style class, and otherwise reads the raw column
|
|
745
|
+
st.opaque.push({ fam, raw: c.type === "passdata" ? c.cols : null });
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* The mapping rule of a feature, made on first use; one rule holds every family the feature explains.
|
|
751
|
+
* @param plan - the target's state
|
|
752
|
+
* @param st - the inference so far; changed in place
|
|
753
|
+
* @param key - the rule's key among the mappings
|
|
754
|
+
* @param id - its id
|
|
755
|
+
* @returns the rule
|
|
756
|
+
*/
|
|
757
|
+
function mappingRule(plan: TargetPlan, st: Inference, key: string, id: string): Draft {
|
|
758
|
+
let r = st.mappings.get(key);
|
|
759
|
+
if (r === undefined) {
|
|
760
|
+
r = { id, target: plan.target, role: "mapping", origin: "inferred", where: { all: true }, map: {} };
|
|
761
|
+
st.mappings.set(key, r);
|
|
762
|
+
}
|
|
763
|
+
return r;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Makes a feature computed for the inference (a style column) a column of the styles.
|
|
768
|
+
* @param plan - the target's state
|
|
769
|
+
* @param info - the feature
|
|
770
|
+
*/
|
|
771
|
+
function materialize(plan: TargetPlan, info: FeatureInfo): void {
|
|
772
|
+
if ("style" in info.feature.ref) {
|
|
773
|
+
plan.columns.set(info.feature.ref.style, info.feature.values);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Records a discrete mapping: one table per property, and a group for the elements the feature gives no value.
|
|
779
|
+
* @param plan - the target's state
|
|
780
|
+
* @param st - the inference so far; changed in place
|
|
781
|
+
* @param fam - the family
|
|
782
|
+
* @param c - the candidate
|
|
783
|
+
* @param truth - the values per property
|
|
784
|
+
*/
|
|
785
|
+
function applyDiscrete(
|
|
786
|
+
plan: TargetPlan,
|
|
787
|
+
st: Inference,
|
|
788
|
+
fam: Family,
|
|
789
|
+
c: DiscreteCandidate,
|
|
790
|
+
truth: ReadonlyMap<string, readonly (StyleValue | null)[]>,
|
|
791
|
+
): void {
|
|
792
|
+
const { target } = plan;
|
|
793
|
+
materialize(plan, c.info);
|
|
794
|
+
const rule = mappingRule(plan, st, `discrete|${c.info.name}`, `${target}.by.${c.info.name}`);
|
|
795
|
+
// the first element of each block stands for the block
|
|
796
|
+
const rep = new Map<number, number>();
|
|
797
|
+
for (const i of fam.styled) {
|
|
798
|
+
if (!rep.has(fam.blocks[i])) {
|
|
799
|
+
rep.set(fam.blocks[i], i);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
for (const p of fam.props) {
|
|
803
|
+
const t = truth.get(p) ?? [];
|
|
804
|
+
const table = Array.from(c.table.values(), ({ raw, block }): [string | number | boolean, StyleValue] => [
|
|
805
|
+
raw,
|
|
806
|
+
t[rep.get(block) ?? -1] as StyleValue,
|
|
807
|
+
]);
|
|
808
|
+
setMapping(rule, p, { kind: "discrete", by: c.info.feature.ref, table });
|
|
809
|
+
}
|
|
810
|
+
if (c.bucket === null) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const set: Record<string, StyleValue> = {};
|
|
814
|
+
for (const p of fam.props) {
|
|
815
|
+
set[p] = (truth.get(p) ?? [])[rep.get(c.bucket) ?? -1] as StyleValue;
|
|
816
|
+
}
|
|
817
|
+
st.groups.push({
|
|
818
|
+
id: `${target}.missing.${c.info.name}`,
|
|
819
|
+
target,
|
|
820
|
+
role: "group",
|
|
821
|
+
origin: "inferred",
|
|
822
|
+
where: { not: { has: c.info.feature.ref } },
|
|
823
|
+
set,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Records a continuous mapping: one ramp per property; a ramp that matches only within the tool's rounding tolerates
|
|
829
|
+
* those elements, noted.
|
|
830
|
+
* @param plan - the target's state
|
|
831
|
+
* @param st - the inference so far; changed in place
|
|
832
|
+
* @param fam - the family
|
|
833
|
+
* @param c - the candidate
|
|
834
|
+
* @param truth - the values per property
|
|
835
|
+
*/
|
|
836
|
+
function applyContinuous(
|
|
837
|
+
plan: TargetPlan,
|
|
838
|
+
st: Inference,
|
|
839
|
+
fam: Family,
|
|
840
|
+
c: ContinuousCandidate,
|
|
841
|
+
truth: ReadonlyMap<string, readonly (StyleValue | null)[]>,
|
|
842
|
+
): void {
|
|
843
|
+
const { target } = plan;
|
|
844
|
+
materialize(plan, c.info);
|
|
845
|
+
const rule = mappingRule(plan, st, `continuous|${c.info.name}`, `${target}.ramp.${c.info.name}`);
|
|
846
|
+
for (const p of fam.props) {
|
|
847
|
+
const points = c.points.get(p) ?? [];
|
|
848
|
+
setMapping(rule, p, { kind: "continuous", by: c.info.feature.ref, points });
|
|
849
|
+
if (!c.tolerant) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const t = truth.get(p) ?? [];
|
|
853
|
+
const set = new Set<number>();
|
|
854
|
+
for (const i of fam.styled) {
|
|
855
|
+
if (evalContinuous(points, numberOf(c.info.feature.values[i])) !== t[i]) {
|
|
856
|
+
set.add(i);
|
|
857
|
+
plan.notes.add(STYLE_ISSUE.RAMP_ROUNDED, target, p, { element: i, sample: t[i] });
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
st.tolerated.set(p, set);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* The opaque pool: style classes when the file uses few combinations, else one copy per property.
|
|
866
|
+
* @param plan - the target's state
|
|
867
|
+
* @param st - the inference so far; changed in place
|
|
868
|
+
* @param budgetHit - how many families the budget sent to the pool
|
|
869
|
+
* @param truth - the values per property
|
|
870
|
+
*/
|
|
871
|
+
function poolOpaque(
|
|
872
|
+
plan: TargetPlan,
|
|
873
|
+
st: Inference,
|
|
874
|
+
budgetHit: number,
|
|
875
|
+
truth: ReadonlyMap<string, readonly (StyleValue | null)[]>,
|
|
876
|
+
): void {
|
|
877
|
+
const { target, n } = plan;
|
|
878
|
+
if (st.opaque.length === 0) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const props = st.opaque.flatMap((o) => o.fam.props);
|
|
882
|
+
const classes = budgetHit > 0 ? null : styleClasses(target, props, truth, n, plan.spend);
|
|
883
|
+
const classed = new Set(classes?.classed ?? []);
|
|
884
|
+
if (classes !== null) {
|
|
885
|
+
addClassRules(plan, st, classes);
|
|
886
|
+
}
|
|
887
|
+
let distinct = 0;
|
|
888
|
+
for (const { fam, raw } of st.opaque) {
|
|
889
|
+
for (const p of fam.props.filter((x) => !classed.has(x))) {
|
|
890
|
+
const viaRaw = raw?.get(p);
|
|
891
|
+
if (viaRaw === undefined) {
|
|
892
|
+
distinct = Math.max(distinct, copyProperty(plan, st.copy, p, truth.get(p) ?? []));
|
|
893
|
+
} else {
|
|
894
|
+
setMapping(st.passRaw, p, { kind: "passthrough", by: viaRaw.feature.ref });
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
st.copy.facts = { reason: budgetHit > 0 ? "BUDGET" : "NO_EXPLANATION", distinctValues: distinct };
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Adds the style classes' column and rules.
|
|
903
|
+
* @param plan - the target's state
|
|
904
|
+
* @param st - the inference so far; changed in place
|
|
905
|
+
* @param classes - the style classes
|
|
906
|
+
*/
|
|
907
|
+
function addClassRules(plan: TargetPlan, st: Inference, classes: NonNullable<ReturnType<typeof styleClasses>>): void {
|
|
908
|
+
plan.columns.set(CLASS_COLUMN, classes.column);
|
|
909
|
+
for (const r of classes.rules) {
|
|
910
|
+
if (r.role === "group") {
|
|
911
|
+
st.groups.push({ ...r, id: r.id, set: { ...r.set } } as Draft);
|
|
912
|
+
} else {
|
|
913
|
+
st.mappings.set(r.id, { ...r, map: { ...r.map } } as Draft);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Copies a property's values into a style column the copy rule passes through.
|
|
920
|
+
* @param plan - the target's state
|
|
921
|
+
* @param copy - the copy rule, changed in place
|
|
922
|
+
* @param p - the property
|
|
923
|
+
* @param t - its values
|
|
924
|
+
* @returns how many distinct values it holds
|
|
925
|
+
*/
|
|
926
|
+
function copyProperty(plan: TargetPlan, copy: Draft, p: string, t: readonly (StyleValue | null)[]): number {
|
|
927
|
+
plan.columns.set(`copy.${p}`, t.slice());
|
|
928
|
+
setMapping(copy, p, { kind: "passthrough", by: { style: `copy.${p}` } });
|
|
929
|
+
plan.spend(plan.n);
|
|
930
|
+
return new Set(t.filter((v) => v !== null)).size;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* The rule as a plain JSON-stable object.
|
|
935
|
+
* @param d - the draft
|
|
936
|
+
* @returns the rule
|
|
937
|
+
*/
|
|
938
|
+
function finish(d: Draft): StyleRule {
|
|
939
|
+
const r: Record<string, unknown> = { id: d.id, target: d.target, role: d.role, origin: d.origin, where: d.where };
|
|
940
|
+
if (d.set !== undefined && Object.keys(d.set).length > 0) {
|
|
941
|
+
r.set = d.set;
|
|
942
|
+
}
|
|
943
|
+
if (d.map !== undefined && Object.keys(d.map).length > 0) {
|
|
944
|
+
r.map = d.map;
|
|
945
|
+
}
|
|
946
|
+
if (d.facts !== undefined) {
|
|
947
|
+
r.facts = Object.fromEntries(Object.entries(d.facts).filter(([, v]) => v !== undefined));
|
|
948
|
+
}
|
|
949
|
+
if (d.ext !== undefined) {
|
|
950
|
+
r.ext = d.ext;
|
|
951
|
+
}
|
|
952
|
+
return r as unknown as StyleRule;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Compares two texts by code unit, the same everywhere.
|
|
957
|
+
* @param a - one text
|
|
958
|
+
* @param b - the other
|
|
959
|
+
* @returns negative, zero or positive
|
|
960
|
+
*/
|
|
961
|
+
function compareText(a: string, b: string): number {
|
|
962
|
+
if (a === b) {
|
|
963
|
+
return 0;
|
|
964
|
+
}
|
|
965
|
+
return a < b ? -1 : 1;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* The entries a person would edit in a rule.
|
|
970
|
+
* @param r - the rule
|
|
971
|
+
* @returns its size
|
|
972
|
+
*/
|
|
973
|
+
function ruleSize(r: Draft): number {
|
|
974
|
+
let size = Object.keys(r.set ?? {}).length;
|
|
975
|
+
for (const m of Object.values(r.map ?? {})) {
|
|
976
|
+
if (m.kind === "discrete") {
|
|
977
|
+
size += m.table.length;
|
|
978
|
+
} else if (m.kind === "continuous") {
|
|
979
|
+
size += m.points.length;
|
|
980
|
+
} else {
|
|
981
|
+
size += 1;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return size;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/** The order in which rules are folded into the copy: inferred first, the tool's defaults last. */
|
|
988
|
+
const FOLD_ORDER: Readonly<Record<string, number>> = { inferred: 0, declared: 1, tool: 2 };
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Keeps at most 15 rules (the exceptions rule makes 16) by moving the least readable rules into the copy, with each
|
|
992
|
+
* property's values as they resolved: exact, never a silent delete.
|
|
993
|
+
* @param plan - the target's state
|
|
994
|
+
* @param drafts - the rules, bottom to top; changed in place
|
|
995
|
+
* @param truth - the file's values per property
|
|
996
|
+
* @param src - the reference source
|
|
997
|
+
*/
|
|
998
|
+
function enforceCap(
|
|
999
|
+
plan: TargetPlan,
|
|
1000
|
+
drafts: Draft[],
|
|
1001
|
+
truth: ReadonlyMap<string, readonly (StyleValue | null)[]>,
|
|
1002
|
+
src: ReturnType<typeof refSource>,
|
|
1003
|
+
): void {
|
|
1004
|
+
const cap = MAX_STYLE_RULES - 1;
|
|
1005
|
+
const copyId = `${plan.target}.copy`;
|
|
1006
|
+
let folded = 0;
|
|
1007
|
+
while (drafts.length > cap) {
|
|
1008
|
+
const cands = drafts.filter((r) => r.id !== copyId);
|
|
1009
|
+
if (cands.length === 0) {
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
cands.sort((a, b) => {
|
|
1013
|
+
const pa = a.role === "passthrough" ? 1 : 0;
|
|
1014
|
+
const pb = b.role === "passthrough" ? 1 : 0;
|
|
1015
|
+
return (
|
|
1016
|
+
pa - pb ||
|
|
1017
|
+
(FOLD_ORDER[a.origin] ?? 1) - (FOLD_ORDER[b.origin] ?? 1) ||
|
|
1018
|
+
ruleSize(b) - ruleSize(a) ||
|
|
1019
|
+
drafts.indexOf(b) - drafts.indexOf(a)
|
|
1020
|
+
);
|
|
1021
|
+
});
|
|
1022
|
+
const victim = cands[0];
|
|
1023
|
+
const before = drafts.map(finish);
|
|
1024
|
+
drafts.splice(drafts.indexOf(victim), 1);
|
|
1025
|
+
let copy = drafts.find((r) => r.id === copyId);
|
|
1026
|
+
if (copy === undefined) {
|
|
1027
|
+
copy = {
|
|
1028
|
+
id: copyId,
|
|
1029
|
+
target: plan.target,
|
|
1030
|
+
role: "passthrough",
|
|
1031
|
+
origin: "inferred",
|
|
1032
|
+
where: { all: true },
|
|
1033
|
+
map: {},
|
|
1034
|
+
};
|
|
1035
|
+
plan.ids.add(copyId);
|
|
1036
|
+
} else {
|
|
1037
|
+
drafts.splice(drafts.indexOf(copy), 1);
|
|
1038
|
+
}
|
|
1039
|
+
for (const p of new Set(writes(victim))) {
|
|
1040
|
+
// the property's values as the whole stack resolved them, supplied on top for every element
|
|
1041
|
+
const t = truth.get(p);
|
|
1042
|
+
const vals = t === undefined ? resolveRules(before, plan.target, p, src, plan.budget) : t.slice();
|
|
1043
|
+
plan.columns.set(`copy.${p}`, vals);
|
|
1044
|
+
setMapping(copy, p, { kind: "passthrough", by: { style: `copy.${p}` } });
|
|
1045
|
+
}
|
|
1046
|
+
copy.facts = { ...copy.facts, reason: "LAYER_CAP" };
|
|
1047
|
+
drafts.push(copy);
|
|
1048
|
+
folded++;
|
|
1049
|
+
}
|
|
1050
|
+
if (folded > 0) {
|
|
1051
|
+
plan.notes.add(STYLE_ISSUE.LAYER_CAP, plan.target, null, { count: folded });
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* The elements whose resolved value is not the one wanted, with the wanted value; none where nothing is wanted, and
|
|
1057
|
+
* none where a ramp is tolerated within the tool's rounding.
|
|
1058
|
+
* @param n - the element count
|
|
1059
|
+
* @param got - the resolved values
|
|
1060
|
+
* @param want - the value wanted for an element, or null for none
|
|
1061
|
+
* @param tolerated - the elements a ramp matches only within the tool's rounding
|
|
1062
|
+
* @returns the wanted values where they differ, or null when none does
|
|
1063
|
+
*/
|
|
1064
|
+
function differences(
|
|
1065
|
+
n: number,
|
|
1066
|
+
got: readonly (StyleValue | null)[],
|
|
1067
|
+
want: (i: number) => StyleValue | null,
|
|
1068
|
+
tolerated: ReadonlySet<number> | undefined,
|
|
1069
|
+
): (StyleValue | null)[] | null {
|
|
1070
|
+
let col: (StyleValue | null)[] | null = null;
|
|
1071
|
+
for (let i = 0; i < n; i++) {
|
|
1072
|
+
const w = want(i);
|
|
1073
|
+
const differs = w !== null && got[i] !== w && !(tolerated?.has(i) === true && within(got[i], w));
|
|
1074
|
+
if (differs) {
|
|
1075
|
+
col ??= new Array<StyleValue | null>(n).fill(null);
|
|
1076
|
+
col[i] = w;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return col;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* The replay: resolves every property the file gives and compares it with the file's value, or the tool's default
|
|
1084
|
+
* where the file gives none. Each difference goes into one sparse exceptions column per property.
|
|
1085
|
+
* @param plan - the target's state
|
|
1086
|
+
* @param drafts - the rules, bottom to top
|
|
1087
|
+
* @param truth - the file's values per property
|
|
1088
|
+
* @param toolSet - the tool's default values, or null
|
|
1089
|
+
* @param tolerated - per property, the elements a ramp matches only within the tool's rounding
|
|
1090
|
+
* @param src - the reference source
|
|
1091
|
+
* @returns the exceptions rule, or null when nothing differs
|
|
1092
|
+
*/
|
|
1093
|
+
function replay(
|
|
1094
|
+
plan: TargetPlan,
|
|
1095
|
+
drafts: readonly Draft[],
|
|
1096
|
+
truth: ReadonlyMap<string, readonly (StyleValue | null)[]>,
|
|
1097
|
+
toolSet: Record<string, StyleValue> | null,
|
|
1098
|
+
tolerated: ReadonlyMap<string, ReadonlySet<number>>,
|
|
1099
|
+
src: ReturnType<typeof refSource>,
|
|
1100
|
+
): Draft | null {
|
|
1101
|
+
const rules = drafts.map(finish);
|
|
1102
|
+
// where the file gives no value, the tool draws its default: its constants, and where a reader adds tool rules
|
|
1103
|
+
// of its own (Graphviz's target arrow on directed edges), what those rules give over the constants
|
|
1104
|
+
const toolRules = rules.filter((r) => r.origin === "tool");
|
|
1105
|
+
const ownToolRules = toolRules.length > (toolSet === null ? 0 : 1);
|
|
1106
|
+
const map: Record<string, Mapping> = {};
|
|
1107
|
+
for (const [p, t] of truth) {
|
|
1108
|
+
const got = resolveRules(rules, plan.target, p, src, plan.budget);
|
|
1109
|
+
const perElement = ownToolRules ? resolveRules(toolRules, plan.target, p, src, plan.budget) : null;
|
|
1110
|
+
const constant = toolSet?.[p] ?? null;
|
|
1111
|
+
const want = (i: number): StyleValue | null => t[i] ?? (perElement === null ? constant : perElement[i]);
|
|
1112
|
+
const col = differences(plan.n, got, want, tolerated.get(p));
|
|
1113
|
+
if (col !== null) {
|
|
1114
|
+
plan.columns.set(`exceptions.${p}`, col);
|
|
1115
|
+
map[p] = { kind: "passthrough", by: { style: `exceptions.${p}` } };
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (Object.keys(map).length === 0) {
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
return {
|
|
1122
|
+
id: uniqueId(plan, `${plan.target}.exceptions`),
|
|
1123
|
+
target: plan.target,
|
|
1124
|
+
role: "exceptions",
|
|
1125
|
+
origin: "inferred",
|
|
1126
|
+
where: { all: true },
|
|
1127
|
+
map,
|
|
1128
|
+
};
|
|
1129
|
+
}
|