@diagrammo/dgmo 0.2.8 → 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/src/echarts.ts CHANGED
@@ -54,6 +54,7 @@ export interface ParsedHeatmapRow {
54
54
  export interface ParsedEChart {
55
55
  type: EChartsChartType;
56
56
  title?: string;
57
+ titleLineNumber?: number;
57
58
  series?: string;
58
59
  seriesNames?: string[];
59
60
  seriesNameColors?: (string | undefined)[];
@@ -174,6 +175,7 @@ export function parseEChart(
174
175
 
175
176
  if (key === 'title') {
176
177
  result.title = value;
178
+ result.titleLineNumber = lineNumber;
177
179
  continue;
178
180
  }
179
181
 
@@ -536,6 +538,7 @@ function buildSankeyOption(
536
538
  type: 'sankey',
537
539
  emphasis: {
538
540
  focus: 'adjacency',
541
+ blurScope: 'global' as const,
539
542
  },
540
543
  nodeAlign: 'left',
541
544
  nodeGap: 12,
@@ -744,6 +747,10 @@ function buildFunctionOption(
744
747
  itemStyle: {
745
748
  color: fnColor,
746
749
  },
750
+ emphasis: {
751
+ focus: 'self' as const,
752
+ blurScope: 'global' as const,
753
+ },
747
754
  };
748
755
  });
749
756
 
@@ -1114,6 +1121,8 @@ function buildHeatmapOption(
1114
1121
  fontWeight: 'bold' as const,
1115
1122
  },
1116
1123
  emphasis: {
1124
+ focus: 'self' as const,
1125
+ blurScope: 'global' as const,
1117
1126
  itemStyle: {
1118
1127
  shadowBlur: 10,
1119
1128
  shadowColor: 'rgba(0, 0, 0, 0.5)',
@@ -1208,6 +1217,8 @@ function buildFunnelOption(
1208
1217
  lineStyle: { color: textColor, opacity: 0.3 },
1209
1218
  },
1210
1219
  emphasis: {
1220
+ focus: 'self' as const,
1221
+ blurScope: 'global' as const,
1211
1222
  label: {
1212
1223
  fontSize: 15,
1213
1224
  },
@@ -1392,6 +1403,10 @@ function buildBarOption(
1392
1403
  {
1393
1404
  type: 'bar',
1394
1405
  data,
1406
+ emphasis: {
1407
+ focus: 'self' as const,
1408
+ blurScope: 'global' as const,
1409
+ },
1395
1410
  },
1396
1411
  ],
1397
1412
  };
@@ -1440,6 +1455,10 @@ function buildLineOption(
1440
1455
  symbolSize: 8,
1441
1456
  lineStyle: { color: lineColor, width: 3 },
1442
1457
  itemStyle: { color: lineColor },
1458
+ emphasis: {
1459
+ focus: 'self' as const,
1460
+ blurScope: 'global' as const,
1461
+ },
1443
1462
  },
1444
1463
  ],
1445
1464
  };
@@ -1474,6 +1493,10 @@ function buildMultiLineOption(
1474
1493
  symbolSize: 8,
1475
1494
  lineStyle: { color, width: 3 },
1476
1495
  itemStyle: { color },
1496
+ emphasis: {
1497
+ focus: 'self' as const,
1498
+ blurScope: 'global' as const,
1499
+ },
1477
1500
  };
1478
1501
  });
1479
1502
 
@@ -1548,11 +1571,26 @@ function buildAreaOption(
1548
1571
  lineStyle: { color: lineColor, width: 3 },
1549
1572
  itemStyle: { color: lineColor },
1550
1573
  areaStyle: { opacity: 0.25 },
1574
+ emphasis: {
1575
+ focus: 'self' as const,
1576
+ blurScope: 'global' as const,
1577
+ },
1551
1578
  },
1552
1579
  ],
1553
1580
  };
1554
1581
  }
1555
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
+
1556
1594
  // ── Pie / Doughnut ───────────────────────────────────────────
1557
1595
 
1558
1596
  function buildPieOption(
@@ -1584,11 +1622,15 @@ function buildPieOption(
1584
1622
  data,
1585
1623
  label: {
1586
1624
  position: 'outside',
1587
- formatter: '{b} — {c} ({d}%)',
1625
+ formatter: segmentLabelFormatter(parsed.labels),
1588
1626
  color: textColor,
1589
1627
  fontFamily: FONT_FAMILY,
1590
1628
  },
1591
1629
  labelLine: { show: true },
1630
+ emphasis: {
1631
+ focus: 'self' as const,
1632
+ blurScope: 'global' as const,
1633
+ },
1592
1634
  },
1593
1635
  ],
1594
1636
  };
@@ -1658,6 +1700,10 @@ function buildRadarOption(
1658
1700
  },
1659
1701
  },
1660
1702
  ],
