@diagrammo/dgmo 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diagrammo/dgmo",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "DGMO diagram markup language — parser, renderer, and color system",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/chart.ts CHANGED
@@ -32,6 +32,7 @@ export interface ParsedChart {
32
32
  orientation?: 'horizontal' | 'vertical';
33
33
  color?: string;
34
34
  label?: string;
35
+ labels?: 'name' | 'value' | 'percent' | 'full';
35
36
  data: ChartDataPoint[];
36
37
  error?: string;
37
38
  }
@@ -140,6 +141,14 @@ export function parseChart(
140
141
  continue;
141
142
  }
142
143
 
144
+ if (key === 'labels') {
145
+ const v = value.toLowerCase();
146
+ if (v === 'name' || v === 'value' || v === 'percent' || v === 'full') {
147
+ result.labels = v;
148
+ }
149
+ continue;
150
+ }
151
+
143
152
  if (key === 'orientation') {
144
153
  const v = value.toLowerCase();
145
154
  if (v === 'horizontal' || v === 'vertical') {
package/src/cli.ts CHANGED
@@ -1,10 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { resolve, basename, extname } from 'node:path';
3
- import { JSDOM } from 'jsdom';
4
3
  import { Resvg } from '@resvg/resvg-js';
5
- import { renderD3ForExport } from './d3';
6
- import { renderEChartsForExport } from './echarts';
7
- import { parseDgmoChartType, getDgmoFramework } from './dgmo-router';
4
+ import { render } from './render';
8
5
  import { getPalette } from './palettes/registry';
9
6
  import { DEFAULT_FONT_NAME } from './fonts';
10
7
 
@@ -108,18 +105,6 @@ function parseArgs(argv: string[]): {
108
105
  return result;
109
106
  }
110
107
 
111
- function setupDom(): void {
112
- const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
113
- const win = dom.window;
114
-
115
- // Expose DOM globals needed by d3-selection and renderers
116
- Object.defineProperty(globalThis, 'document', { value: win.document, configurable: true });
117
- Object.defineProperty(globalThis, 'window', { value: win, configurable: true });
118
- Object.defineProperty(globalThis, 'navigator', { value: win.navigator, configurable: true });
119
- Object.defineProperty(globalThis, 'HTMLElement', { value: win.HTMLElement, configurable: true });
120
- Object.defineProperty(globalThis, 'SVGElement', { value: win.SVGElement, configurable: true });
121
- }
122
-
123
108
  function inferFormat(outputPath: string | undefined): 'svg' | 'png' {
124
109
  if (outputPath && extname(outputPath).toLowerCase() === '.svg') {
125
110
  return 'svg';
@@ -217,28 +202,23 @@ async function main(): Promise<void> {
217
202
  noInput();
218
203
  }
219
204
 
220
- const isDark = opts.theme === 'dark';
221
- const paletteColors = isDark
222
- ? getPalette(opts.palette).dark
223
- : getPalette(opts.palette).light;
224
-
225
- // Determine which rendering framework to use
226
- const chartType = parseDgmoChartType(content);
227
- const framework = chartType ? getDgmoFramework(chartType) : null;
228
-
229
- let svg: string;
205
+ const paletteColors = getPalette(opts.palette)[opts.theme === 'dark' ? 'dark' : 'light'];
230
206
 
231
- if (framework === 'echart') {
232
- svg = await renderEChartsForExport(content, opts.theme, paletteColors);
233
- } else if (framework === 'd3' || framework === null) {
234
- // Set up jsdom before any d3/renderer code runs
235
- setupDom();
236
- svg = await renderD3ForExport(content, opts.theme, paletteColors);
237
- } else {
238
- console.error(`Error: Unknown chart framework "${framework}".`);
207
+ // Word clouds require Canvas APIs (HTMLCanvasElement.getContext('2d'))
208
+ // which are unavailable in Node.js — check before attempting render.
209
+ const wordcloudRe = /^\s*chart\s*:\s*wordcloud\b/im;
210
+ if (wordcloudRe.test(content)) {
211
+ console.error(
212
+ 'Error: Word clouds are not supported in the CLI (requires Canvas). Use the desktop app or browser instead.'
213
+ );
239
214
  process.exit(1);
240
215
  }
241
216
 
217
+ const svg = await render(content, {
218
+ theme: opts.theme,
219
+ palette: opts.palette,
220
+ });
221
+
242
222
  if (!svg) {
243
223
  console.error(
244
224
  'Error: Failed to render diagram. The input may be empty, invalid, or use an unsupported chart type.'
package/src/d3.ts CHANGED
@@ -1179,6 +1179,12 @@ export function renderSlopeChart(
1179
1179
  data.forEach((item, idx) => {
1180
1180
  const color = item.color ?? colors[idx % colors.length];
1181
1181
 
1182
+ // Wrap each series in a group with data-line-number for sync adapter
1183
+ const seriesG = g
1184
+ .append('g')
1185
+ .attr('class', 'slope-series')
1186
+ .attr('data-line-number', String(item.lineNumber));
1187
+
1182
1188
  // Tooltip content – overall change for this series
1183
1189
  const firstVal = item.values[0];
1184
1190
  const lastVal = item.values[item.values.length - 1];
@@ -1193,7 +1199,7 @@ export function renderSlopeChart(
1193
1199
  `Change: ${sign}${absChange}${pctPart}`;
1194
1200
 
1195
1201
  // Line
1196
- g.append('path')
1202
+ seriesG.append('path')
1197
1203
  .datum(item.values)
1198
1204
  .attr('fill', 'none')
1199
1205
  .attr('stroke', color)
@@ -1201,7 +1207,7 @@ export function renderSlopeChart(
1201
1207
  .attr('d', lineGen);
1202
1208
 
1203
1209
  // Invisible wider path for easier hover targeting
1204
- g.append('path')
1210
+ seriesG.append('path')
1205
1211
  .datum(item.values)
1206
1212
  .attr('fill', 'none')
1207
1213
  .attr('stroke', 'transparent')
@@ -1225,7 +1231,7 @@ export function renderSlopeChart(
1225
1231
  const y = yScale(val);
1226
1232
 
1227
1233
  // Point circle
1228
- g.append('circle')
1234
+ seriesG.append('circle')
1229
1235
  .attr('cx', x)
1230
1236
  .attr('cy', y)
1231
1237
  .attr('r', 4)
@@ -1248,7 +1254,7 @@ export function renderSlopeChart(
1248
1254
  const isFirst = i === 0;
1249
1255
  const isLast = i === periods.length - 1;
1250
1256
  if (!isLast) {
1251
- g.append('text')
1257
+ seriesG.append('text')
1252
1258
  .attr('x', isFirst ? x - 10 : x)
1253
1259
  .attr('y', y)
1254
1260
  .attr('dy', '0.35em')
@@ -1266,7 +1272,7 @@ export function renderSlopeChart(
1266
1272
  const availableWidth = rightMargin - 15;
1267
1273
  const maxChars = Math.floor(availableWidth / SLOPE_CHAR_WIDTH);
1268
1274
 
1269
- const labelEl = g
1275
+ const labelEl = seriesG
1270
1276
  .append('text')
1271
1277
  .attr('x', lastX + 10)
1272
1278
  .attr('y', lastY)
@@ -1640,6 +1646,7 @@ export function renderArcDiagram(
1640
1646
  g.append('rect')
1641
1647
  .attr('class', 'arc-group-band')
1642
1648
  .attr('data-group', group.name)
1649
+ .attr('data-line-number', String(group.lineNumber))
1643
1650
  .attr('x', baseX - bandHalfW)
1644
1651
  .attr('y', minY)
1645
1652
  .attr('width', bandHalfW * 2)
@@ -1657,6 +1664,7 @@ export function renderArcDiagram(
1657
1664
  g.append('text')
1658
1665
  .attr('class', 'arc-group-label')
1659
1666
  .attr('data-group', group.name)
1667
+ .attr('data-line-number', String(group.lineNumber))
1660
1668
  .attr('x', baseX - bandHalfW + 6)
1661
1669
  .attr('y', minY + 14)
1662
1670
  .attr('fill', textColor)
@@ -1696,6 +1704,7 @@ export function renderArcDiagram(
1696
1704
  .attr('class', 'arc-link')
1697
1705
  .attr('data-source', link.source)
1698
1706
  .attr('data-target', link.target)
1707
+ .attr('data-line-number', String(link.lineNumber))
1699
1708
  .attr('d', `M ${baseX},${y1} Q ${controlX},${midY} ${baseX},${y2}`)
1700
1709
  .attr('fill', 'none')
1701
1710
  .attr('stroke', color)
@@ -1720,6 +1729,7 @@ export function renderArcDiagram(
1720
1729
  .append('g')
1721
1730
  .attr('class', 'arc-node')
1722
1731
  .attr('data-node', node)
1732
+ .attr('data-line-number', nodeLink?.lineNumber ? String(nodeLink.lineNumber) : null)
1723
1733
  .style('cursor', 'pointer')
1724
1734
  .on('mouseenter', () => handleMouseEnter(node))
1725
1735
  .on('mouseleave', handleMouseLeave)
@@ -1773,6 +1783,7 @@ export function renderArcDiagram(
1773
1783
  g.append('rect')
1774
1784
  .attr('class', 'arc-group-band')
1775
1785
  .attr('data-group', group.name)
1786
+ .attr('data-line-number', String(group.lineNumber))
1776
1787
  .attr('x', minX)
1777
1788
  .attr('y', baseY - bandHalfH)
1778
1789
  .attr('width', maxX - minX)
@@ -1790,6 +1801,7 @@ export function renderArcDiagram(
1790
1801
  g.append('text')
1791
1802
  .attr('class', 'arc-group-label')
1792
1803
  .attr('data-group', group.name)
1804
+ .attr('data-line-number', String(group.lineNumber))
1793
1805
  .attr('x', (minX + maxX) / 2)
1794
1806
  .attr('y', baseY + bandHalfH - 4)
1795
1807
  .attr('text-anchor', 'middle')
@@ -1830,6 +1842,7 @@ export function renderArcDiagram(
1830
1842
  .attr('class', 'arc-link')
1831
1843
  .attr('data-source', link.source)
1832
1844
  .attr('data-target', link.target)
1845
+ .attr('data-line-number', String(link.lineNumber))
1833
1846
  .attr('d', `M ${x1},${baseY} Q ${midX},${controlY} ${x2},${baseY}`)
1834
1847
  .attr('fill', 'none')
1835
1848
  .attr('stroke', color)
@@ -1854,6 +1867,7 @@ export function renderArcDiagram(
1854
1867
  .append('g')
1855
1868
  .attr('class', 'arc-node')
1856
1869
  .attr('data-node', node)
1870
+ .attr('data-line-number', nodeLink?.lineNumber ? String(nodeLink.lineNumber) : null)
1857
1871
  .style('cursor', 'pointer')
1858
1872
  .on('mouseenter', () => handleMouseEnter(node))
1859
1873
  .on('mouseleave', handleMouseLeave)
@@ -2019,6 +2033,7 @@ function renderMarkers(
2019
2033
  .append('g')
2020
2034
  .attr('class', 'tl-marker')
2021
2035
  .attr('data-marker-date', String(dateVal))
2036
+ .attr('data-line-number', String(marker.lineNumber))
2022
2037
  .style('cursor', 'pointer')
2023
2038
  .on('mouseenter', function (event: MouseEvent) {
2024
2039
  if (tooltip) {
@@ -2880,6 +2895,7 @@ export function renderTimeline(
2880
2895
  .append('g')
2881
2896
  .attr('class', 'tl-event')
2882
2897
  .attr('data-group', laneName)
2898
+ .attr('data-line-number', String(ev.lineNumber))
2883
2899
  .attr('data-date', String(parseTimelineDate(ev.date)))
2884
2900
  .attr(
2885
2901
  'data-end-date',
@@ -3090,6 +3106,7 @@ export function renderTimeline(
3090
3106
  .append('g')
3091
3107
  .attr('class', 'tl-event')
3092
3108
  .attr('data-group', ev.group || '')
3109
+ .attr('data-line-number', String(ev.lineNumber))
3093
3110
  .attr('data-date', String(parseTimelineDate(ev.date)))
3094
3111
  .attr(
3095
3112
  'data-end-date',
@@ -3363,6 +3380,7 @@ export function renderTimeline(
3363
3380
  .append('g')
3364
3381
  .attr('class', 'tl-event')
3365
3382
  .attr('data-group', lane.name)
3383
+ .attr('data-line-number', String(ev.lineNumber))
3366
3384
  .attr('data-date', String(parseTimelineDate(ev.date)))
3367
3385
  .attr(
3368
3386
  'data-end-date',
@@ -3643,6 +3661,7 @@ export function renderTimeline(
3643
3661
  .append('g')
3644
3662
  .attr('class', 'tl-event')
3645
3663
  .attr('data-group', ev.group || '')
3664
+ .attr('data-line-number', String(ev.lineNumber))
3646
3665
  .attr('data-date', String(parseTimelineDate(ev.date)))
3647
3666
  .attr(
3648
3667
  'data-end-date',
@@ -3895,6 +3914,10 @@ export function renderWordCloud(
3895
3914
  'transform',
3896
3915
  (d) => `translate(${d.x},${d.y}) rotate(${d.rotate})`
3897
3916
  )
3917
+ .attr('data-line-number', (d) => {
3918
+ const ln = (d as WordCloudWord).lineNumber;
3919
+ return ln ? String(ln) : null;
3920
+ })
3898
3921
  .text((d) => d.text!)
3899
3922
  .on('click', (_event, d) => {
3900
3923
  const ln = (d as WordCloudWord).lineNumber;
@@ -4594,6 +4617,7 @@ export function renderVenn(
4594
4617
  .attr('cy', c.y)
4595
4618
  .attr('r', c.r)
4596
4619
  .attr('fill', 'transparent')
4620
+ .attr('data-line-number', String(vennSets[i].lineNumber))
4597
4621
  .style('cursor', onClickItem ? 'pointer' : 'default')
4598
4622
  .on('mouseenter', (event: MouseEvent) => {
4599
4623
  for (const rg of regionGroups) {
@@ -4848,6 +4872,9 @@ export function renderQuadrant(
4848
4872
  .attr('fill', (d) => getQuadrantLabelColor(d))
4849
4873
  .attr('font-size', '48px')
4850
4874
  .attr('font-weight', '700')
4875
+ .attr('data-line-number', (d) =>
4876
+ d.label?.lineNumber ? String(d.label.lineNumber) : null
4877
+ )
4851
4878
  .style('cursor', (d) =>
4852
4879
  onClickItem && d.label?.lineNumber ? 'pointer' : 'default'
4853
4880
  )
@@ -4871,11 +4898,13 @@ export function renderQuadrant(
4871
4898
  // Low label (centered on left half)
4872
4899
  const xLowLabel = svg
4873
4900
  .append('text')
4901
+ .attr('class', 'quadrant-axis-label')
4874
4902
  .attr('x', margin.left + chartWidth / 4)
4875
4903
  .attr('y', height - 20)
4876
4904
  .attr('text-anchor', 'middle')
4877
4905
  .attr('fill', textColor)
4878
4906
  .attr('font-size', '18px')
4907
+ .attr('data-line-number', quadrantXAxisLineNumber ? String(quadrantXAxisLineNumber) : null)
4879
4908
  .style(
4880
4909
  'cursor',
4881
4910
  onClickItem && quadrantXAxisLineNumber ? 'pointer' : 'default'
@@ -4885,11 +4914,13 @@ export function renderQuadrant(
4885
4914
  // High label (centered on right half)
4886
4915
  const xHighLabel = svg
4887
4916
  .append('text')
4917
+ .attr('class', 'quadrant-axis-label')
4888
4918
  .attr('x', margin.left + (chartWidth * 3) / 4)
4889
4919
  .attr('y', height - 20)
4890
4920
  .attr('text-anchor', 'middle')
4891
4921
  .attr('fill', textColor)
4892
4922
  .attr('font-size', '18px')
4923
+ .attr('data-line-number', quadrantXAxisLineNumber ? String(quadrantXAxisLineNumber) : null)
4893
4924
  .style(
4894
4925
  'cursor',
4895
4926
  onClickItem && quadrantXAxisLineNumber ? 'pointer' : 'default'
@@ -4918,12 +4949,14 @@ export function renderQuadrant(
4918
4949
  // Low label (centered on bottom half)
4919
4950
  const yLowLabel = svg
4920
4951
  .append('text')
4952
+ .attr('class', 'quadrant-axis-label')
4921
4953
  .attr('x', 22)
4922
4954
  .attr('y', yMidBottom)
4923
4955
  .attr('text-anchor', 'middle')
4924
4956
  .attr('fill', textColor)
4925
4957
  .attr('font-size', '18px')
4926
4958
  .attr('transform', `rotate(-90, 22, ${yMidBottom})`)
4959
+ .attr('data-line-number', quadrantYAxisLineNumber ? String(quadrantYAxisLineNumber) : null)
4927
4960
  .style(
4928
4961
  'cursor',
4929
4962
  onClickItem && quadrantYAxisLineNumber ? 'pointer' : 'default'
@@ -4933,12 +4966,14 @@ export function renderQuadrant(
4933
4966
  // High label (centered on top half)
4934
4967
  const yHighLabel = svg
4935
4968
  .append('text')
4969
+ .attr('class', 'quadrant-axis-label')
4936
4970
  .attr('x', 22)
4937
4971
  .attr('y', yMidTop)
4938
4972
  .attr('text-anchor', 'middle')
4939
4973
  .attr('fill', textColor)
4940
4974
  .attr('font-size', '18px')
4941
4975
  .attr('transform', `rotate(-90, 22, ${yMidTop})`)
4976
+ .attr('data-line-number', quadrantYAxisLineNumber ? String(quadrantYAxisLineNumber) : null)
4942
4977
  .style(
4943
4978
  'cursor',
4944
4979
  onClickItem && quadrantYAxisLineNumber ? 'pointer' : 'default'
@@ -4997,7 +5032,8 @@ export function renderQuadrant(
4997
5032
  const pointColor =
4998
5033
  quadDef?.label?.color ?? defaultColors[quadDef?.colorIdx ?? 0];
4999
5034
 
5000
- const pointG = pointsG.append('g').attr('class', 'point-group');
5035
+ const pointG = pointsG.append('g').attr('class', 'point-group')
5036
+ .attr('data-line-number', String(point.lineNumber));
5001
5037
 
5002
5038
  // Circle with white fill and colored border for visibility on opaque quadrants
5003
5039
  pointG
package/src/echarts.ts CHANGED
@@ -538,6 +538,7 @@ function buildSankeyOption(
538
538
  type: 'sankey',
539
539
  emphasis: {
540
540
  focus: 'adjacency',
541
+ blurScope: 'global' as const,
541
542
  },
542
543
  nodeAlign: 'left',
543
544
  nodeGap: 12,
@@ -746,6 +747,10 @@ function buildFunctionOption(
746
747
  itemStyle: {
747
748
  color: fnColor,
748
749
  },
750
+ emphasis: {
751
+ focus: 'self' as const,
752
+ blurScope: 'global' as const,
753
+ },
749
754
  };
750
755
  });
751
756
 
@@ -1116,6 +1121,8 @@ function buildHeatmapOption(
1116
1121
  fontWeight: 'bold' as const,
1117
1122
  },
1118
1123
  emphasis: {
1124
+ focus: 'self' as const,
1125
+ blurScope: 'global' as const,
1119
1126
  itemStyle: {
1120
1127
  shadowBlur: 10,
1121
1128
  shadowColor: 'rgba(0, 0, 0, 0.5)',
@@ -1210,6 +1217,8 @@ function buildFunnelOption(
1210
1217
  lineStyle: { color: textColor, opacity: 0.3 },
1211
1218
  },
1212
1219
  emphasis: {
1220
+ focus: 'self' as const,
1221
+ blurScope: 'global' as const,
1213
1222
  label: {
1214
1223
  fontSize: 15,
1215
1224
  },
@@ -1394,6 +1403,10 @@ function buildBarOption(
1394
1403
  {
1395
1404
  type: 'bar',
1396
1405
  data,
1406
+ emphasis: {
1407
+ focus: 'self' as const,
1408
+ blurScope: 'global' as const,
1409
+ },
1397
1410
  },
1398
1411
  ],
1399
1412
  };
@@ -1442,6 +1455,10 @@ function buildLineOption(
1442
1455
  symbolSize: 8,
1443
1456
  lineStyle: { color: lineColor, width: 3 },
1444
1457
  itemStyle: { color: lineColor },
1458
+ emphasis: {
1459
+ focus: 'self' as const,
1460
+ blurScope: 'global' as const,
1461
+ },
1445
1462
  },
1446
1463
  ],
1447
1464
  };
@@ -1476,6 +1493,10 @@ function buildMultiLineOption(
1476
1493
  symbolSize: 8,
1477
1494
  lineStyle: { color, width: 3 },
1478
1495
  itemStyle: { color },
1496
+ emphasis: {
1497
+ focus: 'self' as const,
1498
+ blurScope: 'global' as const,
1499
+ },
1479
1500
  };
1480
1501
  });
1481
1502
 
@@ -1550,11 +1571,26 @@ function buildAreaOption(
1550
1571
  lineStyle: { color: lineColor, width: 3 },
1551
1572
  itemStyle: { color: lineColor },
1552
1573
  areaStyle: { opacity: 0.25 },
1574
+ emphasis: {
1575
+ focus: 'self' as const,
1576
+ blurScope: 'global' as const,
1577
+ },
1553
1578
  },
1554
1579
  ],
1555
1580
  };
1556
1581
  }
1557
1582
 
1583
+ // ── Segment label formatter ──────────────────────────────────
1584
+
1585
+ function segmentLabelFormatter(mode: ParsedChart['labels']): string {
1586
+ switch (mode) {
1587
+ case 'name': return '{b}';
1588
+ case 'value': return '{b} — {c}';
1589
+ case 'percent': return '{b} — {d}%';
1590
+ default: return '{b} — {c} ({d}%)';
1591
+ }
1592
+ }
1593
+
1558
1594
  // ── Pie / Doughnut ───────────────────────────────────────────
1559
1595
 
1560
1596
  function buildPieOption(
@@ -1586,11 +1622,15 @@ function buildPieOption(
1586
1622
  data,
1587
1623
  label: {
1588
1624
  position: 'outside',
1589
- formatter: '{b} — {c} ({d}%)',
1625
+ formatter: segmentLabelFormatter(parsed.labels),
1590
1626
  color: textColor,
1591
1627
  fontFamily: FONT_FAMILY,
1592
1628
  },
1593
1629
  labelLine: { show: true },
1630
+ emphasis: {
1631
+ focus: 'self' as const,
1632
+ blurScope: 'global' as const,
1633
+ },
1594
1634
  },
1595
1635
  ],
1596
1636
  };
@@ -1660,6 +1700,10 @@ function buildRadarOption(
1660
1700
  },
1661
1701
  },
1662
1702
  ],
1703
+ emphasis: {
1704
+ focus: 'self' as const,
1705
+ blurScope: 'global' as const,
1706
+ },
1663
1707
  },
1664
1708
  ],
1665
1709
  };
@@ -1696,11 +1740,15 @@ function buildPolarAreaOption(
1696
1740
  data,
1697
1741
  label: {
1698
1742
  position: 'outside',
1699
- formatter: '{b} — {c} ({d}%)',
1743
+ formatter: segmentLabelFormatter(parsed.labels),
1700
1744
  color: textColor,
1701
1745
  fontFamily: FONT_FAMILY,
1702
1746
  },
1703
1747
  labelLine: { show: true },
1748
+ emphasis: {
1749
+ focus: 'self' as const,
1750
+ blurScope: 'global' as const,
1751
+ },
1704
1752
  },
1705
1753
  ],
1706
1754
  };
@@ -1743,6 +1791,10 @@ function buildBarStackedOption(
1743
1791
  fontWeight: 'bold' as const,
1744
1792
  fontFamily: FONT_FAMILY,
1745
1793
  },
1794
+ emphasis: {
1795
+ focus: 'self' as const,
1796
+ blurScope: 'global' as const,
1797
+ },
1746
1798
  };
1747
1799
  });
1748
1800
 
package/src/index.ts CHANGED
@@ -1,3 +1,9 @@
1
+ // ============================================================
2
+ // Unified API
3
+ // ============================================================
4
+
5
+ export { render } from './render';
6
+
1
7
  // ============================================================
2
8
  // Router
3
9
  // ============================================================
@@ -109,6 +115,7 @@ export {
109
115
  applyPositionOverrides,
110
116
  applyGroupOrdering,
111
117
  groupMessagesBySection,
118
+ buildNoteMessageMap,
112
119
  } from './sequence/renderer';
113
120
  export type {
114
121
  RenderStep,
package/src/render.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { renderD3ForExport } from './d3';
2
+ import { renderEChartsForExport } from './echarts';
3
+ import { parseDgmoChartType, getDgmoFramework } from './dgmo-router';
4
+ import { getPalette } from './palettes/registry';
5
+
6
+ /**
7
+ * Ensures DOM globals are available for D3 renderers.
8
+ * No-ops in browser environments where `document` already exists.
9
+ * Dynamically imports jsdom only in Node.js to avoid bundling it for browsers.
10
+ */
11
+ async function ensureDom(): Promise<void> {
12
+ if (typeof document !== 'undefined') return;
13
+
14
+ const { JSDOM } = await import('jsdom');
15
+ const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
16
+ const win = dom.window;
17
+
18
+ Object.defineProperty(globalThis, 'document', { value: win.document, configurable: true });
19
+ Object.defineProperty(globalThis, 'window', { value: win, configurable: true });
20
+ Object.defineProperty(globalThis, 'navigator', { value: win.navigator, configurable: true });
21
+ Object.defineProperty(globalThis, 'HTMLElement', { value: win.HTMLElement, configurable: true });
22
+ Object.defineProperty(globalThis, 'SVGElement', { value: win.SVGElement, configurable: true });
23
+ }
24
+
25
+ /**
26
+ * Render DGMO source to an SVG string.
27
+ *
28
+ * Automatically detects the chart type, selects the appropriate renderer,
29
+ * and returns a complete SVG document string.
30
+ *
31
+ * @param content - DGMO source text
32
+ * @param options - Optional theme and palette settings
33
+ * @returns SVG string, or empty string on error
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { render } from '@diagrammo/dgmo';
38
+ *
39
+ * const svg = await render(`chart: pie
40
+ * title: Languages
41
+ * TypeScript: 45
42
+ * Python: 30
43
+ * Rust: 25`);
44
+ * ```
45
+ */
46
+ export async function render(
47
+ content: string,
48
+ options?: {
49
+ theme?: 'light' | 'dark' | 'transparent';
50
+ palette?: string;
51
+ },
52
+ ): Promise<string> {
53
+ const theme = options?.theme ?? 'light';
54
+ const paletteName = options?.palette ?? 'nord';
55
+
56
+ const paletteColors = getPalette(paletteName)[theme === 'dark' ? 'dark' : 'light'];
57
+
58
+ const chartType = parseDgmoChartType(content);
59
+ const framework = chartType ? getDgmoFramework(chartType) : null;
60
+
61
+ if (framework === 'echart') {
62
+ return renderEChartsForExport(content, theme, paletteColors);
63
+ }
64
+
65
+ // D3 and unknown/null frameworks both go through D3 renderer
66
+ await ensureDom();
67
+ return renderD3ForExport(content, theme, paletteColors);
68
+ }