@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/README.md +19 -19
- package/dist/cli.cjs +1498 -3
- package/dist/index.cjs +453 -475
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -16
- package/dist/index.d.ts +20 -16
- package/dist/index.js +450 -473
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/chart.ts +231 -0
- package/src/cli.ts +19 -5
- package/src/dgmo-router.ts +12 -13
- package/src/echarts.ts +590 -0
- package/src/index.ts +6 -7
- package/src/chartjs.ts +0 -784
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diagrammo/dgmo",
|
|
3
|
-
"version": "0.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/src/dgmo-router.ts
CHANGED
|
@@ -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 = '
|
|
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
|
-
*
|
|
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
|
-
//
|
|
22
|
-
bar: '
|
|
23
|
-
line: '
|
|
24
|
-
'multi-line': '
|
|
25
|
-
area: '
|
|
26
|
-
pie: '
|
|
27
|
-
doughnut: '
|
|
28
|
-
radar: '
|
|
29
|
-
'polar-area': '
|
|
30
|
-
'bar-stacked': '
|
|
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',
|