@diagrammo/dgmo 0.47.0 → 0.48.1

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 (345) hide show
  1. package/dist/advanced.js +11 -11
  2. package/dist/auto.js +16 -16
  3. package/dist/auto.mjs +41 -12
  4. package/dist/block.js +4 -4
  5. package/dist/{chunk-FNB3PNGF.js → chunk-7DTWEFUE.js} +3 -3
  6. package/dist/{chunk-2DXMZRWJ.js → chunk-B65G5DNL.js} +8 -1
  7. package/dist/{chunk-RZCF2VVM.js → chunk-B7JUCQUA.js} +3 -1
  8. package/dist/{chunk-DU7W62N5.js → chunk-FANX6UJR.js} +3 -3
  9. package/dist/{chunk-6FZYCKXB.js → chunk-HSH73DP6.js} +2 -2
  10. package/dist/{chunk-P4RH4OSP.js → chunk-ILKORZ3H.js} +2 -2
  11. package/dist/{chunk-Y5QGVTVC.js → chunk-JTSMEYIZ.js} +7 -3
  12. package/dist/{chunk-RCERWOJR.js → chunk-ODZVHU3R.js} +1 -2
  13. package/dist/{chunk-DZAQBW35.js → chunk-SGZYXELT.js} +3 -7
  14. package/dist/{chunk-CUVHQHZ7.js → chunk-THXCFRTU.js} +3 -3
  15. package/dist/{chunk-IH6ZMVQ5.js → chunk-UC7TFHUM.js} +29 -11
  16. package/dist/{chunk-EMS2SK26.js → chunk-ZBWMSD5N.js} +3 -1
  17. package/dist/cli.cjs +15 -15
  18. package/dist/{dgmo-router-DZCIDUO5.js → dgmo-router-X35VLZLM.js} +3 -3
  19. package/dist/element.js +16 -16
  20. package/dist/element.mjs +41 -12
  21. package/dist/{flowchart-renderer-ZAEOL4DX.js → flowchart-renderer-SNDSII4D.js} +2 -2
  22. package/dist/index.d.ts +4 -2
  23. package/dist/index.js +4 -4
  24. package/dist/{parser-FUSKFGZN.js → parser-2EWECDSA.js} +2 -2
  25. package/dist/{renderer-UWA5XS74.js → renderer-4JHRUBE6.js} +3 -3
  26. package/dist/{renderer-WZRREQ2P.js → renderer-5MYBMBYO.js} +2 -2
  27. package/dist/{renderer-TOCOVLHS.js → renderer-B2OMQIQG.js} +3 -3
  28. package/dist/{renderer-2XQQQ6QR.js → renderer-N43WR5GV.js} +3 -3
  29. package/dist/{renderer-T7YKC5VP.js → renderer-ZRNG54CW.js} +3 -3
  30. package/dist/{state-renderer-RFYHSP4K.js → state-renderer-B4DQBM6M.js} +2 -2
  31. package/package.json +23 -71
  32. package/dist/advanced.cjs +0 -71648
  33. package/dist/advanced.d.cts +0 -5719
  34. package/dist/auto.cjs +0 -70648
  35. package/dist/auto.d.cts +0 -54
  36. package/dist/block.cjs +0 -70208
  37. package/dist/block.d.cts +0 -115
  38. package/dist/editor.cjs +0 -526
  39. package/dist/editor.d.cts +0 -26
  40. package/dist/element.cjs +0 -70530
  41. package/dist/element.d.cts +0 -62
  42. package/dist/highlight.cjs +0 -852
  43. package/dist/highlight.d.cts +0 -49
  44. package/dist/index.cjs +0 -70491
  45. package/dist/index.d.cts +0 -160
  46. package/dist/pert.cjs +0 -325
  47. package/dist/pert.d.cts +0 -559
  48. package/dist/themes-1-CuKpeH.d.cts +0 -908
  49. package/src/advanced.ts +0 -873
  50. package/src/arc/parser.ts +0 -15
  51. package/src/arc/renderer.ts +0 -633
  52. package/src/auto/index.ts +0 -621
  53. package/src/auto/shared.ts +0 -370
  54. package/src/auto/styles.ts +0 -73
  55. package/src/block/layout.ts +0 -198
  56. package/src/block/parser.ts +0 -427
  57. package/src/block/renderer.ts +0 -484
  58. package/src/block/types.ts +0 -67
  59. package/src/boxes-and-lines/collapse.ts +0 -96
  60. package/src/boxes-and-lines/focus.ts +0 -257
  61. package/src/boxes-and-lines/label-placement.ts +0 -339
  62. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  63. package/src/boxes-and-lines/layout-layered.ts +0 -722
  64. package/src/boxes-and-lines/layout-search.ts +0 -1678
  65. package/src/boxes-and-lines/layout.ts +0 -398
  66. package/src/boxes-and-lines/parser.ts +0 -1181
  67. package/src/boxes-and-lines/renderer.ts +0 -1465
  68. package/src/boxes-and-lines/types.ts +0 -66
  69. package/src/c4/layout.ts +0 -2441
  70. package/src/c4/parser.ts +0 -1158
  71. package/src/c4/renderer.ts +0 -2008
  72. package/src/c4/types.ts +0 -89
  73. package/src/chart-type-registry.ts +0 -529
  74. package/src/chart-types.ts +0 -225
  75. package/src/chart.ts +0 -685
  76. package/src/charts-d3/bar.ts +0 -338
  77. package/src/charts-d3/chord.ts +0 -140
  78. package/src/charts-d3/function.ts +0 -147
  79. package/src/charts-d3/funnel.ts +0 -104
  80. package/src/charts-d3/heatmap.ts +0 -145
  81. package/src/charts-d3/index.ts +0 -337
  82. package/src/charts-d3/interactions.ts +0 -477
  83. package/src/charts-d3/line.ts +0 -344
  84. package/src/charts-d3/mount.ts +0 -151
  85. package/src/charts-d3/pie.ts +0 -117
  86. package/src/charts-d3/polar.ts +0 -89
  87. package/src/charts-d3/radar.ts +0 -0
  88. package/src/charts-d3/sankey.ts +0 -0
  89. package/src/charts-d3/scatter.ts +0 -185
  90. package/src/charts-d3/shared.ts +0 -208
  91. package/src/class/layout.ts +0 -326
  92. package/src/class/parser.ts +0 -635
  93. package/src/class/renderer.ts +0 -793
  94. package/src/class/types.ts +0 -58
  95. package/src/cli-banner.ts +0 -106
  96. package/src/cli.ts +0 -1080
  97. package/src/colors-diagnostics.ts +0 -35
  98. package/src/colors.ts +0 -379
  99. package/src/completion-types.ts +0 -27
  100. package/src/completion.ts +0 -2668
  101. package/src/cycle/layout.ts +0 -1026
  102. package/src/cycle/parser.ts +0 -387
  103. package/src/cycle/renderer.ts +0 -653
  104. package/src/cycle/types.ts +0 -101
  105. package/src/d3.ts +0 -1434
  106. package/src/data-chart-parser.ts +0 -1007
  107. package/src/dgmo-router.ts +0 -296
  108. package/src/diagnostics-registry.ts +0 -108
  109. package/src/diagnostics.ts +0 -0
  110. package/src/dimensions.ts +0 -20
  111. package/src/directives-registry.ts +0 -396
  112. package/src/editor/dgmo.grammar +0 -76
  113. package/src/editor/dgmo.grammar.d.ts +0 -2
  114. package/src/editor/dgmo.grammar.js +0 -18
  115. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  116. package/src/editor/dgmo.grammar.terms.js +0 -35
  117. package/src/editor/highlight-api.ts +0 -596
  118. package/src/editor/highlight.ts +0 -36
  119. package/src/editor/index.ts +0 -27
  120. package/src/editor/keywords.ts +0 -128
  121. package/src/editor/tokens.ts +0 -30
  122. package/src/element/index.ts +0 -432
  123. package/src/embed/css.ts +0 -312
  124. package/src/embed/escape.ts +0 -15
  125. package/src/embed/index.ts +0 -280
  126. package/src/er/classify.ts +0 -228
  127. package/src/er/layout.ts +0 -530
  128. package/src/er/parser.ts +0 -750
  129. package/src/er/renderer.ts +0 -725
  130. package/src/er/types.ts +0 -52
  131. package/src/error-card.ts +0 -269
  132. package/src/event-line/diagnostics.ts +0 -117
  133. package/src/event-line/parser.ts +0 -643
  134. package/src/event-line/renderer.ts +0 -1742
  135. package/src/event-line/types.ts +0 -74
  136. package/src/fonts.ts +0 -3
  137. package/src/gantt/calculator.ts +0 -979
  138. package/src/gantt/parser.ts +0 -1641
  139. package/src/gantt/renderer.ts +0 -3787
  140. package/src/gantt/resolver.ts +0 -172
  141. package/src/gantt/types.ts +0 -206
  142. package/src/graph/diagnostics.ts +0 -64
  143. package/src/graph/edge-spline.ts +0 -29
  144. package/src/graph/flowchart-parser.ts +0 -759
  145. package/src/graph/flowchart-renderer.ts +0 -817
  146. package/src/graph/layout.ts +0 -472
  147. package/src/graph/notes.ts +0 -21
  148. package/src/graph/state-collapse.ts +0 -78
  149. package/src/graph/state-parser.ts +0 -574
  150. package/src/graph/state-renderer.ts +0 -653
  151. package/src/graph/types.ts +0 -65
  152. package/src/index.ts +0 -261
  153. package/src/infra/compute.ts +0 -1385
  154. package/src/infra/layout.ts +0 -888
  155. package/src/infra/parser.ts +0 -1234
  156. package/src/infra/renderer.ts +0 -2534
  157. package/src/infra/roles.ts +0 -75
  158. package/src/infra/types.ts +0 -216
  159. package/src/infra/validation.ts +0 -201
  160. package/src/journey-map/layout.ts +0 -490
  161. package/src/journey-map/parser.ts +0 -557
  162. package/src/journey-map/renderer.ts +0 -1580
  163. package/src/journey-map/types.ts +0 -47
  164. package/src/kanban/mutations.ts +0 -175
  165. package/src/kanban/parser.ts +0 -509
  166. package/src/kanban/renderer.ts +0 -1449
  167. package/src/kanban/types.ts +0 -39
  168. package/src/label-layout.ts +0 -368
  169. package/src/map/colorize.ts +0 -54
  170. package/src/map/completion.ts +0 -316
  171. package/src/map/context-labels.ts +0 -609
  172. package/src/map/data/PROVENANCE.json +0 -1
  173. package/src/map/data/README.md +0 -40
  174. package/src/map/data/airport-collisions.json +0 -1
  175. package/src/map/data/airports.json +0 -1
  176. package/src/map/data/gazetteer.json +0 -1
  177. package/src/map/data/lakes.json +0 -1
  178. package/src/map/data/mountain-ranges.json +0 -1
  179. package/src/map/data/na-lakes.json +0 -1
  180. package/src/map/data/na-land.json +0 -1
  181. package/src/map/data/region-names.json +0 -1
  182. package/src/map/data/rivers.json +0 -1
  183. package/src/map/data/types.ts +0 -121
  184. package/src/map/data/us-states.json +0 -1
  185. package/src/map/data/water-bodies.json +0 -1
  186. package/src/map/data/world-coarse.json +0 -1
  187. package/src/map/data/world-detail.json +0 -1
  188. package/src/map/diagnostics.ts +0 -134
  189. package/src/map/dimensions.ts +0 -150
  190. package/src/map/geo-query.ts +0 -295
  191. package/src/map/geo.ts +0 -438
  192. package/src/map/invert.ts +0 -111
  193. package/src/map/layout.ts +0 -5069
  194. package/src/map/legend-band.ts +0 -127
  195. package/src/map/load-data.ts +0 -186
  196. package/src/map/parser.ts +0 -783
  197. package/src/map/renderer.ts +0 -1445
  198. package/src/map/resolved-types.ts +0 -160
  199. package/src/map/resolver.ts +0 -1146
  200. package/src/map/types.ts +0 -194
  201. package/src/mindmap/collapse.ts +0 -67
  202. package/src/mindmap/layout.ts +0 -649
  203. package/src/mindmap/parser.ts +0 -430
  204. package/src/mindmap/renderer.ts +0 -637
  205. package/src/mindmap/text-wrap.ts +0 -218
  206. package/src/mindmap/types.ts +0 -56
  207. package/src/org/collapse.ts +0 -151
  208. package/src/org/layout.ts +0 -1262
  209. package/src/org/parser.ts +0 -519
  210. package/src/org/renderer.ts +0 -866
  211. package/src/org/resolver.ts +0 -636
  212. package/src/palettes/atlas.ts +0 -77
  213. package/src/palettes/blueprint.ts +0 -73
  214. package/src/palettes/catppuccin.ts +0 -84
  215. package/src/palettes/color-utils.ts +0 -443
  216. package/src/palettes/index.ts +0 -67
  217. package/src/palettes/nord.ts +0 -67
  218. package/src/palettes/registry.ts +0 -114
  219. package/src/palettes/slate.ts +0 -73
  220. package/src/palettes/tidewater.ts +0 -73
  221. package/src/palettes/tokyo-night.ts +0 -86
  222. package/src/palettes/types.ts +0 -88
  223. package/src/pert/analyzer.ts +0 -1183
  224. package/src/pert/diagnostics.ts +0 -110
  225. package/src/pert/index.ts +0 -43
  226. package/src/pert/internal.ts +0 -252
  227. package/src/pert/layout.ts +0 -971
  228. package/src/pert/monte-carlo.ts +0 -446
  229. package/src/pert/parser.ts +0 -1606
  230. package/src/pert/renderer.ts +0 -4009
  231. package/src/pert/share-normalize.ts +0 -62
  232. package/src/pert/types.ts +0 -604
  233. package/src/pyramid/parser.ts +0 -185
  234. package/src/pyramid/renderer.ts +0 -768
  235. package/src/pyramid/types.ts +0 -28
  236. package/src/quadrant/parser.ts +0 -18
  237. package/src/quadrant/renderer.ts +0 -638
  238. package/src/raci/diagnostics.ts +0 -213
  239. package/src/raci/index.ts +0 -23
  240. package/src/raci/mutations.ts +0 -321
  241. package/src/raci/parser.ts +0 -699
  242. package/src/raci/renderer.ts +0 -1627
  243. package/src/raci/types.ts +0 -82
  244. package/src/raci/variants.ts +0 -320
  245. package/src/render.ts +0 -268
  246. package/src/ring/parser.ts +0 -212
  247. package/src/ring/renderer.ts +0 -490
  248. package/src/ring/types.ts +0 -27
  249. package/src/sequence/collapse.ts +0 -174
  250. package/src/sequence/parser.ts +0 -1596
  251. package/src/sequence/participant-inference.ts +0 -152
  252. package/src/sequence/renderer.ts +0 -3168
  253. package/src/sequence/tag-resolution.ts +0 -164
  254. package/src/sharing.ts +0 -211
  255. package/src/sitemap/collapse.ts +0 -100
  256. package/src/sitemap/layout.ts +0 -925
  257. package/src/sitemap/parser.ts +0 -755
  258. package/src/sitemap/renderer.ts +0 -828
  259. package/src/sitemap/types.ts +0 -42
  260. package/src/slope/parser.ts +0 -18
  261. package/src/slope/renderer.ts +0 -378
  262. package/src/swimlane/diagnostics.ts +0 -94
  263. package/src/swimlane/layout.ts +0 -597
  264. package/src/swimlane/parser.ts +0 -613
  265. package/src/swimlane/renderer.ts +0 -681
  266. package/src/swimlane/types.ts +0 -151
  267. package/src/tech-radar/interactive.ts +0 -1129
  268. package/src/tech-radar/layout.ts +0 -197
  269. package/src/tech-radar/parser.ts +0 -436
  270. package/src/tech-radar/renderer.ts +0 -1239
  271. package/src/tech-radar/shared.ts +0 -151
  272. package/src/tech-radar/types.ts +0 -86
  273. package/src/themes.ts +0 -22
  274. package/src/timeline/parser.ts +0 -472
  275. package/src/timeline/renderer.ts +0 -3024
  276. package/src/timeline/types.ts +0 -33
  277. package/src/timeline/viz-parser.ts +0 -21
  278. package/src/treemap/diagnostics.ts +0 -62
  279. package/src/treemap/layout-radial.ts +0 -233
  280. package/src/treemap/layout.ts +0 -189
  281. package/src/treemap/parser.ts +0 -463
  282. package/src/treemap/renderer-radial.ts +0 -438
  283. package/src/treemap/renderer.ts +0 -572
  284. package/src/treemap/treemap-shared.ts +0 -309
  285. package/src/treemap/types.ts +0 -60
  286. package/src/utils/arrow-markers.ts +0 -51
  287. package/src/utils/arrows-diagnostics.ts +0 -62
  288. package/src/utils/arrows.ts +0 -227
  289. package/src/utils/brand.ts +0 -61
  290. package/src/utils/card.ts +0 -183
  291. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  292. package/src/utils/collapse-engine/tree.ts +0 -117
  293. package/src/utils/d3-helpers.ts +0 -219
  294. package/src/utils/d3-types.ts +0 -9
  295. package/src/utils/description-helpers.ts +0 -38
  296. package/src/utils/duration.ts +0 -312
  297. package/src/utils/export-container.ts +0 -41
  298. package/src/utils/expr-eval.ts +0 -27
  299. package/src/utils/fit-canvas.ts +0 -64
  300. package/src/utils/hover-styles.ts +0 -673
  301. package/src/utils/inline-markdown.ts +0 -96
  302. package/src/utils/legend-constants.ts +0 -48
  303. package/src/utils/legend-d3.ts +0 -618
  304. package/src/utils/legend-integration.ts +0 -107
  305. package/src/utils/legend-layout.ts +0 -776
  306. package/src/utils/legend-svg.ts +0 -214
  307. package/src/utils/legend-types.ts +0 -272
  308. package/src/utils/name-normalize.ts +0 -163
  309. package/src/utils/note-box/constants.ts +0 -25
  310. package/src/utils/note-box/index.ts +0 -11
  311. package/src/utils/note-box/metrics.ts +0 -90
  312. package/src/utils/note-box/svg.ts +0 -331
  313. package/src/utils/notes/bounds.ts +0 -30
  314. package/src/utils/notes/build.ts +0 -131
  315. package/src/utils/notes/index.ts +0 -18
  316. package/src/utils/notes/model.ts +0 -19
  317. package/src/utils/notes/parse.ts +0 -131
  318. package/src/utils/notes/place.ts +0 -177
  319. package/src/utils/notes/resolve.ts +0 -88
  320. package/src/utils/number-format.ts +0 -36
  321. package/src/utils/parsing.ts +0 -953
  322. package/src/utils/reserved-key-registry.ts +0 -239
  323. package/src/utils/safe-href.ts +0 -38
  324. package/src/utils/scaling.ts +0 -97
  325. package/src/utils/svg-embed.ts +0 -251
  326. package/src/utils/tag-groups.ts +0 -607
  327. package/src/utils/text-measure.ts +0 -122
  328. package/src/utils/time-ticks.ts +0 -253
  329. package/src/utils/title-constants.ts +0 -24
  330. package/src/utils/visual-conventions.ts +0 -61
  331. package/src/utils/wrapped-desc.ts +0 -87
  332. package/src/venn/parser.ts +0 -18
  333. package/src/venn/renderer.ts +0 -1047
  334. package/src/version-control/diagnostics.ts +0 -67
  335. package/src/version-control/parser.ts +0 -544
  336. package/src/version-control/renderer.ts +0 -724
  337. package/src/version-control/types.ts +0 -100
  338. package/src/visualizations/parse.ts +0 -1560
  339. package/src/visualizations/types.ts +0 -262
  340. package/src/wireframe/layout.ts +0 -479
  341. package/src/wireframe/parser.ts +0 -1114
  342. package/src/wireframe/renderer.ts +0 -1318
  343. package/src/wireframe/types.ts +0 -110
  344. package/src/wordcloud/parser.ts +0 -18
  345. package/src/wordcloud/renderer.ts +0 -357