1703
+ emphasis: {
1704
+ focus: 'self' as const,
1705
+ blurScope: 'global' as const,
1706
+ },
1661
1707
  },
1662
1708
  ],
1663
1709
  };
@@ -1694,11 +1740,15 @@ function buildPolarAreaOption(
1694
1740
  data,
1695
1741
  label: {
1696
1742
  position: 'outside',
1697
- formatter: '{b} — {c} ({d}%)',
1743
+ formatter: segmentLabelFormatter(parsed.labels),
1698
1744
  color: textColor,
1699
1745
  fontFamily: FONT_FAMILY,
1700
1746
  },
1701
1747
  labelLine: { show: true },
1748
+ emphasis: {
1749
+ focus: 'self' as const,
1750
+ blurScope: 'global' as const,
1751
+ },
1702
1752
  },
1703
1753
  ],
1704
1754
  };
@@ -1741,6 +1791,10 @@ function buildBarStackedOption(
1741
1791
  fontWeight: 'bold' as const,
1742
1792
  fontFamily: FONT_FAMILY,
1743
1793
  },
1794
+ emphasis: {
1795
+ focus: 'self' as const,
1796
+ blurScope: 'global' as const,
1797
+ },
1744
1798
  };
1745
1799
  });
1746
1800
 
@@ -436,6 +436,7 @@ export function parseFlowchart(
436
436
 
437
437
  if (key === 'title') {
438
438
  result.title = value;
439
+ result.titleLineNumber = lineNumber;
439
440
  continue;
440
441
  }
441
442
 
@@ -257,7 +257,6 @@ export function renderFlowchart(
257
257
  .append('svg')
258
258
  .attr('width', width)
259
259
  .attr('height', height)
260
- .style('background', palette.bg)
261
260
  .style('font-family', FONT_FAMILY);
262
261
 
263
262
  // Defs: arrowhead markers
@@ -305,7 +304,7 @@ export function renderFlowchart(
305
304
 
306
305
  // Title
307
306
  if (graph.title) {
308
- mainG
307
+ const titleEl = mainG
309
308
  .append('text')
310
309
  .attr('x', diagramW / 2)
311
310
  .attr('y', TITLE_FONT_SIZE)
@@ -313,8 +312,19 @@ export function renderFlowchart(
313
312
  .attr('fill', palette.text)
314
313
  .attr('font-size', TITLE_FONT_SIZE)
315
314
  .attr('font-weight', 'bold')
316
- .attr('class', 'fc-title')
315
+ .attr('class', 'fc-title chart-title')
316
+ .style('cursor', onClickItem && graph.titleLineNumber ? 'pointer' : 'default')
317
317
  .text(graph.title);
318
+
319
+ if (graph.titleLineNumber) {
320
+ titleEl.attr('data-line-number', graph.titleLineNumber);
321
+ if (onClickItem) {
322
+ titleEl
323
+ .on('click', () => onClickItem(graph.titleLineNumber!))
324
+ .on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
325
+ .on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
326
+ }
327
+ }
318
328
  }
319
329
 
320
330
  // Content group (offset by title)
@@ -425,7 +435,8 @@ export function renderFlowchart(
425
435
  .append('g')
426
436
  .attr('transform', `translate(${node.x}, ${node.y})`)
427
437
  .attr('class', 'fc-node')
428
- .attr('data-line-number', String(node.lineNumber));
438
+ .attr('data-line-number', String(node.lineNumber))
439
+ .attr('data-node-id', node.id);
429
440
 
430
441
  if (onClickItem) {
431
442
  nodeG.style('cursor', 'pointer').on('click', () => {
@@ -36,6 +36,7 @@ export interface GraphGroup {
36
36
  export interface ParsedGraph {
37
37
  type: 'flowchart';
38
38
  title?: string;
39
+ titleLineNumber?: number;
39
40
  direction: GraphDirection;
40
41
  nodes: GraphNode[];
41
42
  edges: GraphEdge[];
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
+ }
@@ -133,6 +133,7 @@ export interface SequenceGroup {
133
133
  */
134
134
  export interface ParsedSequenceDgmo {
135
135
  title: string | null;
136
+ titleLineNumber: number | null;
136
137
  participants: SequenceParticipant[];
137
138
  messages: SequenceMessage[];
138
139
  elements: SequenceElement[];
@@ -227,6 +228,7 @@ function measureIndent(line: string): number {
227
228
  export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
228
229
  const result: ParsedSequenceDgmo = {
229
230
  title: null,
231
+ titleLineNumber: null,
230
232
  participants: [],
231
233
  messages: [],
232
234
  elements: [],
@@ -367,6 +369,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
367
369
 
368
370
  if (key === 'title') {
369
371
  result.title = value;
372
+ result.titleLineNumber = lineNumber;
370
373
  continue;
371
374
  }
372
375