@diagrammo/dgmo 0.1.7 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diagrammo/dgmo",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "DGMO diagram markup language — parser, renderer, and color system",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,13 +30,13 @@
30
30
  "scripts": {
31
31
  "build": "tsup",
32
32
  "typecheck": "tsc --noEmit",
33
- "dev": "tsup --watch"
33
+ "dev": "tsup --watch",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest"
34
36
  },
35
37
  "dependencies": {
36
38
  "@resvg/resvg-js": "^2.6.2",
37
- "chart.js": "^4.4.8",
38
- "chartjs-plugin-datalabels": "^2.2.0",
39
- "d3-array": "^3.2.4",
39
+ "d3-array": "^3.2.4",
40
40
  "d3-cloud": "^1.2.7",
41
41
  "d3-scale": "^4.0.2",
42
42
  "d3-selection": "^3.0.0",
@@ -52,6 +52,7 @@
52
52
  "@types/d3-shape": "^3.1.7",
53
53
  "@types/jsdom": "^21.1.7",
54
54
  "tsup": "^8.5.1",
55
- "typescript": "^5.7.3"
55
+ "typescript": "^5.7.3",
56
+ "vitest": "^3.0.0"
56
57
  }
57
58
  }
package/src/chart.ts ADDED
@@ -0,0 +1,231 @@
1
+ // ============================================================
2
+ // Types
3
+ // ============================================================
4
+
5
+ export type ChartType =
6
+ | 'bar'
7
+ | 'line'
8
+ | 'pie'
9
+ | 'doughnut'
10
+ | 'area'
11
+ | 'polar-area'
12
+ | 'radar'
13
+ | 'bar-stacked';
14
+
15
+ export interface ChartDataPoint {
16
+ label: string;
17
+ value: number;
18
+ extraValues?: number[];
19
+ color?: string;
20
+ lineNumber: number;
21
+ }
22
+
23
+ export interface ParsedChart {
24
+ type: ChartType;
25
+ title?: string;
26
+ series?: string;
27
+ xlabel?: string;
28
+ ylabel?: string;
29
+ seriesNames?: string[];
30
+ seriesNameColors?: (string | undefined)[];
31
+ orientation?: 'horizontal' | 'vertical';
32
+ color?: string;
33
+ label?: string;
34
+ data: ChartDataPoint[];
35
+ error?: string;
36
+ }
37
+
38
+ // ============================================================
39
+ // Colors
40
+ // ============================================================
41
+
42
+ import { resolveColor } from './colors';
43
+ import type { PaletteColors } from './palettes';
44
+
45
+ // ============================================================
46
+ // Parser
47
+ // ============================================================
48
+
49
+ const VALID_TYPES = new Set<ChartType>([
50
+ 'bar',
51
+ 'line',
52
+ 'pie',
53
+ 'doughnut',
54
+ 'area',
55
+ 'polar-area',
56
+ 'radar',
57
+ 'bar-stacked',
58
+ ]);
59
+
60
+ const TYPE_ALIASES: Record<string, ChartType> = {
61
+ 'multi-line': 'line',
62
+ };
63
+
64
+ /**
65
+ * Parses the simple chart text format into a structured object.
66
+ *
67
+ * Format:
68
+ * ```
69
+ * chart: bar
70
+ * title: My Chart
71
+ * series: Revenue
72
+ *
73
+ * Jan: 120
74
+ * Feb: 200
75
+ * Mar: 150
76
+ * ```
77
+ */
78
+ export function parseChart(
79
+ content: string,
80
+ palette?: PaletteColors
81
+ ): ParsedChart {
82
+ const lines = content.split('\n');
83
+ const result: ParsedChart = {
84
+ type: 'bar',
85
+ data: [],
86
+ };
87
+
88
+ for (let i = 0; i < lines.length; i++) {
89
+ const trimmed = lines[i].trim();
90
+ const lineNumber = i + 1;
91
+
92
+ // Skip empty lines
93
+ if (!trimmed) continue;
94
+
95
+ // Recognize ## section headers (skip, but don't treat as comments)
96
+ if (/^#{2,}\s+/.test(trimmed)) continue;
97
+
98
+ // Skip comments
99
+ if (trimmed.startsWith('#') || trimmed.startsWith('//')) continue;
100
+
101
+ // Parse key: value pairs
102
+ const colonIndex = trimmed.indexOf(':');
103
+ if (colonIndex === -1) continue;
104
+
105
+ const key = trimmed.substring(0, colonIndex).trim().toLowerCase();
106
+ const value = trimmed.substring(colonIndex + 1).trim();
107
+
108
+ // Handle metadata
109
+ if (key === 'chart') {
110
+ const raw = value.toLowerCase();
111
+ const chartType = (TYPE_ALIASES[raw] ?? raw) as ChartType;
112
+ if (VALID_TYPES.has(chartType)) {
113
+ result.type = chartType;
114
+ } else {
115
+ result.error = `Unsupported chart type: ${value}. Supported types: ${[...VALID_TYPES].join(', ')}.`;
116
+ return result;
117
+ }
118
+ continue;
119
+ }
120
+
121
+ if (key === 'title') {
122
+ result.title = value;
123
+ continue;
124
+ }
125
+
126
+ if (key === 'xlabel') {
127
+ result.xlabel = value;
128
+ continue;
129
+ }
130
+
131
+ if (key === 'ylabel') {
132
+ result.ylabel = value;
133
+ continue;
134
+ }
135
+
136
+ if (key === 'label') {
137
+ result.label = value;
138
+ continue;
139
+ }
140
+
141
+ if (key === 'orientation') {
142
+ const v = value.toLowerCase();
143
+ if (v === 'horizontal' || v === 'vertical') {
144
+ result.orientation = v;
145
+ }
146
+ continue;
147
+ }
148
+
149
+ if (key === 'color') {
150
+ result.color = resolveColor(value.trim(), palette);
151
+ continue;
152
+ }
153
+
154
+ if (key === 'series') {
155
+ result.series = value;
156
+ // Parse comma-separated series names for multi-series chart types
157
+ const rawNames = value
158
+ .split(',')
159
+ .map((s) => s.trim())
160
+ .filter(Boolean);
161
+ const names: string[] = [];
162
+ const nameColors: (string | undefined)[] = [];
163
+ for (const raw of rawNames) {
164
+ const colorMatch = raw.match(/\(([^)]+)\)\s*$/);
165
+ if (colorMatch) {
166
+ const resolved = resolveColor(colorMatch[1].trim(), palette);
167
+ nameColors.push(resolved);
168
+ names.push(raw.substring(0, colorMatch.index!).trim());
169
+ } else {
170
+ nameColors.push(undefined);
171
+ names.push(raw);
172
+ }
173
+ }
174
+ if (names.length === 1) {
175
+ result.series = names[0];
176
+ }
177
+ if (names.length > 1) {
178
+ result.seriesNames = names;
179
+ }
180
+ if (nameColors.some(Boolean)) result.seriesNameColors = nameColors;
181
+ continue;
182
+ }
183
+
184
+ // Data point: Label: value or Label: v1, v2, ...
185
+ const parts = value.split(',').map((s) => s.trim());
186
+ const numValue = parseFloat(parts[0]);
187
+ if (!isNaN(numValue)) {
188
+ let rawLabel = trimmed.substring(0, colonIndex).trim();
189
+ let pointColor: string | undefined;
190
+ const colorMatch = rawLabel.match(/\(([^)]+)\)\s*$/);
191
+ if (colorMatch) {
192
+ const resolved = resolveColor(colorMatch[1].trim(), palette);
193
+ pointColor = resolved;
194
+ rawLabel = rawLabel.substring(0, colorMatch.index!).trim();
195
+ }
196
+ const extra = parts
197
+ .slice(1)
198
+ .map((s) => parseFloat(s))
199
+ .filter((n) => !isNaN(n));
200
+ result.data.push({
201
+ label: rawLabel,
202
+ value: numValue,
203
+ ...(extra.length > 0 && { extraValues: extra }),
204
+ ...(pointColor && { color: pointColor }),
205
+ lineNumber,
206
+ });
207
+ }
208
+ }
209
+
210
+ // Validation
211
+ if (!result.error && result.data.length === 0) {
212
+ result.error = 'No data points found. Add data in format: Label: 123';
213
+ }
214
+
215
+ if (!result.error && result.type === 'bar-stacked' && !result.seriesNames) {
216
+ result.error = `Chart type "bar-stacked" requires multiple series names. Use: series: Name1, Name2, Name3`;
217
+ }
218
+
219
+ if (!result.error && result.seriesNames) {
220
+ const expectedCount = result.seriesNames.length;
221
+ for (const dp of result.data) {
222
+ const actualCount = 1 + (dp.extraValues?.length ?? 0);
223
+ if (actualCount !== expectedCount) {
224
+ result.error = `Data point "${dp.label}" has ${actualCount} value(s), but ${expectedCount} series defined. Each row must have ${expectedCount} comma-separated values.`;
225
+ break;
226
+ }
227
+ }
228
+ }
229
+
230
+ return result;
231
+ }
package/src/cli.ts CHANGED
@@ -3,6 +3,8 @@ import { resolve, basename, extname } from 'node:path';
3
3
  import { JSDOM } from 'jsdom';