@@ -1,1007 +0,0 @@
1
- // ============================================================
2
- // Data-chart parser + types + legend-group helpers.
3
- // Extracted from the former echarts.ts when ECharts was removed — this is
4
- // the pure DGMO→ParsedExtendedChart parser (no rendering, no ECharts). The
5
- // hand-built D3 renderers in charts-d3/ consume these types + parsers.
6
- // ============================================================
7
-
8
- import type { LegendGroupData } from './utils/legend-types';
9
-
10
- // ============================================================
11
- // Types
12
- // ============================================================
13
-
14
- export type ExtendedChartType =
15
- | 'sankey'
16
- | 'chord'
17
- | 'function'
18
- | 'scatter'
19
- | 'heatmap'
20
- | 'funnel';
21
-
22
- interface ExtendedChartDataPoint {
23
- label: string;
24
- value: number;
25
- color?: string;
26
- lineNumber: number;
27
- }
28
-
29
- interface ParsedSankeyLink {
30
- source: string;
31
- target: string;
32
- value: number;
33
- color?: string;
34
- directed?: boolean;
35
- lineNumber: number;
36
- }
37
-
38
- interface ParsedFunction {
39
- name: string;
40
- expression: string;
41
- color?: string;
42
- lineNumber: number;
43
- }
44
-
45
- interface ParsedScatterPoint {
46
- name: string;
47
- x: number;
48
- y: number;
49
- size?: number;
50
- color?: string;
51
- category?: string;
52
- lineNumber: number;
53
- }
54
-
55
- interface ParsedHeatmapRow {
56
- label: string;
57
- values: number[];
58
- lineNumber: number;
59
- }
60
-
61
- import type { DgmoError } from './diagnostics';
62
-
63
- // ============================================================
64
- // Discriminated union — Story 109.2a (arch-review). Each extended data-chart
65
- // carries only its own type-specific fields; the parser fills a fat
66
- // `ParsedExtendedChartFull` accumulator and returns it narrowed (type-only,
67
- // runtime-identical). Shared fields live on ParsedExtendedBase.
68
- // ============================================================
69
-
70
- /** Fields shared by every extended data-chart. */
71
- export interface ParsedExtendedBase {
72
- title?: string;
73
- titleLineNumber?: number;
74
- series?: string;
75
- seriesLineNumber?: number;
76
- seriesNames?: string[];
77
- seriesNameLineNumbers?: number[];
78
- seriesNameColors?: (string | undefined)[];
79
- data: ExtendedChartDataPoint[];
80
- xlabel?: string;
81
- xlabelLineNumber?: number;
82
- ylabel?: string;
83
- ylabelLineNumber?: number;
84
- /** X-axis range — read by both function plots and scatter. */
85
- xRange?: { min: number; max: number };
86
- noName?: boolean;
87
- noValue?: boolean;
88
- noPercent?: boolean;
89
- shade?: boolean;
90
- /** Render with full intent saturation instead of the canonical 25% tint. */
91
- solidFill?: boolean;
92
- /** Cross-chart-type: when true, the renderer suppresses the chart title. */
93
- noTitle?: boolean;
94
- categoryColors?: Record<string, string>;
95
- categoryLineNumbers?: Record<string, number>;
96
- nodeColors?: Record<string, string>;
97
- diagnostics: DgmoError[];
98
- error: string | null;
99
- }
100
-
101
- export interface ParsedSankey extends ParsedExtendedBase {
102
- type: 'sankey';
103
- links?: ParsedSankeyLink[];
104
- }
105
-
106
- export interface ParsedChord extends ParsedExtendedBase {
107
- type: 'chord';
108
- links?: ParsedSankeyLink[];
109
- /** `layout arc|chord` override (#26). `arc` re-renders the same edges as a
110
- * linear arc; absent ⇒ the `chord` circular preset. */
111
- layout?: 'arc' | 'chord';
112
- }
113
-
114
- export interface ParsedFunctionChart extends ParsedExtendedBase {
115
- type: 'function';
116
- functions?: ParsedFunction[];
117
- }
118
-
119
- export interface ParsedScatter extends ParsedExtendedBase {
120
- type: 'scatter';
121
- scatterPoints?: ParsedScatterPoint[];
122
- sizelabel?: string;
123
- }
124
-
125
- export interface ParsedHeatmap extends ParsedExtendedBase {
126
- type: 'heatmap';
127
- heatmapRows?: ParsedHeatmapRow[];
128
- columns?: string[];
129
- rows?: string[];
130
- }
131
-
132
- export interface ParsedFunnel extends ParsedExtendedBase {
133
- type: 'funnel';
134
- }
135
-
136
- /** What `parseExtendedChart` returns: discriminated on `type`. */
137
- export type ParsedExtendedChart =
138
- | ParsedSankey
139
- | ParsedChord
140
- | ParsedFunctionChart
141
- | ParsedScatter
142
- | ParsedHeatmap
143
- | ParsedFunnel;
144
-
145
- /**
146
- * The parser's mutable accumulator — every type-specific field present, so the
147
- * single state machine can populate whichever the detected type needs.
148
- * `parseExtendedChart` returns this narrowed to {@link ParsedExtendedChart}; the
149
- * object is a structural superset of every variant, so the narrowing is sound
150
- * and changes nothing at runtime.
151
- */
152
- export interface ParsedExtendedChartFull extends ParsedExtendedBase {
153
- type: ExtendedChartType;
154
- links?: ParsedSankeyLink[];
155
- layout?: 'arc' | 'chord';
156
- functions?: ParsedFunction[];
157
- scatterPoints?: ParsedScatterPoint[];
158
- heatmapRows?: ParsedHeatmapRow[];
159
- columns?: string[];
160
- rows?: string[];
161
- xRange?: { min: number; max: number };
162
- sizelabel?: string;
163
- }
164
-
165
- // ============================================================
166
- // Nord Colors for Charts
167
- // ============================================================
168
-
169
- import type { PaletteColors } from './palettes';
170
- import type { ParsedChart } from './chart';
171
- import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
172
- import { resolveColorWithDiagnostic } from './colors';
173
- import {
174
- collectIndentedValues,
175
- extractColor,
176
- measureIndent,
177
- normalizeNumericToken,
178
- parseFirstLine,
179
- parseSeriesNames,
180
- } from './utils/parsing';
181
- import { parseDataRowValues } from './chart';
182
-
183
- // ============================================================
184
- // Shared Constants
185
- // ============================================================
186
-
187
- const VALID_EXTENDED_TYPES = new Set<ExtendedChartType>([
188
- 'sankey',
189
- 'chord',
190
- 'function',
191
- 'scatter',
192
- 'heatmap',
193
- 'funnel',
194
- ]);
195
-
196
- /** Known option keywords for the extended chart parser. */
197
- const KNOWN_EXTENDED_OPTIONS = new Set([
198
- 'chart',
199
- 'title',
200
- 'series',
201
- 'x-label',
202
- 'y-label',
203
- 'size-label',
204
- 'layout',
205
- 'no-name',
206
- 'no-value',
207
- 'no-percent',
208
- 'columns',
209
- 'rows',
210
- 'x',
211
- ]);
212
-
213
- /**
214
- * Parse a scatter data row: "Name x, y[, size]" or "Name(color) x, y[, size]"
215
- * Returns a ParsedScatterPoint or null if the line doesn't match.
216
- */
217
- function parseScatterRow(
218
- line: string,
219
- palette: PaletteColors | undefined,
220
- currentCategory: string,
221
- lineNumber: number,
222
- diagnostics: DgmoError[]
223
- ): ParsedScatterPoint | null {
224
- const dataRow = parseDataRowValues(line, { multiValue: true });
225
- if (!dataRow || dataRow.values.length < 2) return null;
226
- const { label: rawLabel, color: pointColor } = extractColor(
227
- dataRow.label,
228
- palette,
229
- diagnostics,
230
- lineNumber
231
- );
232
- return {
233
- name: rawLabel,
234
- // In-bounds by length >= 2 guard above.
235
- x: dataRow.values[0]!,
236
- y: dataRow.values[1]!,
237
- ...(dataRow.values[2] !== undefined && { size: dataRow.values[2] }),
238
- ...(pointColor && { color: pointColor }),
239
- ...(currentCategory !== 'Default' && { category: currentCategory }),
240
- lineNumber,
241
- };
242
- }
243
-
244
- /**
245
- * Parses extended chart content into a structured object.
246
- *
247
- * Format (colon-free):
248
- * ```
249
- * scatter My Chart
250
- * xlabel Weight
251
- *
252
- * Alice 165, 60
253
- * Bob 180, 85
254
- * ```
255
- */
256
- export function parseExtendedChart(
257
- content: string,
258
- palette?: PaletteColors
259
- ): ParsedExtendedChart {
260
- return parseExtendedChartFull(content, palette) as ParsedExtendedChart;
261
- }
262
-
263
- // ============================================================
264
- // Per-type parser doors — Story 109.2a (arch-review). Each extended data-chart's
265
- // typed entry point into the shared parse state machine, returning its narrowed
266
- // variant. The registry binds each id to its own door; `isExtendedChartParser`
267
- // recognises them by set membership (they replace the old single-identity check).
268
- // ============================================================
269
-
270
- export function parseSankey(
271
- content: string,
272
- palette?: PaletteColors
273
- ): ParsedSankey {
274
- return parseExtendedChart(content, palette) as ParsedSankey;
275
- }
276
-
277
- export function parseChord(
278
- content: string,
279
- palette?: PaletteColors
280
- ): ParsedChord {
281
- return parseExtendedChart(content, palette) as ParsedChord;
282
- }
283
-
284
- export function parseFunctionChart(
285
- content: string,
286
- palette?: PaletteColors
287
- ): ParsedFunctionChart {
288
- return parseExtendedChart(content, palette) as ParsedFunctionChart;
289
- }
290
-
291
- export function parseScatter(
292
- content: string,
293
- palette?: PaletteColors
294
- ): ParsedScatter {
295
- return parseExtendedChart(content, palette) as ParsedScatter;
296
- }
297
-
298
- export function parseHeatmap(
299
- content: string,
300
- palette?: PaletteColors
301
- ): ParsedHeatmap {
302
- return parseExtendedChart(content, palette) as ParsedHeatmap;
303
- }
304
-
305
- export function parseFunnel(
306
- content: string,
307
- palette?: PaletteColors
308
- ): ParsedFunnel {
309
- return parseExtendedChart(content, palette) as ParsedFunnel;
310
- }
311
-
312
- /** The six extended-data-chart parser doors, for `isExtendedChartParser`. */
313
- export const EXTENDED_CHART_DOORS = new Set<unknown>([
314
- parseSankey,
315
- parseChord,
316
- parseFunctionChart,
317
- parseScatter,
318
- parseHeatmap,
319
- parseFunnel,
320
- ]);
321
-
322
- function parseExtendedChartFull(
323
- content: string,
324
- palette?: PaletteColors
325
- ): ParsedExtendedChartFull {
326
- const lines = content.split('\n');
327
- const result: ParsedExtendedChartFull = {
328
- type: 'scatter',
329
- data: [],
330
- diagnostics: [],
331
- error: null,
332
- };
333
-
334
- // Track current category for grouped scatter charts
335
- let currentCategory = 'Default';
336
-
337
- // Sankey indentation state: stack of source nodes by indent level
338
- const sankeyStack: { name: string; indent: number }[] = [];
339
- let firstLineParsed = false;
340
-
341
- // Per-parse alias literal → canonical node name (TD-18). Per C8.
342
- // Used by sankey + chord link slots.
343
- const nameAliasMap = new Map<string, string>();
344
- /** Peel `as <alias>` and resolve bare alias references in one pass. */
345
- function resolveSlot(raw: string): string {
346
- const trimmed = raw.trim();
347
- const m = trimmed.match(/^(.*?)\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*$/);
348
- if (m) {
349
- // Regex capture groups [1] and [2] are non-optional in the pattern.
350
- const canonical = m[1]!.trim();
351
- nameAliasMap.set(m[2]!, canonical);
352
- return canonical;
353
- }
354
- const aliased = nameAliasMap.get(trimmed);
355
- return aliased !== undefined ? aliased : trimmed;
356
- }
357
-
358
- for (let i = 0; i < lines.length; i++) {
359
- // In-bounds by loop guard.
360
- const trimmed = lines[i]!.trim();
361
- const lineNumber = i + 1;
362
-
363
- // Skip empty lines
364
- if (!trimmed) continue;
365
-
366
- // Reject legacy ## category syntax
367
- if (/^#{2,}\s+/.test(trimmed)) {
368
- const name = trimmed
369
- .replace(/^#{2,}\s+/, '')
370
- .replace(/\s*\([^)]*\)\s*$/, '')
371
- .trim();
372
- result.diagnostics.push(
373
- makeDgmoError(
374
- lineNumber,
375
- `'## ${name}' is no longer supported. Use '[${name}]' instead`
376
- )
377
- );
378
- continue;
379
- }
380
-
381
- // Skip comments
382
- if (trimmed.startsWith('//')) continue;
383
-
384
- // First non-empty, non-comment line: chart type + optional title
385
- if (!firstLineParsed) {
386
- firstLineParsed = true;
387
- const firstLine = parseFirstLine(trimmed);
388
- // The extended-chart parser owns its own type vocabulary. `chord` is no
389
- // longer a top-level chart-type keyword (decision #29), so parseFirstLine
390
- // won't return it — but the arc `layout chord` override re-emits canonical
391
- // `chord …` content that must still parse here. Recognize an extended type
392
- // directly from the first token when parseFirstLine declines.
393
- if (!firstLine) {
394
- const rawType = trimmed.split(/\s+/)[0]!.toLowerCase();
395
- if (VALID_EXTENDED_TYPES.has(rawType as ExtendedChartType)) {
396
- result.type = rawType as ExtendedChartType;
397
- const rest = trimmed.slice(rawType.length).trim();
398
- if (rest) {
399
- result.title = rest;
400
- result.titleLineNumber = lineNumber;
401
- }
402
- continue;
403
- }
404
- }
405
- if (firstLine) {
406
- const chartType =
407
- firstLine.chartType.toLowerCase() as ExtendedChartType;
408
- if (VALID_EXTENDED_TYPES.has(chartType)) {
409
- result.type = chartType;
410
- if (firstLine.title) {
411
- result.title = firstLine.title;
412
- result.titleLineNumber = lineNumber;
413
- }
414
- continue;
415
- } else {
416
- const validTypes = [...VALID_EXTENDED_TYPES];
417
- let msg = `Unsupported chart type: ${firstLine.chartType}. Supported types: ${validTypes.join(', ')}.`;
418
- const hint = suggest(chartType, validTypes);
419
- if (hint) msg += ` ${hint}`;
420
- const diag = makeDgmoError(lineNumber, msg);
421
- result.diagnostics.push(diag);
422
- result.error = formatDgmoError(diag);
423
- return result;
424
- }
425
- }
426
- // If the first line is a single word (no spaces, no colon, no numbers),
427
- // treat it as an unrecognized chart type rather than falling through
428
- if (
429
- !trimmed.includes(' ') &&
430
- !trimmed.includes(':') &&
431
- !/\d/.test(trimmed)
432
- ) {
433
- const validTypes = [...VALID_EXTENDED_TYPES];
434
- let msg = `Unsupported chart type: ${trimmed}. Supported types: ${validTypes.join(', ')}.`;
435
- const hint = suggest(trimmed.toLowerCase(), validTypes);
436
- if (hint) msg += ` ${hint}`;
437
- const diag = makeDgmoError(lineNumber, msg);
438
- result.diagnostics.push(diag);
439
- result.error = formatDgmoError(diag);
440
- return result;
441
- }
442
- // Fall through — first line might be a data row or option
443
- }
444
-
445
- // [Category] container header with optional color: [Category Name] or [Category Name](color)
446
- // Category brackets with optional trailing-token color (§1.5):
447
- // `[Name]` or `[Name] color`. Per universal rule, color is a bare token.
448
- const categoryMatch = trimmed.match(/^\[(.+?)\](?:\s+(\S+))?\s*$/);
449
- if (categoryMatch) {
450
- // Regex capture group [1] is non-optional in the pattern.
451
- const catName = categoryMatch[1]!.trim();
452
- const rawCatColor = categoryMatch[2]?.trim();
453
- const catColor = rawCatColor
454
- ? (resolveColorWithDiagnostic(
455
- rawCatColor,
456
- lineNumber,
457
- result.diagnostics,
458
- palette
459
- ) ?? null)
460
- : null;
461
- if (catColor) {
462
- if (!result.categoryColors) result.categoryColors = {};
463
- result.categoryColors[catName] = catColor;
464
- }
465
- if (!result.categoryLineNumbers) result.categoryLineNumbers = {};
466
- result.categoryLineNumbers[catName] = lineNumber;
467
- currentCategory = catName;
468
- continue;
469
- }
470
-
471
- // Sankey/chord link syntax (§1.5 universal trailing-token):
472
- // `Source -> Target value` (directed, no link color)
473
- // `Source -> Target value linkColor` (directed, trailing-token link color)
474
- // `Source -- Target value` (undirected)
475
- // Link color (if present) must be a recognized lowercase palette word.
476
- // Source/target labels still accept trailing-token color via extractColor.
477
- const arrowMatch = trimmed.match(
478
- /^(.+?)\s*(->|--)\s*(.+?)\s+(-?[\d_]+(?:\.[\d]+)?)(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/
479
- );
480
- if (arrowMatch) {
481
- const [, rawSource, arrow, rawTarget, rawVal, rawLinkColor] = arrowMatch;
482
- // Captures 1-4 are non-optional in the regex pattern.
483
- const val = normalizeNumericToken(rawVal!) ?? rawVal!;
484
- // TD-18: peel/resolve aliases on source and target before color extraction.
485
- const sourceResolved = resolveSlot(rawSource!);
486
- const targetResolved = resolveSlot(rawTarget!);
487
- const { label: source, color: sourceColor } = extractColor(
488
- sourceResolved,
489
- palette,
490
- result.diagnostics,
491
- lineNumber
492
- );
493
- const { label: target, color: targetColor } = extractColor(
494
- targetResolved,
495
- palette,
496
- result.diagnostics,
497
- lineNumber
498
- );
499
- if (sourceColor || targetColor) {
500
- if (!result.nodeColors) result.nodeColors = {};
501
- if (sourceColor) result.nodeColors[source] = sourceColor;
502
- if (targetColor) result.nodeColors[target] = targetColor;
503
- }
504
- const linkColor = rawLinkColor
505
- ? resolveColorWithDiagnostic(
506
- rawLinkColor.trim(),
507
- lineNumber,
508
- result.diagnostics,
509
- palette
510
- )
511
- : undefined;
512
- if (!result.links) result.links = [];
513
- result.links.push({
514
- source,
515
- target,
516
- value: parseFloat(val),
517
- ...(linkColor && { color: linkColor }),
518
- directed: arrow === '->',
519
- lineNumber,
520
- });
521
- continue;
522
- }
523
-
524
- // Sankey: bare label (no numeric value) at any indent = source node for indented children
525
- if (result.type === 'sankey') {
526
- // In-bounds by loop guard (i < lines.length).
527
- const indent = measureIndent(lines[i]!);
528
- // Sankey indented child: " Target value (color)" under a source on the stack
529
- if (indent > 0 && sankeyStack.length > 0) {
530
- // Pop entries at same or deeper indent to find the parent
531
- while (sankeyStack.length && sankeyStack.at(-1)!.indent >= indent) {
532
- sankeyStack.pop();
533
- }
534
- if (sankeyStack.length > 0) {
535
- // Indented sankey child (§1.5 trailing-token):
536
- // `TargetName value` — link, no link color
537
- // `TargetName value linkColor` — link with link color
538
- // `TargetName nodeColor value` — node-colored child
539
- // `TargetName nodeColor value linkColor` — both
540
- // Strategy: peel a trailing recognized color word (after the value)
541
- // first, then run parseDataRowValues on the remainder. Trailing
542
- // tokens that aren't recognized colors stay in the data row.
543
- const valColorMatch = trimmed.match(
544
- /(-?[\d_]+(?:\.[\d]+)?)\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\s*$/
545
- );
546
- const strippedLine = valColorMatch
547
- ? trimmed.replace(
548
- /\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\s*$/,
549
- ''
550
- )
551
- : trimmed;
552
- const dataRow = parseDataRowValues(strippedLine);
553
- if (dataRow?.values.length === 1) {
554
- const source = sankeyStack.at(-1)!.name;
555
- const linkColor = valColorMatch?.[2]
556
- ? resolveColorWithDiagnostic(
557
- valColorMatch[2].trim(),
558
- lineNumber,
559
- result.diagnostics,
560
- palette
561
- )
562
- : undefined;
563
- // TD-18: peel/resolve alias on the indented child target name.
564
- const targetResolved = resolveSlot(dataRow.label);
565
- const { label: target, color: targetColor } = extractColor(
566
- targetResolved,
567
- palette,
568
- result.diagnostics,
569
- lineNumber
570
- );
571
- if (targetColor) {
572
- if (!result.nodeColors) result.nodeColors = {};
573
- result.nodeColors[target] = targetColor;
574
- }
575
- if (!result.links) result.links = [];
576
- result.links.push({
577
- source,
578
- target,
579
- // In-bounds by values.length === 1 guard above.
580
- value: dataRow.values[0]!,
581
- ...(linkColor && { color: linkColor }),
582
- lineNumber,
583
- });
584
- sankeyStack.push({ name: target, indent });
585
- continue;
586
- }
587
- }
588
- }
589
-
590
- // Bare label at indent 0 (or any indent without a value) = new source node.
591
- // Skip cross-chart bare-keyword options so they're handled by the
592
- // bare-keyword block below instead of becoming a phantom "solid-fill" node.
593
- const spaceIdx = trimmed.indexOf(' ');
594
- const lastTok = trimmed.substring(trimmed.lastIndexOf(' ') + 1);
595
- const hasNumericSuffix =
596
- spaceIdx >= 0 &&
597
- !isNaN(parseFloat(normalizeNumericToken(lastTok) ?? lastTok));
598
- const isBareKeywordOption =
599
- spaceIdx < 0 &&
600
- /^(solid-fill|no-name|no-value|no-percent|shade|no-title)$/i.test(
601
- trimmed
602
- );
603
- // `layout arc|chord` (#26) is a directive, not a node — let it fall through
604
- // to the valued-option handler below instead of becoming a phantom node.
605
- const isLayoutDirective =
606
- spaceIdx >= 0 &&
607
- trimmed.substring(0, spaceIdx).toLowerCase() === 'layout';
608
- if (!hasNumericSuffix && !isBareKeywordOption && !isLayoutDirective) {
609
- while (sankeyStack.length && sankeyStack.at(-1)!.indent >= indent) {
610
- sankeyStack.pop();
611
- }
612
- // TD-18: peel/resolve alias on the bare source node label.
613
- const trimmedResolved = resolveSlot(trimmed);
614
- const { label: nodeName, color: nodeColor } = extractColor(
615
- trimmedResolved,
616
- palette,
617
- result.diagnostics,
618
- lineNumber
619
- );
620
- if (nodeColor) {
621
- if (!result.nodeColors) result.nodeColors = {};
622
- result.nodeColors[nodeName] = nodeColor;
623
- }
624
- sankeyStack.push({ name: nodeName, indent });
625
- continue;
626
- }
627
- }
628
-
629
- // Extract first token to check for known options
630
- const spaceIdx = trimmed.indexOf(' ');
631
- const firstToken = (
632
- spaceIdx >= 0 ? trimmed.substring(0, spaceIdx) : trimmed
633
- ).toLowerCase();
634
-
635
- // Known option with a value
636
- if (KNOWN_EXTENDED_OPTIONS.has(firstToken) && spaceIdx >= 0) {
637
- const value = trimmed.substring(spaceIdx + 1).trim();
638
-
639
- if (firstToken === 'chart') {
640
- const chartType = value.toLowerCase() as ExtendedChartType;
641
- if (VALID_EXTENDED_TYPES.has(chartType)) {
642
- result.type = chartType;
643
- } else {
644
- const validTypes = [...VALID_EXTENDED_TYPES];
645
- let msg = `Unsupported chart type: ${value}. Supported types: ${validTypes.join(', ')}.`;
646
- const hint = suggest(chartType, validTypes);
647
- if (hint) msg += ` ${hint}`;
648
- const diag = makeDgmoError(lineNumber, msg);
649
- result.diagnostics.push(diag);
650
- result.error = formatDgmoError(diag);
651
- return result;
652
- }
653
- continue;
654
- }
655
-
656
- if (firstToken === 'title') {
657
- result.title = value;
658
- result.titleLineNumber = lineNumber;
659
- continue;
660
- }
661
-
662
- if (firstToken === 'series') {
663
- const parsed = parseSeriesNames(value, lines, i, palette);
664
- i = parsed.newIndex;
665
- result.series = parsed.series;
666
- result.seriesLineNumber = lineNumber;
667
- if (parsed.names.length > 1) {
668
- result.seriesNames = parsed.names;
669
- result.seriesNameLineNumbers = parsed.nameLineNumbers;
670
- }
671
- if (parsed.nameColors.some(Boolean))
672
- result.seriesNameColors = parsed.nameColors;
673
- continue;
674
- }
675
-
676
- if (firstToken === 'x-label') {
677
- result.xlabel = value;
678
- result.xlabelLineNumber = lineNumber;
679
- continue;
680
- }
681
- if (firstToken === 'y-label') {
682
- result.ylabel = value;
683
- result.ylabelLineNumber = lineNumber;
684
- continue;
685
- }
686
- if (firstToken === 'size-label') {
687
- result.sizelabel = value;
688
- continue;
689
- }
690
-
691
- // `layout arc|chord` (#26): render the same pairwise edges as the other
692
- // preset. `arc` → linear arc; default `chord` → circular.
693
- if (firstToken === 'layout') {
694
- const v = value.toLowerCase();
695
- if (v === 'arc' || v === 'chord') result.layout = v;
696
- continue;
697
- }
698
-
699
- if (firstToken === 'columns') {
700
- if (value) {
701
- result.columns = value.includes(',')
702
- ? value.split(',').map((s) => s.trim())
703
- : value.split(/\s+/);
704
- } else {
705
- const collected = collectIndentedValues(lines, i);
706
- i = collected.newIndex;
707
- result.columns = collected.values;
708
- }
709
- continue;
710
- }
711
-
712
- if (firstToken === 'rows') {
713
- if (value) {
714
- result.rows = value.includes(',')
715
- ? value.split(',').map((s) => s.trim())
716
- : value.split(/\s+/);
717
- } else {
718
- const collected = collectIndentedValues(lines, i);
719
- i = collected.newIndex;
720
- result.rows = collected.values;
721
- }
722
- continue;
723
- }
724
-
725
- if (firstToken === 'x') {
726
- const rangeMatch = value.match(/^(-?[\d.]+)\s+to\s+(-?[\d.]+)$/);
727
- if (rangeMatch) {
728
- result.xRange = {
729
- // Regex capture groups [1] and [2] are non-optional in the pattern.
730
- min: parseFloat(rangeMatch[1]!),
731
- max: parseFloat(rangeMatch[2]!),
732
- };
733
- continue;
734
- }
735
- // The `x` keyword owns ONLY the `x <min> to <max>` range form. A
736
- // function curve can legitimately start with `x` (e.g. `x / 2: x / 2`);
737
- // such a colon-bearing line must fall through to the function-curve
738
- // handler below instead of being silently swallowed here.
739
- if (!(result.type === 'function' && trimmed.includes(':'))) {
740
- continue;
741
- }
742
- }
743
- }
744
-
745
- // Bare boolean options
746
- if (firstToken === 'no-name') {
747
- result.noName = true;
748
- continue;
749
- }
750
- if (firstToken === 'no-value') {
751
- result.noValue = true;
752
- continue;
753
- }
754
- if (firstToken === 'no-percent') {
755
- result.noPercent = true;
756
- continue;
757
- }
758
- if (firstToken === 'shade') {
759
- result.shade = true;
760
- continue;
761
- }
762
- if (firstToken === 'solid-fill') {
763
- result.solidFill = true;
764
- continue;
765
- }
766
- if (firstToken === 'no-title' && spaceIdx < 0) {
767
- result.noTitle = true;
768
- continue;
769
- }
770
- // Silent-ignore unrecognized no-* flags (typos, future flags).
771
- if (firstToken.startsWith('no-') && spaceIdx < 0) {
772
- continue;
773
- }
774
-
775
- // Bare keyword options (no value)
776
- if (firstToken === 'series' && spaceIdx === -1) {
777
- const parsed = parseSeriesNames('', lines, i, palette);
778
- i = parsed.newIndex;
779
- result.series = parsed.series;
780
- result.seriesLineNumber = lineNumber;
781
- if (parsed.names.length > 1) {
782
- result.seriesNames = parsed.names;
783
- result.seriesNameLineNumbers = parsed.nameLineNumbers;
784
- }
785
- if (parsed.nameColors.some(Boolean))
786
- result.seriesNameColors = parsed.nameColors;
787
- continue;
788
- }
789
-
790
- if (firstToken === 'columns' && spaceIdx === -1) {
791
- const collected = collectIndentedValues(lines, i);
792
- i = collected.newIndex;
793
- result.columns = collected.values;
794
- continue;
795
- }
796
-
797
- if (firstToken === 'rows' && spaceIdx === -1) {
798
- const collected = collectIndentedValues(lines, i);
799
- i = collected.newIndex;
800
- result.rows = collected.values;
801
- continue;
802
- }
803
-
804
- // Function chart: "name expression" where name may contain parens like f(x)
805
- // Must use colon to separate name from expression since both can contain spaces
806
- if (result.type === 'function') {
807
- const colonIndex = trimmed.indexOf(':');
808
- if (colonIndex >= 0) {
809
- const { label: fnName, color: fnColor } = extractColor(
810
- trimmed.substring(0, colonIndex).trim(),
811
- palette,
812
- result.diagnostics,
813
- lineNumber
814
- );
815
- const fnValue = trimmed.substring(colonIndex + 1).trim();
816
- if (!result.functions) result.functions = [];
817
- result.functions.push({
818
- name: fnName,
819
- expression: fnValue,
820
- ...(fnColor && { color: fnColor }),
821
- lineNumber,
822
- });
823
- continue;
824
- }
825
- }
826
-
827
- // Scatter chart: "Name x, y" or "Name x, y, size"
828
- if (result.type === 'scatter') {
829
- // Parse from right: trailing comma-separated numbers are x, y [, size]
830
- const scatterData = parseScatterRow(
831
- trimmed,
832
- palette,
833
- currentCategory,
834
- lineNumber,
835
- result.diagnostics
836
- );
837
- if (scatterData) {
838
- if (!result.scatterPoints) result.scatterPoints = [];
839
- result.scatterPoints.push(scatterData);
840
- continue;
841
- }
842
- }
843
-
844
- // Heatmap data row: "RowLabel val1, val2, val3, ..." or "RowLabel val1 val2 val3"
845
- if (result.type === 'heatmap') {
846
- const dataRow = parseDataRowValues(trimmed, { multiValue: true });
847
- if (dataRow && dataRow.values.length > 0) {
848
- if (!result.heatmapRows) result.heatmapRows = [];
849
- result.heatmapRows.push({
850
- label: dataRow.label,
851
- values: dataRow.values,
852
- lineNumber,
853
- });
854
- continue;
855
- }
856
- }
857
-
858
- // Funnel / generic data point: "Label value"
859
- const dataRow = parseDataRowValues(trimmed);
860
- if (dataRow?.values.length === 1) {
861
- const { label: rawLabel, color: pointColor } = extractColor(
862
- dataRow.label,
863
- palette,
864
- result.diagnostics,
865
- lineNumber
866
- );
867
- result.data.push({
868
- label: rawLabel,
869
- // In-bounds by values.length === 1 guard above.
870
- value: dataRow.values[0]!,
871
- ...(pointColor && { color: pointColor }),
872
- lineNumber,
873
- });
874
- continue;
875
- }
876
-
877
- // Catch-all: nothing matched this line
878
- result.diagnostics.push(
879
- makeDgmoError(
880
- lineNumber,
881
- `Unexpected line: '${trimmed}'. Expected a data row ('Label value', space-separated) or a known option.`,
882
- 'warning'
883
- )
884
- );
885
- }
886
-
887
- const warn = (line: number, message: string): void => {
888
- result.diagnostics.push(makeDgmoError(line, message, 'warning'));
889
- };
890
-
891
- if (!result.error) {
892
- if (result.type === 'sankey') {
893
- if (!result.links || result.links.length === 0) {
894
- warn(1, 'No links found. Add links in format: Source -> Target 123');
895
- }
896
- } else if (result.type === 'chord') {
897
- if (!result.links || result.links.length === 0) {
898
- warn(1, 'No links found. Add links in format: Source -> Target 123');
899
- }
900
- } else if (result.type === 'function') {
901
- if (!result.functions || result.functions.length === 0) {
902
- warn(
903
- 1,
904
- 'No functions found. Add functions in format: Name: expression'
905
- );
906
- }
907
- if (!result.xRange) {
908
- result.xRange = { min: -10, max: 10 }; // Default range
909
- }
910
- } else if (result.type === 'scatter') {
911
- if (!result.scatterPoints || result.scatterPoints.length === 0) {
912
- warn(
913
- 1,
914
- 'No scatter points found. Add points in format: Name: x, y or Name: x, y, size'
915
- );
916
- }
917
- } else if (result.type === 'heatmap') {
918
- if (!result.heatmapRows || result.heatmapRows.length === 0) {
919
- warn(
920
- 1,
921
- 'No heatmap data found. Add data in format: RowLabel: val1, val2, val3'
922
- );
923
- }
924
- if (!result.columns || result.columns.length === 0) {
925
- warn(
926
- 1,
927
- 'No columns defined. Add columns in format: columns: Col1, Col2, Col3'
928
- );
929
- }
930
- } else if (result.type === 'funnel') {
931
- if (result.data.length === 0) {
932
- warn(1, 'No data found. Add data in format: Label: value');
933
- }
934
- }
935
- }
936
-
937
- return result;
938
- }
939
-
940
- // ============================================================
941
- // ECharts Option Builder
942
- // ============================================================
943
-
944
- /**
945
- * Extracts legend group data from standard chart types (multi-series line/bar).
946
- * Returns empty array if chart has no multi-series legend.
947
- */
948
- export function getSimpleChartLegendGroups(
949
- parsed: ParsedChart,
950
- colors: string[]
951
- ): LegendGroupData[] {
952
- if (!parsed.seriesNames || parsed.seriesNames.length <= 1) return [];
953
- return [
954
- {
955
- name: 'Series',
956
- entries: parsed.seriesNames.map((name, i) => ({
957
- value: name,
958
- // colors is a non-empty palette array; modulo index is always in-bounds.
959
- color: parsed.seriesNameColors?.[i] ?? colors[i % colors.length]!,
960
- })),
961
- },
962
- ];
963
- }
964
-
965
- /**
966
- * Extracts legend group data from extended chart types.
967
- * Supports scatter (categories), chord (nodes), and function (series).
968
- */
969
- export function getExtendedChartLegendGroups(
970
- parsed: ParsedExtendedChart,
971
- colors: string[]
972
- ): LegendGroupData[] {
973
- if (parsed.type === 'scatter') {
974
- const points = parsed.scatterPoints ?? [];
975
- const categories = [
976
- ...new Set(points.map((p) => p.category).filter(Boolean)),
977
- ] as string[];
978
- if (categories.length === 0) return [];
979
- return [
980
- {
981
- name: 'Group',
982
- entries: categories.map((cat, i) => ({
983
- value: cat,
984
- // colors is a non-empty palette array; modulo index is always in-bounds.
985
- color: parsed.categoryColors?.[cat] ?? colors[i % colors.length]!,
986
- })),
987
- },
988
- ];
989
- }
990
-
991
- if (parsed.type === 'function') {
992
- const fns = parsed.functions ?? [];
993
- if (fns.length === 0) return [];
994
- return [
995
- {
996
- name: 'Function',
997
- entries: fns.map((fn, i) => ({
998
- value: fn.name,
999
- // colors is a non-empty palette array; modulo index is always in-bounds.
1000
- color: fn.color ?? colors[i % colors.length]!,
1001
- })),
1002
- },
1003
- ];
1004
- }
1005
-
1006
- return [];
1007
- }