@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
package/src/d3.ts DELETED
@@ -1,1434 +0,0 @@
1
- // ============================================================
2
- // d3.ts — export-render dispatch + public re-export barrel.
3
- //
4
- // Story 109.2 split the six D3 visualizations into per-viz modules under
5
- // src/<viz>/. What remains here is renderForExport's dispatch table (the
6
- // per-type export handlers) plus the public barrel that re-exports the parser,
7
- // types, and renderers from their new homes so downstream import paths
8
- // (@diagrammo/dgmo/advanced, ../src/d3 in tests, the desktop app) don't churn.
9
- // ============================================================
10
-
11
- import type { PaletteColors } from './palettes';
12
- import type { D3ExportDimensions } from './utils/d3-types';
13
- import { parseVisualization } from './visualizations/parse';
14
- import { resolveActiveTagGroup } from './utils/tag-groups';
15
- import {
16
- EXPORT_HEIGHT,
17
- EXPORT_WIDTH,
18
- createExportContainer,
19
- finalizeSvgExport,
20
- resolveExportPalette,
21
- } from './utils/d3-helpers';
22
-
23
- // Per-viz renderers + parser doors used by the export handlers (own modules — Story 109.2).
24
- import { renderSlopeChart } from './slope/renderer';
25
- import { parseSlope } from './slope/parser';
26
- import { renderArcDiagram } from './arc/renderer';
27
- import { parseArc } from './arc/parser';
28
- import { renderTimeline } from './timeline/renderer';
29
- import { parseTimeline } from './timeline/viz-parser';
30
- import { renderWordCloudAsync } from './wordcloud/renderer';
31
- import { parseWordcloud } from './wordcloud/parser';
32
- import { renderVenn } from './venn/renderer';
33
- import { parseVenn } from './venn/parser';
34
- import { renderQuadrant } from './quadrant/renderer';
35
- import { parseQuadrant } from './quadrant/parser';
36
-
37
- // ── Public re-export barrel (paths downstream consumers import from) ──
38
- export { parseVisualization } from './visualizations/parse';
39
- export { parseTimelineDate, addDurationToDate } from './timeline/parser';
40
- export type { D3ExportDimensions } from './utils/d3-types';
41
- export type {
42
- VisualizationType,
43
- ParsedVisualization,
44
- ArcLink,
45
- ArcNodeGroup,
46
- } from './visualizations/types';
47
- export { renderSlopeChart, resolveVerticalCollisions } from './slope/renderer';
48
- export { orderArcNodes, renderArcDiagram } from './arc/renderer';
49
- export { renderTimeline, formatDateLabel } from './timeline/renderer';
50
- export { renderWordCloud } from './wordcloud/renderer';
51
- export { renderVenn } from './venn/renderer';
52
- export { renderQuadrant } from './quadrant/renderer';
53
-
54
- // ============================================================
55
- // Export-render dispatch
56
- // ============================================================
57
-
58
- /**
59
- * Renders a D3 chart to an SVG string for export.
60
- * Creates a detached DOM element, renders into it, extracts the SVG, then cleans up.
61
- */
62
- type RenderForExportOptions = {
63
- c4Level?: 'context' | 'containers' | 'components' | 'deployment';
64
- c4System?: string;
65
- c4Container?: string;
66
- tagGroup?: string;
67
- exportMode?: boolean;
68
- // Browser callers (the app / Obsidian) bundle the map JSON and inject it
69
- // here — the Node fs `loadMapData()` seam can't run in a browser. CLI/SSR
70
- // omit this and fall back to the fs loader.
71
- mapData?: import('./map/resolved-types').MapData;
72
- // WYSIWYG map export: the live preview pane's displayed aspect (w/h). When
73
- // set, the map canvas adopts it + stretch-fills so the PNG matches the
74
- // on-screen map. The app passes this; headless consumers omit it.
75
- mapAspect?: number;
76
- };
77
-
78
- /** Everything an export handler needs — one bundle threaded through dispatch. */
79
- interface ExportContext {
80
- content: string;
81
- theme: 'light' | 'dark' | 'transparent';
82
- palette: PaletteColors | undefined;
83
- viewState: import('./sharing').CompactViewState | undefined;
84
- options: RenderForExportOptions | undefined;
85
- exportMode: boolean;
86
- /** Whether the theme is dark, resolved once in renderForExport (Story 111.2). */
87
- isDark: boolean;
88
- }
89
-
90
- type DiagramExportHandler = (ctx: ExportContext) => Promise<string>;
91
-
92
- /**
93
- * Export-render dispatch, keyed by detected chart type. Story 109.1 (arch-review)
94
- * replaced a 22-branch per-type if-ladder with this table.
95
- * `chart-type-registry.test.ts` asserts every diagram/visualization id in
96
- * CHART_TYPE_REGISTRY is covered here (or by the visualization fallthrough), so a
97
- * newly-registered type can no longer silently render an empty string.
98
- */
99
- export const DIAGRAM_EXPORT_HANDLERS: Record<string, DiagramExportHandler> = {
100
- org: exportOrg,
101
- sitemap: exportSitemap,
102
- kanban: exportKanban,
103
- class: exportClass,
104
- er: exportEr,
105
- 'boxes-and-lines': exportBoxesAndLines,
106
- swimlane: exportSwimlane,
107
- 'version-control': exportVersionControl,
108
- mindmap: exportMindmap,
109
- wireframe: exportWireframe,
110
- c4: exportC4,
111
- flowchart: exportFlowchart,
112
- infra: exportInfra,
113
- pert: exportPert,
114
- gantt: exportGantt,
115
- state: exportState,
116
- 'tech-radar': exportTechRadar,
117
- 'journey-map': exportJourneyMap,
118
- cycle: exportCycle,
119
- map: exportMap,
120
- pyramid: exportPyramid,
121
- ring: exportRing,
122
- treemap: exportTreemap,
123
- block: exportBlock,
124
- raci: exportRaci,
125
- // D3 visualizations — own handler per type (Story 109.2). Only `sequence`
126
- // still falls through to exportVisualization (no chart-type of its own).
127
- slope: exportSlope,
128
- arc: exportArc,
129
- timeline: exportTimeline,
130
- 'event-line': exportEventLine,
131
- wordcloud: exportWordcloud,
132
- venn: exportVenn,
133
- quadrant: exportQuadrant,
134
- };
135
-
136
- /**
137
- * Arc circular-layout override (#26, narrowed by #29): `arc` renders linear by
138
- * default; `layout chord` selects the circular ("chord") preset over the same
139
- * pairwise edge data. `chord` is no longer a chart-type keyword (#29) — the
140
- * circular layout is reachable ONLY through `arc` + `layout chord`. We parse
141
- * with the arc engine, normalize to a flat edge list, and re-emit canonical
142
- * content for the internal circular renderer (arc groups/order are dropped —
143
- * best-effort for a pure layout switch). Returns the rewritten `{content, type}`,
144
- * or null when no override applies.
145
- */
146
- export function resolveArcChordOverride(
147
- content: string,
148
- detectedType: string | null,
149
- palette?: PaletteColors
150
- ): { content: string; type: string } | null {
151
- if (detectedType === 'arc') {
152
- const p = parseArc(content, palette);
153
- if (!p.error && p.layout === 'chord' && p.links.length) {
154
- const emitted = [
155
- `chord ${p.title ?? ''}`.trimEnd(),
156
- ...p.links.map((l) => `${l.source} -> ${l.target} ${l.value}`),
157
- ].join('\n');
158
- return { content: emitted, type: 'chord' };
159
- }
160
- }
161
- return null;
162
- }
163
-
164
- export async function renderForExport(
165
- content: string,
166
- theme: 'light' | 'dark' | 'transparent',
167
- palette?: PaletteColors,
168
- viewState?: import('./sharing').CompactViewState,
169
- options?: RenderForExportOptions
170
- ): Promise<string> {
171
- const exportMode = options?.exportMode ?? false;
172
- const { parseDgmoChartType } = await import('./dgmo-router');
173
- const detectedTypeRaw = parseDgmoChartType(content);
174
- // Arc↔chord `layout` override (#26): re-emit canonical content for the other
175
- // engine so each renders its own grammar.
176
- const override = resolveArcChordOverride(content, detectedTypeRaw, palette);
177
- const renderContent = override?.content ?? content;
178
- const detectedType = override?.type ?? detectedTypeRaw;
179
- // Data-chart types (bar/line/pie/scatter/sankey/…) render via the hand-built
180
- // D3 engine — route them here so renderForExport is a complete export entry
181
- // for every chart type (the app's export path calls this directly).
182
- if (detectedType !== null) {
183
- const { supportsD3DataChart, renderDataChartD3 } =
184
- await import('./charts-d3');
185
- if (supportsD3DataChart(detectedType)) {
186
- return renderDataChartD3(renderContent, theme, palette);
187
- }
188
- }
189
- const ctx: ExportContext = {
190
- content: renderContent,
191
- theme,
192
- palette,
193
- viewState,
194
- options,
195
- exportMode,
196
- isDark: theme === 'dark',
197
- };
198
- // Generic dispatch: every structured diagram AND every D3 visualization now
199
- // resolves through the handler table. Only `sequence` — which has no chart
200
- // type of its own and is auto-detected from arrow syntax — falls through to
201
- // exportVisualization.
202
- const handler =
203
- detectedType !== null ? DIAGRAM_EXPORT_HANDLERS[detectedType] : undefined;
204
- return (handler ?? exportVisualization)(ctx);
205
- }
206
-
207
- /**
208
- * The tag-group override threaded into every handler: an explicit viewState tag
209
- * (app toggle / share link) wins, else the options.tagGroup fallback. Resolved
210
- * from ctx so handlers stop repeating the viewState/options fallback
211
- * shape (Story 111.2).
212
- */
213
- function ctxTagOverride(ctx: ExportContext): string | undefined {
214
- return ctx.viewState?.tag ?? ctx.options?.tagGroup;
215
- }
216
-
217
- async function exportEventLine(ctx: ExportContext): Promise<string> {
218
- const { content, theme, palette } = ctx;
219
- const { parseEventLine } = await import('./event-line/parser');
220
- const { renderEventLineForExport } = await import('./event-line/renderer');
221
-
222
- const effectivePalette = await resolveExportPalette(theme, palette);
223
- const parsed = parseEventLine(content, effectivePalette);
224
- if (parsed.error || parsed.events.length === 0) return '';
225
-
226
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
227
- renderEventLineForExport(
228
- container,
229
- parsed,
230
- effectivePalette,
231
- ctx.isDark,
232
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT },
233
- ctxTagOverride(ctx)
234
- );
235
- return finalizeSvgExport(container, theme, effectivePalette);
236
- }
237
-
238
- async function exportVersionControl(ctx: ExportContext): Promise<string> {
239
- const { content, theme, palette } = ctx;
240
- const { parseVersionControl } = await import('./version-control/parser');
241
- const { renderVersionControlForExport } =
242
- await import('./version-control/renderer');
243
-
244
- const effectivePalette = await resolveExportPalette(theme, palette);
245
- const parsed = parseVersionControl(content, effectivePalette);
246
- if (parsed.error || parsed.nodes.length === 0) return '';
247
-
248
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
249
- renderVersionControlForExport(
250
- container,
251
- parsed,
252
- effectivePalette,
253
- ctx.isDark,
254
- {
255
- width: EXPORT_WIDTH,
256
- height: EXPORT_HEIGHT,
257
- }
258
- );
259
- return finalizeSvgExport(container, theme, effectivePalette);
260
- }
261
-
262
- /**
263
- * Merge the source `hide` directive (comma-separated attribute keys) with any
264
- * interactive `viewState.ha`, lowercasing both so they match the parser's
265
- * lowercased metadata keys. Returns `undefined` when nothing is hidden.
266
- */
267
- function unionHiddenAttributes(
268
- hideOption: string | undefined,
269
- ha: readonly string[] | undefined
270
- ): Set<string> | undefined {
271
- const sourceHidden = hideOption
272
- ? hideOption
273
- .split(',')
274
- .map((s) => s.trim().toLowerCase())
275
- .filter(Boolean)
276
- : [];
277
- const union = new Set([...sourceHidden, ...(ha ?? [])]);
278
- return union.size > 0 ? union : undefined;
279
- }
280
-
281
- async function exportOrg(ctx: ExportContext): Promise<string> {
282
- const { content, theme, palette, viewState, exportMode } = ctx;
283
- const { parseOrg } = await import('./org/parser');
284
- const { layoutOrg } = await import('./org/layout');
285
- const { collapseOrgTree } = await import('./org/collapse');
286
- const { renderOrg } = await import('./org/renderer');
287
-
288
- const isDark = ctx.isDark;
289
- const effectivePalette = await resolveExportPalette(theme, palette);
290
-
291
- const orgParsed = parseOrg(content, effectivePalette);
292
- if (orgParsed.error) return '';
293
-
294
- // Apply interactive collapse state when provided (read from unified viewState)
295
- const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
296
- const activeTagGroup = resolveActiveTagGroup(
297
- orgParsed.tagGroups,
298
- orgParsed.options['active-tag'],
299
- ctxTagOverride(ctx)
300
- );
301
- // Hidden attributes come from the source `hide` directive UNIONed with any
302
- // interactive `viewState.ha` (share link / app). Source alone must hide on a
303
- // plain render — parity with the app, which seeds the same directive.
304
- const hiddenAttributes = unionHiddenAttributes(
305
- orgParsed.options['hide'],
306
- viewState?.ha
307
- );
308
-
309
- const { parsed: effectiveParsed, hiddenCounts } =
310
- collapsedNodes && collapsedNodes.size > 0
311
- ? collapseOrgTree(orgParsed, collapsedNodes)
312
- : { parsed: orgParsed, hiddenCounts: new Map<string, number>() };
313
-
314
- const orgLayout = layoutOrg(
315
- effectiveParsed,
316
- hiddenCounts.size > 0 ? hiddenCounts : undefined,
317
- activeTagGroup,
318
- hiddenAttributes,
319
- false // expandAllLegend off — collapsed-by-default per §1.3
320
- );
321
-
322
- const PADDING = 20;
323
- const titleOffset = effectiveParsed.title ? 30 : 0;
324
- const exportWidth = orgLayout.width + PADDING * 2;
325
- const exportHeight = orgLayout.height + PADDING * 2 + titleOffset;
326
- const container = createExportContainer(exportWidth, exportHeight);
327
-
328
- renderOrg(
329
- container,
330
- effectiveParsed,
331
- orgLayout,
332
- effectivePalette,
333
- isDark,
334
- undefined,
335
- { width: exportWidth, height: exportHeight },
336
- activeTagGroup,
337
- hiddenAttributes,
338
- undefined,
339
- exportMode
340
- );
341
- return finalizeSvgExport(container, theme, effectivePalette);
342
- }
343
-
344
- async function exportSitemap(ctx: ExportContext): Promise<string> {
345
- const { content, theme, palette, viewState, exportMode } = ctx;
346
- const { parseSitemap } = await import('./sitemap/parser');
347
- const { layoutSitemap } = await import('./sitemap/layout');
348
- const { collapseSitemapTree } = await import('./sitemap/collapse');
349
- const { renderSitemap } = await import('./sitemap/renderer');
350
-
351
- const isDark = ctx.isDark;
352
- const effectivePalette = await resolveExportPalette(theme, palette);
353
-
354
- const sitemapParsed = parseSitemap(content, effectivePalette);
355
- if (sitemapParsed.error || sitemapParsed.roots.length === 0) return '';
356
-
357
- // Apply interactive collapse state when provided (read from unified viewState)
358
- const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
359
- const activeTagGroup = resolveActiveTagGroup(
360
- sitemapParsed.tagGroups,
361
- sitemapParsed.options['active-tag'],
362
- ctxTagOverride(ctx)
363
- );
364
- const hiddenAttributes = unionHiddenAttributes(
365
- sitemapParsed.options['hide'],
366
- viewState?.ha
367
- );
368
-
369
- const { parsed: effectiveParsed, hiddenCounts } =
370
- collapsedNodes && collapsedNodes.size > 0
371
- ? collapseSitemapTree(sitemapParsed, collapsedNodes)
372
- : { parsed: sitemapParsed, hiddenCounts: new Map<string, number>() };
373
-
374
- const sitemapLayout = layoutSitemap(
375
- effectiveParsed,
376
- hiddenCounts.size > 0 ? hiddenCounts : undefined,
377
- activeTagGroup,
378
- hiddenAttributes,
379
- true
380
- );
381
-
382
- const PADDING = 20;
383
- const titleOffset = effectiveParsed.title ? 30 : 0;
384
- const exportWidth = sitemapLayout.width + PADDING * 2;
385
- const exportHeight = sitemapLayout.height + PADDING * 2 + titleOffset;
386
- const container = createExportContainer(exportWidth, exportHeight);
387
-
388
- renderSitemap(
389
- container,
390
- effectiveParsed,
391
- sitemapLayout,
392
- effectivePalette,
393
- isDark,
394
- undefined,
395
- { width: exportWidth, height: exportHeight },
396
- activeTagGroup,
397
- hiddenAttributes,
398
- exportMode
399
- );
400
- return finalizeSvgExport(container, theme, effectivePalette);
401
- }
402
-
403
- async function exportKanban(ctx: ExportContext): Promise<string> {
404
- const { content, theme, palette, viewState, exportMode } = ctx;
405
- const { parseKanban } = await import('./kanban/parser');
406
- const { renderKanban } = await import('./kanban/renderer');
407
-
408
- const effectivePalette = await resolveExportPalette(theme, palette);
409
- const kanbanParsed = parseKanban(content, effectivePalette);
410
- if (kanbanParsed.error || kanbanParsed.columns.length === 0) return '';
411
-
412
- // Kanban renderer self-sizes — no explicit width/height needed
413
- const container = document.createElement('div');
414
- container.style.position = 'absolute';
415
- container.style.left = '-9999px';
416
- document.body.appendChild(container);
417
-
418
- const kanbanCollapsedLanes = viewState?.cl
419
- ? new Set(viewState.cl)
420
- : undefined;
421
- // Union source-declared collapsed columns (`[Column] collapsed: true`) with
422
- // any interactive `viewState.cc`, so a plain export honors the source marker.
423
- const sourceCollapsedColumns = kanbanParsed.columns
424
- .filter((c) => c.collapsed)
425
- .map((c) => c.id);
426
- const kanbanCollapsedColumns =
427
- viewState?.cc || sourceCollapsedColumns.length > 0
428
- ? new Set([...sourceCollapsedColumns, ...(viewState?.cc ?? [])])
429
- : undefined;
430
- renderKanban(container, kanbanParsed, effectivePalette, ctx.isDark, {
431
- activeTagGroup: resolveActiveTagGroup(
432
- kanbanParsed.tagGroups,
433
- kanbanParsed.options['active-tag'],
434
- ctxTagOverride(ctx)
435
- ),
436
- currentSwimlaneGroup:
437
- viewState?.swim ?? kanbanParsed.options['lane-by'] ?? null,
438
- ...(kanbanCollapsedLanes !== undefined && {
439
- collapsedLanes: kanbanCollapsedLanes,
440
- }),
441
- ...(kanbanCollapsedColumns !== undefined && {
442
- collapsedColumns: kanbanCollapsedColumns,
443
- }),
444
- ...(viewState?.cm !== undefined && { compactMeta: viewState.cm }),
445
- exportMode,
446
- });
447
- return finalizeSvgExport(container, theme, effectivePalette);
448
- }
449
-
450
- async function exportClass(ctx: ExportContext): Promise<string> {
451
- const { content, theme, palette, exportMode } = ctx;
452
- const { parseClassDiagram } = await import('./class/parser');
453
- const { layoutClassDiagram } = await import('./class/layout');
454
- const { renderClassDiagram } = await import('./class/renderer');
455
-
456
- const effectivePalette = await resolveExportPalette(theme, palette);
457
- const classParsed = parseClassDiagram(content, effectivePalette);
458
- if (classParsed.error || classParsed.classes.length === 0) return '';
459
-
460
- const classLayout = layoutClassDiagram(classParsed);
461
- const PADDING = 20;
462
- const titleOffset = classParsed.title ? 40 : 0;
463
- const exportWidth = classLayout.width + PADDING * 2;
464
- const exportHeight = classLayout.height + PADDING * 2 + titleOffset;
465
- const container = createExportContainer(exportWidth, exportHeight);
466
-
467
- renderClassDiagram(
468
- container,
469
- classParsed,
470
- classLayout,
471
- effectivePalette,
472
- ctx.isDark,
473
- undefined,
474
- { width: exportWidth, height: exportHeight },
475
- undefined,
476
- exportMode
477
- );
478
- return finalizeSvgExport(container, theme, effectivePalette);
479
- }
480
-
481
- async function exportEr(ctx: ExportContext): Promise<string> {
482
- const { content, theme, palette, viewState, exportMode } = ctx;
483
- const { parseERDiagram } = await import('./er/parser');
484
- const { layoutERDiagram } = await import('./er/layout');
485
- const { renderERDiagram } = await import('./er/renderer');
486
-
487
- const effectivePalette = await resolveExportPalette(theme, palette);
488
- const erParsed = parseERDiagram(content, effectivePalette);
489
- if (erParsed.error || erParsed.tables.length === 0) return '';
490
-
491
- const erLayout = layoutERDiagram(erParsed);
492
- const PADDING = 20;
493
- const titleOffset = erParsed.title ? 40 : 0;
494
- const exportWidth = erLayout.width + PADDING * 2;
495
- const exportHeight = erLayout.height + PADDING * 2 + titleOffset;
496
- const container = createExportContainer(exportWidth, exportHeight);
497
-
498
- renderERDiagram(
499
- container,
500
- erParsed,
501
- erLayout,
502
- effectivePalette,
503
- ctx.isDark,
504
- undefined,
505
- { width: exportWidth, height: exportHeight },
506
- resolveActiveTagGroup(
507
- erParsed.tagGroups,
508
- erParsed.options['active-tag'],
509
- ctxTagOverride(ctx)
510
- ),
511
- // Semantic colors are on by default; the source `no-semantic-colors` flag
512
- // suppresses them. An interactive `viewState.sem` overrides source.
513
- viewState?.sem ??
514
- (erParsed.options['no-semantic-colors'] === 'on' ? false : undefined),
515
- exportMode
516
- );
517
- return finalizeSvgExport(container, theme, effectivePalette);
518
- }
519
-
520
- async function exportBoxesAndLines(ctx: ExportContext): Promise<string> {
521
- const { content, theme, palette, viewState, exportMode } = ctx;
522
- const { parseBoxesAndLines } = await import('./boxes-and-lines/parser');
523
- const effectivePalette = await resolveExportPalette(theme, palette);
524
- const blParsed = parseBoxesAndLines(content, effectivePalette);
525
- if (blParsed.error || blParsed.nodes.length === 0) return '';
526
-
527
- // Convert viewState.htv (Record<string, string[]>) to Map<string, Set<string>>
528
- let blHiddenTagValues: Map<string, Set<string>> | undefined;
529
- if (viewState?.htv) {
530
- blHiddenTagValues = new Map();
531
- for (const [k, v] of Object.entries(viewState.htv)) {
532
- blHiddenTagValues.set(k, new Set(v));
533
- }
534
- }
535
-
536
- const { renderBoxesAndLinesForExport } =
537
- await import('./boxes-and-lines/renderer');
538
- const { layoutBoxesAndLines } = await import('./boxes-and-lines/layout');
539
- const blLayout = await layoutBoxesAndLines(blParsed);
540
- const PADDING = 20;
541
- const titleOffset = blParsed.title ? 40 : 0;
542
- const exportWidth = blLayout.width + PADDING * 2;
543
- const exportHeight = blLayout.height + PADDING * 2 + titleOffset;
544
- const container = createExportContainer(exportWidth, exportHeight);
545
-
546
- const blActiveTagGroup = ctxTagOverride(ctx);
547
- renderBoxesAndLinesForExport(
548
- container,
549
- blParsed,
550
- blLayout,
551
- effectivePalette,
552
- ctx.isDark,
553
- {
554
- exportDims: { width: exportWidth, height: exportHeight },
555
- ...(blActiveTagGroup !== undefined && {
556
- activeTagGroup: blActiveTagGroup,
557
- }),
558
- ...(blHiddenTagValues !== undefined && {
559
- hiddenTagValues: blHiddenTagValues,
560
- }),
561
- exportMode,
562
- }
563
- );
564
- return finalizeSvgExport(container, theme, effectivePalette);
565
- }
566
-
567
- async function exportSwimlane(ctx: ExportContext): Promise<string> {
568
- const { content, theme, palette } = ctx;
569
- const { parseSwimlane } = await import('./swimlane/parser');
570
- const { layoutSwimlane } = await import('./swimlane/layout');
571
- const { renderSwimlaneForExport } = await import('./swimlane/renderer');
572
-
573
- const effectivePalette = await resolveExportPalette(theme, palette);
574
- const swimParsed = parseSwimlane(content, effectivePalette);
575
- if (swimParsed.error || swimParsed.nodes.length === 0) return '';
576
-
577
- const swimLayout = layoutSwimlane(swimParsed);
578
- const PADDING = 20;
579
- const titleOffset = swimParsed.title ? 40 : 0;
580
- const exportWidth = swimLayout.width + PADDING * 2;
581
- const exportHeight = swimLayout.height + PADDING * 2 + titleOffset;
582
- const container = createExportContainer(exportWidth, exportHeight);
583
-
584
- renderSwimlaneForExport(
585
- container,
586
- swimParsed,
587
- swimLayout,
588
- effectivePalette,
589
- ctx.isDark,
590
- {
591
- exportDims: { width: exportWidth, height: exportHeight },
592
- activeTagGroup: resolveActiveTagGroup(
593
- swimParsed.tagGroups,
594
- swimParsed.options['active-tag'],
595
- ctxTagOverride(ctx)
596
- ),
597
- exportMode: ctx.exportMode,
598
- }
599
- );
600
- return finalizeSvgExport(container, theme, effectivePalette);
601
- }
602
-
603
- async function exportMindmap(ctx: ExportContext): Promise<string> {
604
- const { content, theme, palette, viewState, exportMode } = ctx;
605
- const { parseMindmap } = await import('./mindmap/parser');
606
- const { layoutMindmap } = await import('./mindmap/layout');
607
- const { collapseMindmapTree } = await import('./mindmap/collapse');
608
- const { renderMindmap } = await import('./mindmap/renderer');
609
-
610
- const isDark = ctx.isDark;
611
- const effectivePalette = await resolveExportPalette(theme, palette);
612
-
613
- const mmParsed = parseMindmap(content, effectivePalette);
614
- if (mmParsed.error) return '';
615
-
616
- // Collapse set = runtime view-state (`cg`) ∪ source-authored `collapsed: true`
617
- // markers (node.collapsed). Honoring the source markers is what lets any
618
- // consumer (CLI, remark-dgmo, Obsidian, embeds) reproduce the app's collapsed
619
- // view from the `.dgmo` text alone. `cg` is additive here (the mindmap share
620
- // path emits no `cg` today); true cg-vs-source precedence is a later concern.
621
- const collapsedNodes = new Set<string>(viewState?.cg ?? []);
622
- const collectCollapsed = (nodes: typeof mmParsed.roots): void => {
623
- for (const n of nodes) {
624
- if (n.collapsed) collapsedNodes.add(n.id);
625
- if (n.children.length) collectCollapsed(n.children);
626
- }
627
- };
628
- collectCollapsed(mmParsed.roots);
629
- const activeTagGroup = resolveActiveTagGroup(
630
- mmParsed.tagGroups,
631
- mmParsed.options['active-tag'],
632
- ctxTagOverride(ctx)
633
- );
634
- const hideDescriptions =
635
- mmParsed.options['no-descriptions'] === 'true' || viewState?.hd === true;
636
-
637
- const { roots: effectiveRoots, hiddenCounts } =
638
- collapsedNodes && collapsedNodes.size > 0
639
- ? collapseMindmapTree(mmParsed.roots, collapsedNodes)
640
- : { roots: mmParsed.roots, hiddenCounts: new Map<string, number>() };
641
-
642
- const effectiveParsed = { ...mmParsed, roots: effectiveRoots };
643
-
644
- const mmLayout = layoutMindmap(effectiveParsed, effectivePalette, {
645
- interactive: false,
646
- ...(hiddenCounts.size > 0 && { hiddenCounts }),
647
- activeTagGroup,
648
- hideDescriptions,
649
- });
650
-
651
- const PADDING = 20;
652
- const titleOffset = effectiveParsed.title ? 30 : 0;
653
- const exportWidth = mmLayout.width + PADDING * 2;
654
- const exportHeight = mmLayout.height + PADDING * 2 + titleOffset;
655
- const container = createExportContainer(exportWidth, exportHeight);
656
-
657
- // Colour-by-depth from the source `color-by-depth` flag, with an interactive
658
- // `viewState.cbd` override.
659
- const colorByDepth =
660
- viewState?.cbd ?? mmParsed.options['color-by-depth'] === 'on';
661
-
662
- renderMindmap(
663
- container,
664
- effectiveParsed,
665
- mmLayout,
666
- effectivePalette,
667
- isDark,
668
- undefined,
669
- { width: exportWidth, height: exportHeight },
670
- undefined,
671
- hideDescriptions,
672
- colorByDepth ? null : activeTagGroup,
673
- colorByDepth ? { colorByDepth: true, exportMode } : { exportMode }
674
- );
675
- return finalizeSvgExport(container, theme, effectivePalette);
676
- }
677
-
678
- async function exportWireframe(ctx: ExportContext): Promise<string> {
679
- const { content, theme, palette } = ctx;
680
- const { parseWireframe } = await import('./wireframe/parser');
681
- const { layoutWireframe } = await import('./wireframe/layout');
682
- const { renderWireframe } = await import('./wireframe/renderer');
683
-
684
- const effectivePalette = await resolveExportPalette(theme, palette);
685
- const wireframeParsed = parseWireframe(content);
686
- if (
687
- wireframeParsed.error ||
688
- (wireframeParsed.roots.length === 0 && wireframeParsed.modals.length === 0)
689
- )
690
- return '';
691
-
692
- const wireframeLayout = layoutWireframe(wireframeParsed);
693
-
694
- const exportWidth = wireframeLayout.width;
695
- const exportHeight = wireframeLayout.height;
696
- const container = createExportContainer(exportWidth, exportHeight);
697
-
698
- renderWireframe(
699
- container,
700
- wireframeParsed,
701
- wireframeLayout,
702
- effectivePalette,
703
- ctx.isDark,
704
- undefined,
705
- { width: exportWidth, height: exportHeight },
706
- theme
707
- );
708
- return finalizeSvgExport(container, theme, effectivePalette);
709
- }
710
-
711
- async function exportC4(ctx: ExportContext): Promise<string> {
712
- const { content, theme, palette, viewState, exportMode } = ctx;
713
- const { parseC4 } = await import('./c4/parser');
714
- const {
715
- layoutC4Context,
716
- layoutC4Containers,
717
- layoutC4Components,
718
- layoutC4Deployment,
719
- } = await import('./c4/layout');
720
- const { renderC4Context, renderC4Containers } = await import('./c4/renderer');
721
-
722
- const effectivePalette = await resolveExportPalette(theme, palette);
723
- const c4Parsed = parseC4(content, effectivePalette);
724
- if (c4Parsed.error || c4Parsed.elements.length === 0) return '';
725
-
726
- // Container/component-level rendering (viewState fallback for share links)
727
- const c4Level =
728
- ctx.options?.c4Level ??
729
- (viewState?.c4l as
730
- | 'context'
731
- | 'containers'
732
- | 'components'
733
- | 'deployment'
734
- | undefined) ??
735
- 'context';
736
- const c4System = ctx.options?.c4System ?? viewState?.c4s;
737
- const c4Container = ctx.options?.c4Container ?? viewState?.c4c;
738
-
739
- const c4Layout =
740
- c4Level === 'deployment'
741
- ? layoutC4Deployment(c4Parsed)
742
- : c4Level === 'components' && c4System && c4Container
743
- ? layoutC4Components(c4Parsed, c4System, c4Container)
744
- : c4Level === 'containers' && c4System
745
- ? layoutC4Containers(c4Parsed, c4System)
746
- : layoutC4Context(c4Parsed);
747
-
748
- if (c4Layout.nodes.length === 0) return '';
749
-
750
- const PADDING = 20;
751
- const titleOffset = c4Parsed.title ? 40 : 0;
752
- const exportWidth = c4Layout.width + PADDING * 2;
753
- const exportHeight = c4Layout.height + PADDING * 2 + titleOffset;
754
- const container = createExportContainer(exportWidth, exportHeight);
755
-
756
- const renderFn =
757
- c4Level === 'deployment' ||
758
- (c4Level === 'components' && c4System && c4Container) ||
759
- (c4Level === 'containers' && c4System)
760
- ? renderC4Containers
761
- : renderC4Context;
762
-
763
- renderFn(
764
- container,
765
- c4Parsed,
766
- c4Layout,
767
- effectivePalette,
768
- ctx.isDark,
769
- undefined,
770
- { width: exportWidth, height: exportHeight },
771
- resolveActiveTagGroup(
772
- c4Parsed.tagGroups,
773
- c4Parsed.options['active-tag'],
774
- ctxTagOverride(ctx)
775
- ),
776
- exportMode
777
- );
778
- return finalizeSvgExport(container, theme, effectivePalette);
779
- }
780
-
781
- async function exportFlowchart(ctx: ExportContext): Promise<string> {
782
- const { content, theme, palette } = ctx;
783
- const { parseFlowchart } = await import('./graph/flowchart-parser');
784
- const { layoutGraph } = await import('./graph/layout');
785
- const { renderFlowchart } = await import('./graph/flowchart-renderer');
786
-
787
- const effectivePalette = await resolveExportPalette(theme, palette);
788
- const fcParsed = parseFlowchart(content, effectivePalette);
789
- if (fcParsed.error || fcParsed.nodes.length === 0) return '';
790
-
791
- const layout = layoutGraph(fcParsed);
792
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
793
-
794
- renderFlowchart(
795
- container,
796
- fcParsed,
797
- layout,
798
- effectivePalette,
799
- ctx.isDark,
800
- undefined,
801
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
802
- );
803
- return finalizeSvgExport(container, theme, effectivePalette);
804
- }
805
-
806
- async function exportInfra(ctx: ExportContext): Promise<string> {
807
- const { content, theme, palette, viewState } = ctx;
808
- const { parseInfra } = await import('./infra/parser');
809
- const { computeInfra } = await import('./infra/compute');
810
- const { layoutInfra } = await import('./infra/layout');
811
- const { renderInfra, computeInfraLegendGroups } =
812
- await import('./infra/renderer');
813
-
814
- const effectivePalette = await resolveExportPalette(theme, palette);
815
- const infraParsed = parseInfra(content);
816
- if (infraParsed.error || infraParsed.nodes.length === 0) return '';
817
-
818
- const infraComputed = computeInfra(infraParsed);
819
- const infraLayout = layoutInfra(infraComputed);
820
- const activeTagGroup = resolveActiveTagGroup(
821
- infraParsed.tagGroups,
822
- infraParsed.options['active-tag'],
823
- ctxTagOverride(ctx)
824
- );
825
-
826
- const showInfraTitle =
827
- !!infraParsed.title && infraParsed.options['no-title'] !== 'on';
828
- const titleOffset = showInfraTitle ? 40 : 0;
829
- const infraTagGroups = [...infraParsed.tagGroups];
830
- const legendGroups = computeInfraLegendGroups(
831
- infraLayout.nodes,
832
- infraTagGroups,
833
- effectivePalette
834
- );
835
- const legendOffset = legendGroups.length > 0 ? 28 : 0;
836
- const exportWidth = infraLayout.width;
837
- const exportHeight = infraLayout.height + titleOffset + legendOffset;
838
- const container = createExportContainer(exportWidth, exportHeight);
839
-
840
- renderInfra(
841
- container,
842
- infraLayout,
843
- effectivePalette,
844
- ctx.isDark,
845
- showInfraTitle ? infraParsed.title : null,
846
- showInfraTitle ? infraParsed.titleLineNumber : null,
847
- infraTagGroups,
848
- activeTagGroup,
849
- false,
850
- null,
851
- null,
852
- true,
853
- viewState?.cg ? new Set(viewState.cg) : null
854
- );
855
- // Restore explicit pixel dimensions for resvg (renderer uses 100%/viewBox for app scaling)
856
- const infraSvg = container.querySelector('svg');
857
- if (infraSvg) {
858
- infraSvg.setAttribute('width', String(exportWidth));
859
- infraSvg.setAttribute('height', String(exportHeight));
860
- }
861
- return finalizeSvgExport(container, theme, effectivePalette);
862
- }
863
-
864
- async function exportPert(ctx: ExportContext): Promise<string> {
865
- const { content, theme, palette, viewState } = ctx;
866
- const { parsePert } = await import('./pert/parser');
867
- const { analyzePert } = await import('./pert/analyzer');
868
- const { layoutPert } = await import('./pert/layout');
869
- const { renderPert, measurePertAnalysisBlock } =
870
- await import('./pert/renderer');
871
-
872
- const effectivePalette = await resolveExportPalette(theme, palette);
873
- const pertParsed = parsePert(content, { palette: effectivePalette });
874
- if (pertParsed.error || pertParsed.activities.length === 0) return '';
875
-
876
- const pertResolved = analyzePert(pertParsed);
877
- const pertLayout = layoutPert(pertResolved);
878
-
879
- const titleHeight = pertParsed.title && !pertParsed.options.noTitle ? 80 : 0;
880
- const PERT_PADDING = 20;
881
- // Analysis layer renders by default whenever MC ran. Precedence:
882
- // an explicit viewState.an (app toggle / share link) wins; else the
883
- // `no-analysis` source directive suppresses it; else on. The
884
- // renderer silently omits it in analytical mode (no MC output).
885
- const analysisOn = viewState?.an ?? !pertParsed.options.noAnalysis;
886
- const fieldLabelsOn = viewState?.fl === true;
887
- const exportW = pertLayout.width + PERT_PADDING * 2;
888
- const analysisMeasured =
889
- analysisOn || fieldLabelsOn
890
- ? measurePertAnalysisBlock(pertResolved, exportW - 2 * PERT_PADDING, {
891
- showSummary: false,
892
- showTornado: analysisOn,
893
- showScurve: analysisOn,
894
- showFieldLegend: fieldLabelsOn,
895
- })
896
- : { width: 0, height: 0 };
897
- const exportH =
898
- pertLayout.height +
899
- PERT_PADDING * 2 +
900
- titleHeight +
901
- analysisMeasured.height;
902
- const container = createExportContainer(exportW, exportH);
903
-
904
- renderPert(
905
- container,
906
- pertResolved,
907
- pertLayout,
908
- effectivePalette,
909
- ctx.isDark,
910
- {
911
- title: pertParsed.title,
912
- exportDims: { width: exportW, height: exportH },
913
- showSummary: false,
914
- showTornado: analysisOn,
915
- showScurve: analysisOn,
916
- showFieldLegend: fieldLabelsOn,
917
- }
918
- );
919
- return finalizeSvgExport(container, theme, effectivePalette);
920
- }
921
-
922
- async function exportGantt(ctx: ExportContext): Promise<string> {
923
- const { content, theme, palette, viewState, exportMode } = ctx;
924
- const { parseGantt } = await import('./gantt/parser');
925
- const { calculateSchedule } = await import('./gantt/calculator');
926
- const { renderGantt } = await import('./gantt/renderer');
927
-
928
- const effectivePalette = await resolveExportPalette(theme, palette);
929
- const ganttParsed = parseGantt(content, effectivePalette);
930
- const resolved = calculateSchedule(ganttParsed);
931
- if (resolved.tasks.length === 0) return '';
932
-
933
- const EXPORT_W = 1200;
934
- const EXPORT_H = 800;
935
- const container = createExportContainer(EXPORT_W, EXPORT_H);
936
-
937
- // Union source-declared collapsed groups (`[Group] collapsed: true`) with any
938
- // interactive `viewState.cg`, so a plain export honors the source marker.
939
- const sourceCollapsedGroups = resolved.groups
940
- .filter((g) => g.collapsed)
941
- .map((g) => g.name);
942
- const ganttCollapsedGroups =
943
- viewState?.cg || sourceCollapsedGroups.length > 0
944
- ? new Set([...sourceCollapsedGroups, ...(viewState?.cg ?? [])])
945
- : undefined;
946
- // Swimlane axis from source (`lane-by <group>` / `sort tag:<group>` →
947
- // defaultSwimlaneGroup when sort is 'tag'), with a viewState.swim override.
948
- const sourceSwimlane =
949
- resolved.options.sort === 'tag'
950
- ? (resolved.options.defaultSwimlaneGroup ?? undefined)
951
- : undefined;
952
- const ganttSwimlaneGroup = viewState?.swim ?? sourceSwimlane;
953
- const ganttCollapsedLanes = viewState?.cl ? new Set(viewState.cl) : undefined;
954
- renderGantt(
955
- container,
956
- resolved,
957
- effectivePalette,
958
- ctx.isDark,
959
- {
960
- ...(ganttCollapsedGroups !== undefined && {
961
- collapsedGroups: ganttCollapsedGroups,
962
- }),
963
- ...(ganttSwimlaneGroup !== undefined && {
964
- currentSwimlaneGroup: ganttSwimlaneGroup,
965
- }),
966
- ...(ganttCollapsedLanes !== undefined && {
967
- collapsedLanes: ganttCollapsedLanes,
968
- }),
969
- currentActiveGroup: resolveActiveTagGroup(
970
- resolved.tagGroups,
971
- resolved.options.activeTag ?? undefined,
972
- ctxTagOverride(ctx)
973
- ),
974
- exportMode,
975
- },
976
- { width: EXPORT_W, height: EXPORT_H }
977
- );
978
- return finalizeSvgExport(container, theme, effectivePalette);
979
- }
980
-
981
- async function exportState(ctx: ExportContext): Promise<string> {
982
- const { content, theme, palette, viewState } = ctx;
983
- const { parseState } = await import('./graph/state-parser');
984
- const { layoutGraph } = await import('./graph/layout');
985
- const { renderState } = await import('./graph/state-renderer');
986
- const { collapseStateGroups } = await import('./graph/state-collapse');
987
-
988
- const effectivePalette = await resolveExportPalette(theme, palette);
989
- const stateParsed = parseState(content, effectivePalette);
990
- if (stateParsed.error || stateParsed.nodes.length === 0) return '';
991
-
992
- // Union source-declared collapsed groups (`[Group] collapsed: true`) with any
993
- // interactive viewState.cg, so a plain export honors the source marker.
994
- const sourceCollapsed = (stateParsed.groups ?? [])
995
- .filter((g) => g.collapsed)
996
- .map((g) => g.id);
997
- const collapsedGroups = new Set([
998
- ...sourceCollapsed,
999
- ...(viewState?.cg ?? []),
1000
- ]);
1001
- const {
1002
- parsed: effectiveParsed,
1003
- collapsedChildCounts,
1004
- originalGroups,
1005
- } = collapsedGroups.size > 0
1006
- ? collapseStateGroups(stateParsed, collapsedGroups)
1007
- : {
1008
- parsed: stateParsed,
1009
- collapsedChildCounts: new Map<string, number>(),
1010
- originalGroups: stateParsed.groups ?? [],
1011
- };
1012
-
1013
- const layout = layoutGraph(effectiveParsed, {
1014
- collapsedChildCounts,
1015
- originalGroups,
1016
- });
1017
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
1018
-
1019
- renderState(
1020
- container,
1021
- effectiveParsed,
1022
- layout,
1023
- effectivePalette,
1024
- ctx.isDark,
1025
- undefined,
1026
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
1027
- );
1028
- return finalizeSvgExport(container, theme, effectivePalette);
1029
- }
1030
-
1031
- async function exportTechRadar(ctx: ExportContext): Promise<string> {
1032
- const { content, theme, palette, viewState, exportMode } = ctx;
1033
- const { parseTechRadar } = await import('./tech-radar/parser');
1034
- const { renderTechRadarForExport } = await import('./tech-radar/renderer');
1035
-
1036
- const effectivePalette = await resolveExportPalette(theme, palette);
1037
- const radarParsed = parseTechRadar(content);
1038
- if (radarParsed.error || radarParsed.quadrants.length === 0) return '';
1039
-
1040
- const RADAR_EXPORT_W = 1300;
1041
- const RADAR_EXPORT_H = 1500;
1042
- const container = createExportContainer(RADAR_EXPORT_W, RADAR_EXPORT_H);
1043
- renderTechRadarForExport(
1044
- container,
1045
- radarParsed,
1046
- effectivePalette,
1047
- ctx.isDark,
1048
- { width: RADAR_EXPORT_W, height: RADAR_EXPORT_H },
1049
- viewState,
1050
- exportMode
1051
- );
1052
- return finalizeSvgExport(container, theme, effectivePalette);
1053
- }
1054
-
1055
- async function exportJourneyMap(ctx: ExportContext): Promise<string> {
1056
- const { content, theme, palette, exportMode } = ctx;
1057
- const { parseJourneyMap } = await import('./journey-map/parser');
1058
- const { renderJourneyMap } = await import('./journey-map/renderer');
1059
- const { layoutJourneyMap } = await import('./journey-map/layout');
1060
-
1061
- const effectivePalette = await resolveExportPalette(theme, palette);
1062
- const jmParsed = parseJourneyMap(content, effectivePalette);
1063
- if (
1064
- jmParsed.error ||
1065
- (jmParsed.phases.length === 0 && jmParsed.steps.length === 0)
1066
- )
1067
- return '';
1068
-
1069
- const jmLayout = layoutJourneyMap(jmParsed, effectivePalette, {
1070
- isDark: ctx.isDark,
1071
- });
1072
- const container = createExportContainer(
1073
- jmLayout.totalWidth,
1074
- jmLayout.totalHeight
1075
- );
1076
- renderJourneyMap(container, jmParsed, effectivePalette, ctx.isDark, {
1077
- exportDims: { width: jmLayout.totalWidth, height: jmLayout.totalHeight },
1078
- exportMode,
1079
- });
1080
- return finalizeSvgExport(container, theme, effectivePalette);
1081
- }
1082
-
1083
- async function exportCycle(ctx: ExportContext): Promise<string> {
1084
- const { content, theme, palette, viewState, exportMode } = ctx;
1085
- const { parseCycle } = await import('./cycle/parser');
1086
- const { renderCycleForExport } = await import('./cycle/renderer');
1087
-
1088
- const effectivePalette = await resolveExportPalette(theme, palette);
1089
- const cycleParsed = parseCycle(content);
1090
- if (cycleParsed.error || cycleParsed.nodes.length === 0) return '';
1091
-
1092
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
1093
- renderCycleForExport(
1094
- container,
1095
- cycleParsed,
1096
- effectivePalette,
1097
- ctx.isDark,
1098
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT },
1099
- viewState,
1100
- exportMode
1101
- );
1102
- return finalizeSvgExport(container, theme, effectivePalette);
1103
- }
1104
-
1105
- async function exportMap(ctx: ExportContext): Promise<string> {
1106
- const { content, theme, palette, options } = ctx;
1107
- const { parseMap } = await import('./map/parser');
1108
- const { resolveMap } = await import('./map/resolver');
1109
- const { renderMapForExport } = await import('./map/renderer');
1110
- const { mapExportDimensions } = await import('./map/dimensions');
1111
-
1112
- const effectivePalette = await resolveExportPalette(theme, palette);
1113
- const mapParsed = parseMap(content, effectivePalette);
1114
- // Always render — an empty or partially-resolved map still draws the
1115
- // inferred base map (§24B.10 / layout AC23); diagnostics surface separately.
1116
- // Prefer injected `mapData` (browser bundles it; the fs loader can't run
1117
- // there); fall back to the Node fs loader for CLI/SSR. Degrade like every
1118
- // other branch (return '') if neither yields data.
1119
- let mapData = options?.mapData;
1120
- if (!mapData) {
1121
- const { loadMapData } = await import('./map/load-data');
1122
- try {
1123
- mapData = await loadMapData();
1124
- } catch {
1125
- return '';
1126
- }
1127
- }
1128
- const mapResolved = resolveMap(mapParsed, mapData);
1129
-
1130
- // Content-aware canvas: derive the height from the map's intrinsic projected
1131
- // aspect (world ~2.3:1, a region taller, etc.) instead of the fixed 800, so the
1132
- // export matches the content's natural shape — no vertical stretch, no
1133
- // letterbox bands. `preferContain` rides along to the renderer.
1134
- const dims = mapExportDimensions(
1135
- mapResolved,
1136
- mapData,
1137
- EXPORT_WIDTH,
1138
- options?.mapAspect
1139
- );
1140
- const container = createExportContainer(dims.width, dims.height);
1141
- renderMapForExport(
1142
- container,
1143
- mapResolved,
1144
- mapData,
1145
- effectivePalette,
1146
- ctx.isDark,
1147
- dims
1148
- );
1149
- return finalizeSvgExport(container, theme, effectivePalette);
1150
- }
1151
-
1152
- async function exportPyramid(ctx: ExportContext): Promise<string> {
1153
- const { content, theme, palette } = ctx;
1154
- const { parsePyramid } = await import('./pyramid/parser');
1155
- const { renderPyramidForExport } = await import('./pyramid/renderer');
1156
-
1157
- const effectivePalette = await resolveExportPalette(theme, palette);
1158
- const pyramidParsed = parsePyramid(content);
1159
- if (pyramidParsed.error || pyramidParsed.layers.length === 0) return '';
1160
-
1161
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
1162
- renderPyramidForExport(
1163
- container,
1164
- pyramidParsed,
1165
- effectivePalette,
1166
- ctx.isDark,
1167
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
1168
- );
1169
- return finalizeSvgExport(container, theme, effectivePalette);
1170
- }
1171
-
1172
- async function exportRing(ctx: ExportContext): Promise<string> {
1173
- const { content, theme, palette } = ctx;
1174
- const { parseRing } = await import('./ring/parser');
1175
- const { renderRingForExport } = await import('./ring/renderer');
1176
-
1177
- const effectivePalette = await resolveExportPalette(theme, palette);
1178
- const ringParsed = parseRing(content);
1179
- if (ringParsed.error || ringParsed.layers.length === 0) return '';
1180
-
1181
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
1182
- renderRingForExport(container, ringParsed, effectivePalette, ctx.isDark, {
1183
- width: EXPORT_WIDTH,
1184
- height: EXPORT_HEIGHT,
1185
- });
1186
- return finalizeSvgExport(container, theme, effectivePalette);
1187
- }
1188
-
1189
- async function exportTreemap(ctx: ExportContext): Promise<string> {
1190
- const { content, theme, palette } = ctx;
1191
- const { parseTreemap } = await import('./treemap/parser');
1192
-
1193
- const effectivePalette = await resolveExportPalette(theme, palette);
1194
- const treemapParsed = parseTreemap(content, effectivePalette);
1195
- if (treemapParsed.error || treemapParsed.roots.length === 0) return '';
1196
-
1197
- // Headless export: full tree (no depth window), source-declared color mode.
1198
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
1199
- const dims = { width: EXPORT_WIDTH, height: EXPORT_HEIGHT };
1200
-
1201
- if (treemapParsed.options.radial) {
1202
- // Sunburst mode: parallel lazy imports of the radial layout + renderer.
1203
- const { renderTreemapRadialForExport } =
1204
- await import('./treemap/renderer-radial');
1205
- renderTreemapRadialForExport(
1206
- container,
1207
- treemapParsed,
1208
- effectivePalette,
1209
- ctx.isDark,
1210
- dims
1211
- );
1212
- } else {
1213
- const { renderTreemapForExport } = await import('./treemap/renderer');
1214
- renderTreemapForExport(
1215
- container,
1216
- treemapParsed,
1217
- effectivePalette,
1218
- ctx.isDark,
1219
- dims
1220
- );
1221
- }
1222
- return finalizeSvgExport(container, theme, effectivePalette);
1223
- }
1224
-
1225
- async function exportBlock(ctx: ExportContext): Promise<string> {
1226
- const { content, theme, palette } = ctx;
1227
- const { parseBlock } = await import('./block/parser');
1228
- const { renderBlockForExport } = await import('./block/renderer');
1229
-
1230
- const effectivePalette = await resolveExportPalette(theme, palette);
1231
- const blockParsed = parseBlock(content, effectivePalette);
1232
- if (blockParsed.error || blockParsed.top.rows.length === 0) return '';
1233
-
1234
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
1235
- renderBlockForExport(container, blockParsed, effectivePalette, ctx.isDark, {
1236
- width: EXPORT_WIDTH,
1237
- height: EXPORT_HEIGHT,
1238
- });
1239
- return finalizeSvgExport(container, theme, effectivePalette);
1240
- }
1241
-
1242
- async function exportRaci(ctx: ExportContext): Promise<string> {
1243
- const { content, theme, palette } = ctx;
1244
- const { parseRaci } = await import('./raci/parser');
1245
- const { renderRaciForExport } = await import('./raci/renderer');
1246
-
1247
- const effectivePalette = await resolveExportPalette(theme, palette);
1248
- const raciParsed = parseRaci(content, effectivePalette);
1249
- if (raciParsed.error) return '';
1250
-
1251
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
1252
- renderRaciForExport(container, raciParsed, effectivePalette, ctx.isDark, {
1253
- width: EXPORT_WIDTH,
1254
- height: EXPORT_HEIGHT,
1255
- });
1256
- return finalizeSvgExport(container, theme, effectivePalette);
1257
- }
1258
-
1259
- /**
1260
- * Shared export prelude for the D3 visualizations: offscreen container + the
1261
- * canonical export dimensions. Each per-viz handler renders into the container
1262
- * then finalizes it.
1263
- */
1264
- function beginVizExport(): {
1265
- container: HTMLDivElement;
1266
- dims: D3ExportDimensions;
1267
- } {
1268
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
1269
- const dims: D3ExportDimensions = {
1270
- width: EXPORT_WIDTH,
1271
- height: EXPORT_HEIGHT,
1272
- };
1273
- return { container, dims };
1274
- }
1275
-
1276
- async function exportSlope(ctx: ExportContext): Promise<string> {
1277
- const { content, theme, palette } = ctx;
1278
- const parsed = parseSlope(content, palette);
1279
- if (parsed.error || parsed.data.length === 0) return '';
1280
- const effectivePalette = await resolveExportPalette(theme, palette);
1281
- const { container, dims } = beginVizExport();
1282
- renderSlopeChart(
1283
- container,
1284
- parsed,
1285
- effectivePalette,
1286
- ctx.isDark,
1287
- undefined,
1288
- dims
1289
- );
1290
- return finalizeSvgExport(container, theme, effectivePalette);
1291
- }
1292
-
1293
- async function exportArc(ctx: ExportContext): Promise<string> {
1294
- const { content, theme, palette } = ctx;
1295
- const parsed = parseArc(content, palette);
1296
- if (parsed.error || parsed.links.length === 0) return '';
1297
- const effectivePalette = await resolveExportPalette(theme, palette);
1298
- const { container, dims } = beginVizExport();
1299
- renderArcDiagram(
1300
- container,
1301
- parsed,
1302
- effectivePalette,
1303
- ctx.isDark,
1304
- undefined,
1305
- dims
1306
- );
1307
- return finalizeSvgExport(container, theme, effectivePalette);
1308
- }
1309
-
1310
- async function exportTimeline(ctx: ExportContext): Promise<string> {
1311
- const { content, theme, palette, viewState, exportMode } = ctx;
1312
- const parsed = parseTimeline(content, palette);
1313
- if (parsed.error || parsed.timelineEvents.length === 0) return '';
1314
- const effectivePalette = await resolveExportPalette(theme, palette);
1315
- const { container, dims } = beginVizExport();
1316
- renderTimeline(
1317
- container,
1318
- parsed,
1319
- effectivePalette,
1320
- ctx.isDark,
1321
- undefined,
1322
- dims,
1323
- resolveActiveTagGroup(
1324
- parsed.timelineTagGroups,
1325
- parsed.timelineActiveTag,
1326
- ctxTagOverride(ctx)
1327
- ),
1328
- // Swimlane axis from source (`lane-by <group>` / `sort tag:<group>` →
1329
- // timelineDefaultSwimlaneTG), with a viewState.swim override.
1330
- viewState?.swim ?? parsed.timelineDefaultSwimlaneTG,
1331
- undefined,
1332
- undefined,
1333
- exportMode
1334
- );
1335
- return finalizeSvgExport(container, theme, effectivePalette);
1336
- }
1337
-
1338
- async function exportWordcloud(ctx: ExportContext): Promise<string> {
1339
- const { content, theme, palette } = ctx;
1340
- const parsed = parseWordcloud(content, palette);
1341
- if (parsed.error || parsed.words.length === 0) return '';
1342
- const effectivePalette = await resolveExportPalette(theme, palette);
1343
- const { container, dims } = beginVizExport();
1344
- await renderWordCloudAsync(
1345
- container,
1346
- parsed,
1347
- effectivePalette,
1348
- ctx.isDark,
1349
- dims
1350
- );
1351
- return finalizeSvgExport(container, theme, effectivePalette);
1352
- }
1353
-
1354
- async function exportVenn(ctx: ExportContext): Promise<string> {
1355
- const { content, theme, palette } = ctx;
1356
- const parsed = parseVenn(content, palette);
1357
- if (parsed.error || parsed.vennSets.length < 2) return '';
1358
- const effectivePalette = await resolveExportPalette(theme, palette);
1359
- const { container, dims } = beginVizExport();
1360
- renderVenn(container, parsed, effectivePalette, ctx.isDark, undefined, dims);
1361
- return finalizeSvgExport(container, theme, effectivePalette);
1362
- }
1363
-
1364
- async function exportQuadrant(ctx: ExportContext): Promise<string> {
1365
- const { content, theme, palette } = ctx;
1366
- const parsed = parseQuadrant(content, palette);
1367
- if (parsed.error || parsed.quadrantPoints.length === 0) return '';
1368
- const effectivePalette = await resolveExportPalette(theme, palette);
1369
- const { container, dims } = beginVizExport();
1370
- renderQuadrant(
1371
- container,
1372
- parsed,
1373
- effectivePalette,
1374
- ctx.isDark,
1375
- undefined,
1376
- dims
1377
- );
1378
- return finalizeSvgExport(container, theme, effectivePalette);
1379
- }
1380
-
1381
- /**
1382
- * Fallthrough export for `sequence` — the only type without a chart-type of its
1383
- * own (auto-detected from arrow syntax, parsed by parseSequenceDgmo). All other
1384
- * D3 visualizations now have their own handler in DIAGRAM_EXPORT_HANDLERS.
1385
- */
1386
- async function exportVisualization(ctx: ExportContext): Promise<string> {
1387
- const { content, theme, palette, viewState } = ctx;
1388
- const parsed = parseVisualization(content, palette);
1389
- // Allow sequence diagrams through even if parseVisualization errors —
1390
- // sequence is parsed by its own dedicated parser (parseSequenceDgmo)
1391
- // and may not have a "chart:" line (auto-detected from arrow syntax).
1392
- if (parsed.type !== 'sequence') {
1393
- if (parsed.error) {
1394
- const looksLikeSequence = /->|~>|<-/.test(content);
1395
- if (!looksLikeSequence) return '';
1396
- } else {
1397
- return '';
1398
- }
1399
- }
1400
-
1401
- const effectivePalette = await resolveExportPalette(theme, palette);
1402
- const isDark = ctx.isDark;
1403
- const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
1404
-
1405
- const { parseSequenceDgmo } = await import('./sequence/parser');
1406
- const { renderSequenceDiagram } = await import('./sequence/renderer');
1407
- const seqParsed = parseSequenceDgmo(content, effectivePalette);
1408
- if (seqParsed.error || seqParsed.participants.length === 0) return '';
1409
- // Apply interactive view state from share links (read from unified viewState).
1410
- // Sequences key both sections and groups by source line number; `cg` is the
1411
- // shared string[] field, so coerce its entries back to numbers.
1412
- const collapsedSections = viewState?.cs ? new Set(viewState.cs) : undefined;
1413
- const collapsedGroups = viewState?.cg
1414
- ? new Set(viewState.cg.map(Number).filter((n) => Number.isFinite(n)))
1415
- : undefined;
1416
- const seqActiveTagGroup = ctxTagOverride(ctx);
1417
- renderSequenceDiagram(
1418
- container,
1419
- seqParsed,
1420
- effectivePalette,
1421
- isDark,
1422
- undefined,
1423
- {
1424
- exportWidth: EXPORT_WIDTH,
1425
- ...(seqActiveTagGroup !== undefined && {
1426
- activeTagGroup: seqActiveTagGroup,
1427
- }),
1428
- ...(collapsedSections !== undefined && { collapsedSections }),
1429
- ...(collapsedGroups !== undefined && { collapsedGroups }),
1430
- }
1431
- );
1432
-
1433
- return finalizeSvgExport(container, theme, effectivePalette);
1434
- }