@diagrammo/dgmo 0.47.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/dist/auto.js +1 -1
  2. package/dist/auto.mjs +1 -1
  3. package/dist/element.js +1 -1
  4. package/dist/element.mjs +1 -1
  5. package/package.json +19 -70
  6. package/dist/advanced.cjs +0 -71648
  7. package/dist/advanced.d.cts +0 -5719
  8. package/dist/auto.cjs +0 -70648
  9. package/dist/auto.d.cts +0 -54
  10. package/dist/block.cjs +0 -70208
  11. package/dist/block.d.cts +0 -115
  12. package/dist/editor.cjs +0 -526
  13. package/dist/editor.d.cts +0 -26
  14. package/dist/element.cjs +0 -70530
  15. package/dist/element.d.cts +0 -62
  16. package/dist/highlight.cjs +0 -852
  17. package/dist/highlight.d.cts +0 -49
  18. package/dist/index.cjs +0 -70491
  19. package/dist/index.d.cts +0 -160
  20. package/dist/pert.cjs +0 -325
  21. package/dist/pert.d.cts +0 -559
  22. package/dist/themes-1-CuKpeH.d.cts +0 -908
  23. package/src/advanced.ts +0 -873
  24. package/src/arc/parser.ts +0 -15
  25. package/src/arc/renderer.ts +0 -633
  26. package/src/auto/index.ts +0 -621
  27. package/src/auto/shared.ts +0 -370
  28. package/src/auto/styles.ts +0 -73
  29. package/src/block/layout.ts +0 -198
  30. package/src/block/parser.ts +0 -427
  31. package/src/block/renderer.ts +0 -484
  32. package/src/block/types.ts +0 -67
  33. package/src/boxes-and-lines/collapse.ts +0 -96
  34. package/src/boxes-and-lines/focus.ts +0 -257
  35. package/src/boxes-and-lines/label-placement.ts +0 -339
  36. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  37. package/src/boxes-and-lines/layout-layered.ts +0 -722
  38. package/src/boxes-and-lines/layout-search.ts +0 -1678
  39. package/src/boxes-and-lines/layout.ts +0 -398
  40. package/src/boxes-and-lines/parser.ts +0 -1181
  41. package/src/boxes-and-lines/renderer.ts +0 -1465
  42. package/src/boxes-and-lines/types.ts +0 -66
  43. package/src/c4/layout.ts +0 -2441
  44. package/src/c4/parser.ts +0 -1158
  45. package/src/c4/renderer.ts +0 -2008
  46. package/src/c4/types.ts +0 -89
  47. package/src/chart-type-registry.ts +0 -529
  48. package/src/chart-types.ts +0 -225
  49. package/src/chart.ts +0 -685
  50. package/src/charts-d3/bar.ts +0 -338
  51. package/src/charts-d3/chord.ts +0 -140
  52. package/src/charts-d3/function.ts +0 -147
  53. package/src/charts-d3/funnel.ts +0 -104
  54. package/src/charts-d3/heatmap.ts +0 -145
  55. package/src/charts-d3/index.ts +0 -337
  56. package/src/charts-d3/interactions.ts +0 -477
  57. package/src/charts-d3/line.ts +0 -344
  58. package/src/charts-d3/mount.ts +0 -151
  59. package/src/charts-d3/pie.ts +0 -117
  60. package/src/charts-d3/polar.ts +0 -89
  61. package/src/charts-d3/radar.ts +0 -0
  62. package/src/charts-d3/sankey.ts +0 -0
  63. package/src/charts-d3/scatter.ts +0 -185
  64. package/src/charts-d3/shared.ts +0 -208
  65. package/src/class/layout.ts +0 -326
  66. package/src/class/parser.ts +0 -635
  67. package/src/class/renderer.ts +0 -793
  68. package/src/class/types.ts +0 -58
  69. package/src/cli-banner.ts +0 -106
  70. package/src/cli.ts +0 -1080
  71. package/src/colors-diagnostics.ts +0 -35
  72. package/src/colors.ts +0 -379
  73. package/src/completion-types.ts +0 -27
  74. package/src/completion.ts +0 -2668
  75. package/src/cycle/layout.ts +0 -1026
  76. package/src/cycle/parser.ts +0 -387
  77. package/src/cycle/renderer.ts +0 -653
  78. package/src/cycle/types.ts +0 -101
  79. package/src/d3.ts +0 -1434
  80. package/src/data-chart-parser.ts +0 -1007
  81. package/src/dgmo-router.ts +0 -296
  82. package/src/diagnostics-registry.ts +0 -108
  83. package/src/diagnostics.ts +0 -0
  84. package/src/dimensions.ts +0 -20
  85. package/src/directives-registry.ts +0 -396
  86. package/src/editor/dgmo.grammar +0 -76
  87. package/src/editor/dgmo.grammar.d.ts +0 -2
  88. package/src/editor/dgmo.grammar.js +0 -18
  89. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  90. package/src/editor/dgmo.grammar.terms.js +0 -35
  91. package/src/editor/highlight-api.ts +0 -596
  92. package/src/editor/highlight.ts +0 -36
  93. package/src/editor/index.ts +0 -27
  94. package/src/editor/keywords.ts +0 -128
  95. package/src/editor/tokens.ts +0 -30
  96. package/src/element/index.ts +0 -432
  97. package/src/embed/css.ts +0 -312
  98. package/src/embed/escape.ts +0 -15
  99. package/src/embed/index.ts +0 -280
  100. package/src/er/classify.ts +0 -228
  101. package/src/er/layout.ts +0 -530
  102. package/src/er/parser.ts +0 -750
  103. package/src/er/renderer.ts +0 -725
  104. package/src/er/types.ts +0 -52
  105. package/src/error-card.ts +0 -269
  106. package/src/event-line/diagnostics.ts +0 -117
  107. package/src/event-line/parser.ts +0 -643
  108. package/src/event-line/renderer.ts +0 -1742
  109. package/src/event-line/types.ts +0 -74
  110. package/src/fonts.ts +0 -3
  111. package/src/gantt/calculator.ts +0 -979
  112. package/src/gantt/parser.ts +0 -1641
  113. package/src/gantt/renderer.ts +0 -3787
  114. package/src/gantt/resolver.ts +0 -172
  115. package/src/gantt/types.ts +0 -206
  116. package/src/graph/diagnostics.ts +0 -64
  117. package/src/graph/edge-spline.ts +0 -29
  118. package/src/graph/flowchart-parser.ts +0 -759
  119. package/src/graph/flowchart-renderer.ts +0 -817
  120. package/src/graph/layout.ts +0 -472
  121. package/src/graph/notes.ts +0 -21
  122. package/src/graph/state-collapse.ts +0 -78
  123. package/src/graph/state-parser.ts +0 -574
  124. package/src/graph/state-renderer.ts +0 -653
  125. package/src/graph/types.ts +0 -65
  126. package/src/index.ts +0 -261
  127. package/src/infra/compute.ts +0 -1385
  128. package/src/infra/layout.ts +0 -888
  129. package/src/infra/parser.ts +0 -1234
  130. package/src/infra/renderer.ts +0 -2534
  131. package/src/infra/roles.ts +0 -75
  132. package/src/infra/types.ts +0 -216
  133. package/src/infra/validation.ts +0 -201
  134. package/src/journey-map/layout.ts +0 -490
  135. package/src/journey-map/parser.ts +0 -557
  136. package/src/journey-map/renderer.ts +0 -1580
  137. package/src/journey-map/types.ts +0 -47
  138. package/src/kanban/mutations.ts +0 -175
  139. package/src/kanban/parser.ts +0 -509
  140. package/src/kanban/renderer.ts +0 -1449
  141. package/src/kanban/types.ts +0 -39
  142. package/src/label-layout.ts +0 -368
  143. package/src/map/colorize.ts +0 -54
  144. package/src/map/completion.ts +0 -316
  145. package/src/map/context-labels.ts +0 -609
  146. package/src/map/data/PROVENANCE.json +0 -1
  147. package/src/map/data/README.md +0 -40
  148. package/src/map/data/airport-collisions.json +0 -1
  149. package/src/map/data/airports.json +0 -1
  150. package/src/map/data/gazetteer.json +0 -1
  151. package/src/map/data/lakes.json +0 -1
  152. package/src/map/data/mountain-ranges.json +0 -1
  153. package/src/map/data/na-lakes.json +0 -1
  154. package/src/map/data/na-land.json +0 -1
  155. package/src/map/data/region-names.json +0 -1
  156. package/src/map/data/rivers.json +0 -1
  157. package/src/map/data/types.ts +0 -121
  158. package/src/map/data/us-states.json +0 -1
  159. package/src/map/data/water-bodies.json +0 -1
  160. package/src/map/data/world-coarse.json +0 -1
  161. package/src/map/data/world-detail.json +0 -1
  162. package/src/map/diagnostics.ts +0 -134
  163. package/src/map/dimensions.ts +0 -150
  164. package/src/map/geo-query.ts +0 -295
  165. package/src/map/geo.ts +0 -438
  166. package/src/map/invert.ts +0 -111
  167. package/src/map/layout.ts +0 -5069
  168. package/src/map/legend-band.ts +0 -127
  169. package/src/map/load-data.ts +0 -186
  170. package/src/map/parser.ts +0 -783
  171. package/src/map/renderer.ts +0 -1445
  172. package/src/map/resolved-types.ts +0 -160
  173. package/src/map/resolver.ts +0 -1146
  174. package/src/map/types.ts +0 -194
  175. package/src/mindmap/collapse.ts +0 -67
  176. package/src/mindmap/layout.ts +0 -649
  177. package/src/mindmap/parser.ts +0 -430
  178. package/src/mindmap/renderer.ts +0 -637
  179. package/src/mindmap/text-wrap.ts +0 -218
  180. package/src/mindmap/types.ts +0 -56
  181. package/src/org/collapse.ts +0 -151
  182. package/src/org/layout.ts +0 -1262
  183. package/src/org/parser.ts +0 -519
  184. package/src/org/renderer.ts +0 -866
  185. package/src/org/resolver.ts +0 -636
  186. package/src/palettes/atlas.ts +0 -77
  187. package/src/palettes/blueprint.ts +0 -73
  188. package/src/palettes/catppuccin.ts +0 -84
  189. package/src/palettes/color-utils.ts +0 -443
  190. package/src/palettes/index.ts +0 -67
  191. package/src/palettes/nord.ts +0 -67
  192. package/src/palettes/registry.ts +0 -114
  193. package/src/palettes/slate.ts +0 -73
  194. package/src/palettes/tidewater.ts +0 -73
  195. package/src/palettes/tokyo-night.ts +0 -86
  196. package/src/palettes/types.ts +0 -88
  197. package/src/pert/analyzer.ts +0 -1183
  198. package/src/pert/diagnostics.ts +0 -110
  199. package/src/pert/index.ts +0 -43
  200. package/src/pert/internal.ts +0 -252
  201. package/src/pert/layout.ts +0 -971
  202. package/src/pert/monte-carlo.ts +0 -446
  203. package/src/pert/parser.ts +0 -1606
  204. package/src/pert/renderer.ts +0 -4009
  205. package/src/pert/share-normalize.ts +0 -62
  206. package/src/pert/types.ts +0 -604
  207. package/src/pyramid/parser.ts +0 -185
  208. package/src/pyramid/renderer.ts +0 -768
  209. package/src/pyramid/types.ts +0 -28
  210. package/src/quadrant/parser.ts +0 -18
  211. package/src/quadrant/renderer.ts +0 -638
  212. package/src/raci/diagnostics.ts +0 -213
  213. package/src/raci/index.ts +0 -23
  214. package/src/raci/mutations.ts +0 -321
  215. package/src/raci/parser.ts +0 -699
  216. package/src/raci/renderer.ts +0 -1627
  217. package/src/raci/types.ts +0 -82
  218. package/src/raci/variants.ts +0 -320
  219. package/src/render.ts +0 -268
  220. package/src/ring/parser.ts +0 -212
  221. package/src/ring/renderer.ts +0 -490
  222. package/src/ring/types.ts +0 -27
  223. package/src/sequence/collapse.ts +0 -174
  224. package/src/sequence/parser.ts +0 -1596
  225. package/src/sequence/participant-inference.ts +0 -152
  226. package/src/sequence/renderer.ts +0 -3168
  227. package/src/sequence/tag-resolution.ts +0 -164
  228. package/src/sharing.ts +0 -211
  229. package/src/sitemap/collapse.ts +0 -100
  230. package/src/sitemap/layout.ts +0 -925
  231. package/src/sitemap/parser.ts +0 -755
  232. package/src/sitemap/renderer.ts +0 -828
  233. package/src/sitemap/types.ts +0 -42
  234. package/src/slope/parser.ts +0 -18
  235. package/src/slope/renderer.ts +0 -378
  236. package/src/swimlane/diagnostics.ts +0 -94
  237. package/src/swimlane/layout.ts +0 -597
  238. package/src/swimlane/parser.ts +0 -613
  239. package/src/swimlane/renderer.ts +0 -681
  240. package/src/swimlane/types.ts +0 -151
  241. package/src/tech-radar/interactive.ts +0 -1129
  242. package/src/tech-radar/layout.ts +0 -197
  243. package/src/tech-radar/parser.ts +0 -436
  244. package/src/tech-radar/renderer.ts +0 -1239
  245. package/src/tech-radar/shared.ts +0 -151
  246. package/src/tech-radar/types.ts +0 -86
  247. package/src/themes.ts +0 -22
  248. package/src/timeline/parser.ts +0 -472
  249. package/src/timeline/renderer.ts +0 -3024
  250. package/src/timeline/types.ts +0 -33
  251. package/src/timeline/viz-parser.ts +0 -21
  252. package/src/treemap/diagnostics.ts +0 -62
  253. package/src/treemap/layout-radial.ts +0 -233
  254. package/src/treemap/layout.ts +0 -189
  255. package/src/treemap/parser.ts +0 -463
  256. package/src/treemap/renderer-radial.ts +0 -438
  257. package/src/treemap/renderer.ts +0 -572
  258. package/src/treemap/treemap-shared.ts +0 -309
  259. package/src/treemap/types.ts +0 -60
  260. package/src/utils/arrow-markers.ts +0 -51
  261. package/src/utils/arrows-diagnostics.ts +0 -62
  262. package/src/utils/arrows.ts +0 -227
  263. package/src/utils/brand.ts +0 -61
  264. package/src/utils/card.ts +0 -183
  265. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  266. package/src/utils/collapse-engine/tree.ts +0 -117
  267. package/src/utils/d3-helpers.ts +0 -219
  268. package/src/utils/d3-types.ts +0 -9
  269. package/src/utils/description-helpers.ts +0 -38
  270. package/src/utils/duration.ts +0 -312
  271. package/src/utils/export-container.ts +0 -41
  272. package/src/utils/expr-eval.ts +0 -27
  273. package/src/utils/fit-canvas.ts +0 -64
  274. package/src/utils/hover-styles.ts +0 -673
  275. package/src/utils/inline-markdown.ts +0 -96
  276. package/src/utils/legend-constants.ts +0 -48
  277. package/src/utils/legend-d3.ts +0 -618
  278. package/src/utils/legend-integration.ts +0 -107
  279. package/src/utils/legend-layout.ts +0 -776
  280. package/src/utils/legend-svg.ts +0 -214
  281. package/src/utils/legend-types.ts +0 -272
  282. package/src/utils/name-normalize.ts +0 -163
  283. package/src/utils/note-box/constants.ts +0 -25
  284. package/src/utils/note-box/index.ts +0 -11
  285. package/src/utils/note-box/metrics.ts +0 -90
  286. package/src/utils/note-box/svg.ts +0 -331
  287. package/src/utils/notes/bounds.ts +0 -30
  288. package/src/utils/notes/build.ts +0 -131
  289. package/src/utils/notes/index.ts +0 -18
  290. package/src/utils/notes/model.ts +0 -19
  291. package/src/utils/notes/parse.ts +0 -131
  292. package/src/utils/notes/place.ts +0 -177
  293. package/src/utils/notes/resolve.ts +0 -88
  294. package/src/utils/number-format.ts +0 -36
  295. package/src/utils/parsing.ts +0 -953
  296. package/src/utils/reserved-key-registry.ts +0 -239
  297. package/src/utils/safe-href.ts +0 -38
  298. package/src/utils/scaling.ts +0 -97
  299. package/src/utils/svg-embed.ts +0 -251
  300. package/src/utils/tag-groups.ts +0 -607
  301. package/src/utils/text-measure.ts +0 -122
  302. package/src/utils/time-ticks.ts +0 -253
  303. package/src/utils/title-constants.ts +0 -24
  304. package/src/utils/visual-conventions.ts +0 -61
  305. package/src/utils/wrapped-desc.ts +0 -87
  306. package/src/venn/parser.ts +0 -18
  307. package/src/venn/renderer.ts +0 -1047
  308. package/src/version-control/diagnostics.ts +0 -67
  309. package/src/version-control/parser.ts +0 -544
  310. package/src/version-control/renderer.ts +0 -724
  311. package/src/version-control/types.ts +0 -100
  312. package/src/visualizations/parse.ts +0 -1560
  313. package/src/visualizations/types.ts +0 -262
  314. package/src/wireframe/layout.ts +0 -479
  315. package/src/wireframe/parser.ts +0 -1114
  316. package/src/wireframe/renderer.ts +0 -1318
  317. package/src/wireframe/types.ts +0 -110
  318. package/src/wordcloud/parser.ts +0 -18
  319. 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
- }