4
4
  import { Resvg } from '@resvg/resvg-js';
5
5
  import { renderD3ForExport } from './d3';
6
+ import { renderEChartsForExport } from './echarts';
7
+ import { parseDgmoChartType, getDgmoFramework } from './dgmo-router';
6
8
  import { getPalette } from './palettes/registry';
7
9
  import { DEFAULT_FONT_NAME } from './fonts';
8
10
 
@@ -215,19 +217,31 @@ async function main(): Promise<void> {
215
217
  noInput();
216
218
  }
217
219
 
218
- // Set up jsdom before any d3/renderer code runs
219
- setupDom();
220
-
221
220
  const isDark = opts.theme === 'dark';
222
221
  const paletteColors = isDark
223
222
  ? getPalette(opts.palette).dark
224
223
  : getPalette(opts.palette).light;
225
224
 
226
- const svg = await renderD3ForExport(content, opts.theme, paletteColors);
225
+ // Determine which rendering framework to use
226
+ const chartType = parseDgmoChartType(content);
227
+ const framework = chartType ? getDgmoFramework(chartType) : null;
228
+
229
+ let svg: string;
230
+
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}".`);
239
+ process.exit(1);
240
+ }
227
241
 
228
242
  if (!svg) {
229
243
  console.error(
230
- 'Error: Failed to render diagram. The input may be empty, invalid, or use an unsupported chart type (e.g. Chart.js/ECharts charts require a browser).'
244
+ 'Error: Failed to render diagram. The input may be empty, invalid, or use an unsupported chart type.'
231
245
  );
232
246
  process.exit(1);
233
247
  }
