@diagrammo/dgmo 0.2.27 → 0.3.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/.claude/skills/dgmo-chart/SKILL.md +107 -0
- package/.claude/skills/dgmo-flowchart/SKILL.md +61 -0
- package/.claude/skills/dgmo-generate/SKILL.md +58 -0
- package/.claude/skills/dgmo-sequence/SKILL.md +83 -0
- package/.cursorrules +117 -0
- package/.github/copilot-instructions.md +117 -0
- package/.windsurfrules +117 -0
- package/README.md +10 -3
- package/dist/cli.cjs +366 -918
- package/dist/index.cjs +581 -396
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -24
- package/dist/index.d.ts +39 -24
- package/dist/index.js +578 -395
- package/dist/index.js.map +1 -1
- package/docs/ai-integration.md +125 -0
- package/docs/language-reference.md +786 -0
- package/package.json +15 -8
- package/src/c4/parser.ts +90 -74
- package/src/c4/renderer.ts +13 -12
- package/src/c4/types.ts +6 -4
- package/src/chart.ts +3 -2
- package/src/class/layout.ts +17 -12
- package/src/class/parser.ts +22 -52
- package/src/class/renderer.ts +44 -46
- package/src/class/types.ts +1 -1
- package/src/cli.ts +130 -19
- package/src/d3.ts +1 -1
- package/src/dgmo-mermaid.ts +1 -1
- package/src/dgmo-router.ts +1 -1
- package/src/echarts.ts +33 -13
- package/src/er/parser.ts +34 -43
- package/src/er/types.ts +1 -1
- package/src/graph/flowchart-parser.ts +2 -25
- package/src/graph/types.ts +1 -1
- package/src/index.ts +5 -0
- package/src/initiative-status/parser.ts +36 -7
- package/src/initiative-status/types.ts +1 -1
- package/src/kanban/parser.ts +32 -53
- package/src/kanban/renderer.ts +9 -8
- package/src/kanban/types.ts +6 -14
- package/src/org/parser.ts +47 -87
- package/src/org/resolver.ts +11 -12
- package/src/sequence/parser.ts +97 -15
- package/src/sequence/renderer.ts +62 -69
- package/src/utils/arrows.ts +75 -0
- package/src/utils/inline-markdown.ts +75 -0
- package/src/utils/parsing.ts +67 -0
- package/src/utils/tag-groups.ts +76 -0
package/src/class/renderer.ts
CHANGED
|
@@ -406,43 +406,42 @@ export function renderClassDiagram(
|
|
|
406
406
|
const methods = node.members.filter((m) => m.isMethod);
|
|
407
407
|
|
|
408
408
|
if (isEnum) {
|
|
409
|
-
// Enum:
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
memberY += MEMBER_LINE_HEIGHT;
|
|
431
|
-
}
|
|
409
|
+
// Enum: single values compartment
|
|
410
|
+
// Separator
|
|
411
|
+
nodeG.append('line')
|
|
412
|
+
.attr('x1', -w / 2)
|
|
413
|
+
.attr('y1', yPos)
|
|
414
|
+
.attr('x2', w / 2)
|
|
415
|
+
.attr('y2', yPos)
|
|
416
|
+
.attr('stroke', stroke)
|
|
417
|
+
.attr('stroke-width', 0.5)
|
|
418
|
+
.attr('stroke-opacity', 0.5);
|
|
419
|
+
|
|
420
|
+
let memberY = yPos + COMPARTMENT_PADDING_Y;
|
|
421
|
+
for (const member of node.members) {
|
|
422
|
+
nodeG.append('text')
|
|
423
|
+
.attr('x', -w / 2 + MEMBER_PADDING_X)
|
|
424
|
+
.attr('y', memberY + MEMBER_LINE_HEIGHT / 2)
|
|
425
|
+
.attr('dominant-baseline', 'central')
|
|
426
|
+
.attr('fill', palette.text)
|
|
427
|
+
.attr('font-size', MEMBER_FONT_SIZE)
|
|
428
|
+
.text(member.name);
|
|
429
|
+
memberY += MEMBER_LINE_HEIGHT;
|
|
432
430
|
}
|
|
433
431
|
} else {
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
432
|
+
// UML 3-compartment layout: always show both separators
|
|
433
|
+
|
|
434
|
+
// Fields separator
|
|
435
|
+
nodeG.append('line')
|
|
436
|
+
.attr('x1', -w / 2)
|
|
437
|
+
.attr('y1', yPos)
|
|
438
|
+
.attr('x2', w / 2)
|
|
439
|
+
.attr('y2', yPos)
|
|
440
|
+
.attr('stroke', stroke)
|
|
441
|
+
.attr('stroke-width', 0.5)
|
|
442
|
+
.attr('stroke-opacity', 0.5);
|
|
445
443
|
|
|
444
|
+
if (fields.length > 0) {
|
|
446
445
|
let memberY = yPos + COMPARTMENT_PADDING_Y;
|
|
447
446
|
for (const field of fields) {
|
|
448
447
|
const vis = visibilitySymbol(field.visibility);
|
|
@@ -463,21 +462,20 @@ export function renderClassDiagram(
|
|
|
463
462
|
|
|
464
463
|
memberY += MEMBER_LINE_HEIGHT;
|
|
465
464
|
}
|
|
466
|
-
yPos += node.fieldsHeight;
|
|
467
465
|
}
|
|
466
|
+
yPos += node.fieldsHeight;
|
|
467
|
+
|
|
468
|
+
// Methods separator
|
|
469
|
+
nodeG.append('line')
|
|
470
|
+
.attr('x1', -w / 2)
|
|
471
|
+
.attr('y1', yPos)
|
|
472
|
+
.attr('x2', w / 2)
|
|
473
|
+
.attr('y2', yPos)
|
|
474
|
+
.attr('stroke', stroke)
|
|
475
|
+
.attr('stroke-width', 0.5)
|
|
476
|
+
.attr('stroke-opacity', 0.5);
|
|
468
477
|
|
|
469
|
-
// Methods compartment
|
|
470
478
|
if (methods.length > 0) {
|
|
471
|
-
// Separator
|
|
472
|
-
nodeG.append('line')
|
|
473
|
-
.attr('x1', -w / 2)
|
|
474
|
-
.attr('y1', yPos)
|
|
475
|
-
.attr('x2', w / 2)
|
|
476
|
-
.attr('y2', yPos)
|
|
477
|
-
.attr('stroke', stroke)
|
|
478
|
-
.attr('stroke-width', 0.5)
|
|
479
|
-
.attr('stroke-opacity', 0.5);
|
|
480
|
-
|
|
481
479
|
let memberY = yPos + COMPARTMENT_PADDING_Y;
|
|
482
480
|
for (const method of methods) {
|
|
483
481
|
const vis = visibilitySymbol(method.visibility);
|
package/src/class/types.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -3,7 +3,7 @@ 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';
|
|
6
|
+
import { parseDgmo, DGMO_CHART_TYPE_MAP } from './dgmo-router';
|
|
7
7
|
import { parseDgmoChartType } from './dgmo-router';
|
|
8
8
|
import { formatDgmoError } from './diagnostics';
|
|
9
9
|
import { getPalette } from './palettes/registry';
|
|
@@ -24,6 +24,38 @@ const PALETTES = [
|
|
|
24
24
|
|
|
25
25
|
const THEMES = ['light', 'dark', 'transparent'] as const;
|
|
26
26
|
|
|
27
|
+
const CHART_TYPE_DESCRIPTIONS: Record<string, string> = {
|
|
28
|
+
bar: 'Bar chart — categorical comparisons',
|
|
29
|
+
line: 'Line chart — trends over time',
|
|
30
|
+
'multi-line': 'Multi-line chart — multiple series trends',
|
|
31
|
+
area: 'Area chart — filled line chart',
|
|
32
|
+
pie: 'Pie chart — part-to-whole proportions',
|
|
33
|
+
doughnut: 'Doughnut chart — ring-style pie chart',
|
|
34
|
+
radar: 'Radar chart — multi-dimensional metrics',
|
|
35
|
+
'polar-area': 'Polar area chart — radial bar chart',
|
|
36
|
+
'bar-stacked': 'Stacked bar chart — multi-series categorical',
|
|
37
|
+
scatter: 'Scatter plot — 2D data points or bubble chart',
|
|
38
|
+
sankey: 'Sankey diagram — flow/allocation visualization',
|
|
39
|
+
chord: 'Chord diagram — circular flow relationships',
|
|
40
|
+
function: 'Function plot — mathematical expressions',
|
|
41
|
+
heatmap: 'Heatmap — matrix intensity visualization',
|
|
42
|
+
funnel: 'Funnel chart — conversion pipeline',
|
|
43
|
+
slope: 'Slope chart — change between two periods',
|
|
44
|
+
wordcloud: 'Word cloud — term frequency visualization',
|
|
45
|
+
arc: 'Arc diagram — network relationships',
|
|
46
|
+
timeline: 'Timeline — events, eras, and date ranges',
|
|
47
|
+
venn: 'Venn diagram — set overlaps',
|
|
48
|
+
quadrant: 'Quadrant chart — 2x2 positioning matrix',
|
|
49
|
+
sequence: 'Sequence diagram — message/interaction flows',
|
|
50
|
+
flowchart: 'Flowchart — decision trees and process flows',
|
|
51
|
+
class: 'Class diagram — UML class hierarchies',
|
|
52
|
+
er: 'ER diagram — database schemas and relationships',
|
|
53
|
+
org: 'Org chart — hierarchical tree structures',
|
|
54
|
+
kanban: 'Kanban board — task/workflow columns',
|
|
55
|
+
c4: 'C4 diagram — system architecture (context, container, component, deployment)',
|
|
56
|
+
'initiative-status': 'Initiative status — project roadmap with dependency tracking',
|
|
57
|
+
};
|
|
58
|
+
|
|
27
59
|
function printHelp(): void {
|
|
28
60
|
console.log(`Usage: dgmo <input> [options]
|
|
29
61
|
cat input.dgmo | dgmo [options]
|
|
@@ -42,6 +74,8 @@ Options:
|
|
|
42
74
|
--c4-container <name> Container to drill into (with --c4-level components)
|
|
43
75
|
--no-branding Omit diagrammo.app branding from exports
|
|
44
76
|
--copy Copy URL to clipboard (only with -o url)
|
|
77
|
+
--json Output structured JSON to stdout
|
|
78
|
+
--chart-types List all supported chart types
|
|
45
79
|
--help Show this help
|
|
46
80
|
--version Show version`);
|
|
47
81
|
}
|
|
@@ -62,6 +96,8 @@ function parseArgs(argv: string[]): {
|
|
|
62
96
|
version: boolean;
|
|
63
97
|
noBranding: boolean;
|
|
64
98
|
copy: boolean;
|
|
99
|
+
json: boolean;
|
|
100
|
+
chartTypes: boolean;
|
|
65
101
|
c4Level: 'context' | 'containers' | 'components' | 'deployment';
|
|
66
102
|
c4System: string | undefined;
|
|
67
103
|
c4Container: string | undefined;
|
|
@@ -75,6 +111,8 @@ function parseArgs(argv: string[]): {
|
|
|
75
111
|
version: false,
|
|
76
112
|
noBranding: false,
|
|
77
113
|
copy: false,
|
|
114
|
+
json: false,
|
|
115
|
+
chartTypes: false,
|
|
78
116
|
c4Level: 'context' as 'context' | 'containers' | 'components' | 'deployment',
|
|
79
117
|
c4System: undefined as string | undefined,
|
|
80
118
|
c4Container: undefined as string | undefined,
|
|
@@ -134,6 +172,12 @@ function parseArgs(argv: string[]): {
|
|
|
134
172
|
} else if (arg === '--no-branding') {
|
|
135
173
|
result.noBranding = true;
|
|
136
174
|
i++;
|
|
175
|
+
} else if (arg === '--json') {
|
|
176
|
+
result.json = true;
|
|
177
|
+
i++;
|
|
178
|
+
} else if (arg === '--chart-types') {
|
|
179
|
+
result.chartTypes = true;
|
|
180
|
+
i++;
|
|
137
181
|
} else if (arg === '--copy') {
|
|
138
182
|
result.copy = true;
|
|
139
183
|
i++;
|
|
@@ -220,6 +264,23 @@ async function main(): Promise<void> {
|
|
|
220
264
|
return;
|
|
221
265
|
}
|
|
222
266
|
|
|
267
|
+
if (opts.chartTypes) {
|
|
268
|
+
const types = Object.keys(DGMO_CHART_TYPE_MAP);
|
|
269
|
+
if (opts.json) {
|
|
270
|
+
const chartTypes = types.map((id) => ({
|
|
271
|
+
id,
|
|
272
|
+
description: CHART_TYPE_DESCRIPTIONS[id] ?? id,
|
|
273
|
+
}));
|
|
274
|
+
process.stdout.write(JSON.stringify({ chartTypes }, null, 2) + '\n');
|
|
275
|
+
} else {
|
|
276
|
+
for (const id of types) {
|
|
277
|
+
const desc = CHART_TYPE_DESCRIPTIONS[id];
|
|
278
|
+
console.log(desc ? `${id} — ${desc.split(' — ')[1]}` : id);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
223
284
|
// Determine input source
|
|
224
285
|
let content: string;
|
|
225
286
|
let inputBasename: string | undefined;
|
|
@@ -277,14 +338,30 @@ async function main(): Promise<void> {
|
|
|
277
338
|
process.exit(1);
|
|
278
339
|
}
|
|
279
340
|
|
|
341
|
+
const chartType = parseDgmoChartType(content);
|
|
342
|
+
|
|
343
|
+
// Helper for JSON error output
|
|
344
|
+
function exitWithJsonError(error: string, line?: number): never {
|
|
345
|
+
if (opts.json) {
|
|
346
|
+
process.stdout.write(JSON.stringify({
|
|
347
|
+
success: false,
|
|
348
|
+
error,
|
|
349
|
+
...(line != null ? { line } : {}),
|
|
350
|
+
...(chartType ? { chartType } : {}),
|
|
351
|
+
}, null, 2) + '\n');
|
|
352
|
+
} else {
|
|
353
|
+
console.error(error);
|
|
354
|
+
}
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
|
|
280
358
|
// URL output — encode DSL directly, no rendering needed
|
|
281
359
|
if (format === 'url') {
|
|
282
360
|
const result = encodeDiagramUrl(content);
|
|
283
361
|
if (result.error) {
|
|
284
|
-
|
|
362
|
+
exitWithJsonError(
|
|
285
363
|
`Error: Diagram too large for URL sharing (${result.compressedSize} bytes, limit ${result.limit} bytes)`
|
|
286
364
|
);
|
|
287
|
-
process.exit(1);
|
|
288
365
|
}
|
|
289
366
|
|
|
290
367
|
if (opts.copy) {
|
|
@@ -303,7 +380,15 @@ async function main(): Promise<void> {
|
|
|
303
380
|
}
|
|
304
381
|
}
|
|
305
382
|
|
|
306
|
-
|
|
383
|
+
if (opts.json) {
|
|
384
|
+
process.stdout.write(JSON.stringify({
|
|
385
|
+
success: true,
|
|
386
|
+
url: result.url,
|
|
387
|
+
...(chartType ? { chartType } : {}),
|
|
388
|
+
}, null, 2) + '\n');
|
|
389
|
+
} else {
|
|
390
|
+
process.stdout.write(result.url + '\n');
|
|
391
|
+
}
|
|
307
392
|
return;
|
|
308
393
|
}
|
|
309
394
|
|
|
@@ -313,10 +398,9 @@ async function main(): Promise<void> {
|
|
|
313
398
|
// which are unavailable in Node.js — check before attempting render.
|
|
314
399
|
const wordcloudRe = /^\s*chart\s*:\s*wordcloud\b/im;
|
|
315
400
|
if (wordcloudRe.test(content)) {
|
|
316
|
-
|
|
401
|
+
exitWithJsonError(
|
|
317
402
|
'Error: Word clouds are not supported in the CLI (requires Canvas). Use the desktop app or browser instead.'
|
|
318
403
|
);
|
|
319
|
-
process.exit(1);
|
|
320
404
|
}
|
|
321
405
|
|
|
322
406
|
// Parse first to collect diagnostics
|
|
@@ -325,28 +409,36 @@ async function main(): Promise<void> {
|
|
|
325
409
|
const warnings = diagnostics.filter((d) => d.severity === 'warning');
|
|
326
410
|
|
|
327
411
|
// Print warnings even if rendering succeeds
|
|
328
|
-
|
|
329
|
-
|
|
412
|
+
if (!opts.json) {
|
|
413
|
+
for (const w of warnings) {
|
|
414
|
+
console.error(`\u26A0 ${formatDgmoError(w)}`);
|
|
415
|
+
}
|
|
330
416
|
}
|
|
331
417
|
|
|
332
|
-
// Print errors
|
|
333
|
-
|
|
334
|
-
|
|
418
|
+
// Print errors and exit
|
|
419
|
+
if (errors.length > 0) {
|
|
420
|
+
if (opts.json) {
|
|
421
|
+
const firstError = errors[0];
|
|
422
|
+
exitWithJsonError(
|
|
423
|
+
formatDgmoError(firstError),
|
|
424
|
+
firstError.line,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
for (const e of errors) {
|
|
428
|
+
console.error(`\u2716 ${formatDgmoError(e)}`);
|
|
429
|
+
}
|
|
335
430
|
}
|
|
336
431
|
|
|
337
432
|
// Validate C4 options
|
|
338
433
|
if (opts.c4Level === 'containers' && !opts.c4System) {
|
|
339
|
-
|
|
340
|
-
process.exit(1);
|
|
434
|
+
exitWithJsonError('Error: --c4-system is required when --c4-level is containers');
|
|
341
435
|
}
|
|
342
436
|
if (opts.c4Level === 'components') {
|
|
343
437
|
if (!opts.c4System) {
|
|
344
|
-
|
|
345
|
-
process.exit(1);
|
|
438
|
+
exitWithJsonError('Error: --c4-system is required when --c4-level is components');
|
|
346
439
|
}
|
|
347
440
|
if (!opts.c4Container) {
|
|
348
|
-
|
|
349
|
-
process.exit(1);
|
|
441
|
+
exitWithJsonError('Error: --c4-container is required when --c4-level is components');
|
|
350
442
|
}
|
|
351
443
|
}
|
|
352
444
|
|
|
@@ -361,7 +453,7 @@ async function main(): Promise<void> {
|
|
|
361
453
|
|
|
362
454
|
if (!svg) {
|
|
363
455
|
if (errors.length === 0) {
|
|
364
|
-
|
|
456
|
+
exitWithJsonError(
|
|
365
457
|
'Error: Failed to render diagram. The input may be empty, invalid, or use an unsupported chart type.'
|
|
366
458
|
);
|
|
367
459
|
}
|
|
@@ -371,7 +463,26 @@ async function main(): Promise<void> {
|
|
|
371
463
|
// Determine output destination
|
|
372
464
|
const pngBg = opts.theme === 'transparent' ? undefined : paletteColors.bg;
|
|
373
465
|
|
|
374
|
-
if (opts.
|
|
466
|
+
if (opts.json) {
|
|
467
|
+
// JSON mode: write file as normal but output JSON result to stdout
|
|
468
|
+
let outputPath: string | undefined;
|
|
469
|
+
if (opts.output) {
|
|
470
|
+
outputPath = resolve(opts.output);
|
|
471
|
+
if (format === 'svg') {
|
|
472
|
+
writeFileSync(outputPath, svg, 'utf-8');
|
|
473
|
+
} else {
|
|
474
|
+
writeFileSync(outputPath, svgToPng(svg, pngBg));
|
|
475
|
+
}
|
|
476
|
+
} else if (inputBasename) {
|
|
477
|
+
outputPath = resolve(`${inputBasename}.png`);
|
|
478
|
+
writeFileSync(outputPath, svgToPng(svg, pngBg));
|
|
479
|
+
}
|
|
480
|
+
process.stdout.write(JSON.stringify({
|
|
481
|
+
success: true,
|
|
482
|
+
...(outputPath ? { output: outputPath } : {}),
|
|
483
|
+
...(chartType ? { chartType } : {}),
|
|
484
|
+
}, null, 2) + '\n');
|
|
485
|
+
} else if (opts.output) {
|
|
375
486
|
// Explicit output path
|
|
376
487
|
const outputPath = resolve(opts.output);
|
|
377
488
|
if (format === 'svg') {
|
package/src/d3.ts
CHANGED
package/src/dgmo-mermaid.ts
CHANGED
|
@@ -82,7 +82,7 @@ export function parseQuadrant(content: string): ParsedQuadrant {
|
|
|
82
82
|
const lineNumber = i + 1; // 1-indexed for editor
|
|
83
83
|
|
|
84
84
|
// Skip empty lines and comments
|
|
85
|
-
if (!line || line.startsWith('
|
|
85
|
+
if (!line || line.startsWith('//')) continue;
|
|
86
86
|
|
|
87
87
|
// Skip the chart: directive (already consumed by router)
|
|
88
88
|
if (/^chart\s*:/i.test(line)) continue;
|
package/src/dgmo-router.ts
CHANGED
|
@@ -81,7 +81,7 @@ export function parseDgmoChartType(content: string): string | null {
|
|
|
81
81
|
for (const line of lines) {
|
|
82
82
|
const trimmed = line.trim();
|
|
83
83
|
// Skip empty lines and comments
|
|
84
|
-
if (!trimmed || trimmed.startsWith('
|
|
84
|
+
if (!trimmed || trimmed.startsWith('//'))
|
|
85
85
|
continue;
|
|
86
86
|
const match = trimmed.match(/^chart\s*:\s*(.+)/i);
|
|
87
87
|
if (match) return match[1].trim().toLowerCase();
|
package/src/echarts.ts
CHANGED
|
@@ -75,7 +75,7 @@ export interface ParsedEChart {
|
|
|
75
75
|
showLabels?: boolean;
|
|
76
76
|
categoryColors?: Record<string, string>;
|
|
77
77
|
diagnostics: DgmoError[];
|
|
78
|
-
error
|
|
78
|
+
error: string | null;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// ============================================================
|
|
@@ -116,6 +116,7 @@ export function parseEChart(
|
|
|
116
116
|
type: 'scatter',
|
|
117
117
|
data: [],
|
|
118
118
|
diagnostics: [],
|
|
119
|
+
error: null,
|
|
119
120
|
};
|
|
120
121
|
|
|
121
122
|
// Track current category for grouped scatter charts
|
|
@@ -144,7 +145,7 @@ export function parseEChart(
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
// Skip comments
|
|
147
|
-
if (trimmed.startsWith('
|
|
148
|
+
if (trimmed.startsWith('//')) continue;
|
|
148
149
|
|
|
149
150
|
// Check for category header: [Category Name]
|
|
150
151
|
const categoryMatch = trimmed.match(/^\[(.+)\]$/);
|
|
@@ -964,7 +965,7 @@ function buildScatterOption(
|
|
|
964
965
|
},
|
|
965
966
|
}),
|
|
966
967
|
grid: {
|
|
967
|
-
left: parsed.ylabel ? '
|
|
968
|
+
left: parsed.ylabel ? '12%' : '3%',
|
|
968
969
|
right: '4%',
|
|
969
970
|
bottom: hasCategories ? '15%' : parsed.xlabel ? '10%' : '3%',
|
|
970
971
|
top: parsed.title ? '15%' : '5%',
|
|
@@ -1289,18 +1290,29 @@ function makeGridAxis(
|
|
|
1289
1290
|
splitLineColor: string,
|
|
1290
1291
|
gridOpacity: number,
|
|
1291
1292
|
label?: string,
|
|
1292
|
-
data?: string[]
|
|
1293
|
+
data?: string[],
|
|
1294
|
+
nameGapOverride?: number
|
|
1293
1295
|
): Record<string, unknown> {
|
|
1296
|
+
const defaultGap = type === 'value' ? 75 : 40;
|
|
1294
1297
|
return {
|
|
1295
1298
|
type,
|
|
1296
1299
|
...(data && { data }),
|
|
1297
1300
|
axisLine: { lineStyle: { color: axisLineColor } },
|
|
1298
|
-
axisLabel: {
|
|
1301
|
+
axisLabel: {
|
|
1302
|
+
color: textColor,
|
|
1303
|
+
fontSize: type === 'category' && data ? (data.length > 10 ? 11 : data.length > 5 ? 12 : 16) : 16,
|
|
1304
|
+
fontFamily: FONT_FAMILY,
|
|
1305
|
+
...(type === 'category' && {
|
|
1306
|
+
interval: 0,
|
|
1307
|
+
formatter: (value: string) =>
|
|
1308
|
+
value.replace(/([a-z])([A-Z])/g, '$1\n$2').replace(/ /g, '\n'),
|
|
1309
|
+
}),
|
|
1310
|
+
},
|
|
1299
1311
|
splitLine: { lineStyle: { color: splitLineColor, opacity: gridOpacity } },
|
|
1300
1312
|
...(label && {
|
|
1301
1313
|
name: label,
|
|
1302
1314
|
nameLocation: 'middle',
|
|
1303
|
-
nameGap:
|
|
1315
|
+
nameGap: nameGapOverride ?? defaultGap,
|
|
1304
1316
|
nameTextStyle: { color: textColor, fontSize: 18, fontFamily: FONT_FAMILY },
|
|
1305
1317
|
}),
|
|
1306
1318
|
};
|
|
@@ -1385,7 +1397,12 @@ function buildBarOption(
|
|
|
1385
1397
|
itemStyle: { color: d.color ?? colors[i % colors.length] },
|
|
1386
1398
|
}));
|
|
1387
1399
|
|
|
1388
|
-
|
|
1400
|
+
// When category labels are on the y-axis (horizontal bars), they can be wide —
|
|
1401
|
+
// compute a nameGap that clears the longest label so the ylabel doesn't overlap.
|
|
1402
|
+
const hCatGap = isHorizontal && yLabel
|
|
1403
|
+
? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16)
|
|
1404
|
+
: undefined;
|
|
1405
|
+
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
|
|
1389
1406
|
const valueAxis = makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
|
|
1390
1407
|
|
|
1391
1408
|
// xAxis is always the bottom axis, yAxis is always the left axis in ECharts
|
|
@@ -1400,7 +1417,7 @@ function buildBarOption(
|
|
|
1400
1417
|
axisPointer: { type: 'shadow' },
|
|
1401
1418
|
},
|
|
1402
1419
|
grid: {
|
|
1403
|
-
left: yLabel ? '
|
|
1420
|
+
left: yLabel ? '12%' : '3%',
|
|
1404
1421
|
right: '4%',
|
|
1405
1422
|
bottom: xLabel ? '10%' : '3%',
|
|
1406
1423
|
top: parsed.title ? '15%' : '5%',
|
|
@@ -1448,7 +1465,7 @@ function buildLineOption(
|
|
|
1448
1465
|
axisPointer: { type: 'line' },
|
|
1449
1466
|
},
|
|
1450
1467
|
grid: {
|
|
1451
|
-
left: yLabel ? '
|
|
1468
|
+
left: yLabel ? '12%' : '3%',
|
|
1452
1469
|
right: '4%',
|
|
1453
1470
|
bottom: xLabel ? '10%' : '3%',
|
|
1454
1471
|
top: parsed.title ? '15%' : '5%',
|
|
@@ -1524,7 +1541,7 @@ function buildMultiLineOption(
|
|
|
1524
1541
|
textStyle: { color: textColor },
|
|
1525
1542
|
},
|
|
1526
1543
|
grid: {
|
|
1527
|
-
left: yLabel ? '
|
|
1544
|
+
left: yLabel ? '12%' : '3%',
|
|
1528
1545
|
right: '4%',
|
|
1529
1546
|
bottom: '15%',
|
|
1530
1547
|
top: parsed.title ? '15%' : '5%',
|
|
@@ -1563,7 +1580,7 @@ function buildAreaOption(
|
|
|
1563
1580
|
axisPointer: { type: 'line' },
|
|
1564
1581
|
},
|
|
1565
1582
|
grid: {
|
|
1566
|
-
left: yLabel ? '
|
|
1583
|
+
left: yLabel ? '12%' : '3%',
|
|
1567
1584
|
right: '4%',
|
|
1568
1585
|
bottom: xLabel ? '10%' : '3%',
|
|
1569
1586
|
top: parsed.title ? '15%' : '5%',
|
|
@@ -1807,7 +1824,10 @@ function buildBarStackedOption(
|
|
|
1807
1824
|
};
|
|
1808
1825
|
});
|
|
1809
1826
|
|
|
1810
|
-
const
|
|
1827
|
+
const hCatGap = isHorizontal && yLabel
|
|
1828
|
+
? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16)
|
|
1829
|
+
: undefined;
|
|
1830
|
+
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
|
|
1811
1831
|
const valueAxis = makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
|
|
1812
1832
|
|
|
1813
1833
|
return {
|
|
@@ -1825,7 +1845,7 @@ function buildBarStackedOption(
|
|
|
1825
1845
|
textStyle: { color: textColor },
|
|
1826
1846
|
},
|
|
1827
1847
|
grid: {
|
|
1828
|
-
left: yLabel ? '
|
|
1848
|
+
left: yLabel ? '12%' : '3%',
|
|
1829
1849
|
right: '4%',
|
|
1830
1850
|
bottom: '15%',
|
|
1831
1851
|
top: parsed.title ? '15%' : '5%',
|