@diagrammo/dgmo 0.46.0 → 0.48.0
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/advanced.d.ts +3 -855
- package/dist/advanced.js +541 -70107
- package/dist/analyzer-UYKBXAKR.js +15 -0
- package/dist/auto.js +1 -1
- package/dist/auto.mjs +1 -1
- package/dist/block.js +83 -69417
- package/dist/calculator-LDYQCDDY.js +11 -0
- package/dist/charts-d3-QOZHKTCT.js +25 -0
- package/dist/chunk-2DXMZRWJ.js +800 -0
- package/dist/chunk-2LACINWN.js +786 -0
- package/dist/chunk-33LCUH4H.js +893 -0
- package/dist/chunk-3CIOJ5YJ.js +35 -0
- package/dist/chunk-3HDBAXF5.js +86 -0
- package/dist/chunk-3JLKYGBQ.js +290 -0
- package/dist/chunk-3MQQNXPD.js +415 -0
- package/dist/chunk-3R5POCTZ.js +967 -0
- package/dist/chunk-3SMGIJDD.js +112 -0
- package/dist/chunk-44CM72SQ.js +409 -0
- package/dist/chunk-4GRI7MEB.js +1604 -0
- package/dist/chunk-4OHACBGX.js +606 -0
- package/dist/chunk-5DH3KYFH.js +21 -0
- package/dist/chunk-5MLYVTYM.js +76 -0
- package/dist/chunk-5QXL4NYS.js +553 -0
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-6EK55ART.js +117 -0
- package/dist/chunk-6FZYCKXB.js +2065 -0
- package/dist/chunk-6HO5ZTXK.js +277 -0
- package/dist/chunk-6KFWC4CO.js +7 -0
- package/dist/chunk-6MTWIQVO.js +121 -0
- package/dist/chunk-6NYWYNBI.js +329 -0
- package/dist/chunk-6SM5NJQH.js +620 -0
- package/dist/chunk-725LDASD.js +422 -0
- package/dist/chunk-76OWZ3BA.js +130 -0
- package/{src/block/parser.ts → dist/chunk-7JEGTRLD.js} +100 -189
- package/dist/chunk-A44USUV7.js +923 -0
- package/dist/chunk-A4GOY3PE.js +25 -0
- package/dist/chunk-A4VFQH5G.js +251 -0
- package/dist/chunk-AELOBFW5.js +375 -0
- package/dist/chunk-AN2MOO5T.js +522 -0
- package/dist/chunk-BMR4CIEO.js +41 -0
- package/dist/chunk-BS7SV7C3.js +51 -0
- package/dist/chunk-BUPDIHML.js +613 -0
- package/dist/chunk-BUVJFRPY.js +887 -0
- package/dist/chunk-C3Z4DX3V.js +112 -0
- package/dist/chunk-CI2G3OEB.js +1818 -0
- package/dist/chunk-CKNVO24D.js +54 -0
- package/dist/chunk-CUVHQHZ7.js +391 -0
- package/dist/chunk-CXK4DZH3.js +114 -0
- package/dist/{editor.cjs → chunk-DKEPS5LH.js} +14 -225
- package/dist/chunk-DU7W62N5.js +487 -0
- package/dist/chunk-DZAQBW35.js +751 -0
- package/dist/chunk-EB6FPQUP.js +316 -0
- package/dist/chunk-EMS2SK26.js +376 -0
- package/dist/chunk-ERLBPDGU.js +510 -0
- package/dist/chunk-EVCVDTXE.js +732 -0
- package/dist/chunk-FCIIHT24.js +384 -0
- package/dist/chunk-FFUN5D66.js +97 -0
- package/dist/chunk-FITOLXSX.js +714 -0
- package/dist/chunk-FMDSF3OV.js +68 -0
- package/dist/chunk-FNB3PNGF.js +410 -0
- package/dist/chunk-G2H74XJZ.js +2104 -0
- package/dist/chunk-GMR5MFHJ.js +339 -0
- package/dist/chunk-H6PI3KJT.js +305 -0
- package/dist/chunk-I46X5VUA.js +59 -0
- package/dist/chunk-I5IGB56C.js +255 -0
- package/dist/chunk-IH6ZMVQ5.js +3950 -0
- package/dist/chunk-IJZFN3HM.js +463 -0
- package/dist/chunk-IPWPWM6R.js +3079 -0
- package/dist/chunk-IYULMRBE.js +23 -0
- package/dist/chunk-IZMRON6C.js +167 -0
- package/dist/chunk-J5FIXQRT.js +813 -0
- package/dist/chunk-JED6ZDEE.js +73 -0
- package/dist/chunk-JEUJOX5X.js +136 -0
- package/dist/chunk-JM4LEPY3.js +436 -0
- package/dist/chunk-L3UHP6TD.js +101 -0
- package/dist/chunk-L66GEFB6.js +524 -0
- package/dist/chunk-LIOHW36Z.js +469 -0
- package/dist/chunk-LP7EC4MI.js +16 -0
- package/dist/chunk-MITI6NOL.js +548 -0
- package/dist/chunk-MO5YHHMQ.js +168 -0
- package/dist/chunk-MZSBFAPJ.js +885 -0
- package/dist/chunk-NNAAJVBU.js +9 -0
- package/dist/chunk-NOTA6TEI.js +47 -0
- package/dist/chunk-NURGB5Z4.js +335 -0
- package/dist/chunk-NX4EQS3U.js +1062 -0
- package/dist/chunk-NXMESY4J.js +1 -0
- package/dist/chunk-OJR3MQYV.js +1458 -0
- package/dist/chunk-OURHMODR.js +454 -0
- package/dist/chunk-P235SVAH.js +1057 -0
- package/dist/chunk-P4RH4OSP.js +1038 -0
- package/dist/chunk-P6T66CCZ.js +226 -0
- package/dist/chunk-PBAHEPLQ.js +223 -0
- package/dist/chunk-PBHE5UG6.js +1530 -0
- package/dist/chunk-PDKUANDZ.js +35 -0
- package/dist/chunk-PWIHZ6NJ.js +296 -0
- package/{src/c4/layout.ts → dist/chunk-QEBUY5NY.js} +384 -1140
- package/dist/chunk-QLCCJEJI.js +253 -0
- package/dist/chunk-QOOUC2J5.js +52 -0
- package/dist/chunk-QYTPPDAO.js +402 -0
- package/dist/chunk-R3XI2VC7.js +801 -0
- package/dist/chunk-RCERWOJR.js +154 -0
- package/dist/chunk-RJE5JTVK.js +362 -0
- package/dist/chunk-RTLBSQ3G.js +1155 -0
- package/dist/chunk-RUNNI6VE.js +448 -0
- package/dist/chunk-RVMRZAPB.js +354 -0
- package/dist/chunk-RYEGFMUO.js +955 -0
- package/dist/chunk-RZCF2VVM.js +528 -0
- package/dist/chunk-SML67VPV.js +1025 -0
- package/dist/chunk-SQ7IY53Q.js +44 -0
- package/dist/chunk-SQAG3UXH.js +161 -0
- package/dist/chunk-SRTU6HMQ.js +1851 -0
- package/dist/chunk-T2OE3YQB.js +550 -0
- package/dist/chunk-T3UBLACL.js +299 -0
- package/dist/chunk-TLVIHFRO.js +296 -0
- package/dist/chunk-TYS6CSS5.js +52 -0
- package/dist/chunk-U6EQI3U6.js +130 -0
- package/dist/chunk-UJ4FJLBF.js +86 -0
- package/dist/chunk-ULOEATBZ.js +347 -0
- package/dist/chunk-UNCCZ3NU.js +127 -0
- package/dist/chunk-USZW2HHU.js +197 -0
- package/dist/chunk-UWN33VNX.js +1610 -0
- package/dist/chunk-UZ7EZW3B.js +40 -0
- package/dist/chunk-VOKGH627.js +37 -0
- package/dist/chunk-WBRZJOEC.js +204 -0
- package/dist/chunk-WCSOJFU3.js +380 -0
- package/dist/chunk-WCT3NYCV.js +160 -0
- package/dist/chunk-WVAJFAIA.js +529 -0
- package/dist/chunk-X5LCYCL5.js +394 -0
- package/dist/chunk-XAKEUMEJ.js +1256 -0
- package/dist/chunk-Y2ZE3MSG.js +590 -0
- package/dist/chunk-Y3J7VR52.js +646 -0
- package/dist/chunk-Y5QGVTVC.js +33 -0
- package/dist/chunk-YAA6IID3.js +548 -0
- package/dist/chunk-YGJTDXVR.js +692 -0
- package/{src/utils/time-ticks.ts → dist/chunk-YRYKTYMQ.js} +45 -112
- package/dist/chunk-YYVTBZZC.js +61 -0
- package/dist/chunk-Z5CGSFJB.js +647 -0
- package/{src/gantt/calculator.ts → dist/chunk-Z72XRXD4.js} +236 -428
- package/dist/chunk-ZGR7ULFJ.js +978 -0
- package/dist/chunk-ZJEL4H2R.js +851 -0
- package/dist/chunk-ZO27FGJS.js +554 -0
- package/dist/collapse-CLPJD2RL.js +11 -0
- package/dist/collapse-SLINJQW6.js +9 -0
- package/dist/collapse-ZWUFFCK2.js +9 -0
- package/dist/compute-G55RG53N.js +8 -0
- package/dist/dgmo-router-DZCIDUO5.js +73 -0
- package/dist/dimensions-J3FHIMJ6.js +22 -0
- package/dist/element.js +1 -1
- package/dist/element.mjs +1 -1
- package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
- package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
- package/dist/index.d.ts +3 -469
- package/dist/index.js +137 -69685
- package/dist/layout-46N4JJYK.js +17 -0
- package/dist/layout-5RA6QWR6.js +26 -0
- package/dist/layout-5T6RH5M6.js +14 -0
- package/dist/layout-7QXCNOVS.js +13 -0
- package/dist/layout-EE6GE46Y.js +17 -0
- package/dist/layout-G23F7LDH.js +9 -0
- package/dist/layout-HRBZPFCN.js +23 -0
- package/dist/layout-HWDSRX5E.js +20 -0
- package/dist/layout-HYZPWAK5.js +18 -0
- package/dist/layout-N76NR4ZO.js +19 -0
- package/dist/layout-OB3RI63S.js +14 -0
- package/dist/layout-S2QBYSK2.js +15 -0
- package/dist/layout-UM5F4DJB.js +15 -0
- package/dist/layout-search-OYV77HHS.js +1655 -0
- package/dist/load-data-EHDDZ5ZS.js +8 -0
- package/dist/palettes-PRJ2RI6D.js +56 -0
- package/dist/parser-2MFP2H2E.js +11 -0
- package/dist/parser-3STE62GB.js +21 -0
- package/dist/parser-4FL2CMDG.js +18 -0
- package/dist/parser-4OGOBJJ4.js +13 -0
- package/dist/parser-6BTRLKRZ.js +15 -0
- package/dist/parser-AQETEVRW.js +21 -0
- package/dist/parser-CFZD5IBL.js +12 -0
- package/dist/parser-FUSKFGZN.js +16 -0
- package/dist/parser-I6KXEYFK.js +13 -0
- package/dist/parser-IXSZZQHY.js +18 -0
- package/dist/parser-JAL2HD4I.js +13 -0
- package/dist/parser-KNSQSKQO.js +20 -0
- package/dist/parser-N5O4IHUN.js +13 -0
- package/dist/parser-NGU3ETO6.js +16 -0
- package/dist/parser-NRAI6O25.js +19 -0
- package/dist/parser-O5J4VBII.js +11 -0
- package/dist/parser-OV7NOBW6.js +21 -0
- package/dist/parser-PQ3POX45.js +23 -0
- package/dist/parser-TFZFBVWN.js +11 -0
- package/dist/parser-TRTHDFPP.js +18 -0
- package/dist/parser-VQP2UJ6W.js +11 -0
- package/dist/parser-XDVELHRK.js +13 -0
- package/dist/parser-XKRP7QKP.js +11 -0
- package/dist/parser-Y6WTBU76.js +14 -0
- package/dist/parser-ZSDPZYQP.js +12 -0
- package/dist/renderer-2XQQQ6QR.js +28 -0
- package/dist/renderer-5NDVNDAZ.js +26 -0
- package/dist/renderer-6EEZHMH4.js +26 -0
- package/dist/renderer-6W5462RB.js +43 -0
- package/dist/renderer-7MVLWWQD.js +22 -0
- package/dist/renderer-AWWAF6XD.js +33 -0
- package/dist/renderer-BYCNMBF7.js +34 -0
- package/dist/renderer-CLJHQPBJ.js +29 -0
- package/dist/renderer-GHPZMFU2.js +15 -0
- package/dist/renderer-JGBKUCZD.js +16 -0
- package/dist/renderer-KQB7UGDS.js +19 -0
- package/dist/renderer-PGX2TKVS.js +24 -0
- package/dist/renderer-QIWVHWZO.js +15 -0
- package/dist/renderer-RPOA5YPZ.js +35 -0
- package/dist/renderer-SMIBCRVB.js +24 -0
- package/dist/renderer-T7YKC5VP.js +28 -0
- package/dist/renderer-TOCOVLHS.js +37 -0
- package/dist/renderer-UWA5XS74.js +39 -0
- package/dist/renderer-VACVQYML.js +25 -0
- package/dist/renderer-W4M6QLIJ.js +37 -0
- package/dist/renderer-WZRREQ2P.js +23 -0
- package/dist/renderer-YY2OSF6E.js +27 -0
- package/dist/renderer-Z7I3W2PP.js +24 -0
- package/dist/renderer-ZGNTUWC4.js +22 -0
- package/dist/renderer-ZTR3DER3.js +19 -0
- package/dist/renderer-radial-C32GPJQ7.js +21 -0
- package/dist/resolver-NOAA7ID7.js +13 -0
- package/dist/state-collapse-HCGD6IVJ.js +8 -0
- package/dist/state-parser-Z2V23ATR.js +18 -0
- package/dist/state-renderer-RFYHSP4K.js +34 -0
- package/dist/themes-1-CuKpeH.d.ts +908 -0
- package/package.json +19 -70
- package/dist/advanced.cjs +0 -71648
- package/dist/advanced.d.cts +0 -6571
- package/dist/auto.cjs +0 -70648
- package/dist/auto.d.cts +0 -54
- package/dist/block.cjs +0 -70208
- package/dist/block.d.cts +0 -115
- package/dist/editor.d.cts +0 -26
- package/dist/element.cjs +0 -70530
- package/dist/element.d.cts +0 -62
- package/dist/highlight.cjs +0 -852
- package/dist/highlight.d.cts +0 -49
- package/dist/index.cjs +0 -70491
- package/dist/index.d.cts +0 -626
- package/dist/pert.cjs +0 -325
- package/dist/pert.d.cts +0 -559
- package/src/advanced.ts +0 -873
- package/src/arc/parser.ts +0 -15
- package/src/arc/renderer.ts +0 -633
- package/src/auto/index.ts +0 -621
- package/src/auto/shared.ts +0 -370
- package/src/auto/styles.ts +0 -73
- package/src/block/layout.ts +0 -198
- package/src/block/renderer.ts +0 -484
- package/src/block/types.ts +0 -67
- package/src/boxes-and-lines/collapse.ts +0 -96
- package/src/boxes-and-lines/focus.ts +0 -257
- package/src/boxes-and-lines/label-placement.ts +0 -339
- package/src/boxes-and-lines/layout-grouped.ts +0 -912
- package/src/boxes-and-lines/layout-layered.ts +0 -722
- package/src/boxes-and-lines/layout-search.ts +0 -1678
- package/src/boxes-and-lines/layout.ts +0 -398
- package/src/boxes-and-lines/parser.ts +0 -1181
- package/src/boxes-and-lines/renderer.ts +0 -1465
- package/src/boxes-and-lines/types.ts +0 -66
- package/src/c4/parser.ts +0 -1158
- package/src/c4/renderer.ts +0 -2008
- package/src/c4/types.ts +0 -89
- package/src/chart-type-registry.ts +0 -529
- package/src/chart-types.ts +0 -225
- package/src/chart.ts +0 -685
- package/src/charts-d3/bar.ts +0 -338
- package/src/charts-d3/chord.ts +0 -140
- package/src/charts-d3/function.ts +0 -147
- package/src/charts-d3/funnel.ts +0 -104
- package/src/charts-d3/heatmap.ts +0 -145
- package/src/charts-d3/index.ts +0 -337
- package/src/charts-d3/interactions.ts +0 -477
- package/src/charts-d3/line.ts +0 -344
- package/src/charts-d3/mount.ts +0 -151
- package/src/charts-d3/pie.ts +0 -117
- package/src/charts-d3/polar.ts +0 -89
- package/src/charts-d3/radar.ts +0 -0
- package/src/charts-d3/sankey.ts +0 -0
- package/src/charts-d3/scatter.ts +0 -185
- package/src/charts-d3/shared.ts +0 -208
- package/src/class/layout.ts +0 -326
- package/src/class/parser.ts +0 -635
- package/src/class/renderer.ts +0 -793
- package/src/class/types.ts +0 -58
- package/src/cli-banner.ts +0 -106
- package/src/cli.ts +0 -1080
- package/src/colors-diagnostics.ts +0 -35
- package/src/colors.ts +0 -379
- package/src/completion-types.ts +0 -27
- package/src/completion.ts +0 -2668
- package/src/cycle/layout.ts +0 -1026
- package/src/cycle/parser.ts +0 -387
- package/src/cycle/renderer.ts +0 -653
- package/src/cycle/types.ts +0 -101
- package/src/d3.ts +0 -1434
- package/src/data-chart-parser.ts +0 -1007
- package/src/dgmo-router.ts +0 -296
- package/src/diagnostics-registry.ts +0 -108
- package/src/diagnostics.ts +0 -0
- package/src/dimensions.ts +0 -20
- package/src/directives-registry.ts +0 -396
- package/src/editor/dgmo.grammar +0 -76
- package/src/editor/dgmo.grammar.d.ts +0 -2
- package/src/editor/dgmo.grammar.js +0 -18
- package/src/editor/dgmo.grammar.terms.d.ts +0 -5
- package/src/editor/dgmo.grammar.terms.js +0 -35
- package/src/editor/highlight-api.ts +0 -596
- package/src/editor/highlight.ts +0 -36
- package/src/editor/index.ts +0 -27
- package/src/editor/keywords.ts +0 -128
- package/src/editor/tokens.ts +0 -30
- package/src/element/index.ts +0 -432
- package/src/embed/css.ts +0 -312
- package/src/embed/escape.ts +0 -15
- package/src/embed/index.ts +0 -280
- package/src/er/classify.ts +0 -228
- package/src/er/layout.ts +0 -530
- package/src/er/parser.ts +0 -750
- package/src/er/renderer.ts +0 -725
- package/src/er/types.ts +0 -52
- package/src/error-card.ts +0 -269
- package/src/event-line/diagnostics.ts +0 -117
- package/src/event-line/parser.ts +0 -643
- package/src/event-line/renderer.ts +0 -1742
- package/src/event-line/types.ts +0 -74
- package/src/fonts.ts +0 -3
- package/src/gantt/parser.ts +0 -1641
- package/src/gantt/renderer.ts +0 -3787
- package/src/gantt/resolver.ts +0 -172
- package/src/gantt/types.ts +0 -206
- package/src/graph/diagnostics.ts +0 -64
- package/src/graph/edge-spline.ts +0 -29
- package/src/graph/flowchart-parser.ts +0 -759
- package/src/graph/flowchart-renderer.ts +0 -817
- package/src/graph/layout.ts +0 -472
- package/src/graph/notes.ts +0 -21
- package/src/graph/state-collapse.ts +0 -78
- package/src/graph/state-parser.ts +0 -574
- package/src/graph/state-renderer.ts +0 -653
- package/src/graph/types.ts +0 -65
- package/src/index.ts +0 -261
- package/src/infra/compute.ts +0 -1385
- package/src/infra/layout.ts +0 -888
- package/src/infra/parser.ts +0 -1234
- package/src/infra/renderer.ts +0 -2534
- package/src/infra/roles.ts +0 -75
- package/src/infra/types.ts +0 -216
- package/src/infra/validation.ts +0 -201
- package/src/journey-map/layout.ts +0 -490
- package/src/journey-map/parser.ts +0 -557
- package/src/journey-map/renderer.ts +0 -1580
- package/src/journey-map/types.ts +0 -47
- package/src/kanban/mutations.ts +0 -175
- package/src/kanban/parser.ts +0 -509
- package/src/kanban/renderer.ts +0 -1449
- package/src/kanban/types.ts +0 -39
- package/src/label-layout.ts +0 -368
- package/src/map/colorize.ts +0 -54
- package/src/map/completion.ts +0 -316
- package/src/map/context-labels.ts +0 -609
- package/src/map/data/PROVENANCE.json +0 -1
- package/src/map/data/README.md +0 -40
- package/src/map/data/airport-collisions.json +0 -1
- package/src/map/data/airports.json +0 -1
- package/src/map/data/gazetteer.json +0 -1
- package/src/map/data/lakes.json +0 -1
- package/src/map/data/mountain-ranges.json +0 -1
- package/src/map/data/na-lakes.json +0 -1
- package/src/map/data/na-land.json +0 -1
- package/src/map/data/region-names.json +0 -1
- package/src/map/data/rivers.json +0 -1
- package/src/map/data/types.ts +0 -121
- package/src/map/data/us-states.json +0 -1
- package/src/map/data/water-bodies.json +0 -1
- package/src/map/data/world-coarse.json +0 -1
- package/src/map/data/world-detail.json +0 -1
- package/src/map/diagnostics.ts +0 -134
- package/src/map/dimensions.ts +0 -150
- package/src/map/geo-query.ts +0 -295
- package/src/map/geo.ts +0 -438
- package/src/map/invert.ts +0 -111
- package/src/map/layout.ts +0 -5069
- package/src/map/legend-band.ts +0 -127
- package/src/map/load-data.ts +0 -186
- package/src/map/parser.ts +0 -783
- package/src/map/renderer.ts +0 -1445
- package/src/map/resolved-types.ts +0 -160
- package/src/map/resolver.ts +0 -1146
- package/src/map/types.ts +0 -194
- package/src/mindmap/collapse.ts +0 -67
- package/src/mindmap/layout.ts +0 -649
- package/src/mindmap/parser.ts +0 -430
- package/src/mindmap/renderer.ts +0 -637
- package/src/mindmap/text-wrap.ts +0 -218
- package/src/mindmap/types.ts +0 -56
- package/src/org/collapse.ts +0 -151
- package/src/org/layout.ts +0 -1262
- package/src/org/parser.ts +0 -519
- package/src/org/renderer.ts +0 -866
- package/src/org/resolver.ts +0 -636
- package/src/palettes/atlas.ts +0 -77
- package/src/palettes/blueprint.ts +0 -73
- package/src/palettes/catppuccin.ts +0 -84
- package/src/palettes/color-utils.ts +0 -443
- package/src/palettes/index.ts +0 -67
- package/src/palettes/nord.ts +0 -67
- package/src/palettes/registry.ts +0 -114
- package/src/palettes/slate.ts +0 -73
- package/src/palettes/tidewater.ts +0 -73
- package/src/palettes/tokyo-night.ts +0 -86
- package/src/palettes/types.ts +0 -88
- package/src/pert/analyzer.ts +0 -1183
- package/src/pert/diagnostics.ts +0 -110
- package/src/pert/index.ts +0 -43
- package/src/pert/internal.ts +0 -252
- package/src/pert/layout.ts +0 -971
- package/src/pert/monte-carlo.ts +0 -446
- package/src/pert/parser.ts +0 -1606
- package/src/pert/renderer.ts +0 -4009
- package/src/pert/share-normalize.ts +0 -62
- package/src/pert/types.ts +0 -604
- package/src/pyramid/parser.ts +0 -185
- package/src/pyramid/renderer.ts +0 -768
- package/src/pyramid/types.ts +0 -28
- package/src/quadrant/parser.ts +0 -18
- package/src/quadrant/renderer.ts +0 -638
- package/src/raci/diagnostics.ts +0 -213
- package/src/raci/index.ts +0 -23
- package/src/raci/mutations.ts +0 -321
- package/src/raci/parser.ts +0 -699
- package/src/raci/renderer.ts +0 -1627
- package/src/raci/types.ts +0 -82
- package/src/raci/variants.ts +0 -320
- package/src/render.ts +0 -268
- package/src/ring/parser.ts +0 -212
- package/src/ring/renderer.ts +0 -490
- package/src/ring/types.ts +0 -27
- package/src/sequence/collapse.ts +0 -174
- package/src/sequence/parser.ts +0 -1596
- package/src/sequence/participant-inference.ts +0 -152
- package/src/sequence/renderer.ts +0 -3168
- package/src/sequence/tag-resolution.ts +0 -164
- package/src/sharing.ts +0 -211
- package/src/sitemap/collapse.ts +0 -100
- package/src/sitemap/layout.ts +0 -925
- package/src/sitemap/parser.ts +0 -755
- package/src/sitemap/renderer.ts +0 -828
- package/src/sitemap/types.ts +0 -42
- package/src/slope/parser.ts +0 -18
- package/src/slope/renderer.ts +0 -378
- package/src/swimlane/diagnostics.ts +0 -94
- package/src/swimlane/layout.ts +0 -597
- package/src/swimlane/parser.ts +0 -613
- package/src/swimlane/renderer.ts +0 -681
- package/src/swimlane/types.ts +0 -151
- package/src/tech-radar/interactive.ts +0 -1129
- package/src/tech-radar/layout.ts +0 -197
- package/src/tech-radar/parser.ts +0 -436
- package/src/tech-radar/renderer.ts +0 -1239
- package/src/tech-radar/shared.ts +0 -151
- package/src/tech-radar/types.ts +0 -86
- package/src/themes.ts +0 -22
- package/src/timeline/parser.ts +0 -472
- package/src/timeline/renderer.ts +0 -3024
- package/src/timeline/types.ts +0 -33
- package/src/timeline/viz-parser.ts +0 -21
- package/src/treemap/diagnostics.ts +0 -62
- package/src/treemap/layout-radial.ts +0 -233
- package/src/treemap/layout.ts +0 -189
- package/src/treemap/parser.ts +0 -463
- package/src/treemap/renderer-radial.ts +0 -438
- package/src/treemap/renderer.ts +0 -572
- package/src/treemap/treemap-shared.ts +0 -309
- package/src/treemap/types.ts +0 -60
- package/src/utils/arrow-markers.ts +0 -51
- package/src/utils/arrows-diagnostics.ts +0 -62
- package/src/utils/arrows.ts +0 -227
- package/src/utils/brand.ts +0 -61
- package/src/utils/card.ts +0 -183
- package/src/utils/collapse-engine/tree-edges.ts +0 -115
- package/src/utils/collapse-engine/tree.ts +0 -117
- package/src/utils/d3-helpers.ts +0 -219
- package/src/utils/d3-types.ts +0 -9
- package/src/utils/description-helpers.ts +0 -38
- package/src/utils/duration.ts +0 -312
- package/src/utils/export-container.ts +0 -41
- package/src/utils/expr-eval.ts +0 -27
- package/src/utils/fit-canvas.ts +0 -64
- package/src/utils/hover-styles.ts +0 -673
- package/src/utils/inline-markdown.ts +0 -96
- package/src/utils/legend-constants.ts +0 -48
- package/src/utils/legend-d3.ts +0 -618
- package/src/utils/legend-integration.ts +0 -107
- package/src/utils/legend-layout.ts +0 -776
- package/src/utils/legend-svg.ts +0 -214
- package/src/utils/legend-types.ts +0 -272
- package/src/utils/name-normalize.ts +0 -163
- package/src/utils/note-box/constants.ts +0 -25
- package/src/utils/note-box/index.ts +0 -11
- package/src/utils/note-box/metrics.ts +0 -90
- package/src/utils/note-box/svg.ts +0 -331
- package/src/utils/notes/bounds.ts +0 -30
- package/src/utils/notes/build.ts +0 -131
- package/src/utils/notes/index.ts +0 -18
- package/src/utils/notes/model.ts +0 -19
- package/src/utils/notes/parse.ts +0 -131
- package/src/utils/notes/place.ts +0 -177
- package/src/utils/notes/resolve.ts +0 -88
- package/src/utils/number-format.ts +0 -36
- package/src/utils/parsing.ts +0 -953
- package/src/utils/reserved-key-registry.ts +0 -239
- package/src/utils/safe-href.ts +0 -38
- package/src/utils/scaling.ts +0 -97
- package/src/utils/svg-embed.ts +0 -251
- package/src/utils/tag-groups.ts +0 -607
- package/src/utils/text-measure.ts +0 -122
- package/src/utils/title-constants.ts +0 -24
- package/src/utils/visual-conventions.ts +0 -61
- package/src/utils/wrapped-desc.ts +0 -87
- package/src/venn/parser.ts +0 -18
- package/src/venn/renderer.ts +0 -1047
- package/src/version-control/diagnostics.ts +0 -67
- package/src/version-control/parser.ts +0 -544
- package/src/version-control/renderer.ts +0 -724
- package/src/version-control/types.ts +0 -100
- package/src/visualizations/parse.ts +0 -1560
- package/src/visualizations/types.ts +0 -262
- package/src/wireframe/layout.ts +0 -479
- package/src/wireframe/parser.ts +0 -1114
- package/src/wireframe/renderer.ts +0 -1318
- package/src/wireframe/types.ts +0 -110
- package/src/wordcloud/parser.ts +0 -18
- package/src/wordcloud/renderer.ts +0 -357
|
@@ -1,1560 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Visualization parser — Story 109.2 (arch-review).
|
|
3
|
-
//
|
|
4
|
-
// The unified parse state machine, relocated verbatim out of d3.ts. Moving it
|
|
5
|
-
// here breaks the d3 -> dgmo-router -> chart-type-registry -> d3 cycle that
|
|
6
|
-
// Story 109.1 left behind: dgmo-router + chart-type-registry now import
|
|
7
|
-
// parseVisualization from this module, not from d3.ts. Behavior is identical.
|
|
8
|
-
// ============================================================
|
|
9
|
-
|
|
10
|
-
import type { PaletteColors } from '../palettes';
|
|
11
|
-
import { resolveColorWithDiagnostic } from '../colors';
|
|
12
|
-
import { formatDgmoError, makeDgmoError, suggest } from '../diagnostics';
|
|
13
|
-
import {
|
|
14
|
-
collectIndentedValues,
|
|
15
|
-
extractColor,
|
|
16
|
-
measureIndent,
|
|
17
|
-
normalizeNumericToken,
|
|
18
|
-
parseFirstLine,
|
|
19
|
-
peelTrailingColorName,
|
|
20
|
-
splitNameAndMeta,
|
|
21
|
-
} from '../utils/parsing';
|
|
22
|
-
import {
|
|
23
|
-
TIMELINE_REGISTRY,
|
|
24
|
-
withTagAliases,
|
|
25
|
-
} from '../utils/reserved-key-registry';
|
|
26
|
-
import {
|
|
27
|
-
matchTagBlockHeading,
|
|
28
|
-
stripDefaultModifier,
|
|
29
|
-
validateTagGroupNames,
|
|
30
|
-
validateTagValues,
|
|
31
|
-
tagAttrKey,
|
|
32
|
-
} from '../utils/tag-groups';
|
|
33
|
-
import type { TagGroup } from '../utils/tag-groups';
|
|
34
|
-
import type { Writable } from '../utils/brand';
|
|
35
|
-
import { parseTimelineEventLine } from '../timeline/parser';
|
|
36
|
-
import {
|
|
37
|
-
DEFAULT_CLOUD_OPTIONS,
|
|
38
|
-
type ParsedVisualization,
|
|
39
|
-
type QuadrantLabel,
|
|
40
|
-
type VennOverlap,
|
|
41
|
-
type WordCloudWord,
|
|
42
|
-
type ParsedVizFull,
|
|
43
|
-
} from './types';
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Parses D3 chart text format into structured data. Returns the discriminated
|
|
47
|
-
* {@link ParsedVisualization} union; internally the single state machine fills a
|
|
48
|
-
* fat {@link ParsedVizFull} accumulator, which is a structural superset of every
|
|
49
|
-
* variant, so the narrowing is sound and runtime-identical.
|
|
50
|
-
*/
|
|
51
|
-
export function parseVisualization(
|
|
52
|
-
content: string,
|
|
53
|
-
palette?: PaletteColors
|
|
54
|
-
): ParsedVisualization {
|
|
55
|
-
return parseVisualizationFull(content, palette) as ParsedVisualization;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function parseVisualizationFull(
|
|
59
|
-
content: string,
|
|
60
|
-
palette?: PaletteColors
|
|
61
|
-
): ParsedVizFull {
|
|
62
|
-
const result: ParsedVizFull = {
|
|
63
|
-
type: null,
|
|
64
|
-
title: null,
|
|
65
|
-
titleLineNumber: null,
|
|
66
|
-
orientation: 'horizontal',
|
|
67
|
-
periods: [],
|
|
68
|
-
data: [],
|
|
69
|
-
words: [],
|
|
70
|
-
cloudOptions: { ...DEFAULT_CLOUD_OPTIONS },
|
|
71
|
-
links: [],
|
|
72
|
-
arcOrder: 'appearance',
|
|
73
|
-
arcNodeGroups: [],
|
|
74
|
-
timelineEvents: [],
|
|
75
|
-
timelineGroups: [],
|
|
76
|
-
timelineEras: [],
|
|
77
|
-
timelineMarkers: [],
|
|
78
|
-
timelineTagGroups: [],
|
|
79
|
-
timelineSort: null,
|
|
80
|
-
timelineScale: true,
|
|
81
|
-
timelineSwimlanes: false,
|
|
82
|
-
vennSets: [],
|
|
83
|
-
vennOverlaps: [],
|
|
84
|
-
quadrantLabels: {
|
|
85
|
-
topRight: null,
|
|
86
|
-
topLeft: null,
|
|
87
|
-
bottomLeft: null,
|
|
88
|
-
bottomRight: null,
|
|
89
|
-
},
|
|
90
|
-
quadrantPoints: [],
|
|
91
|
-
quadrantXAxis: null,
|
|
92
|
-
quadrantXAxisLineNumber: null,
|
|
93
|
-
quadrantYAxis: null,
|
|
94
|
-
quadrantYAxisLineNumber: null,
|
|
95
|
-
quadrantTitleLineNumber: null,
|
|
96
|
-
diagnostics: [],
|
|
97
|
-
error: null,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const fail = (line: number, message: string): ParsedVizFull => {
|
|
101
|
-
const diag = makeDgmoError(line, message);
|
|
102
|
-
result.diagnostics.push(diag);
|
|
103
|
-
result.error = formatDgmoError(diag);
|
|
104
|
-
return result;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const warn = (line: number, message: string): void => {
|
|
108
|
-
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
if (!content?.trim()) {
|
|
112
|
-
return fail(0, 'Empty content');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const lines = content.split('\n');
|
|
116
|
-
const freeformLines: string[] = [];
|
|
117
|
-
let currentArcGroup: string | null = null;
|
|
118
|
-
let currentTimelineGroup: string | null = null;
|
|
119
|
-
let currentTimelineGroupMeta: Record<string, string> = {};
|
|
120
|
-
let currentTimelineTagGroup: Writable<TagGroup> | null = null;
|
|
121
|
-
let inTimelineEraBlock = false;
|
|
122
|
-
let timelineEraBlockIndent = 0;
|
|
123
|
-
let inTimelineMarkerBlock = false;
|
|
124
|
-
let timelineMarkerBlockIndent = 0;
|
|
125
|
-
let inSlopePeriodBlock = false;
|
|
126
|
-
const timelineAliasMap = new Map<string, string>();
|
|
127
|
-
const VALID_D3_TYPES = new Set([
|
|
128
|
-
'slope',
|
|
129
|
-
'wordcloud',
|
|
130
|
-
'arc',
|
|
131
|
-
'timeline',
|
|
132
|
-
'venn',
|
|
133
|
-
'quadrant',
|
|
134
|
-
'sequence',
|
|
135
|
-
]);
|
|
136
|
-
let firstLineParsed = false;
|
|
137
|
-
|
|
138
|
-
for (let i = 0; i < lines.length; i++) {
|
|
139
|
-
// In-bounds by loop guard.
|
|
140
|
-
const rawLine = lines[i]!;
|
|
141
|
-
const line = rawLine.trim();
|
|
142
|
-
const indent = measureIndent(rawLine);
|
|
143
|
-
const lineNumber = i + 1;
|
|
144
|
-
|
|
145
|
-
// Skip empty lines
|
|
146
|
-
if (!line) continue;
|
|
147
|
-
|
|
148
|
-
// Skip comments
|
|
149
|
-
if (line.startsWith('//')) continue;
|
|
150
|
-
|
|
151
|
-
// First non-empty, non-comment line: chart type + optional title
|
|
152
|
-
if (!firstLineParsed) {
|
|
153
|
-
firstLineParsed = true;
|
|
154
|
-
const firstLineResult = parseFirstLine(line);
|
|
155
|
-
if (firstLineResult && VALID_D3_TYPES.has(firstLineResult.chartType)) {
|
|
156
|
-
result.type = firstLineResult.chartType as ParsedVisualization['type'];
|
|
157
|
-
if (firstLineResult.title) {
|
|
158
|
-
result.title = firstLineResult.title;
|
|
159
|
-
result.titleLineNumber = lineNumber;
|
|
160
|
-
}
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
// Not a bare chart type — fall through to normal parsing
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Timeline tag group heading: `tag Name [alias X]`
|
|
167
|
-
if (result.type === 'timeline' && indent === 0) {
|
|
168
|
-
const tagBlockMatch = matchTagBlockHeading(line);
|
|
169
|
-
if (tagBlockMatch) {
|
|
170
|
-
const newGroup: Writable<TagGroup> = {
|
|
171
|
-
name: tagBlockMatch.name,
|
|
172
|
-
...(tagBlockMatch.alias !== undefined && {
|
|
173
|
-
alias: tagBlockMatch.alias,
|
|
174
|
-
}),
|
|
175
|
-
entries: [],
|
|
176
|
-
lineNumber,
|
|
177
|
-
};
|
|
178
|
-
currentTimelineTagGroup = newGroup;
|
|
179
|
-
if (tagBlockMatch.alias) {
|
|
180
|
-
timelineAliasMap.set(
|
|
181
|
-
tagBlockMatch.alias.toLowerCase(),
|
|
182
|
-
tagBlockMatch.name.toLowerCase()
|
|
183
|
-
);
|
|
184
|
-
} else {
|
|
185
|
-
timelineAliasMap.set(
|
|
186
|
-
tagBlockMatch.name.toLowerCase(),
|
|
187
|
-
tagBlockMatch.name.toLowerCase()
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
result.timelineTagGroups.push(newGroup);
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Timeline tag group entries (indented under tag heading)
|
|
196
|
-
if (currentTimelineTagGroup && indent > 0) {
|
|
197
|
-
const { text: entryText, isDefault } = stripDefaultModifier(line);
|
|
198
|
-
const { label, color } = extractColor(
|
|
199
|
-
entryText,
|
|
200
|
-
palette,
|
|
201
|
-
result.diagnostics,
|
|
202
|
-
lineNumber
|
|
203
|
-
);
|
|
204
|
-
if (color) {
|
|
205
|
-
if (isDefault) {
|
|
206
|
-
currentTimelineTagGroup.defaultValue = label;
|
|
207
|
-
} else if (currentTimelineTagGroup.entries.length === 0) {
|
|
208
|
-
currentTimelineTagGroup.defaultValue = label;
|
|
209
|
-
}
|
|
210
|
-
currentTimelineTagGroup.entries.push({
|
|
211
|
-
value: label,
|
|
212
|
-
color,
|
|
213
|
-
lineNumber,
|
|
214
|
-
});
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// End tag group on non-indented line
|
|
220
|
-
if (currentTimelineTagGroup && indent === 0) {
|
|
221
|
-
currentTimelineTagGroup = null;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// [Group] container headers for arc / timeline (§1.5 trailing-token):
|
|
225
|
-
// `[Group]` — no color
|
|
226
|
-
// `[Group] color` — trailing-token color (recognized palette word)
|
|
227
|
-
// `[Group] key: value` — timeline group metadata
|
|
228
|
-
if (result.type === 'timeline') {
|
|
229
|
-
const tlGroupMatch = line.match(/^\[(.+?)\]\s*(.*?)\s*$/);
|
|
230
|
-
if (tlGroupMatch) {
|
|
231
|
-
const name = tlGroupMatch[1]!.trim();
|
|
232
|
-
const trailing = tlGroupMatch[2] ?? '';
|
|
233
|
-
let color: string | null = null;
|
|
234
|
-
let groupMeta: Record<string, string> = {};
|
|
235
|
-
|
|
236
|
-
if (trailing) {
|
|
237
|
-
const { label: peeled, colorName } = peelTrailingColorName(
|
|
238
|
-
' ' + trailing
|
|
239
|
-
);
|
|
240
|
-
if (colorName && peeled.trim() === '') {
|
|
241
|
-
color =
|
|
242
|
-
resolveColorWithDiagnostic(
|
|
243
|
-
colorName,
|
|
244
|
-
lineNumber,
|
|
245
|
-
result.diagnostics,
|
|
246
|
-
palette
|
|
247
|
-
) ?? null;
|
|
248
|
-
} else {
|
|
249
|
-
const tlRegistry = withTagAliases(
|
|
250
|
-
TIMELINE_REGISTRY,
|
|
251
|
-
new Set([
|
|
252
|
-
...timelineAliasMap.keys(),
|
|
253
|
-
...timelineAliasMap.values(),
|
|
254
|
-
])
|
|
255
|
-
);
|
|
256
|
-
const split = splitNameAndMeta(
|
|
257
|
-
trailing,
|
|
258
|
-
tlRegistry,
|
|
259
|
-
timelineAliasMap
|
|
260
|
-
);
|
|
261
|
-
if (split.color) {
|
|
262
|
-
color =
|
|
263
|
-
resolveColorWithDiagnostic(
|
|
264
|
-
split.color,
|
|
265
|
-
lineNumber,
|
|
266
|
-
result.diagnostics,
|
|
267
|
-
palette
|
|
268
|
-
) ?? null;
|
|
269
|
-
}
|
|
270
|
-
groupMeta = { ...split.meta };
|
|
271
|
-
if (groupMeta['color']) {
|
|
272
|
-
color =
|
|
273
|
-
resolveColorWithDiagnostic(
|
|
274
|
-
groupMeta['color'],
|
|
275
|
-
lineNumber,
|
|
276
|
-
result.diagnostics,
|
|
277
|
-
palette
|
|
278
|
-
) ?? null;
|
|
279
|
-
delete groupMeta['color'];
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
result.timelineGroups.push({
|
|
285
|
-
name,
|
|
286
|
-
color,
|
|
287
|
-
metadata: groupMeta,
|
|
288
|
-
lineNumber,
|
|
289
|
-
});
|
|
290
|
-
currentTimelineGroup = name;
|
|
291
|
-
currentTimelineGroupMeta = groupMeta;
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// [Group] for arc (original narrow regex)
|
|
297
|
-
const groupMatch = line.match(
|
|
298
|
-
/^\[(.+?)\](?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/
|
|
299
|
-
);
|
|
300
|
-
if (groupMatch) {
|
|
301
|
-
if (result.type === 'arc') {
|
|
302
|
-
const name = groupMatch[1]!.trim();
|
|
303
|
-
const color = groupMatch[2]
|
|
304
|
-
? (resolveColorWithDiagnostic(
|
|
305
|
-
groupMatch[2].trim(),
|
|
306
|
-
lineNumber,
|
|
307
|
-
result.diagnostics,
|
|
308
|
-
palette
|
|
309
|
-
) ?? null)
|
|
310
|
-
: null;
|
|
311
|
-
result.arcNodeGroups.push({ name, nodes: [], color, lineNumber });
|
|
312
|
-
currentArcGroup = name;
|
|
313
|
-
}
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Reject legacy ## group syntax
|
|
318
|
-
if (
|
|
319
|
-
/^#{2,}\s+/.test(line) &&
|
|
320
|
-
(result.type === 'arc' || result.type === 'timeline')
|
|
321
|
-
) {
|
|
322
|
-
const name = line
|
|
323
|
-
.replace(/^#{2,}\s+/, '')
|
|
324
|
-
.replace(/\s*\([^)]*\)\s*$/, '')
|
|
325
|
-
.trim();
|
|
326
|
-
result.diagnostics.push(
|
|
327
|
-
makeDgmoError(
|
|
328
|
-
lineNumber,
|
|
329
|
-
`'## ${name}' is no longer supported. Use '[${name}]' instead`,
|
|
330
|
-
'warning'
|
|
331
|
-
)
|
|
332
|
-
);
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Clear group context on un-indented lines (except [Group] already handled above)
|
|
337
|
-
if (indent === 0) {
|
|
338
|
-
currentArcGroup = null;
|
|
339
|
-
currentTimelineGroup = null;
|
|
340
|
-
currentTimelineGroupMeta = {};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Arc link line (§1.5 trailing-token):
|
|
344
|
-
// `source -> target [color] [weight]` — color before weight
|
|
345
|
-
if (result.type === 'arc') {
|
|
346
|
-
const linkMatch = line.match(
|
|
347
|
-
/^(.+?)\s*->\s*(.+?)(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?(?:\s+(-?[\d,_]+(?:\.[\d]+)?))?$/
|
|
348
|
-
);
|
|
349
|
-
if (linkMatch) {
|
|
350
|
-
// Capture groups 1 and 2 are guaranteed by the regex match.
|
|
351
|
-
const source = linkMatch[1]!.trim();
|
|
352
|
-
const target = linkMatch[2]!.trim();
|
|
353
|
-
if (source.endsWith(':') || target.endsWith(':')) {
|
|
354
|
-
result.diagnostics.push(
|
|
355
|
-
makeDgmoError(
|
|
356
|
-
lineNumber,
|
|
357
|
-
`Trailing colon is not valid in arc edges — write '${source.replace(/:$/, '')} -> ${target.replace(/:$/, '')}' instead`
|
|
358
|
-
)
|
|
359
|
-
);
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
const linkColor = linkMatch[3]
|
|
363
|
-
? (resolveColorWithDiagnostic(
|
|
364
|
-
linkMatch[3].trim(),
|
|
365
|
-
lineNumber,
|
|
366
|
-
result.diagnostics,
|
|
367
|
-
palette
|
|
368
|
-
) ?? null)
|
|
369
|
-
: null;
|
|
370
|
-
const arcValue = linkMatch[4]
|
|
371
|
-
? parseFloat(normalizeNumericToken(linkMatch[4]) ?? linkMatch[4])
|
|
372
|
-
: 1;
|
|
373
|
-
result.links.push({
|
|
374
|
-
source,
|
|
375
|
-
target,
|
|
376
|
-
value: arcValue,
|
|
377
|
-
color: linkColor,
|
|
378
|
-
lineNumber,
|
|
379
|
-
});
|
|
380
|
-
// Assign nodes to current group (first-appearance wins)
|
|
381
|
-
if (currentArcGroup !== null) {
|
|
382
|
-
const group = result.arcNodeGroups.find(
|
|
383
|
-
(g) => g.name === currentArcGroup
|
|
384
|
-
);
|
|
385
|
-
if (group) {
|
|
386
|
-
const allGrouped = new Set(
|
|
387
|
-
result.arcNodeGroups.flatMap((g) => g.nodes)
|
|
388
|
-
);
|
|
389
|
-
if (!allGrouped.has(source)) group.nodes.push(source);
|
|
390
|
-
if (!allGrouped.has(target)) group.nodes.push(target);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Timeline era block entries (indented under bare `era`)
|
|
398
|
-
if (result.type === 'timeline' && inTimelineEraBlock) {
|
|
399
|
-
if (indent <= timelineEraBlockIndent) {
|
|
400
|
-
inTimelineEraBlock = false;
|
|
401
|
-
// fall through to process this line normally
|
|
402
|
-
} else {
|
|
403
|
-
if (line.startsWith('//')) continue;
|
|
404
|
-
// Timeline era block entry (\u00a71.5 trailing-token):
|
|
405
|
-
// `<start> -> <end> Label` (no color)
|
|
406
|
-
// `<start> -> <end> Label color` (trailing color word)
|
|
407
|
-
// Color (group 4) must be a recognized lowercase palette word.
|
|
408
|
-
const eraEntryMatch = line.match(
|
|
409
|
-
/^(\d{4}(?:-\d{2})?(?:-\d{2}(?: \d{2}:\d{2})?)?)\s*(?:->|\u2013>)\s*(\d{4}(?:-\d{2})?(?:-\d{2}(?: \d{2}:\d{2})?)?)\s+(.+?)(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/
|
|
410
|
-
);
|
|
411
|
-
if (eraEntryMatch) {
|
|
412
|
-
const colorAnnotation = eraEntryMatch[4]?.trim() || null;
|
|
413
|
-
result.timelineEras.push({
|
|
414
|
-
// Capture groups 1-3 guaranteed by the regex match.
|
|
415
|
-
startDate: eraEntryMatch[1]!,
|
|
416
|
-
endDate: eraEntryMatch[2]!,
|
|
417
|
-
label: eraEntryMatch[3]!.trim(),
|
|
418
|
-
color: colorAnnotation
|
|
419
|
-
? (resolveColorWithDiagnostic(
|
|
420
|
-
colorAnnotation,
|
|
421
|
-
lineNumber,
|
|
422
|
-
result.diagnostics,
|
|
423
|
-
palette
|
|
424
|
-
) ?? null)
|
|
425
|
-
: null,
|
|
426
|
-
lineNumber,
|
|
427
|
-
});
|
|
428
|
-
} else {
|
|
429
|
-
warn(
|
|
430
|
-
lineNumber,
|
|
431
|
-
`Unrecognized era entry: "${line}" — use '<start> -> <end> Label [color]' (e.g. '1716 -> 1717 Reign').`
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Timeline marker block entries (indented under bare `marker`)
|
|
439
|
-
if (result.type === 'timeline' && inTimelineMarkerBlock) {
|
|
440
|
-
if (indent <= timelineMarkerBlockIndent) {
|
|
441
|
-
inTimelineMarkerBlock = false;
|
|
442
|
-
// fall through to process this line normally
|
|
443
|
-
} else {
|
|
444
|
-
if (line.startsWith('//')) continue;
|
|
445
|
-
// Timeline marker block entry (§1.5 trailing-token).
|
|
446
|
-
const markerEntryMatch = line.match(
|
|
447
|
-
/^(\d{4}(?:-\d{2})?(?:-\d{2}(?: \d{2}:\d{2})?)?)\s+(.+?)(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/
|
|
448
|
-
);
|
|
449
|
-
if (markerEntryMatch) {
|
|
450
|
-
const colorAnnotation = markerEntryMatch[3]?.trim() || null;
|
|
451
|
-
result.timelineMarkers.push({
|
|
452
|
-
// Capture groups 1-2 guaranteed by the regex match.
|
|
453
|
-
date: markerEntryMatch[1]!,
|
|
454
|
-
label: markerEntryMatch[2]!.trim(),
|
|
455
|
-
color: colorAnnotation
|
|
456
|
-
? (resolveColorWithDiagnostic(
|
|
457
|
-
colorAnnotation,
|
|
458
|
-
lineNumber,
|
|
459
|
-
result.diagnostics,
|
|
460
|
-
palette
|
|
461
|
-
) ?? null)
|
|
462
|
-
: null,
|
|
463
|
-
lineNumber,
|
|
464
|
-
});
|
|
465
|
-
} else {
|
|
466
|
-
warn(
|
|
467
|
-
lineNumber,
|
|
468
|
-
`Unrecognized marker entry: "${line}" — use '<date> Label [color]' (e.g. '1776 Independence').`
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Timeline era/marker block starters and inline forms
|
|
476
|
-
if (result.type === 'timeline') {
|
|
477
|
-
// Bare `era` keyword starts a block
|
|
478
|
-
if (line.toLowerCase() === 'era') {
|
|
479
|
-
inTimelineEraBlock = true;
|
|
480
|
-
timelineEraBlockIndent = indent;
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Bare `marker` keyword starts a block
|
|
485
|
-
if (line.toLowerCase() === 'marker') {
|
|
486
|
-
inTimelineMarkerBlock = true;
|
|
487
|
-
timelineMarkerBlockIndent = indent;
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Timeline era lines, inline (\u00a71.5 trailing-token):
|
|
492
|
-
// `era YYYY->YYYY Label` \u2014 no color
|
|
493
|
-
// `era YYYY->YYYY Label color` \u2014 trailing-token color (recognized palette word)
|
|
494
|
-
const eraMatch = line.match(
|
|
495
|
-
/^era\s+(\d{4}(?:-\d{2})?(?:-\d{2}(?: \d{2}:\d{2})?)?)\s*(?:->|\u2013>)\s*(\d{4}(?:-\d{2})?(?:-\d{2}(?: \d{2}:\d{2})?)?)\s+(.+?)(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/
|
|
496
|
-
);
|
|
497
|
-
if (eraMatch) {
|
|
498
|
-
const colorAnnotation = eraMatch[4]?.trim() || null;
|
|
499
|
-
result.timelineEras.push({
|
|
500
|
-
// Capture groups 1-3 guaranteed by the regex match.
|
|
501
|
-
startDate: eraMatch[1]!,
|
|
502
|
-
endDate: eraMatch[2]!,
|
|
503
|
-
label: eraMatch[3]!.trim(),
|
|
504
|
-
color: colorAnnotation
|
|
505
|
-
? (resolveColorWithDiagnostic(
|
|
506
|
-
colorAnnotation,
|
|
507
|
-
lineNumber,
|
|
508
|
-
result.diagnostics,
|
|
509
|
-
palette
|
|
510
|
-
) ?? null)
|
|
511
|
-
: null,
|
|
512
|
-
lineNumber,
|
|
513
|
-
});
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Timeline marker lines, inline (§1.5 trailing-token):
|
|
518
|
-
// `marker YYYY Label` — no color
|
|
519
|
-
// `marker YYYY Label color` — trailing-token color
|
|
520
|
-
const markerMatch = line.match(
|
|
521
|
-
/^marker\s+(\d{4}(?:-\d{2})?(?:-\d{2}(?: \d{2}:\d{2})?)?)\s+(.+?)(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/
|
|
522
|
-
);
|
|
523
|
-
if (markerMatch) {
|
|
524
|
-
const colorAnnotation = markerMatch[3]?.trim() || null;
|
|
525
|
-
result.timelineMarkers.push({
|
|
526
|
-
// Capture groups 1-2 guaranteed by the regex match.
|
|
527
|
-
date: markerMatch[1]!,
|
|
528
|
-
label: markerMatch[2]!.trim(),
|
|
529
|
-
color: colorAnnotation
|
|
530
|
-
? (resolveColorWithDiagnostic(
|
|
531
|
-
colorAnnotation,
|
|
532
|
-
lineNumber,
|
|
533
|
-
result.diagnostics,
|
|
534
|
-
palette
|
|
535
|
-
) ?? null)
|
|
536
|
-
: null,
|
|
537
|
-
lineNumber,
|
|
538
|
-
});
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Timeline sort directive: `sort time | group | tag:X`
|
|
544
|
-
if (result.type === 'timeline') {
|
|
545
|
-
const sortMatch = line.match(/^sort\s+(time|group|tag:(.+))$/i);
|
|
546
|
-
if (sortMatch) {
|
|
547
|
-
const kind = sortMatch[1]!.toLowerCase();
|
|
548
|
-
if (kind === 'time') result.timelineSort = 'time';
|
|
549
|
-
else if (kind === 'group') result.timelineSort = 'group';
|
|
550
|
-
else {
|
|
551
|
-
result.timelineSort = 'tag';
|
|
552
|
-
result.timelineDefaultSwimlaneTG = sortMatch[2]!.trim();
|
|
553
|
-
}
|
|
554
|
-
continue;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Timeline display directives (§15.6, space-separated, no colon):
|
|
559
|
-
// no-scale — drop the date axis/scale
|
|
560
|
-
// swimlanes — force lane backgrounds
|
|
561
|
-
// active-tag <X> — authored active tag group / `none` / value-ramp label
|
|
562
|
-
if (result.type === 'timeline') {
|
|
563
|
-
const lower = line.toLowerCase();
|
|
564
|
-
if (lower === 'no-scale') {
|
|
565
|
-
result.timelineScale = false;
|
|
566
|
-
continue;
|
|
567
|
-
}
|
|
568
|
-
if (lower === 'swimlanes') {
|
|
569
|
-
result.timelineSwimlanes = true;
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
// `lane-by <group>`: the canonical swimlane-axis directive (view-state).
|
|
573
|
-
// Equivalent to `sort tag:<group>`. `swimlane` is a chart type, hence the
|
|
574
|
-
// `lane-by` keyword.
|
|
575
|
-
const laneByMatch = line.match(/^lane-by\s+(.+)$/i);
|
|
576
|
-
if (laneByMatch) {
|
|
577
|
-
result.timelineSort = 'tag';
|
|
578
|
-
result.timelineDefaultSwimlaneTG = laneByMatch[1]!.trim();
|
|
579
|
-
continue;
|
|
580
|
-
}
|
|
581
|
-
const activeTagMatch = line.match(/^active-tag\s+(.+)$/i);
|
|
582
|
-
if (activeTagMatch) {
|
|
583
|
-
result.timelineActiveTag = activeTagMatch[1]!.trim();
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Timeline event lines: date-first syntax
|
|
589
|
-
if (result.type === 'timeline') {
|
|
590
|
-
const tlRegistry = withTagAliases(
|
|
591
|
-
TIMELINE_REGISTRY,
|
|
592
|
-
new Set([...timelineAliasMap.keys(), ...timelineAliasMap.values()])
|
|
593
|
-
);
|
|
594
|
-
|
|
595
|
-
const eventResult = parseTimelineEventLine(
|
|
596
|
-
line,
|
|
597
|
-
lineNumber,
|
|
598
|
-
currentTimelineGroup,
|
|
599
|
-
currentTimelineGroupMeta,
|
|
600
|
-
timelineAliasMap,
|
|
601
|
-
tlRegistry
|
|
602
|
-
);
|
|
603
|
-
|
|
604
|
-
if (eventResult) {
|
|
605
|
-
result.diagnostics.push(...eventResult.diagnostics);
|
|
606
|
-
if (eventResult.event) {
|
|
607
|
-
result.timelineEvents.push(eventResult.event);
|
|
608
|
-
}
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// Bare name with no date prefix (not a keyword)
|
|
613
|
-
if (
|
|
614
|
-
!/^(era|marker|tag|sort|active-tag|swimlanes|no-scale|lane-by)\b/i.test(
|
|
615
|
-
line
|
|
616
|
-
) &&
|
|
617
|
-
!line.startsWith('[')
|
|
618
|
-
) {
|
|
619
|
-
warn(lineNumber, `Expected a date at the start — e.g., '1718 ${line}'`);
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Venn diagram DSL
|
|
625
|
-
if (result.type === 'venn') {
|
|
626
|
-
// Skip cross-chart bare-keyword options so they don't get parsed as
|
|
627
|
-
// a 4th set name (the bare-keyword block at line ~1132 runs AFTER
|
|
628
|
-
// type-specific parsing).
|
|
629
|
-
if (/^(solid-fill|no-name|no-value|no-percent|no-title)$/i.test(line)) {
|
|
630
|
-
// Fall through to the bare-keyword block below.
|
|
631
|
-
} else if (/\+/.test(line)) {
|
|
632
|
-
// Build lookup of known set names and aliases for label extraction
|
|
633
|
-
const knownSetRefs = new Set<string>();
|
|
634
|
-
for (const s of result.vennSets) {
|
|
635
|
-
knownSetRefs.add(s.name.toLowerCase());
|
|
636
|
-
if (s.alias) knownSetRefs.add(s.alias.toLowerCase());
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const segments = line
|
|
640
|
-
.split('+')
|
|
641
|
-
.map((s) => s.trim())
|
|
642
|
-
.filter(Boolean);
|
|
643
|
-
if (segments.length >= 2) {
|
|
644
|
-
// All segments except the last are pure set references
|
|
645
|
-
const rawSets = segments.slice(0, -1);
|
|
646
|
-
// In-bounds by length check (segments.length >= 2).
|
|
647
|
-
const lastSeg = segments[segments.length - 1]!;
|
|
648
|
-
|
|
649
|
-
// For the last segment, extract set reference and optional label.
|
|
650
|
-
// Find where the set reference ends and label begins.
|
|
651
|
-
// Try progressively shorter prefixes against known set names/aliases.
|
|
652
|
-
const words = lastSeg.split(/\s+/);
|
|
653
|
-
let matchLen = 0;
|
|
654
|
-
for (let w = words.length; w >= 1; w--) {
|
|
655
|
-
const candidate = words.slice(0, w).join(' ');
|
|
656
|
-
if (knownSetRefs.has(candidate.toLowerCase())) {
|
|
657
|
-
matchLen = w;
|
|
658
|
-
break;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
let lastSetRef: string;
|
|
662
|
-
let label: string | null;
|
|
663
|
-
if (matchLen > 0) {
|
|
664
|
-
lastSetRef = words.slice(0, matchLen).join(' ');
|
|
665
|
-
label =
|
|
666
|
-
words.length > matchLen ? words.slice(matchLen).join(' ') : null;
|
|
667
|
-
} else {
|
|
668
|
-
// No known set matched — assume first word is the set ref, rest is label
|
|
669
|
-
// words is non-empty (split always returns at least one).
|
|
670
|
-
lastSetRef = words[0]!;
|
|
671
|
-
label = words.length > 1 ? words.slice(1).join(' ') : null;
|
|
672
|
-
}
|
|
673
|
-
rawSets.push(lastSetRef);
|
|
674
|
-
result.vennOverlaps.push({ sets: rawSets, label, lineNumber });
|
|
675
|
-
continue;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// Set declaration (§1.5 universal trailing-token, §2A.2 modifier order):
|
|
680
|
-
// `Name` / `Name color` / `Name as <alias>` / `Name as <alias> color`
|
|
681
|
-
// Color is the line-trailing token, peeled first; alias follows the name
|
|
682
|
-
// via the `as` keyword. Only attempt set parsing if the line wasn't a
|
|
683
|
-
// bare-keyword option (handled above).
|
|
684
|
-
if (!/^(solid-fill|no-name|no-value|no-percent|no-title)$/i.test(line)) {
|
|
685
|
-
// Peel a trailing color word from the whole line first so the
|
|
686
|
-
// remaining text is `Name [alias <alias>]` / `Name [as <alias>]`.
|
|
687
|
-
const { label: lineWithoutColor, colorName } =
|
|
688
|
-
peelTrailingColorName(line);
|
|
689
|
-
let color: string | null = null;
|
|
690
|
-
if (colorName) {
|
|
691
|
-
color =
|
|
692
|
-
resolveColorWithDiagnostic(
|
|
693
|
-
colorName,
|
|
694
|
-
lineNumber,
|
|
695
|
-
result.diagnostics,
|
|
696
|
-
palette
|
|
697
|
-
) ?? null;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
const setDeclMatch = lineWithoutColor.match(
|
|
701
|
-
/^(.+?)(?:\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11}))?\s*$/i
|
|
702
|
-
);
|
|
703
|
-
if (setDeclMatch) {
|
|
704
|
-
// Capture group 1 guaranteed by the regex match.
|
|
705
|
-
const name = setDeclMatch[1]!.trim();
|
|
706
|
-
const alias = setDeclMatch[2]?.trim() ?? null;
|
|
707
|
-
result.vennSets.push({ name, alias, color, lineNumber });
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Quadrant-specific parsing
|
|
714
|
-
if (result.type === 'quadrant') {
|
|
715
|
-
// x-label Low, High — or indented multi-line
|
|
716
|
-
const xAxisMatch = line.match(/^x-label\s+(.*)/i);
|
|
717
|
-
if (xAxisMatch) {
|
|
718
|
-
// Capture group 1 guaranteed by the regex match.
|
|
719
|
-
const val = xAxisMatch[1]!.trim();
|
|
720
|
-
let parts: string[];
|
|
721
|
-
if (val) {
|
|
722
|
-
parts = val.split(',').map((s) => s.trim());
|
|
723
|
-
} else {
|
|
724
|
-
const collected = collectIndentedValues(lines, i);
|
|
725
|
-
i = collected.newIndex;
|
|
726
|
-
parts = collected.values;
|
|
727
|
-
}
|
|
728
|
-
if (parts.length >= 2) {
|
|
729
|
-
// In-bounds by length check above.
|
|
730
|
-
result.quadrantXAxis = [parts[0]!, parts[1]!];
|
|
731
|
-
result.quadrantXAxisLineNumber = lineNumber;
|
|
732
|
-
}
|
|
733
|
-
continue;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// y-label Low, High — or indented multi-line
|
|
737
|
-
const yAxisMatch = line.match(/^y-label\s+(.*)/i);
|
|
738
|
-
if (yAxisMatch) {
|
|
739
|
-
// Capture group 1 guaranteed by the regex match.
|
|
740
|
-
const val = yAxisMatch[1]!.trim();
|
|
741
|
-
let parts: string[];
|
|
742
|
-
if (val) {
|
|
743
|
-
parts = val.split(',').map((s) => s.trim());
|
|
744
|
-
} else {
|
|
745
|
-
const collected = collectIndentedValues(lines, i);
|
|
746
|
-
i = collected.newIndex;
|
|
747
|
-
parts = collected.values;
|
|
748
|
-
}
|
|
749
|
-
if (parts.length >= 2) {
|
|
750
|
-
// In-bounds by length check above.
|
|
751
|
-
result.quadrantYAxis = [parts[0]!, parts[1]!];
|
|
752
|
-
result.quadrantYAxisLineNumber = lineNumber;
|
|
753
|
-
}
|
|
754
|
-
continue;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Quadrant position labels (§1.5 trailing-token):
|
|
758
|
-
// `top-right Label` — no color
|
|
759
|
-
// `top-right Label color` — trailing-token color (recognized palette word)
|
|
760
|
-
const quadrantLabelRe =
|
|
761
|
-
/^(top-right|top-left|bottom-left|bottom-right)\s+(.+)/i;
|
|
762
|
-
const quadrantMatch = line.match(quadrantLabelRe);
|
|
763
|
-
if (quadrantMatch) {
|
|
764
|
-
// Capture groups 1-2 guaranteed by the regex match.
|
|
765
|
-
const position = quadrantMatch[1]!.toLowerCase();
|
|
766
|
-
const labelPart = quadrantMatch[2]!.trim();
|
|
767
|
-
// Peel trailing recognized color word from the label.
|
|
768
|
-
const { label: text, colorName } = peelTrailingColorName(labelPart);
|
|
769
|
-
const color = colorName
|
|
770
|
-
? (resolveColorWithDiagnostic(
|
|
771
|
-
colorName,
|
|
772
|
-
lineNumber,
|
|
773
|
-
result.diagnostics,
|
|
774
|
-
palette
|
|
775
|
-
) ?? null)
|
|
776
|
-
: null;
|
|
777
|
-
const label: QuadrantLabel = { text, color, lineNumber };
|
|
778
|
-
|
|
779
|
-
if (position === 'top-right') result.quadrantLabels.topRight = label;
|
|
780
|
-
else if (position === 'top-left') result.quadrantLabels.topLeft = label;
|
|
781
|
-
else if (position === 'bottom-left')
|
|
782
|
-
result.quadrantLabels.bottomLeft = label;
|
|
783
|
-
else if (position === 'bottom-right')
|
|
784
|
-
result.quadrantLabels.bottomRight = label;
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// Data points: Label x, y OR Label x y
|
|
789
|
-
const pointMatch = line.match(
|
|
790
|
-
/^(.+?)\s+(-?[0-9][0-9,_]*(?:\.[0-9]+)?)\s*([,\s])\s*(-?[0-9][0-9,_]*(?:\.[0-9]+)?)\s*$/
|
|
791
|
-
);
|
|
792
|
-
if (pointMatch) {
|
|
793
|
-
// Capture groups 1-4 guaranteed by the regex match.
|
|
794
|
-
const label = pointMatch[1]!.trim();
|
|
795
|
-
// Skip if it looks like a quadrant position keyword
|
|
796
|
-
const lowerLabel = label.toLowerCase();
|
|
797
|
-
if (
|
|
798
|
-
lowerLabel !== 'top-right' &&
|
|
799
|
-
lowerLabel !== 'top-left' &&
|
|
800
|
-
lowerLabel !== 'bottom-left' &&
|
|
801
|
-
lowerLabel !== 'bottom-right'
|
|
802
|
-
) {
|
|
803
|
-
const x = parseFloat(
|
|
804
|
-
normalizeNumericToken(pointMatch[2]!) ?? pointMatch[2]!
|
|
805
|
-
);
|
|
806
|
-
const y = parseFloat(
|
|
807
|
-
normalizeNumericToken(pointMatch[4]!) ?? pointMatch[4]!
|
|
808
|
-
);
|
|
809
|
-
result.quadrantPoints.push({
|
|
810
|
-
label,
|
|
811
|
-
x,
|
|
812
|
-
y,
|
|
813
|
-
lineNumber,
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
continue;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
// ── Space-separated options (no colon) ──────────────────
|
|
821
|
-
const spaceIdx = line.indexOf(' ');
|
|
822
|
-
if (spaceIdx >= 0) {
|
|
823
|
-
const firstToken = line.substring(0, spaceIdx).toLowerCase();
|
|
824
|
-
const restValue = line.substring(spaceIdx + 1).trim();
|
|
825
|
-
|
|
826
|
-
if (
|
|
827
|
-
firstToken === 'chart' &&
|
|
828
|
-
VALID_D3_TYPES.has(restValue.toLowerCase())
|
|
829
|
-
) {
|
|
830
|
-
result.type = restValue.toLowerCase() as ParsedVisualization['type'];
|
|
831
|
-
continue;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
if (firstToken === 'title') {
|
|
835
|
-
result.title = restValue;
|
|
836
|
-
result.titleLineNumber = lineNumber;
|
|
837
|
-
if (result.type === 'quadrant') {
|
|
838
|
-
result.quadrantTitleLineNumber = lineNumber;
|
|
839
|
-
}
|
|
840
|
-
continue;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
if (firstToken === 'order') {
|
|
844
|
-
const v = restValue.toLowerCase();
|
|
845
|
-
if (v === 'name' || v === 'group' || v === 'degree') {
|
|
846
|
-
result.arcOrder = v;
|
|
847
|
-
}
|
|
848
|
-
continue;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// `layout arc|chord` (#26): render the same pairwise edges as the other
|
|
852
|
-
// preset. `chord` → circular chord; default `arc` → linear.
|
|
853
|
-
if (firstToken === 'layout') {
|
|
854
|
-
const v = restValue.toLowerCase();
|
|
855
|
-
if (v === 'arc' || v === 'chord') result.layout = v;
|
|
856
|
-
continue;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
if (firstToken === 'rotate') {
|
|
860
|
-
const v = restValue.toLowerCase();
|
|
861
|
-
if (v === 'none' || v === 'mixed' || v === 'angled') {
|
|
862
|
-
result.cloudOptions.rotate = v;
|
|
863
|
-
}
|
|
864
|
-
continue;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
if (firstToken === 'max') {
|
|
868
|
-
const v = parseInt(restValue, 10);
|
|
869
|
-
if (!isNaN(v) && v > 0) {
|
|
870
|
-
result.cloudOptions.max = v;
|
|
871
|
-
}
|
|
872
|
-
continue;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
if (firstToken === 'size') {
|
|
876
|
-
const parts = restValue.split(',').map((s) => parseInt(s.trim(), 10));
|
|
877
|
-
if (
|
|
878
|
-
parts.length === 2 &&
|
|
879
|
-
parts.every((n) => !isNaN(n) && n > 0) &&
|
|
880
|
-
// In-bounds by length === 2 check.
|
|
881
|
-
parts[0]! < parts[1]!
|
|
882
|
-
) {
|
|
883
|
-
// In-bounds by length === 2 check.
|
|
884
|
-
result.cloudOptions.minSize = parts[0]!;
|
|
885
|
-
result.cloudOptions.maxSize = parts[1]!;
|
|
886
|
-
}
|
|
887
|
-
continue;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// ── Bare-keyword no-* flags (show-everything default) ──────
|
|
892
|
-
{
|
|
893
|
-
const bareToken = line.toLowerCase();
|
|
894
|
-
if (bareToken === 'no-name') {
|
|
895
|
-
result.noName = true;
|
|
896
|
-
continue;
|
|
897
|
-
}
|
|
898
|
-
if (bareToken === 'no-value') {
|
|
899
|
-
result.noValue = true;
|
|
900
|
-
continue;
|
|
901
|
-
}
|
|
902
|
-
if (bareToken === 'no-percent') {
|
|
903
|
-
result.noPercent = true;
|
|
904
|
-
continue;
|
|
905
|
-
}
|
|
906
|
-
if (bareToken === 'solid-fill') {
|
|
907
|
-
result.solidFill = true;
|
|
908
|
-
continue;
|
|
909
|
-
}
|
|
910
|
-
if (bareToken === 'no-title') {
|
|
911
|
-
result.noTitle = true;
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
// Silent-ignore unrecognized no-* flags (typos, future flags).
|
|
915
|
-
if (bareToken.startsWith('no-')) {
|
|
916
|
-
continue;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// ── Slope chart: period directive + right-scan data rows ──
|
|
921
|
-
if (result.type === 'slope') {
|
|
922
|
-
// Period block: indented lines inside `period` block
|
|
923
|
-
// (blank lines are pre-filtered at loop top, so only non-indented lines close the block)
|
|
924
|
-
if (inSlopePeriodBlock) {
|
|
925
|
-
if (indent > 0) {
|
|
926
|
-
result.periods.push(line);
|
|
927
|
-
continue;
|
|
928
|
-
}
|
|
929
|
-
// Non-indented line → close block, fall through to process normally
|
|
930
|
-
inSlopePeriodBlock = false;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
// Period directive: `period Label1 Label2` or bare `period` (block open)
|
|
934
|
-
// Only accept before data rows start (F4: prevent keyword shadowing labels)
|
|
935
|
-
if (result.data.length === 0) {
|
|
936
|
-
const periodMatch = line.match(/^period\b(.*)$/i);
|
|
937
|
-
if (periodMatch) {
|
|
938
|
-
if (result.periods.length > 0 && !inSlopePeriodBlock) {
|
|
939
|
-
// F5: warn on duplicate period directives
|
|
940
|
-
warn(
|
|
941
|
-
lineNumber,
|
|
942
|
-
`Duplicate 'period' directive — periods are already defined`
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
// Capture group 1 guaranteed by the regex match.
|
|
946
|
-
const rest = periodMatch[1]!.trim();
|
|
947
|
-
if (rest) {
|
|
948
|
-
// One-line: `period 1715 1725`
|
|
949
|
-
const periodLabels = rest.split(/\s+/);
|
|
950
|
-
result.periods.push(...periodLabels);
|
|
951
|
-
} else {
|
|
952
|
-
// Block open: bare `period`
|
|
953
|
-
inSlopePeriodBlock = true;
|
|
954
|
-
}
|
|
955
|
-
continue;
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// Migration error: bare period line (old syntax — comma-separated, no keyword)
|
|
960
|
-
// F1: Only fire when ALL comma-separated tokens are short (≤20 chars) and non-empty
|
|
961
|
-
if (
|
|
962
|
-
result.periods.length === 0 &&
|
|
963
|
-
line.includes(',') &&
|
|
964
|
-
!line.includes(':')
|
|
965
|
-
) {
|
|
966
|
-
const tokens = line
|
|
967
|
-
.split(',')
|
|
968
|
-
.map((t) => t.trim())
|
|
969
|
-
.filter(Boolean);
|
|
970
|
-
const looksLikePeriods =
|
|
971
|
-
tokens.length >= 2 && tokens.every((t) => t.length <= 20);
|
|
972
|
-
if (looksLikePeriods) {
|
|
973
|
-
return fail(
|
|
974
|
-
lineNumber,
|
|
975
|
-
`Period lines require the 'period' keyword — use 'period ${tokens.join(' ')}'`
|
|
976
|
-
);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
// Migration error: old colon syntax in data rows
|
|
981
|
-
// F2: Only fire when content after colon is predominantly numeric (old "Label: val1, val2" pattern)
|
|
982
|
-
if (line.includes(':')) {
|
|
983
|
-
const colonPos = line.indexOf(':');
|
|
984
|
-
const afterColon = line.substring(colonPos + 1).trim();
|
|
985
|
-
const numericTokens = afterColon
|
|
986
|
-
.split(/[,\s]+/)
|
|
987
|
-
.filter((v) => /^-?\d/.test(v));
|
|
988
|
-
// Only trigger if most tokens after the colon are numeric (old data pattern)
|
|
989
|
-
if (numericTokens.length >= 1) {
|
|
990
|
-
const allTokens = afterColon.split(/[,\s]+/).filter(Boolean);
|
|
991
|
-
if (numericTokens.length >= allTokens.length * 0.5) {
|
|
992
|
-
const label = line.substring(0, colonPos).trim();
|
|
993
|
-
return fail(
|
|
994
|
-
lineNumber,
|
|
995
|
-
`Colons are no longer used in slope data rows — use '${label} ${numericTokens.join(' ')}'`
|
|
996
|
-
);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// Right-scan data row parsing (requires periods to be known)
|
|
1002
|
-
if (result.periods.length >= 2) {
|
|
1003
|
-
const P = result.periods.length;
|
|
1004
|
-
const tokens = line.split(/\s+/);
|
|
1005
|
-
const values: number[] = [];
|
|
1006
|
-
|
|
1007
|
-
// Scan from right, capped at P values
|
|
1008
|
-
let rightIdx = tokens.length - 1;
|
|
1009
|
-
while (rightIdx >= 0 && values.length < P) {
|
|
1010
|
-
// In-bounds by while condition (rightIdx >= 0 && < tokens.length).
|
|
1011
|
-
const tok = tokens[rightIdx]!;
|
|
1012
|
-
const raw = normalizeNumericToken(tok) ?? tok;
|
|
1013
|
-
const num = parseFloat(raw);
|
|
1014
|
-
if (!isNaN(num) && /^-?\d/.test(raw)) {
|
|
1015
|
-
values.unshift(num);
|
|
1016
|
-
rightIdx--;
|
|
1017
|
-
} else {
|
|
1018
|
-
break;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
if (values.length < P) {
|
|
1023
|
-
warn(
|
|
1024
|
-
lineNumber,
|
|
1025
|
-
`Data row has ${values.length} numeric value(s) but ${P} period(s) are defined — expected ${P} values`
|
|
1026
|
-
);
|
|
1027
|
-
continue;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
// Remaining left tokens = label
|
|
1031
|
-
const labelTokens = tokens.slice(0, rightIdx + 1);
|
|
1032
|
-
const joinedLabel = labelTokens.join(' ');
|
|
1033
|
-
|
|
1034
|
-
if (!joinedLabel) {
|
|
1035
|
-
warn(
|
|
1036
|
-
lineNumber,
|
|
1037
|
-
`Data row has no label — add a label before the numeric values`
|
|
1038
|
-
);
|
|
1039
|
-
continue;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// Color annotation (§1.5 trailing-token): `Label color` → split.
|
|
1043
|
-
const { label: labelPart, colorName: colorWord } =
|
|
1044
|
-
peelTrailingColorName(joinedLabel);
|
|
1045
|
-
const colorPart = colorWord
|
|
1046
|
-
? (resolveColorWithDiagnostic(
|
|
1047
|
-
colorWord,
|
|
1048
|
-
lineNumber,
|
|
1049
|
-
result.diagnostics,
|
|
1050
|
-
palette
|
|
1051
|
-
) ?? null)
|
|
1052
|
-
: null;
|
|
1053
|
-
|
|
1054
|
-
if (!labelPart) {
|
|
1055
|
-
warn(
|
|
1056
|
-
lineNumber,
|
|
1057
|
-
`Data row has no label — add a label before the numeric values`
|
|
1058
|
-
);
|
|
1059
|
-
continue;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// F3: Warn on purely numeric labels — likely a mistake
|
|
1063
|
-
if (/^\d[\d,.]*$/.test(labelPart)) {
|
|
1064
|
-
warn(
|
|
1065
|
-
lineNumber,
|
|
1066
|
-
`Label '${labelPart}' looks numeric — this may indicate too many values or a missing label`
|
|
1067
|
-
);
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
result.data.push({
|
|
1071
|
-
label: labelPart,
|
|
1072
|
-
values,
|
|
1073
|
-
color: colorPart,
|
|
1074
|
-
lineNumber,
|
|
1075
|
-
});
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// If we get here in a slope chart, it's an unrecognized line
|
|
1080
|
-
if (firstLineParsed) {
|
|
1081
|
-
warn(lineNumber, `Unexpected line: '${line}'.`);
|
|
1082
|
-
}
|
|
1083
|
-
continue;
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
// ── Colon-separated metadata / options (legacy + data lines) ──
|
|
1087
|
-
const colonIndex = line.indexOf(':');
|
|
1088
|
-
|
|
1089
|
-
if (colonIndex !== -1) {
|
|
1090
|
-
const rawKey = line.substring(0, colonIndex).trim();
|
|
1091
|
-
const key = rawKey.toLowerCase();
|
|
1092
|
-
|
|
1093
|
-
// Check for color annotation in raw key: "Label(color)"
|
|
1094
|
-
const colorMatch = rawKey.match(/^(.+?)\(([^)]+)\)\s*$/);
|
|
1095
|
-
|
|
1096
|
-
if (key === 'title') {
|
|
1097
|
-
result.title = line.substring(colonIndex + 1).trim();
|
|
1098
|
-
result.titleLineNumber = lineNumber;
|
|
1099
|
-
if (result.type === 'quadrant') {
|
|
1100
|
-
result.quadrantTitleLineNumber = lineNumber;
|
|
1101
|
-
}
|
|
1102
|
-
continue;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
if (key === 'order') {
|
|
1106
|
-
const v = line
|
|
1107
|
-
.substring(colonIndex + 1)
|
|
1108
|
-
.trim()
|
|
1109
|
-
.toLowerCase();
|
|
1110
|
-
if (v === 'name' || v === 'group' || v === 'degree') {
|
|
1111
|
-
result.arcOrder = v;
|
|
1112
|
-
}
|
|
1113
|
-
continue;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
if (key === 'rotate') {
|
|
1117
|
-
const v = line
|
|
1118
|
-
.substring(colonIndex + 1)
|
|
1119
|
-
.trim()
|
|
1120
|
-
.toLowerCase();
|
|
1121
|
-
if (v === 'none' || v === 'mixed' || v === 'angled') {
|
|
1122
|
-
result.cloudOptions.rotate = v;
|
|
1123
|
-
}
|
|
1124
|
-
continue;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
if (key === 'max') {
|
|
1128
|
-
const v = parseInt(line.substring(colonIndex + 1).trim(), 10);
|
|
1129
|
-
if (!isNaN(v) && v > 0) {
|
|
1130
|
-
result.cloudOptions.max = v;
|
|
1131
|
-
}
|
|
1132
|
-
continue;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
if (key === 'size') {
|
|
1136
|
-
const v = line.substring(colonIndex + 1).trim();
|
|
1137
|
-
const parts = v.split(',').map((s) => parseInt(s.trim(), 10));
|
|
1138
|
-
if (
|
|
1139
|
-
parts.length === 2 &&
|
|
1140
|
-
parts.every((n) => !isNaN(n) && n > 0) &&
|
|
1141
|
-
// In-bounds by length === 2 check.
|
|
1142
|
-
parts[0]! < parts[1]!
|
|
1143
|
-
) {
|
|
1144
|
-
// In-bounds by length === 2 check.
|
|
1145
|
-
result.cloudOptions.minSize = parts[0]!;
|
|
1146
|
-
result.cloudOptions.maxSize = parts[1]!;
|
|
1147
|
-
}
|
|
1148
|
-
continue;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
// Data line: "Label: value1, value2" or "Label(color): value1, value2"
|
|
1152
|
-
// Capture groups 1-2 guaranteed by the regex match.
|
|
1153
|
-
const labelPart = colorMatch ? colorMatch[1]!.trim() : rawKey;
|
|
1154
|
-
const colorPart = colorMatch
|
|
1155
|
-
? (resolveColorWithDiagnostic(
|
|
1156
|
-
colorMatch[2]!.trim(),
|
|
1157
|
-
lineNumber,
|
|
1158
|
-
result.diagnostics,
|
|
1159
|
-
palette
|
|
1160
|
-
) ?? null)
|
|
1161
|
-
: null;
|
|
1162
|
-
const valuePart = line.substring(colonIndex + 1).trim();
|
|
1163
|
-
const values = valuePart.split(',').map((v) => v.trim());
|
|
1164
|
-
|
|
1165
|
-
// Check if this looks like a data line (values should be numeric)
|
|
1166
|
-
const numericValues: number[] = [];
|
|
1167
|
-
let allNumeric = true;
|
|
1168
|
-
for (const v of values) {
|
|
1169
|
-
const num = parseFloat(v);
|
|
1170
|
-
if (isNaN(num)) {
|
|
1171
|
-
allNumeric = false;
|
|
1172
|
-
break;
|
|
1173
|
-
}
|
|
1174
|
-
numericValues.push(num);
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
if (allNumeric && numericValues.length > 0) {
|
|
1178
|
-
// Wordcloud does not use colon data format — skip to freeform handling
|
|
1179
|
-
if (result.type !== 'wordcloud') {
|
|
1180
|
-
result.data.push({
|
|
1181
|
-
label: labelPart,
|
|
1182
|
-
values: numericValues,
|
|
1183
|
-
color: colorPart,
|
|
1184
|
-
lineNumber,
|
|
1185
|
-
});
|
|
1186
|
-
continue;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
// For wordcloud: collect non-metadata lines for freeform fallback
|
|
1192
|
-
if (result.type === 'wordcloud') {
|
|
1193
|
-
if (colonIndex === -1 && !line.includes(' ')) {
|
|
1194
|
-
// Single bare word — structured mode
|
|
1195
|
-
result.words.push({ text: line, weight: 10, lineNumber });
|
|
1196
|
-
} else if (colonIndex === -1) {
|
|
1197
|
-
// Try "word weight" or "multi-word-label weight" space-separated format
|
|
1198
|
-
const lastSpace = line.lastIndexOf(' ');
|
|
1199
|
-
const rawWeight = lastSpace >= 0 ? line.substring(lastSpace + 1) : '';
|
|
1200
|
-
const maybeWeight =
|
|
1201
|
-
lastSpace >= 0
|
|
1202
|
-
? parseFloat(normalizeNumericToken(rawWeight) ?? rawWeight)
|
|
1203
|
-
: NaN;
|
|
1204
|
-
if (lastSpace >= 0 && !isNaN(maybeWeight) && maybeWeight > 0) {
|
|
1205
|
-
const wordText = line.substring(0, lastSpace).trim();
|
|
1206
|
-
result.words.push({
|
|
1207
|
-
text: wordText,
|
|
1208
|
-
weight: maybeWeight,
|
|
1209
|
-
lineNumber,
|
|
1210
|
-
});
|
|
1211
|
-
} else {
|
|
1212
|
-
freeformLines.push(line);
|
|
1213
|
-
}
|
|
1214
|
-
} else {
|
|
1215
|
-
// Non-numeric colon line — freeform text
|
|
1216
|
-
freeformLines.push(line);
|
|
1217
|
-
}
|
|
1218
|
-
continue;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
// Catch-all: nothing matched this line
|
|
1222
|
-
// Skip on first line — chart type suggestion is handled post-loop
|
|
1223
|
-
if (firstLineParsed) {
|
|
1224
|
-
warn(lineNumber, `Unexpected line: '${line}'.`);
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
// Validation
|
|
1229
|
-
if (!result.type) {
|
|
1230
|
-
const validD3Types = [...VALID_D3_TYPES];
|
|
1231
|
-
const firstNonEmpty =
|
|
1232
|
-
lines.find((l) => l.trim() && !l.trim().startsWith('//'))?.trim() ?? '';
|
|
1233
|
-
// split always returns at least one element.
|
|
1234
|
-
const hint = suggest(
|
|
1235
|
-
firstNonEmpty.split(/\s/)[0]!.toLowerCase(),
|
|
1236
|
-
validD3Types
|
|
1237
|
-
);
|
|
1238
|
-
let msg = `Unsupported chart type: "${firstNonEmpty.split(/\s/)[0]}". Supported types: ${validD3Types.join(', ')}`;
|
|
1239
|
-
if (hint) msg += `. ${hint}`;
|
|
1240
|
-
return fail(1, msg);
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
// Sequence diagrams are parsed by their own dedicated parser
|
|
1244
|
-
if (result.type === 'sequence') {
|
|
1245
|
-
return result;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
if (result.type === 'wordcloud') {
|
|
1249
|
-
// If no structured words were found, parse freeform text as word frequencies
|
|
1250
|
-
if (result.words.length === 0 && freeformLines.length > 0) {
|
|
1251
|
-
result.words = tokenizeFreeformText(freeformLines.join(' '));
|
|
1252
|
-
}
|
|
1253
|
-
if (result.words.length === 0) {
|
|
1254
|
-
warn(
|
|
1255
|
-
1,
|
|
1256
|
-
'No words found. Add words as "word weight" (space-separated), one per line, or paste freeform text'
|
|
1257
|
-
);
|
|
1258
|
-
}
|
|
1259
|
-
// Apply max word limit (words are already sorted by weight desc for freeform)
|
|
1260
|
-
if (
|
|
1261
|
-
result.cloudOptions.max > 0 &&
|
|
1262
|
-
result.words.length > result.cloudOptions.max
|
|
1263
|
-
) {
|
|
1264
|
-
result.words = result.words
|
|
1265
|
-
.slice()
|
|
1266
|
-
.sort((a, b) => b.weight - a.weight)
|
|
1267
|
-
.slice(0, result.cloudOptions.max);
|
|
1268
|
-
}
|
|
1269
|
-
return result;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
if (result.type === 'arc') {
|
|
1273
|
-
if (result.links.length === 0) {
|
|
1274
|
-
warn(
|
|
1275
|
-
1,
|
|
1276
|
-
'No links found. Add links as "Source -> Target weight" (e.g., "Alice -> Bob 5")'
|
|
1277
|
-
);
|
|
1278
|
-
}
|
|
1279
|
-
// Validate arc ordering vs groups
|
|
1280
|
-
if (result.arcNodeGroups.length > 0) {
|
|
1281
|
-
if (result.arcOrder === 'name' || result.arcOrder === 'degree') {
|
|
1282
|
-
warn(
|
|
1283
|
-
1,
|
|
1284
|
-
`Cannot use "order ${result.arcOrder}" with [Group] headers. Use "order group" or remove group headers.`
|
|
1285
|
-
);
|
|
1286
|
-
result.arcOrder = 'group';
|
|
1287
|
-
}
|
|
1288
|
-
if (result.arcOrder === 'appearance') {
|
|
1289
|
-
result.arcOrder = 'group';
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
return result;
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
if (result.type === 'timeline') {
|
|
1296
|
-
if (result.timelineEvents.length === 0) {
|
|
1297
|
-
warn(
|
|
1298
|
-
1,
|
|
1299
|
-
'No events found. Add events like "1718 Event Name" or "1716 -> 1717 Event Name"'
|
|
1300
|
-
);
|
|
1301
|
-
}
|
|
1302
|
-
// Validate tag values and inject defaults
|
|
1303
|
-
if (result.timelineTagGroups.length > 0) {
|
|
1304
|
-
validateTagValues(
|
|
1305
|
-
result.timelineEvents,
|
|
1306
|
-
result.timelineTagGroups,
|
|
1307
|
-
(line, msg) =>
|
|
1308
|
-
result.diagnostics.push(makeDgmoError(line, msg, 'warning')),
|
|
1309
|
-
suggest
|
|
1310
|
-
);
|
|
1311
|
-
validateTagGroupNames(
|
|
1312
|
-
result.timelineTagGroups,
|
|
1313
|
-
(line, msg) =>
|
|
1314
|
-
result.diagnostics.push(makeDgmoError(line, msg, 'warning')),
|
|
1315
|
-
(line, msg) => {
|
|
1316
|
-
const diag = makeDgmoError(line, msg);
|
|
1317
|
-
result.diagnostics.push(diag);
|
|
1318
|
-
if (!result.error) result.error = formatDgmoError(diag);
|
|
1319
|
-
}
|
|
1320
|
-
);
|
|
1321
|
-
for (const group of result.timelineTagGroups) {
|
|
1322
|
-
if (!group.defaultValue) continue;
|
|
1323
|
-
const key = tagAttrKey(group.name);
|
|
1324
|
-
for (const event of result.timelineEvents) {
|
|
1325
|
-
if (!event.metadata[key]) {
|
|
1326
|
-
event.metadata[key] = group.defaultValue;
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
return result;
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
if (result.type === 'venn') {
|
|
1336
|
-
if (result.vennSets.length < 2) {
|
|
1337
|
-
return fail(
|
|
1338
|
-
1,
|
|
1339
|
-
'At least 2 sets are required. Add set names (e.g., "Apples", "Oranges")'
|
|
1340
|
-
);
|
|
1341
|
-
}
|
|
1342
|
-
if (result.vennSets.length > 3) {
|
|
1343
|
-
return fail(1, 'Venn diagrams support 2–3 sets');
|
|
1344
|
-
}
|
|
1345
|
-
// Build lookup: full name (lowercase) and alias → canonical name
|
|
1346
|
-
const setNameLower = new Map<string, string>(
|
|
1347
|
-
result.vennSets.map((s) => [s.name.toLowerCase(), s.name])
|
|
1348
|
-
);
|
|
1349
|
-
const aliasLower = new Map<string, string>();
|
|
1350
|
-
for (const s of result.vennSets) {
|
|
1351
|
-
if (s.alias) aliasLower.set(s.alias.toLowerCase(), s.name);
|
|
1352
|
-
}
|
|
1353
|
-
const resolveSetRef = (ref: string): string | null =>
|
|
1354
|
-
setNameLower.get(ref.toLowerCase()) ??
|
|
1355
|
-
aliasLower.get(ref.toLowerCase()) ??
|
|
1356
|
-
null;
|
|
1357
|
-
|
|
1358
|
-
// Resolve intersection set references; drop invalid ones with a diagnostic
|
|
1359
|
-
const validOverlaps: VennOverlap[] = [];
|
|
1360
|
-
for (const ov of result.vennOverlaps) {
|
|
1361
|
-
const resolvedSets: string[] = [];
|
|
1362
|
-
let valid = true;
|
|
1363
|
-
for (const ref of ov.sets) {
|
|
1364
|
-
const resolved = resolveSetRef(ref);
|
|
1365
|
-
if (!resolved) {
|
|
1366
|
-
result.diagnostics.push(
|
|
1367
|
-
makeDgmoError(
|
|
1368
|
-
ov.lineNumber,
|
|
1369
|
-
`Intersection references unknown set or alias "${ref}" — declare the set first on its own line ('Name [as <alias>] [color]', e.g. 'Navigation as nav blue'), then reference it by that name or alias. (§16.4)`
|
|
1370
|
-
)
|
|
1371
|
-
);
|
|
1372
|
-
if (!result.error)
|
|
1373
|
-
// diagnostics non-empty: we just pushed to it.
|
|
1374
|
-
result.error = formatDgmoError(
|
|
1375
|
-
result.diagnostics[result.diagnostics.length - 1]!
|
|
1376
|
-
);
|
|
1377
|
-
valid = false;
|
|
1378
|
-
break;
|
|
1379
|
-
}
|
|
1380
|
-
resolvedSets.push(resolved);
|
|
1381
|
-
}
|
|
1382
|
-
if (valid) validOverlaps.push({ ...ov, sets: resolvedSets.sort() });
|
|
1383
|
-
}
|
|
1384
|
-
result.vennOverlaps = validOverlaps;
|
|
1385
|
-
return result;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
if (result.type === 'quadrant') {
|
|
1389
|
-
if (result.quadrantPoints.length === 0) {
|
|
1390
|
-
warn(
|
|
1391
|
-
1,
|
|
1392
|
-
'No data points found. Add points as "Label x, y" (e.g., "Item A 0.5, 0.7")'
|
|
1393
|
-
);
|
|
1394
|
-
}
|
|
1395
|
-
return result;
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// Slope chart validation
|
|
1399
|
-
if (result.periods.length < 2) {
|
|
1400
|
-
return fail(
|
|
1401
|
-
1,
|
|
1402
|
-
"Missing 'period' directive. Add 'period 2020 2024' before data rows (minimum 2 periods required)"
|
|
1403
|
-
);
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
if (result.data.length === 0) {
|
|
1407
|
-
warn(
|
|
1408
|
-
1,
|
|
1409
|
-
"No data lines found. Add data as 'Label value1 value2' (e.g., 'Blackbeard 40 4')"
|
|
1410
|
-
);
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
// Validate value counts match period count — warn and skip mismatched items
|
|
1414
|
-
for (const item of result.data) {
|
|
1415
|
-
if (item.values.length !== result.periods.length) {
|
|
1416
|
-
warn(
|
|
1417
|
-
item.lineNumber,
|
|
1418
|
-
`Data item "${item.label}" has ${item.values.length} value(s) but ${result.periods.length} period(s) are defined`
|
|
1419
|
-
);
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
result.data = result.data.filter(
|
|
1423
|
-
(item) => item.values.length === result.periods.length
|
|
1424
|
-
);
|
|
1425
|
-
|
|
1426
|
-
return result;
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
const STOP_WORDS = new Set([
|
|
1430
|
-
'a',
|
|
1431
|
-
'an',
|
|
1432
|
-
'the',
|
|
1433
|
-
'and',
|
|
1434
|
-
'or',
|
|
1435
|
-
'but',
|
|
1436
|
-
'in',
|
|
1437
|
-
'on',
|
|
1438
|
-
'at',
|
|
1439
|
-
'to',
|
|
1440
|
-
'for',
|
|
1441
|
-
'of',
|
|
1442
|
-
'with',
|
|
1443
|
-
'by',
|
|
1444
|
-
'is',
|
|
1445
|
-
'am',
|
|
1446
|
-
'are',
|
|
1447
|
-
'was',
|
|
1448
|
-
'were',
|
|
1449
|
-
'be',
|
|
1450
|
-
'been',
|
|
1451
|
-
'being',
|
|
1452
|
-
'have',
|
|
1453
|
-
'has',
|
|
1454
|
-
'had',
|
|
1455
|
-
'do',
|
|
1456
|
-
'does',
|
|
1457
|
-
'did',
|
|
1458
|
-
'will',
|
|
1459
|
-
'would',
|
|
1460
|
-
'could',
|
|
1461
|
-
'should',
|
|
1462
|
-
'may',
|
|
1463
|
-
'might',
|
|
1464
|
-
'shall',
|
|
1465
|
-
'can',
|
|
1466
|
-
'it',
|
|
1467
|
-
'its',
|
|
1468
|
-
'this',
|
|
1469
|
-
'that',
|
|
1470
|
-
'these',
|
|
1471
|
-
'those',
|
|
1472
|
-
'i',
|
|
1473
|
-
'me',
|
|
1474
|
-
'my',
|
|
1475
|
-
'we',
|
|
1476
|
-
'us',
|
|
1477
|
-
'our',
|
|
1478
|
-
'you',
|
|
1479
|
-
'your',
|
|
1480
|
-
'he',
|
|
1481
|
-
'him',
|
|
1482
|
-
'his',
|
|
1483
|
-
'she',
|
|
1484
|
-
'her',
|
|
1485
|
-
'they',
|
|
1486
|
-
'them',
|
|
1487
|
-
'their',
|
|
1488
|
-
'what',
|
|
1489
|
-
'which',
|
|
1490
|
-
'who',
|
|
1491
|
-
'whom',
|
|
1492
|
-
'how',
|
|
1493
|
-
'when',
|
|
1494
|
-
'where',
|
|
1495
|
-
'why',
|
|
1496
|
-
'not',
|
|
1497
|
-
'no',
|
|
1498
|
-
'nor',
|
|
1499
|
-
'so',
|
|
1500
|
-
'if',
|
|
1501
|
-
'then',
|
|
1502
|
-
'than',
|
|
1503
|
-
'too',
|
|
1504
|
-
'very',
|
|
1505
|
-
'just',
|
|
1506
|
-
'about',
|
|
1507
|
-
'up',
|
|
1508
|
-
'out',
|
|
1509
|
-
'from',
|
|
1510
|
-
'into',
|
|
1511
|
-
'over',
|
|
1512
|
-
'after',
|
|
1513
|
-
'before',
|
|
1514
|
-
'between',
|
|
1515
|
-
'under',
|
|
1516
|
-
'again',
|
|
1517
|
-
'there',
|
|
1518
|
-
'here',
|
|
1519
|
-
'all',
|
|
1520
|
-
'each',
|
|
1521
|
-
'every',
|
|
1522
|
-
'both',
|
|
1523
|
-
'few',
|
|
1524
|
-
'more',
|
|
1525
|
-
'most',
|
|
1526
|
-
'other',
|
|
1527
|
-
'some',
|
|
1528
|
-
'such',
|
|
1529
|
-
'only',
|
|
1530
|
-
'own',
|
|
1531
|
-
'same',
|
|
1532
|
-
'also',
|
|
1533
|
-
'as',
|
|
1534
|
-
'because',
|
|
1535
|
-
'until',
|
|
1536
|
-
'while',
|
|
1537
|
-
'during',
|
|
1538
|
-
'through',
|
|
1539
|
-
]);
|
|
1540
|
-
|
|
1541
|
-
function tokenizeFreeformText(text: string): WordCloudWord[] {
|
|
1542
|
-
const counts = new Map<string, number>();
|
|
1543
|
-
|
|
1544
|
-
// Split on non-letter/non-apostrophe chars, lowercase everything
|
|
1545
|
-
const tokens = text
|
|
1546
|
-
.toLowerCase()
|
|
1547
|
-
.split(/[^a-zA-Z']+/)
|
|
1548
|
-
.filter(Boolean);
|
|
1549
|
-
|
|
1550
|
-
for (const raw of tokens) {
|
|
1551
|
-
// Strip leading/trailing apostrophes
|
|
1552
|
-
const word = raw.replace(/^'+|'+$/g, '');
|
|
1553
|
-
if (word.length < 2 || STOP_WORDS.has(word)) continue;
|
|
1554
|
-
counts.set(word, (counts.get(word) ?? 0) + 1);
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
return Array.from(counts.entries())
|
|
1558
|
-
.map(([text, count]) => ({ text, weight: count, lineNumber: 0 }))
|
|
1559
|
-
.sort((a, b) => b.weight - a.weight);
|
|
1560
|
-
}
|