@diagrammo/dgmo 0.2.19 → 0.2.21
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 +33 -33
- package/dist/cli.cjs +150 -144
- package/dist/index.cjs +9475 -8087
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -1
- package/dist/index.d.ts +124 -1
- package/dist/index.js +9345 -7965
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chart.ts +40 -9
- package/src/class/parser.ts +37 -6
- package/src/class/renderer.ts +11 -8
- package/src/class/types.ts +4 -0
- package/src/cli.ts +38 -3
- package/src/d3.ts +159 -48
- package/src/dgmo-mermaid.ts +7 -1
- package/src/dgmo-router.ts +74 -4
- package/src/diagnostics.ts +77 -0
- package/src/echarts.ts +23 -14
- package/src/er/layout.ts +49 -7
- package/src/er/parser.ts +31 -4
- package/src/er/renderer.ts +2 -1
- package/src/er/types.ts +3 -0
- package/src/graph/flowchart-parser.ts +34 -4
- package/src/graph/flowchart-renderer.ts +35 -32
- package/src/graph/types.ts +4 -0
- package/src/index.ts +22 -0
- package/src/kanban/mutations.ts +183 -0
- package/src/kanban/parser.ts +389 -0
- package/src/kanban/renderer.ts +564 -0
- package/src/kanban/types.ts +45 -0
- package/src/org/layout.ts +97 -66
- package/src/org/parser.ts +50 -15
- package/src/org/renderer.ts +91 -159
- package/src/org/resolver.ts +470 -0
- package/src/sequence/parser.ts +90 -33
- package/src/sequence/renderer.ts +13 -5
package/package.json
CHANGED
package/src/chart.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface ChartDataPoint {
|
|
|
20
20
|
lineNumber: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
import type { DgmoError } from './diagnostics';
|
|
24
|
+
|
|
23
25
|
export interface ParsedChart {
|
|
24
26
|
type: ChartType;
|
|
25
27
|
title?: string;
|
|
@@ -34,6 +36,7 @@ export interface ParsedChart {
|
|
|
34
36
|
label?: string;
|
|
35
37
|
labels?: 'name' | 'value' | 'percent' | 'full';
|
|
36
38
|
data: ChartDataPoint[];
|
|
39
|
+
diagnostics: DgmoError[];
|
|
37
40
|
error?: string;
|
|
38
41
|
}
|
|
39
42
|
|
|
@@ -43,6 +46,7 @@ export interface ParsedChart {
|
|
|
43
46
|
|
|
44
47
|
import { resolveColor } from './colors';
|
|
45
48
|
import type { PaletteColors } from './palettes';
|
|
49
|
+
import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
|
|
46
50
|
|
|
47
51
|
// ============================================================
|
|
48
52
|
// Parser
|
|
@@ -85,6 +89,14 @@ export function parseChart(
|
|
|
85
89
|
const result: ParsedChart = {
|
|
86
90
|
type: 'bar',
|
|
87
91
|
data: [],
|
|
92
|
+
diagnostics: [],
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const fail = (line: number, message: string): ParsedChart => {
|
|
96
|
+
const diag = makeDgmoError(line, message);
|
|
97
|
+
result.diagnostics.push(diag);
|
|
98
|
+
result.error = formatDgmoError(diag);
|
|
99
|
+
return result;
|
|
88
100
|
};
|
|
89
101
|
|
|
90
102
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -114,8 +126,10 @@ export function parseChart(
|
|
|
114
126
|
if (VALID_TYPES.has(chartType)) {
|
|
115
127
|
result.type = chartType;
|
|
116
128
|
} else {
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
let msg = `Unsupported chart type: ${value}. Supported types: ${[...VALID_TYPES].join(', ')}.`;
|
|
130
|
+
const hint = suggest(raw, [...VALID_TYPES]);
|
|
131
|
+
if (hint) msg += ` ${hint}`;
|
|
132
|
+
return fail(lineNumber, msg);
|
|
119
133
|
}
|
|
120
134
|
continue;
|
|
121
135
|
}
|
|
@@ -150,9 +164,12 @@ export function parseChart(
|
|
|
150
164
|
}
|
|
151
165
|
|
|
152
166
|
if (key === 'orientation') {
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
167
|
+
// Only bar and bar-stacked support orientation (axis swapping)
|
|
168
|
+
if (result.type === 'bar' || result.type === 'bar-stacked') {
|
|
169
|
+
const v = value.toLowerCase();
|
|
170
|
+
if (v === 'horizontal' || v === 'vertical') {
|
|
171
|
+
result.orientation = v;
|
|
172
|
+
}
|
|
156
173
|
}
|
|
157
174
|
continue;
|
|
158
175
|
}
|
|
@@ -219,12 +236,22 @@ export function parseChart(
|
|
|
219
236
|
}
|
|
220
237
|
|
|
221
238
|
// Validation
|
|
239
|
+
const setChartError = (line: number, message: string) => {
|
|
240
|
+
const diag = makeDgmoError(line, message);
|
|
241
|
+
result.diagnostics.push(diag);
|
|
242
|
+
result.error = formatDgmoError(diag);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const warn = (line: number, message: string): void => {
|
|
246
|
+
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
247
|
+
};
|
|
248
|
+
|
|
222
249
|
if (!result.error && result.data.length === 0) {
|
|
223
|
-
|
|
250
|
+
warn(1, 'No data points found. Add data in format: Label: 123');
|
|
224
251
|
}
|
|
225
252
|
|
|
226
253
|
if (!result.error && result.type === 'bar-stacked' && !result.seriesNames) {
|
|
227
|
-
|
|
254
|
+
setChartError(1, 'Chart type "bar-stacked" requires multiple series names. Use: series: Name1, Name2, Name3');
|
|
228
255
|
}
|
|
229
256
|
|
|
230
257
|
if (!result.error && result.seriesNames) {
|
|
@@ -232,10 +259,14 @@ export function parseChart(
|
|
|
232
259
|
for (const dp of result.data) {
|
|
233
260
|
const actualCount = 1 + (dp.extraValues?.length ?? 0);
|
|
234
261
|
if (actualCount !== expectedCount) {
|
|
235
|
-
|
|
236
|
-
break;
|
|
262
|
+
warn(dp.lineNumber, `Data point "${dp.label}" has ${actualCount} value(s), but ${expectedCount} series defined. Each row must have ${expectedCount} comma-separated values.`);
|
|
237
263
|
}
|
|
238
264
|
}
|
|
265
|
+
// Filter out mismatched data points so renderers get clean data
|
|
266
|
+
result.data = result.data.filter((dp) => {
|
|
267
|
+
const actualCount = 1 + (dp.extraValues?.length ?? 0);
|
|
268
|
+
return actualCount === expectedCount;
|
|
269
|
+
});
|
|
239
270
|
}
|
|
240
271
|
|
|
241
272
|
return result;
|
package/src/class/parser.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveColor } from '../colors';
|
|
2
2
|
import type { PaletteColors } from '../palettes';
|
|
3
|
+
import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
3
4
|
import type {
|
|
4
5
|
ParsedClassDiagram,
|
|
5
6
|
ClassNode,
|
|
@@ -169,6 +170,15 @@ export function parseClassDiagram(
|
|
|
169
170
|
type: 'class',
|
|
170
171
|
classes: [],
|
|
171
172
|
relationships: [],
|
|
173
|
+
options: {},
|
|
174
|
+
diagnostics: [],
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const fail = (line: number, message: string): ParsedClassDiagram => {
|
|
178
|
+
const diag = makeDgmoError(line, message);
|
|
179
|
+
result.diagnostics.push(diag);
|
|
180
|
+
result.error = formatDgmoError(diag);
|
|
181
|
+
return result;
|
|
172
182
|
};
|
|
173
183
|
|
|
174
184
|
const classMap = new Map<string, ClassNode>();
|
|
@@ -216,8 +226,11 @@ export function parseClassDiagram(
|
|
|
216
226
|
// Only recognize known metadata keys
|
|
217
227
|
if (key === 'chart') {
|
|
218
228
|
if (value.toLowerCase() !== 'class') {
|
|
219
|
-
|
|
220
|
-
|
|
229
|
+
const allTypes = ['class', 'flowchart', 'sequence', 'er', 'org', 'bar', 'line', 'pie', 'scatter', 'sankey', 'venn', 'timeline', 'arc', 'slope'];
|
|
230
|
+
let msg = `Expected chart type "class", got "${value}"`;
|
|
231
|
+
const hint = suggest(value.toLowerCase(), allTypes);
|
|
232
|
+
if (hint) msg += `. ${hint}`;
|
|
233
|
+
return fail(lineNumber, msg);
|
|
221
234
|
}
|
|
222
235
|
continue;
|
|
223
236
|
}
|
|
@@ -228,8 +241,11 @@ export function parseClassDiagram(
|
|
|
228
241
|
continue;
|
|
229
242
|
}
|
|
230
243
|
|
|
231
|
-
//
|
|
232
|
-
if (!/\s/.test(key))
|
|
244
|
+
// Store diagram-level options (e.g., color: off)
|
|
245
|
+
if (!/\s/.test(key)) {
|
|
246
|
+
result.options[key] = value;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
233
249
|
}
|
|
234
250
|
|
|
235
251
|
// Indented lines = members of current class
|
|
@@ -314,8 +330,23 @@ export function parseClassDiagram(
|
|
|
314
330
|
|
|
315
331
|
// Validation
|
|
316
332
|
if (result.classes.length === 0 && !result.error) {
|
|
317
|
-
|
|
318
|
-
|
|
333
|
+
const diag = makeDgmoError(1, 'No classes found. Add class declarations like "ClassName" or "ClassName [interface]".');
|
|
334
|
+
result.diagnostics.push(diag);
|
|
335
|
+
result.error = formatDgmoError(diag);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Warn about isolated classes (not in any relationship)
|
|
339
|
+
if (result.classes.length >= 2 && result.relationships.length >= 1 && !result.error) {
|
|
340
|
+
const connectedIds = new Set<string>();
|
|
341
|
+
for (const rel of result.relationships) {
|
|
342
|
+
connectedIds.add(rel.source);
|
|
343
|
+
connectedIds.add(rel.target);
|
|
344
|
+
}
|
|
345
|
+
for (const cls of result.classes) {
|
|
346
|
+
if (!connectedIds.has(cls.id)) {
|
|
347
|
+
result.diagnostics.push(makeDgmoError(cls.lineNumber, `Class "${cls.name}" is not connected to any other class`, 'warning'));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
319
350
|
}
|
|
320
351
|
|
|
321
352
|
return result;
|
package/src/class/renderer.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { layoutClassDiagram } from './layout';
|
|
|
16
16
|
// ============================================================
|
|
17
17
|
|
|
18
18
|
const DIAGRAM_PADDING = 20;
|
|
19
|
+
const MAX_SCALE = 3;
|
|
19
20
|
const CLASS_FONT_SIZE = 13;
|
|
20
21
|
const MEMBER_FONT_SIZE = 11;
|
|
21
22
|
const EDGE_LABEL_FONT_SIZE = 11;
|
|
@@ -40,7 +41,8 @@ function mix(a: string, b: string, pct: number): string {
|
|
|
40
41
|
return `#${c(ar,br)}${c(ag,bg)}${c(ab,bb)}`;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function modifierColor(modifier: ClassModifier | undefined, palette: PaletteColors): string {
|
|
44
|
+
function modifierColor(modifier: ClassModifier | undefined, palette: PaletteColors, colorOff?: boolean): string {
|
|
45
|
+
if (colorOff) return palette.textMuted;
|
|
44
46
|
switch (modifier) {
|
|
45
47
|
case 'interface': return palette.colors.blue;
|
|
46
48
|
case 'abstract': return palette.colors.purple;
|
|
@@ -49,13 +51,13 @@ function modifierColor(modifier: ClassModifier | undefined, palette: PaletteColo
|
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
function nodeFill(palette: PaletteColors, isDark: boolean, modifier: ClassModifier | undefined, nodeColor?: string): string {
|
|
53
|
-
const color = nodeColor ?? modifierColor(modifier, palette);
|
|
54
|
+
function nodeFill(palette: PaletteColors, isDark: boolean, modifier: ClassModifier | undefined, nodeColor?: string, colorOff?: boolean): string {
|
|
55
|
+
const color = nodeColor ?? modifierColor(modifier, palette, colorOff);
|
|
54
56
|
return mix(color, isDark ? palette.surface : palette.bg, 20);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
function nodeStroke(palette: PaletteColors, modifier: ClassModifier | undefined, nodeColor?: string): string {
|
|
58
|
-
return nodeColor ?? modifierColor(modifier, palette);
|
|
59
|
+
function nodeStroke(palette: PaletteColors, modifier: ClassModifier | undefined, nodeColor?: string, colorOff?: boolean): string {
|
|
60
|
+
return nodeColor ?? modifierColor(modifier, palette, colorOff);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// ============================================================
|
|
@@ -130,7 +132,7 @@ export function renderClassDiagram(
|
|
|
130
132
|
const availH = height - titleHeight;
|
|
131
133
|
const scaleX = (width - DIAGRAM_PADDING * 2) / diagramW;
|
|
132
134
|
const scaleY = (availH - DIAGRAM_PADDING * 2) / diagramH;
|
|
133
|
-
const scale = Math.min(
|
|
135
|
+
const scale = Math.min(MAX_SCALE, scaleX, scaleY);
|
|
134
136
|
|
|
135
137
|
const scaledW = diagramW * scale;
|
|
136
138
|
const scaledH = diagramH * scale;
|
|
@@ -340,8 +342,9 @@ export function renderClassDiagram(
|
|
|
340
342
|
|
|
341
343
|
const w = node.width;
|
|
342
344
|
const h = node.height;
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
+
const colorOff = parsed.options?.color === 'off';
|
|
346
|
+
const fill = nodeFill(palette, isDark, node.modifier, node.color, colorOff);
|
|
347
|
+
const stroke = nodeStroke(palette, node.modifier, node.color, colorOff);
|
|
345
348
|
|
|
346
349
|
// Outer rectangle
|
|
347
350
|
nodeG.append('rect')
|
package/src/class/types.ts
CHANGED
|
@@ -41,11 +41,15 @@ export interface ClassRelationship {
|
|
|
41
41
|
lineNumber: number;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
import type { DgmoError } from '../diagnostics';
|
|
45
|
+
|
|
44
46
|
export interface ParsedClassDiagram {
|
|
45
47
|
type: 'class';
|
|
46
48
|
title?: string;
|
|
47
49
|
titleLineNumber?: number;
|
|
48
50
|
classes: ClassNode[];
|
|
49
51
|
relationships: ClassRelationship[];
|
|
52
|
+
options: Record<string, string>;
|
|
53
|
+
diagnostics: DgmoError[];
|
|
50
54
|
error?: string;
|
|
51
55
|
}
|
package/src/cli.ts
CHANGED
|
@@ -3,9 +3,13 @@ import { execSync } from 'node:child_process';
|
|
|
3
3
|
import { resolve, basename, extname } from 'node:path';
|
|
4
4
|
import { Resvg } from '@resvg/resvg-js';
|
|
5
5
|
import { render } from './render';
|
|
6
|
+
import { parseDgmo } from './dgmo-router';
|
|
7
|
+
import { parseDgmoChartType } from './dgmo-router';
|
|
8
|
+
import { formatDgmoError } from './diagnostics';
|
|
6
9
|
import { getPalette } from './palettes/registry';
|
|
7
10
|
import { DEFAULT_FONT_NAME } from './fonts';
|
|
8
11
|
import { encodeDiagramUrl } from './sharing';
|
|
12
|
+
import { resolveOrgImports } from './org/resolver';
|
|
9
13
|
|
|
10
14
|
const PALETTES = [
|
|
11
15
|
'nord',
|
|
@@ -220,6 +224,20 @@ async function main(): Promise<void> {
|
|
|
220
224
|
noInput();
|
|
221
225
|
}
|
|
222
226
|
|
|
227
|
+
// Resolve org chart imports (tags: and import: directives)
|
|
228
|
+
if (opts.input && parseDgmoChartType(content) === 'org') {
|
|
229
|
+
const inputPath = resolve(opts.input);
|
|
230
|
+
const resolved = await resolveOrgImports(
|
|
231
|
+
content,
|
|
232
|
+
inputPath,
|
|
233
|
+
(p) => readFileSync(p, 'utf-8'),
|
|
234
|
+
);
|
|
235
|
+
for (const diag of resolved.diagnostics) {
|
|
236
|
+
console.error(formatDgmoError(diag));
|
|
237
|
+
}
|
|
238
|
+
content = resolved.content;
|
|
239
|
+
}
|
|
240
|
+
|
|
223
241
|
// Determine output format early to handle URL before rendering
|
|
224
242
|
const format = inferFormat(opts.output);
|
|
225
243
|
|
|
@@ -271,6 +289,21 @@ async function main(): Promise<void> {
|
|
|
271
289
|
process.exit(1);
|
|
272
290
|
}
|
|
273
291
|
|
|
292
|
+
// Parse first to collect diagnostics
|
|
293
|
+
const { diagnostics } = parseDgmo(content);
|
|
294
|
+
const errors = diagnostics.filter((d) => d.severity === 'error');
|
|
295
|
+
const warnings = diagnostics.filter((d) => d.severity === 'warning');
|
|
296
|
+
|
|
297
|
+
// Print warnings even if rendering succeeds
|
|
298
|
+
for (const w of warnings) {
|
|
299
|
+
console.error(`\u26A0 ${formatDgmoError(w)}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Print errors
|
|
303
|
+
for (const e of errors) {
|
|
304
|
+
console.error(`\u2716 ${formatDgmoError(e)}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
274
307
|
const svg = await render(content, {
|
|
275
308
|
theme: opts.theme,
|
|
276
309
|
palette: opts.palette,
|
|
@@ -278,9 +311,11 @@ async function main(): Promise<void> {
|
|
|
278
311
|
});
|
|
279
312
|
|
|
280
313
|
if (!svg) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
314
|
+
if (errors.length === 0) {
|
|
315
|
+
console.error(
|
|
316
|
+
'Error: Failed to render diagram. The input may be empty, invalid, or use an unsupported chart type.'
|
|
317
|
+
);
|
|
318
|
+
}
|
|
284
319
|
process.exit(1);
|
|
285
320
|
}
|
|
286
321
|
|