@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.
Files changed (534) hide show
  1. package/dist/advanced.d.ts +3 -855
  2. package/dist/advanced.js +541 -70107
  3. package/dist/analyzer-UYKBXAKR.js +15 -0
  4. package/dist/auto.js +1 -1
  5. package/dist/auto.mjs +1 -1
  6. package/dist/block.js +83 -69417
  7. package/dist/calculator-LDYQCDDY.js +11 -0
  8. package/dist/charts-d3-QOZHKTCT.js +25 -0
  9. package/dist/chunk-2DXMZRWJ.js +800 -0
  10. package/dist/chunk-2LACINWN.js +786 -0
  11. package/dist/chunk-33LCUH4H.js +893 -0
  12. package/dist/chunk-3CIOJ5YJ.js +35 -0
  13. package/dist/chunk-3HDBAXF5.js +86 -0
  14. package/dist/chunk-3JLKYGBQ.js +290 -0
  15. package/dist/chunk-3MQQNXPD.js +415 -0
  16. package/dist/chunk-3R5POCTZ.js +967 -0
  17. package/dist/chunk-3SMGIJDD.js +112 -0
  18. package/dist/chunk-44CM72SQ.js +409 -0
  19. package/dist/chunk-4GRI7MEB.js +1604 -0
  20. package/dist/chunk-4OHACBGX.js +606 -0
  21. package/dist/chunk-5DH3KYFH.js +21 -0
  22. package/dist/chunk-5MLYVTYM.js +76 -0
  23. package/dist/chunk-5QXL4NYS.js +553 -0
  24. package/dist/chunk-5WRI5ZAA.js +31 -0
  25. package/dist/chunk-6EK55ART.js +117 -0
  26. package/dist/chunk-6FZYCKXB.js +2065 -0
  27. package/dist/chunk-6HO5ZTXK.js +277 -0
  28. package/dist/chunk-6KFWC4CO.js +7 -0
  29. package/dist/chunk-6MTWIQVO.js +121 -0
  30. package/dist/chunk-6NYWYNBI.js +329 -0
  31. package/dist/chunk-6SM5NJQH.js +620 -0
  32. package/dist/chunk-725LDASD.js +422 -0
  33. package/dist/chunk-76OWZ3BA.js +130 -0
  34. package/{src/block/parser.ts → dist/chunk-7JEGTRLD.js} +100 -189
  35. package/dist/chunk-A44USUV7.js +923 -0
  36. package/dist/chunk-A4GOY3PE.js +25 -0
  37. package/dist/chunk-A4VFQH5G.js +251 -0
  38. package/dist/chunk-AELOBFW5.js +375 -0
  39. package/dist/chunk-AN2MOO5T.js +522 -0
  40. package/dist/chunk-BMR4CIEO.js +41 -0
  41. package/dist/chunk-BS7SV7C3.js +51 -0
  42. package/dist/chunk-BUPDIHML.js +613 -0
  43. package/dist/chunk-BUVJFRPY.js +887 -0
  44. package/dist/chunk-C3Z4DX3V.js +112 -0
  45. package/dist/chunk-CI2G3OEB.js +1818 -0
  46. package/dist/chunk-CKNVO24D.js +54 -0
  47. package/dist/chunk-CUVHQHZ7.js +391 -0
  48. package/dist/chunk-CXK4DZH3.js +114 -0
  49. package/dist/{editor.cjs → chunk-DKEPS5LH.js} +14 -225
  50. package/dist/chunk-DU7W62N5.js +487 -0
  51. package/dist/chunk-DZAQBW35.js +751 -0
  52. package/dist/chunk-EB6FPQUP.js +316 -0
  53. package/dist/chunk-EMS2SK26.js +376 -0
  54. package/dist/chunk-ERLBPDGU.js +510 -0
  55. package/dist/chunk-EVCVDTXE.js +732 -0
  56. package/dist/chunk-FCIIHT24.js +384 -0
  57. package/dist/chunk-FFUN5D66.js +97 -0
  58. package/dist/chunk-FITOLXSX.js +714 -0
  59. package/dist/chunk-FMDSF3OV.js +68 -0
  60. package/dist/chunk-FNB3PNGF.js +410 -0
  61. package/dist/chunk-G2H74XJZ.js +2104 -0
  62. package/dist/chunk-GMR5MFHJ.js +339 -0
  63. package/dist/chunk-H6PI3KJT.js +305 -0
  64. package/dist/chunk-I46X5VUA.js +59 -0
  65. package/dist/chunk-I5IGB56C.js +255 -0
  66. package/dist/chunk-IH6ZMVQ5.js +3950 -0
  67. package/dist/chunk-IJZFN3HM.js +463 -0
  68. package/dist/chunk-IPWPWM6R.js +3079 -0
  69. package/dist/chunk-IYULMRBE.js +23 -0
  70. package/dist/chunk-IZMRON6C.js +167 -0
  71. package/dist/chunk-J5FIXQRT.js +813 -0
  72. package/dist/chunk-JED6ZDEE.js +73 -0
  73. package/dist/chunk-JEUJOX5X.js +136 -0
  74. package/dist/chunk-JM4LEPY3.js +436 -0
  75. package/dist/chunk-L3UHP6TD.js +101 -0
  76. package/dist/chunk-L66GEFB6.js +524 -0
  77. package/dist/chunk-LIOHW36Z.js +469 -0
  78. package/dist/chunk-LP7EC4MI.js +16 -0
  79. package/dist/chunk-MITI6NOL.js +548 -0
  80. package/dist/chunk-MO5YHHMQ.js +168 -0
  81. package/dist/chunk-MZSBFAPJ.js +885 -0
  82. package/dist/chunk-NNAAJVBU.js +9 -0
  83. package/dist/chunk-NOTA6TEI.js +47 -0
  84. package/dist/chunk-NURGB5Z4.js +335 -0
  85. package/dist/chunk-NX4EQS3U.js +1062 -0
  86. package/dist/chunk-NXMESY4J.js +1 -0
  87. package/dist/chunk-OJR3MQYV.js +1458 -0
  88. package/dist/chunk-OURHMODR.js +454 -0
  89. package/dist/chunk-P235SVAH.js +1057 -0
  90. package/dist/chunk-P4RH4OSP.js +1038 -0
  91. package/dist/chunk-P6T66CCZ.js +226 -0
  92. package/dist/chunk-PBAHEPLQ.js +223 -0
  93. package/dist/chunk-PBHE5UG6.js +1530 -0
  94. package/dist/chunk-PDKUANDZ.js +35 -0
  95. package/dist/chunk-PWIHZ6NJ.js +296 -0
  96. package/{src/c4/layout.ts → dist/chunk-QEBUY5NY.js} +384 -1140
  97. package/dist/chunk-QLCCJEJI.js +253 -0
  98. package/dist/chunk-QOOUC2J5.js +52 -0
  99. package/dist/chunk-QYTPPDAO.js +402 -0
  100. package/dist/chunk-R3XI2VC7.js +801 -0
  101. package/dist/chunk-RCERWOJR.js +154 -0
  102. package/dist/chunk-RJE5JTVK.js +362 -0
  103. package/dist/chunk-RTLBSQ3G.js +1155 -0
  104. package/dist/chunk-RUNNI6VE.js +448 -0
  105. package/dist/chunk-RVMRZAPB.js +354 -0
  106. package/dist/chunk-RYEGFMUO.js +955 -0
  107. package/dist/chunk-RZCF2VVM.js +528 -0
  108. package/dist/chunk-SML67VPV.js +1025 -0
  109. package/dist/chunk-SQ7IY53Q.js +44 -0
  110. package/dist/chunk-SQAG3UXH.js +161 -0
  111. package/dist/chunk-SRTU6HMQ.js +1851 -0
  112. package/dist/chunk-T2OE3YQB.js +550 -0
  113. package/dist/chunk-T3UBLACL.js +299 -0
  114. package/dist/chunk-TLVIHFRO.js +296 -0
  115. package/dist/chunk-TYS6CSS5.js +52 -0
  116. package/dist/chunk-U6EQI3U6.js +130 -0
  117. package/dist/chunk-UJ4FJLBF.js +86 -0
  118. package/dist/chunk-ULOEATBZ.js +347 -0
  119. package/dist/chunk-UNCCZ3NU.js +127 -0
  120. package/dist/chunk-USZW2HHU.js +197 -0
  121. package/dist/chunk-UWN33VNX.js +1610 -0
  122. package/dist/chunk-UZ7EZW3B.js +40 -0
  123. package/dist/chunk-VOKGH627.js +37 -0
  124. package/dist/chunk-WBRZJOEC.js +204 -0
  125. package/dist/chunk-WCSOJFU3.js +380 -0
  126. package/dist/chunk-WCT3NYCV.js +160 -0
  127. package/dist/chunk-WVAJFAIA.js +529 -0
  128. package/dist/chunk-X5LCYCL5.js +394 -0
  129. package/dist/chunk-XAKEUMEJ.js +1256 -0
  130. package/dist/chunk-Y2ZE3MSG.js +590 -0
  131. package/dist/chunk-Y3J7VR52.js +646 -0
  132. package/dist/chunk-Y5QGVTVC.js +33 -0
  133. package/dist/chunk-YAA6IID3.js +548 -0
  134. package/dist/chunk-YGJTDXVR.js +692 -0
  135. package/{src/utils/time-ticks.ts → dist/chunk-YRYKTYMQ.js} +45 -112
  136. package/dist/chunk-YYVTBZZC.js +61 -0
  137. package/dist/chunk-Z5CGSFJB.js +647 -0
  138. package/{src/gantt/calculator.ts → dist/chunk-Z72XRXD4.js} +236 -428
  139. package/dist/chunk-ZGR7ULFJ.js +978 -0
  140. package/dist/chunk-ZJEL4H2R.js +851 -0
  141. package/dist/chunk-ZO27FGJS.js +554 -0
  142. package/dist/collapse-CLPJD2RL.js +11 -0
  143. package/dist/collapse-SLINJQW6.js +9 -0
  144. package/dist/collapse-ZWUFFCK2.js +9 -0
  145. package/dist/compute-G55RG53N.js +8 -0
  146. package/dist/dgmo-router-DZCIDUO5.js +73 -0
  147. package/dist/dimensions-J3FHIMJ6.js +22 -0
  148. package/dist/element.js +1 -1
  149. package/dist/element.mjs +1 -1
  150. package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
  151. package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
  152. package/dist/index.d.ts +3 -469
  153. package/dist/index.js +137 -69685
  154. package/dist/layout-46N4JJYK.js +17 -0
  155. package/dist/layout-5RA6QWR6.js +26 -0
  156. package/dist/layout-5T6RH5M6.js +14 -0
  157. package/dist/layout-7QXCNOVS.js +13 -0
  158. package/dist/layout-EE6GE46Y.js +17 -0
  159. package/dist/layout-G23F7LDH.js +9 -0
  160. package/dist/layout-HRBZPFCN.js +23 -0
  161. package/dist/layout-HWDSRX5E.js +20 -0
  162. package/dist/layout-HYZPWAK5.js +18 -0
  163. package/dist/layout-N76NR4ZO.js +19 -0
  164. package/dist/layout-OB3RI63S.js +14 -0
  165. package/dist/layout-S2QBYSK2.js +15 -0
  166. package/dist/layout-UM5F4DJB.js +15 -0
  167. package/dist/layout-search-OYV77HHS.js +1655 -0
  168. package/dist/load-data-EHDDZ5ZS.js +8 -0
  169. package/dist/palettes-PRJ2RI6D.js +56 -0
  170. package/dist/parser-2MFP2H2E.js +11 -0
  171. package/dist/parser-3STE62GB.js +21 -0
  172. package/dist/parser-4FL2CMDG.js +18 -0
  173. package/dist/parser-4OGOBJJ4.js +13 -0
  174. package/dist/parser-6BTRLKRZ.js +15 -0
  175. package/dist/parser-AQETEVRW.js +21 -0
  176. package/dist/parser-CFZD5IBL.js +12 -0
  177. package/dist/parser-FUSKFGZN.js +16 -0
  178. package/dist/parser-I6KXEYFK.js +13 -0
  179. package/dist/parser-IXSZZQHY.js +18 -0
  180. package/dist/parser-JAL2HD4I.js +13 -0
  181. package/dist/parser-KNSQSKQO.js +20 -0
  182. package/dist/parser-N5O4IHUN.js +13 -0
  183. package/dist/parser-NGU3ETO6.js +16 -0
  184. package/dist/parser-NRAI6O25.js +19 -0
  185. package/dist/parser-O5J4VBII.js +11 -0
  186. package/dist/parser-OV7NOBW6.js +21 -0
  187. package/dist/parser-PQ3POX45.js +23 -0
  188. package/dist/parser-TFZFBVWN.js +11 -0
  189. package/dist/parser-TRTHDFPP.js +18 -0
  190. package/dist/parser-VQP2UJ6W.js +11 -0
  191. package/dist/parser-XDVELHRK.js +13 -0
  192. package/dist/parser-XKRP7QKP.js +11 -0
  193. package/dist/parser-Y6WTBU76.js +14 -0
  194. package/dist/parser-ZSDPZYQP.js +12 -0
  195. package/dist/renderer-2XQQQ6QR.js +28 -0
  196. package/dist/renderer-5NDVNDAZ.js +26 -0
  197. package/dist/renderer-6EEZHMH4.js +26 -0
  198. package/dist/renderer-6W5462RB.js +43 -0
  199. package/dist/renderer-7MVLWWQD.js +22 -0
  200. package/dist/renderer-AWWAF6XD.js +33 -0
  201. package/dist/renderer-BYCNMBF7.js +34 -0
  202. package/dist/renderer-CLJHQPBJ.js +29 -0
  203. package/dist/renderer-GHPZMFU2.js +15 -0
  204. package/dist/renderer-JGBKUCZD.js +16 -0
  205. package/dist/renderer-KQB7UGDS.js +19 -0
  206. package/dist/renderer-PGX2TKVS.js +24 -0
  207. package/dist/renderer-QIWVHWZO.js +15 -0
  208. package/dist/renderer-RPOA5YPZ.js +35 -0
  209. package/dist/renderer-SMIBCRVB.js +24 -0
  210. package/dist/renderer-T7YKC5VP.js +28 -0
  211. package/dist/renderer-TOCOVLHS.js +37 -0
  212. package/dist/renderer-UWA5XS74.js +39 -0
  213. package/dist/renderer-VACVQYML.js +25 -0
  214. package/dist/renderer-W4M6QLIJ.js +37 -0
  215. package/dist/renderer-WZRREQ2P.js +23 -0
  216. package/dist/renderer-YY2OSF6E.js +27 -0
  217. package/dist/renderer-Z7I3W2PP.js +24 -0
  218. package/dist/renderer-ZGNTUWC4.js +22 -0
  219. package/dist/renderer-ZTR3DER3.js +19 -0
  220. package/dist/renderer-radial-C32GPJQ7.js +21 -0
  221. package/dist/resolver-NOAA7ID7.js +13 -0
  222. package/dist/state-collapse-HCGD6IVJ.js +8 -0
  223. package/dist/state-parser-Z2V23ATR.js +18 -0
  224. package/dist/state-renderer-RFYHSP4K.js +34 -0
  225. package/dist/themes-1-CuKpeH.d.ts +908 -0
  226. package/package.json +19 -70
  227. package/dist/advanced.cjs +0 -71648
  228. package/dist/advanced.d.cts +0 -6571
  229. package/dist/auto.cjs +0 -70648
  230. package/dist/auto.d.cts +0 -54
  231. package/dist/block.cjs +0 -70208
  232. package/dist/block.d.cts +0 -115
  233. package/dist/editor.d.cts +0 -26
  234. package/dist/element.cjs +0 -70530
  235. package/dist/element.d.cts +0 -62
  236. package/dist/highlight.cjs +0 -852
  237. package/dist/highlight.d.cts +0 -49
  238. package/dist/index.cjs +0 -70491
  239. package/dist/index.d.cts +0 -626
  240. package/dist/pert.cjs +0 -325
  241. package/dist/pert.d.cts +0 -559
  242. package/src/advanced.ts +0 -873
  243. package/src/arc/parser.ts +0 -15
  244. package/src/arc/renderer.ts +0 -633
  245. package/src/auto/index.ts +0 -621
  246. package/src/auto/shared.ts +0 -370
  247. package/src/auto/styles.ts +0 -73
  248. package/src/block/layout.ts +0 -198
  249. package/src/block/renderer.ts +0 -484
  250. package/src/block/types.ts +0 -67
  251. package/src/boxes-and-lines/collapse.ts +0 -96
  252. package/src/boxes-and-lines/focus.ts +0 -257
  253. package/src/boxes-and-lines/label-placement.ts +0 -339
  254. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  255. package/src/boxes-and-lines/layout-layered.ts +0 -722
  256. package/src/boxes-and-lines/layout-search.ts +0 -1678
  257. package/src/boxes-and-lines/layout.ts +0 -398
  258. package/src/boxes-and-lines/parser.ts +0 -1181
  259. package/src/boxes-and-lines/renderer.ts +0 -1465
  260. package/src/boxes-and-lines/types.ts +0 -66
  261. package/src/c4/parser.ts +0 -1158
  262. package/src/c4/renderer.ts +0 -2008
  263. package/src/c4/types.ts +0 -89
  264. package/src/chart-type-registry.ts +0 -529
  265. package/src/chart-types.ts +0 -225
  266. package/src/chart.ts +0 -685
  267. package/src/charts-d3/bar.ts +0 -338
  268. package/src/charts-d3/chord.ts +0 -140
  269. package/src/charts-d3/function.ts +0 -147
  270. package/src/charts-d3/funnel.ts +0 -104
  271. package/src/charts-d3/heatmap.ts +0 -145
  272. package/src/charts-d3/index.ts +0 -337
  273. package/src/charts-d3/interactions.ts +0 -477
  274. package/src/charts-d3/line.ts +0 -344
  275. package/src/charts-d3/mount.ts +0 -151
  276. package/src/charts-d3/pie.ts +0 -117
  277. package/src/charts-d3/polar.ts +0 -89
  278. package/src/charts-d3/radar.ts +0 -0
  279. package/src/charts-d3/sankey.ts +0 -0
  280. package/src/charts-d3/scatter.ts +0 -185
  281. package/src/charts-d3/shared.ts +0 -208
  282. package/src/class/layout.ts +0 -326
  283. package/src/class/parser.ts +0 -635
  284. package/src/class/renderer.ts +0 -793
  285. package/src/class/types.ts +0 -58
  286. package/src/cli-banner.ts +0 -106
  287. package/src/cli.ts +0 -1080
  288. package/src/colors-diagnostics.ts +0 -35
  289. package/src/colors.ts +0 -379
  290. package/src/completion-types.ts +0 -27
  291. package/src/completion.ts +0 -2668
  292. package/src/cycle/layout.ts +0 -1026
  293. package/src/cycle/parser.ts +0 -387
  294. package/src/cycle/renderer.ts +0 -653
  295. package/src/cycle/types.ts +0 -101
  296. package/src/d3.ts +0 -1434
  297. package/src/data-chart-parser.ts +0 -1007
  298. package/src/dgmo-router.ts +0 -296
  299. package/src/diagnostics-registry.ts +0 -108
  300. package/src/diagnostics.ts +0 -0
  301. package/src/dimensions.ts +0 -20
  302. package/src/directives-registry.ts +0 -396
  303. package/src/editor/dgmo.grammar +0 -76
  304. package/src/editor/dgmo.grammar.d.ts +0 -2
  305. package/src/editor/dgmo.grammar.js +0 -18
  306. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  307. package/src/editor/dgmo.grammar.terms.js +0 -35
  308. package/src/editor/highlight-api.ts +0 -596
  309. package/src/editor/highlight.ts +0 -36
  310. package/src/editor/index.ts +0 -27
  311. package/src/editor/keywords.ts +0 -128
  312. package/src/editor/tokens.ts +0 -30
  313. package/src/element/index.ts +0 -432
  314. package/src/embed/css.ts +0 -312
  315. package/src/embed/escape.ts +0 -15
  316. package/src/embed/index.ts +0 -280
  317. package/src/er/classify.ts +0 -228
  318. package/src/er/layout.ts +0 -530
  319. package/src/er/parser.ts +0 -750
  320. package/src/er/renderer.ts +0 -725
  321. package/src/er/types.ts +0 -52
  322. package/src/error-card.ts +0 -269
  323. package/src/event-line/diagnostics.ts +0 -117
  324. package/src/event-line/parser.ts +0 -643
  325. package/src/event-line/renderer.ts +0 -1742
  326. package/src/event-line/types.ts +0 -74
  327. package/src/fonts.ts +0 -3
  328. package/src/gantt/parser.ts +0 -1641
  329. package/src/gantt/renderer.ts +0 -3787
  330. package/src/gantt/resolver.ts +0 -172
  331. package/src/gantt/types.ts +0 -206
  332. package/src/graph/diagnostics.ts +0 -64
  333. package/src/graph/edge-spline.ts +0 -29
  334. package/src/graph/flowchart-parser.ts +0 -759
  335. package/src/graph/flowchart-renderer.ts +0 -817
  336. package/src/graph/layout.ts +0 -472
  337. package/src/graph/notes.ts +0 -21
  338. package/src/graph/state-collapse.ts +0 -78
  339. package/src/graph/state-parser.ts +0 -574
  340. package/src/graph/state-renderer.ts +0 -653
  341. package/src/graph/types.ts +0 -65
  342. package/src/index.ts +0 -261
  343. package/src/infra/compute.ts +0 -1385
  344. package/src/infra/layout.ts +0 -888
  345. package/src/infra/parser.ts +0 -1234
  346. package/src/infra/renderer.ts +0 -2534
  347. package/src/infra/roles.ts +0 -75
  348. package/src/infra/types.ts +0 -216
  349. package/src/infra/validation.ts +0 -201
  350. package/src/journey-map/layout.ts +0 -490
  351. package/src/journey-map/parser.ts +0 -557
  352. package/src/journey-map/renderer.ts +0 -1580
  353. package/src/journey-map/types.ts +0 -47
  354. package/src/kanban/mutations.ts +0 -175
  355. package/src/kanban/parser.ts +0 -509
  356. package/src/kanban/renderer.ts +0 -1449
  357. package/src/kanban/types.ts +0 -39
  358. package/src/label-layout.ts +0 -368
  359. package/src/map/colorize.ts +0 -54
  360. package/src/map/completion.ts +0 -316
  361. package/src/map/context-labels.ts +0 -609
  362. package/src/map/data/PROVENANCE.json +0 -1
  363. package/src/map/data/README.md +0 -40
  364. package/src/map/data/airport-collisions.json +0 -1
  365. package/src/map/data/airports.json +0 -1
  366. package/src/map/data/gazetteer.json +0 -1
  367. package/src/map/data/lakes.json +0 -1
  368. package/src/map/data/mountain-ranges.json +0 -1
  369. package/src/map/data/na-lakes.json +0 -1
  370. package/src/map/data/na-land.json +0 -1
  371. package/src/map/data/region-names.json +0 -1
  372. package/src/map/data/rivers.json +0 -1
  373. package/src/map/data/types.ts +0 -121
  374. package/src/map/data/us-states.json +0 -1
  375. package/src/map/data/water-bodies.json +0 -1
  376. package/src/map/data/world-coarse.json +0 -1
  377. package/src/map/data/world-detail.json +0 -1
  378. package/src/map/diagnostics.ts +0 -134
  379. package/src/map/dimensions.ts +0 -150
  380. package/src/map/geo-query.ts +0 -295
  381. package/src/map/geo.ts +0 -438
  382. package/src/map/invert.ts +0 -111
  383. package/src/map/layout.ts +0 -5069
  384. package/src/map/legend-band.ts +0 -127
  385. package/src/map/load-data.ts +0 -186
  386. package/src/map/parser.ts +0 -783
  387. package/src/map/renderer.ts +0 -1445
  388. package/src/map/resolved-types.ts +0 -160
  389. package/src/map/resolver.ts +0 -1146
  390. package/src/map/types.ts +0 -194
  391. package/src/mindmap/collapse.ts +0 -67
  392. package/src/mindmap/layout.ts +0 -649
  393. package/src/mindmap/parser.ts +0 -430
  394. package/src/mindmap/renderer.ts +0 -637
  395. package/src/mindmap/text-wrap.ts +0 -218
  396. package/src/mindmap/types.ts +0 -56
  397. package/src/org/collapse.ts +0 -151
  398. package/src/org/layout.ts +0 -1262
  399. package/src/org/parser.ts +0 -519
  400. package/src/org/renderer.ts +0 -866
  401. package/src/org/resolver.ts +0 -636
  402. package/src/palettes/atlas.ts +0 -77
  403. package/src/palettes/blueprint.ts +0 -73
  404. package/src/palettes/catppuccin.ts +0 -84
  405. package/src/palettes/color-utils.ts +0 -443
  406. package/src/palettes/index.ts +0 -67
  407. package/src/palettes/nord.ts +0 -67
  408. package/src/palettes/registry.ts +0 -114
  409. package/src/palettes/slate.ts +0 -73
  410. package/src/palettes/tidewater.ts +0 -73
  411. package/src/palettes/tokyo-night.ts +0 -86
  412. package/src/palettes/types.ts +0 -88
  413. package/src/pert/analyzer.ts +0 -1183
  414. package/src/pert/diagnostics.ts +0 -110
  415. package/src/pert/index.ts +0 -43
  416. package/src/pert/internal.ts +0 -252
  417. package/src/pert/layout.ts +0 -971
  418. package/src/pert/monte-carlo.ts +0 -446
  419. package/src/pert/parser.ts +0 -1606
  420. package/src/pert/renderer.ts +0 -4009
  421. package/src/pert/share-normalize.ts +0 -62
  422. package/src/pert/types.ts +0 -604
  423. package/src/pyramid/parser.ts +0 -185
  424. package/src/pyramid/renderer.ts +0 -768
  425. package/src/pyramid/types.ts +0 -28
  426. package/src/quadrant/parser.ts +0 -18
  427. package/src/quadrant/renderer.ts +0 -638
  428. package/src/raci/diagnostics.ts +0 -213
  429. package/src/raci/index.ts +0 -23
  430. package/src/raci/mutations.ts +0 -321
  431. package/src/raci/parser.ts +0 -699
  432. package/src/raci/renderer.ts +0 -1627
  433. package/src/raci/types.ts +0 -82
  434. package/src/raci/variants.ts +0 -320
  435. package/src/render.ts +0 -268
  436. package/src/ring/parser.ts +0 -212
  437. package/src/ring/renderer.ts +0 -490
  438. package/src/ring/types.ts +0 -27
  439. package/src/sequence/collapse.ts +0 -174
  440. package/src/sequence/parser.ts +0 -1596
  441. package/src/sequence/participant-inference.ts +0 -152
  442. package/src/sequence/renderer.ts +0 -3168
  443. package/src/sequence/tag-resolution.ts +0 -164
  444. package/src/sharing.ts +0 -211
  445. package/src/sitemap/collapse.ts +0 -100
  446. package/src/sitemap/layout.ts +0 -925
  447. package/src/sitemap/parser.ts +0 -755
  448. package/src/sitemap/renderer.ts +0 -828
  449. package/src/sitemap/types.ts +0 -42
  450. package/src/slope/parser.ts +0 -18
  451. package/src/slope/renderer.ts +0 -378
  452. package/src/swimlane/diagnostics.ts +0 -94
  453. package/src/swimlane/layout.ts +0 -597
  454. package/src/swimlane/parser.ts +0 -613
  455. package/src/swimlane/renderer.ts +0 -681
  456. package/src/swimlane/types.ts +0 -151
  457. package/src/tech-radar/interactive.ts +0 -1129
  458. package/src/tech-radar/layout.ts +0 -197
  459. package/src/tech-radar/parser.ts +0 -436
  460. package/src/tech-radar/renderer.ts +0 -1239
  461. package/src/tech-radar/shared.ts +0 -151
  462. package/src/tech-radar/types.ts +0 -86
  463. package/src/themes.ts +0 -22
  464. package/src/timeline/parser.ts +0 -472
  465. package/src/timeline/renderer.ts +0 -3024
  466. package/src/timeline/types.ts +0 -33
  467. package/src/timeline/viz-parser.ts +0 -21
  468. package/src/treemap/diagnostics.ts +0 -62
  469. package/src/treemap/layout-radial.ts +0 -233
  470. package/src/treemap/layout.ts +0 -189
  471. package/src/treemap/parser.ts +0 -463
  472. package/src/treemap/renderer-radial.ts +0 -438
  473. package/src/treemap/renderer.ts +0 -572
  474. package/src/treemap/treemap-shared.ts +0 -309
  475. package/src/treemap/types.ts +0 -60
  476. package/src/utils/arrow-markers.ts +0 -51
  477. package/src/utils/arrows-diagnostics.ts +0 -62
  478. package/src/utils/arrows.ts +0 -227
  479. package/src/utils/brand.ts +0 -61
  480. package/src/utils/card.ts +0 -183
  481. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  482. package/src/utils/collapse-engine/tree.ts +0 -117
  483. package/src/utils/d3-helpers.ts +0 -219
  484. package/src/utils/d3-types.ts +0 -9
  485. package/src/utils/description-helpers.ts +0 -38
  486. package/src/utils/duration.ts +0 -312
  487. package/src/utils/export-container.ts +0 -41
  488. package/src/utils/expr-eval.ts +0 -27
  489. package/src/utils/fit-canvas.ts +0 -64
  490. package/src/utils/hover-styles.ts +0 -673
  491. package/src/utils/inline-markdown.ts +0 -96
  492. package/src/utils/legend-constants.ts +0 -48
  493. package/src/utils/legend-d3.ts +0 -618
  494. package/src/utils/legend-integration.ts +0 -107
  495. package/src/utils/legend-layout.ts +0 -776
  496. package/src/utils/legend-svg.ts +0 -214
  497. package/src/utils/legend-types.ts +0 -272
  498. package/src/utils/name-normalize.ts +0 -163
  499. package/src/utils/note-box/constants.ts +0 -25
  500. package/src/utils/note-box/index.ts +0 -11
  501. package/src/utils/note-box/metrics.ts +0 -90
  502. package/src/utils/note-box/svg.ts +0 -331
  503. package/src/utils/notes/bounds.ts +0 -30
  504. package/src/utils/notes/build.ts +0 -131
  505. package/src/utils/notes/index.ts +0 -18
  506. package/src/utils/notes/model.ts +0 -19
  507. package/src/utils/notes/parse.ts +0 -131
  508. package/src/utils/notes/place.ts +0 -177
  509. package/src/utils/notes/resolve.ts +0 -88
  510. package/src/utils/number-format.ts +0 -36
  511. package/src/utils/parsing.ts +0 -953
  512. package/src/utils/reserved-key-registry.ts +0 -239
  513. package/src/utils/safe-href.ts +0 -38
  514. package/src/utils/scaling.ts +0 -97
  515. package/src/utils/svg-embed.ts +0 -251
  516. package/src/utils/tag-groups.ts +0 -607
  517. package/src/utils/text-measure.ts +0 -122
  518. package/src/utils/title-constants.ts +0 -24
  519. package/src/utils/visual-conventions.ts +0 -61
  520. package/src/utils/wrapped-desc.ts +0 -87
  521. package/src/venn/parser.ts +0 -18
  522. package/src/venn/renderer.ts +0 -1047
  523. package/src/version-control/diagnostics.ts +0 -67
  524. package/src/version-control/parser.ts +0 -544
  525. package/src/version-control/renderer.ts +0 -724
  526. package/src/version-control/types.ts +0 -100
  527. package/src/visualizations/parse.ts +0 -1560
  528. package/src/visualizations/types.ts +0 -262
  529. package/src/wireframe/layout.ts +0 -479
  530. package/src/wireframe/parser.ts +0 -1114
  531. package/src/wireframe/renderer.ts +0 -1318
  532. package/src/wireframe/types.ts +0 -110
  533. package/src/wordcloud/parser.ts +0 -18
  534. 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
- }