@@ -8,26 +8,25 @@ import { looksLikeSequence } from './sequence/parser';
8
8
  * Framework identifiers used by the .dgmo router.
9
9
  * Maps to the existing preview components and export paths.
10
10
  */
11
- export type DgmoFramework = 'chartjs' | 'echart' | 'd3' | 'mermaid';
11
+ export type DgmoFramework = 'echart' | 'd3' | 'mermaid';
12
12
 
13
13
  /**
14
14
  * Maps every supported chart type string to its backing framework.
15
15
  *
16
- * Chart.js: standard chart types (bar, line, pie, etc.)
17
- * ECharts: scatter, flow/relationship diagrams, math, heatmap
16
+ * ECharts: standard chart types (bar, line, pie, etc.), scatter, flow/relationship diagrams, math, heatmap
18
17
  * D3: slope, wordcloud, arc diagram, timeline
19
18
  */
20
19
  export const DGMO_CHART_TYPE_MAP: Record<string, DgmoFramework> = {
21
- // Chart.js
22
- bar: 'chartjs',
23
- line: 'chartjs',
24
- 'multi-line': 'chartjs',
25
- area: 'chartjs',
26
- pie: 'chartjs',
27
- doughnut: 'chartjs',
28
- radar: 'chartjs',
29
- 'polar-area': 'chartjs',
30
- 'bar-stacked': 'chartjs',
20
+ // Standard charts (via ECharts)
21
+ bar: 'echart',
22
+ line: 'echart',
23
+ 'multi-line': 'echart',
24
+ area: 'echart',
25
+ pie: 'echart',
26
+ doughnut: 'echart',
27
+ radar: 'echart',
28
+ 'polar-area': 'echart',
29
+ 'bar-stacked': 'echart',
31
30
 
32
31
  // ECharts
33
32
  scatter: 'echart',