@diagrammo/dgmo 0.3.1 → 0.4.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 +11 -14
- package/dist/cli.cjs +150 -151
- package/dist/index.cjs +460 -901
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -6
- package/dist/index.d.ts +4 -6
- package/dist/index.js +459 -901
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +18 -19
- package/package.json +1 -1
- package/src/chart.ts +8 -39
- package/src/cli.ts +6 -6
- package/src/d3.ts +299 -715
- package/src/dgmo-router.ts +21 -42
- package/src/echarts.ts +134 -241
- package/src/index.ts +1 -0
- package/src/sequence/parser.ts +55 -106
- package/src/sequence/renderer.ts +4 -60
- package/src/utils/arrows.ts +43 -18
- package/src/utils/parsing.ts +43 -0
package/src/echarts.ts
CHANGED
|
@@ -82,13 +82,19 @@ export interface ParsedEChart {
|
|
|
82
82
|
// Nord Colors for Charts
|
|
83
83
|
// ============================================================
|
|
84
84
|
|
|
85
|
-
import { resolveColor } from './colors';
|
|
86
85
|
import type { PaletteColors } from './palettes';
|
|
87
86
|
import { getSeriesColors, getSegmentColors } from './palettes';
|
|
88
87
|
import { parseChart } from './chart';
|
|
89
88
|
import type { ParsedChart } from './chart';
|
|
90
89
|
import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
|
|
91
|
-
import { collectIndentedValues } from './utils/parsing';
|
|
90
|
+
import { collectIndentedValues, extractColor, parseSeriesNames } from './utils/parsing';
|
|
91
|
+
|
|
92
|
+
// ============================================================
|
|
93
|
+
// Shared Constants
|
|
94
|
+
// ============================================================
|
|
95
|
+
|
|
96
|
+
const EMPHASIS_SELF = { focus: 'self' as const, blurScope: 'global' as const };
|
|
97
|
+
const CHART_BASE: Pick<EChartsOption, 'backgroundColor' | 'animation'> = { backgroundColor: 'transparent', animation: false };
|
|
92
98
|
|
|
93
99
|
// ============================================================
|
|
94
100
|
// Parser
|
|
@@ -133,13 +139,10 @@ export function parseEChart(
|
|
|
133
139
|
// Check for markdown-style category header: ## Category Name or ## Category Name(color)
|
|
134
140
|
const mdCategoryMatch = trimmed.match(/^#{2,}\s+(.+)$/);
|
|
135
141
|
if (mdCategoryMatch) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (catColorMatch) {
|
|
139
|
-
const resolved = resolveColor(catColorMatch[1].trim(), palette);
|
|
142
|
+
const { label: catName, color: catColor } = extractColor(mdCategoryMatch[1].trim(), palette);
|
|
143
|
+
if (catColor) {
|
|
140
144
|
if (!result.categoryColors) result.categoryColors = {};
|
|
141
|
-
catName =
|
|
142
|
-
result.categoryColors[catName] = resolved;
|
|
145
|
+
result.categoryColors[catName] = catColor;
|
|
143
146
|
}
|
|
144
147
|
currentCategory = catName;
|
|
145
148
|
continue;
|
|
@@ -194,32 +197,13 @@ export function parseEChart(
|
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
if (key === 'series') {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const collected = collectIndentedValues(lines, i);
|
|
203
|
-
i = collected.newIndex;
|
|
204
|
-
rawNames = collected.values;
|
|
205
|
-
result.series = rawNames.join(', ');
|
|
200
|
+
const parsed = parseSeriesNames(value, lines, i, palette);
|
|
201
|
+
i = parsed.newIndex;
|
|
202
|
+
result.series = parsed.series;
|
|
203
|
+
if (parsed.names.length > 1) {
|
|
204
|
+
result.seriesNames = parsed.names;
|
|
206
205
|
}
|
|
207
|
-
|
|
208
|
-
const nameColors: (string | undefined)[] = [];
|
|
209
|
-
for (const raw of rawNames) {
|
|
210
|
-
const colorMatch = raw.match(/\(([^)]+)\)\s*$/);
|
|
211
|
-
if (colorMatch) {
|
|
212
|
-
nameColors.push(resolveColor(colorMatch[1].trim(), palette));
|
|
213
|
-
names.push(raw.substring(0, colorMatch.index!).trim());
|
|
214
|
-
} else {
|
|
215
|
-
nameColors.push(undefined);
|
|
216
|
-
names.push(raw);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (names.length === 1) {
|
|
220
|
-
result.series = names[0];
|
|
221
|
-
}
|
|
222
|
-
if (nameColors.some(Boolean)) result.seriesNameColors = nameColors;
|
|
206
|
+
if (parsed.nameColors.some(Boolean)) result.seriesNameColors = parsed.nameColors;
|
|
223
207
|
continue;
|
|
224
208
|
}
|
|
225
209
|
|
|
@@ -296,13 +280,7 @@ export function parseEChart(
|
|
|
296
280
|
|
|
297
281
|
// For function charts, treat non-numeric values as function expressions
|
|
298
282
|
if (result.type === 'function') {
|
|
299
|
-
|
|
300
|
-
let fnColor: string | undefined;
|
|
301
|
-
const colorMatch = fnName.match(/\(([^)]+)\)\s*$/);
|
|
302
|
-
if (colorMatch) {
|
|
303
|
-
fnColor = resolveColor(colorMatch[1].trim(), palette);
|
|
304
|
-
fnName = fnName.substring(0, colorMatch.index!).trim();
|
|
305
|
-
}
|
|
283
|
+
const { label: fnName, color: fnColor } = extractColor(trimmed.substring(0, colonIndex).trim(), palette);
|
|
306
284
|
if (!result.functions) result.functions = [];
|
|
307
285
|
result.functions.push({
|
|
308
286
|
name: fnName,
|
|
@@ -319,13 +297,7 @@ export function parseEChart(
|
|
|
319
297
|
/^(-?[\d.]+)\s*,\s*(-?[\d.]+)(?:\s*,\s*(-?[\d.]+))?$/
|
|
320
298
|
);
|
|
321
299
|
if (scatterMatch) {
|
|
322
|
-
|
|
323
|
-
let scatterColor: string | undefined;
|
|
324
|
-
const colorMatch = scatterName.match(/\(([^)]+)\)\s*$/);
|
|
325
|
-
if (colorMatch) {
|
|
326
|
-
scatterColor = resolveColor(colorMatch[1].trim(), palette);
|
|
327
|
-
scatterName = scatterName.substring(0, colorMatch.index!).trim();
|
|
328
|
-
}
|
|
300
|
+
const { label: scatterName, color: scatterColor } = extractColor(trimmed.substring(0, colonIndex).trim(), palette);
|
|
329
301
|
if (!result.scatterPoints) result.scatterPoints = [];
|
|
330
302
|
result.scatterPoints.push({
|
|
331
303
|
name: scatterName,
|
|
@@ -354,14 +326,7 @@ export function parseEChart(
|
|
|
354
326
|
// Otherwise treat as data point (label: value)
|
|
355
327
|
const numValue = parseFloat(value);
|
|
356
328
|
if (!isNaN(numValue)) {
|
|
357
|
-
|
|
358
|
-
let rawLabel = trimmed.substring(0, colonIndex).trim();
|
|
359
|
-
let pointColor: string | undefined;
|
|
360
|
-
const colorMatch = rawLabel.match(/\(([^)]+)\)\s*$/);
|
|
361
|
-
if (colorMatch) {
|
|
362
|
-
pointColor = resolveColor(colorMatch[1].trim(), palette);
|
|
363
|
-
rawLabel = rawLabel.substring(0, colorMatch.index!).trim();
|
|
364
|
-
}
|
|
329
|
+
const { label: rawLabel, color: pointColor } = extractColor(trimmed.substring(0, colonIndex).trim(), palette);
|
|
365
330
|
result.data.push({
|
|
366
331
|
label: rawLabel,
|
|
367
332
|
value: numValue,
|
|
@@ -416,6 +381,20 @@ export function parseEChart(
|
|
|
416
381
|
// ECharts Option Builder
|
|
417
382
|
// ============================================================
|
|
418
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Computes the shared set of theme-derived variables used by all chart option builders.
|
|
386
|
+
*/
|
|
387
|
+
function buildChartCommons(parsed: { title?: string; error?: string | null }, palette: PaletteColors, isDark: boolean) {
|
|
388
|
+
const textColor = palette.text;
|
|
389
|
+
const axisLineColor = palette.border;
|
|
390
|
+
const splitLineColor = palette.border;
|
|
391
|
+
const gridOpacity = isDark ? 0.7 : 0.55;
|
|
392
|
+
const colors = getSeriesColors(palette);
|
|
393
|
+
const titleConfig = parsed.title ? { text: parsed.title, left: 'center' as const, top: 8, textStyle: { color: textColor, fontSize: 20, fontWeight: 'bold' as const, fontFamily: FONT_FAMILY } } : undefined;
|
|
394
|
+
const tooltipTheme = { backgroundColor: palette.surface, borderColor: palette.border, textStyle: { color: palette.text } };
|
|
395
|
+
return { textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme };
|
|
396
|
+
}
|
|
397
|
+
|
|
419
398
|
/**
|
|
420
399
|
* Converts parsed echart data to ECharts option object.
|
|
421
400
|
*/
|
|
@@ -424,37 +403,12 @@ export function buildEChartsOption(
|
|
|
424
403
|
palette: PaletteColors,
|
|
425
404
|
isDark: boolean
|
|
426
405
|
): EChartsOption {
|
|
427
|
-
const textColor = palette.text;
|
|
428
|
-
const axisLineColor = palette.border;
|
|
429
|
-
const gridOpacity = isDark ? 0.7 : 0.55;
|
|
430
|
-
const colors = getSeriesColors(palette);
|
|
431
|
-
|
|
432
406
|
if (parsed.error) {
|
|
433
407
|
// Return empty option, error will be shown separately
|
|
434
408
|
return {};
|
|
435
409
|
}
|
|
436
410
|
|
|
437
|
-
|
|
438
|
-
const titleConfig = parsed.title
|
|
439
|
-
? {
|
|
440
|
-
text: parsed.title,
|
|
441
|
-
left: 'center' as const,
|
|
442
|
-
top: 8,
|
|
443
|
-
textStyle: {
|
|
444
|
-
color: textColor,
|
|
445
|
-
fontSize: 20,
|
|
446
|
-
fontWeight: 'bold' as const,
|
|
447
|
-
fontFamily: FONT_FAMILY,
|
|
448
|
-
},
|
|
449
|
-
}
|
|
450
|
-
: undefined;
|
|
451
|
-
|
|
452
|
-
// Shared tooltip theme so tooltips match light/dark mode
|
|
453
|
-
const tooltipTheme = {
|
|
454
|
-
backgroundColor: palette.surface,
|
|
455
|
-
borderColor: palette.border,
|
|
456
|
-
textStyle: { color: palette.text },
|
|
457
|
-
};
|
|
411
|
+
const { textColor, axisLineColor, gridOpacity, colors, titleConfig, tooltipTheme } = buildChartCommons(parsed, palette, isDark);
|
|
458
412
|
|
|
459
413
|
// Sankey chart has different structure
|
|
460
414
|
if (parsed.type === 'sankey') {
|
|
@@ -555,8 +509,7 @@ function buildSankeyOption(
|
|
|
555
509
|
}));
|
|
556
510
|
|
|
557
511
|
return {
|
|
558
|
-
|
|
559
|
-
animation: false,
|
|
512
|
+
...CHART_BASE,
|
|
560
513
|
title: titleConfig,
|
|
561
514
|
tooltip: {
|
|
562
515
|
show: false,
|
|
@@ -633,8 +586,7 @@ function buildChordOption(
|
|
|
633
586
|
}));
|
|
634
587
|
|
|
635
588
|
return {
|
|
636
|
-
|
|
637
|
-
animation: false,
|
|
589
|
+
...CHART_BASE,
|
|
638
590
|
title: titleConfig,
|
|
639
591
|
tooltip: {
|
|
640
592
|
trigger: 'item',
|
|
@@ -776,16 +728,12 @@ function buildFunctionOption(
|
|
|
776
728
|
itemStyle: {
|
|
777
729
|
color: fnColor,
|
|
778
730
|
},
|
|
779
|
-
emphasis:
|
|
780
|
-
focus: 'self' as const,
|
|
781
|
-
blurScope: 'global' as const,
|
|
782
|
-
},
|
|
731
|
+
emphasis: EMPHASIS_SELF,
|
|
783
732
|
};
|
|
784
733
|
});
|
|
785
734
|
|
|
786
735
|
return {
|
|
787
|
-
|
|
788
|
-
animation: false,
|
|
736
|
+
...CHART_BASE,
|
|
789
737
|
title: titleConfig,
|
|
790
738
|
tooltip: {
|
|
791
739
|
trigger: 'axis',
|
|
@@ -971,8 +919,7 @@ function buildScatterOption(
|
|
|
971
919
|
const yPad = (yMax - yMin) * 0.1 || 1;
|
|
972
920
|
|
|
973
921
|
return {
|
|
974
|
-
|
|
975
|
-
animation: false,
|
|
922
|
+
...CHART_BASE,
|
|
976
923
|
title: titleConfig,
|
|
977
924
|
tooltip,
|
|
978
925
|
...(legendData && {
|
|
@@ -1072,8 +1019,7 @@ function buildHeatmapOption(
|
|
|
1072
1019
|
});
|
|
1073
1020
|
|
|
1074
1021
|
return {
|
|
1075
|
-
|
|
1076
|
-
animation: false,
|
|
1022
|
+
...CHART_BASE,
|
|
1077
1023
|
title: titleConfig,
|
|
1078
1024
|
tooltip: {
|
|
1079
1025
|
trigger: 'item',
|
|
@@ -1150,8 +1096,7 @@ function buildHeatmapOption(
|
|
|
1150
1096
|
fontWeight: 'bold' as const,
|
|
1151
1097
|
},
|
|
1152
1098
|
emphasis: {
|
|
1153
|
-
|
|
1154
|
-
blurScope: 'global' as const,
|
|
1099
|
+
...EMPHASIS_SELF,
|
|
1155
1100
|
itemStyle: {
|
|
1156
1101
|
shadowBlur: 10,
|
|
1157
1102
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
|
@@ -1206,8 +1151,7 @@ function buildFunnelOption(
|
|
|
1206
1151
|
};
|
|
1207
1152
|
|
|
1208
1153
|
return {
|
|
1209
|
-
|
|
1210
|
-
animation: false,
|
|
1154
|
+
...CHART_BASE,
|
|
1211
1155
|
title: titleConfig,
|
|
1212
1156
|
tooltip: {
|
|
1213
1157
|
trigger: 'item',
|
|
@@ -1245,8 +1189,7 @@ function buildFunnelOption(
|
|
|
1245
1189
|
lineStyle: { color: textColor, opacity: 0.3 },
|
|
1246
1190
|
},
|
|
1247
1191
|
emphasis: {
|
|
1248
|
-
|
|
1249
|
-
blurScope: 'global' as const,
|
|
1192
|
+
...EMPHASIS_SELF,
|
|
1250
1193
|
label: {
|
|
1251
1194
|
fontSize: 15,
|
|
1252
1195
|
},
|
|
@@ -1309,21 +1252,45 @@ function makeGridAxis(
|
|
|
1309
1252
|
gridOpacity: number,
|
|
1310
1253
|
label?: string,
|
|
1311
1254
|
data?: string[],
|
|
1312
|
-
nameGapOverride?: number
|
|
1255
|
+
nameGapOverride?: number,
|
|
1256
|
+
chartWidthHint?: number
|
|
1313
1257
|
): Record<string, unknown> {
|
|
1314
1258
|
const defaultGap = type === 'value' ? 75 : 40;
|
|
1259
|
+
|
|
1260
|
+
// Compute category label sizing: font size and width constraint
|
|
1261
|
+
let catFontSize = 16;
|
|
1262
|
+
let catLabelExtras: Record<string, unknown> = {};
|
|
1263
|
+
if (type === 'category' && data && data.length > 0) {
|
|
1264
|
+
const maxLabelLen = Math.max(...data.map((l) => l.length));
|
|
1265
|
+
const count = data.length;
|
|
1266
|
+
// Reduce font size based on density and label length
|
|
1267
|
+
if (count > 10 || maxLabelLen > 20) catFontSize = 10;
|
|
1268
|
+
else if (count > 5 || maxLabelLen > 14) catFontSize = 11;
|
|
1269
|
+
else if (maxLabelLen > 8) catFontSize = 12;
|
|
1270
|
+
|
|
1271
|
+
// Constrain labels to their allotted slot width so ECharts wraps instead of hiding
|
|
1272
|
+
if (chartWidthHint && count > 0) {
|
|
1273
|
+
const availPerLabel = Math.floor((chartWidthHint * 0.85) / count);
|
|
1274
|
+
catLabelExtras = {
|
|
1275
|
+
width: availPerLabel,
|
|
1276
|
+
overflow: 'break',
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1315
1281
|
return {
|
|
1316
1282
|
type,
|
|
1317
1283
|
...(data && { data }),
|
|
1318
1284
|
axisLine: { lineStyle: { color: axisLineColor } },
|
|
1319
1285
|
axisLabel: {
|
|
1320
1286
|
color: textColor,
|
|
1321
|
-
fontSize: type === 'category' && data ?
|
|
1287
|
+
fontSize: type === 'category' && data ? catFontSize : 16,
|
|
1322
1288
|
fontFamily: FONT_FAMILY,
|
|
1323
1289
|
...(type === 'category' && {
|
|
1324
1290
|
interval: 0,
|
|
1325
1291
|
formatter: (value: string) =>
|
|
1326
|
-
value.replace(/([a-z])([A-Z])/g, '$1\n$2')
|
|
1292
|
+
value.replace(/([a-z])([A-Z])/g, '$1\n$2'),
|
|
1293
|
+
...catLabelExtras,
|
|
1327
1294
|
}),
|
|
1328
1295
|
},
|
|
1329
1296
|
splitLine: { lineStyle: { color: splitLineColor, opacity: gridOpacity } },
|
|
@@ -1343,47 +1310,24 @@ function makeGridAxis(
|
|
|
1343
1310
|
export function buildEChartsOptionFromChart(
|
|
1344
1311
|
parsed: ParsedChart,
|
|
1345
1312
|
palette: PaletteColors,
|
|
1346
|
-
isDark: boolean
|
|
1313
|
+
isDark: boolean,
|
|
1314
|
+
chartWidth?: number
|
|
1347
1315
|
): EChartsOption {
|
|
1348
1316
|
if (parsed.error) return {};
|
|
1349
1317
|
|
|
1350
|
-
const textColor = palette
|
|
1351
|
-
const axisLineColor = palette.border;
|
|
1352
|
-
const splitLineColor = palette.border;
|
|
1353
|
-
const gridOpacity = isDark ? 0.7 : 0.55;
|
|
1354
|
-
const colors = getSeriesColors(palette);
|
|
1355
|
-
|
|
1356
|
-
const titleConfig = parsed.title
|
|
1357
|
-
? {
|
|
1358
|
-
text: parsed.title,
|
|
1359
|
-
left: 'center' as const,
|
|
1360
|
-
top: 8,
|
|
1361
|
-
textStyle: {
|
|
1362
|
-
color: textColor,
|
|
1363
|
-
fontSize: 20,
|
|
1364
|
-
fontWeight: 'bold' as const,
|
|
1365
|
-
fontFamily: FONT_FAMILY,
|
|
1366
|
-
},
|
|
1367
|
-
}
|
|
1368
|
-
: undefined;
|
|
1369
|
-
|
|
1370
|
-
const tooltipTheme = {
|
|
1371
|
-
backgroundColor: palette.surface,
|
|
1372
|
-
borderColor: palette.border,
|
|
1373
|
-
textStyle: { color: palette.text },
|
|
1374
|
-
};
|
|
1318
|
+
const { textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme } = buildChartCommons(parsed, palette, isDark);
|
|
1375
1319
|
|
|
1376
1320
|
switch (parsed.type) {
|
|
1377
1321
|
case 'bar':
|
|
1378
|
-
return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme);
|
|
1322
|
+
return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
|
|
1379
1323
|
case 'bar-stacked':
|
|
1380
|
-
return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme);
|
|
1324
|
+
return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
|
|
1381
1325
|
case 'line':
|
|
1382
1326
|
return parsed.seriesNames
|
|
1383
|
-
? buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme)
|
|
1384
|
-
: buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme);
|
|
1327
|
+
? buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth)
|
|
1328
|
+
: buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
|
|
1385
1329
|
case 'area':
|
|
1386
|
-
return buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme);
|
|
1330
|
+
return buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
|
|
1387
1331
|
case 'pie':
|
|
1388
1332
|
return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme, false);
|
|
1389
1333
|
case 'doughnut':
|
|
@@ -1395,6 +1339,19 @@ export function buildEChartsOptionFromChart(
|
|
|
1395
1339
|
}
|
|
1396
1340
|
}
|
|
1397
1341
|
|
|
1342
|
+
/**
|
|
1343
|
+
* Builds a standard chart grid object with consistent spacing rules.
|
|
1344
|
+
*/
|
|
1345
|
+
function makeChartGrid(options: { xLabel?: string; yLabel?: string; hasTitle: boolean; hasLegend?: boolean }): Record<string, unknown> {
|
|
1346
|
+
return {
|
|
1347
|
+
left: options.yLabel ? '12%' : '3%',
|
|
1348
|
+
right: '4%',
|
|
1349
|
+
bottom: options.hasLegend ? '15%' : options.xLabel ? '10%' : '3%',
|
|
1350
|
+
top: options.hasTitle ? '15%' : '5%',
|
|
1351
|
+
containLabel: true,
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1398
1355
|
// ── Bar ──────────────────────────────────────────────────────
|
|
1399
1356
|
|
|
1400
1357
|
function buildBarOption(
|
|
@@ -1405,7 +1362,8 @@ function buildBarOption(
|
|
|
1405
1362
|
gridOpacity: number,
|
|
1406
1363
|
colors: string[],
|
|
1407
1364
|
titleConfig: EChartsOption['title'],
|
|
1408
|
-
tooltipTheme: Record<string, unknown
|
|
1365
|
+
tooltipTheme: Record<string, unknown>,
|
|
1366
|
+
chartWidth?: number
|
|
1409
1367
|
): EChartsOption {
|
|
1410
1368
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1411
1369
|
const isHorizontal = parsed.orientation === 'horizontal';
|
|
@@ -1420,37 +1378,27 @@ function buildBarOption(
|
|
|
1420
1378
|
const hCatGap = isHorizontal && yLabel
|
|
1421
1379
|
? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16)
|
|
1422
1380
|
: undefined;
|
|
1423
|
-
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
|
|
1381
|
+
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap, !isHorizontal ? chartWidth : undefined);
|
|
1424
1382
|
const valueAxis = makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
|
|
1425
1383
|
|
|
1426
1384
|
// xAxis is always the bottom axis, yAxis is always the left axis in ECharts
|
|
1427
1385
|
|
|
1428
1386
|
return {
|
|
1429
|
-
|
|
1430
|
-
animation: false,
|
|
1387
|
+
...CHART_BASE,
|
|
1431
1388
|
title: titleConfig,
|
|
1432
1389
|
tooltip: {
|
|
1433
1390
|
trigger: 'axis',
|
|
1434
1391
|
...tooltipTheme,
|
|
1435
1392
|
axisPointer: { type: 'shadow' },
|
|
1436
1393
|
},
|
|
1437
|
-
grid: {
|
|
1438
|
-
left: yLabel ? '12%' : '3%',
|
|
1439
|
-
right: '4%',
|
|
1440
|
-
bottom: xLabel ? '10%' : '3%',
|
|
1441
|
-
top: parsed.title ? '15%' : '5%',
|
|
1442
|
-
containLabel: true,
|
|
1443
|
-
},
|
|
1394
|
+
grid: makeChartGrid({ xLabel, yLabel, hasTitle: !!parsed.title }),
|
|
1444
1395
|
xAxis: isHorizontal ? valueAxis : categoryAxis,
|
|
1445
1396
|
yAxis: isHorizontal ? categoryAxis : valueAxis,
|
|
1446
1397
|
series: [
|
|
1447
1398
|
{
|
|
1448
1399
|
type: 'bar',
|
|
1449
1400
|
data,
|
|
1450
|
-
emphasis:
|
|
1451
|
-
focus: 'self' as const,
|
|
1452
|
-
blurScope: 'global' as const,
|
|
1453
|
-
},
|
|
1401
|
+
emphasis: EMPHASIS_SELF,
|
|
1454
1402
|
},
|
|
1455
1403
|
],
|
|
1456
1404
|
};
|
|
@@ -1466,7 +1414,8 @@ function buildLineOption(
|
|
|
1466
1414
|
splitLineColor: string,
|
|
1467
1415
|
gridOpacity: number,
|
|
1468
1416
|
titleConfig: EChartsOption['title'],
|
|
1469
|
-
tooltipTheme: Record<string, unknown
|
|
1417
|
+
tooltipTheme: Record<string, unknown>,
|
|
1418
|
+
chartWidth?: number
|
|
1470
1419
|
): EChartsOption {
|
|
1471
1420
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1472
1421
|
const lineColor = parsed.color ?? parsed.seriesNameColors?.[0] ?? palette.primary;
|
|
@@ -1474,22 +1423,15 @@ function buildLineOption(
|
|
|
1474
1423
|
const values = parsed.data.map((d) => d.value);
|
|
1475
1424
|
|
|
1476
1425
|
return {
|
|
1477
|
-
|
|
1478
|
-
animation: false,
|
|
1426
|
+
...CHART_BASE,
|
|
1479
1427
|
title: titleConfig,
|
|
1480
1428
|
tooltip: {
|
|
1481
1429
|
trigger: 'axis',
|
|
1482
1430
|
...tooltipTheme,
|
|
1483
1431
|
axisPointer: { type: 'line' },
|
|
1484
1432
|
},
|
|
1485
|
-
grid: {
|
|
1486
|
-
|
|
1487
|
-
right: '4%',
|
|
1488
|
-
bottom: xLabel ? '10%' : '3%',
|
|
1489
|
-
top: parsed.title ? '15%' : '5%',
|
|
1490
|
-
containLabel: true,
|
|
1491
|
-
},
|
|
1492
|
-
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels),
|
|
1433
|
+
grid: makeChartGrid({ xLabel, yLabel, hasTitle: !!parsed.title }),
|
|
1434
|
+
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels, undefined, chartWidth),
|
|
1493
1435
|
yAxis: makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, yLabel),
|
|
1494
1436
|
series: [
|
|
1495
1437
|
{
|
|
@@ -1499,10 +1441,7 @@ function buildLineOption(
|
|
|
1499
1441
|
symbolSize: 8,
|
|
1500
1442
|
lineStyle: { color: lineColor, width: 3 },
|
|
1501
1443
|
itemStyle: { color: lineColor },
|
|
1502
|
-
emphasis:
|
|
1503
|
-
focus: 'self' as const,
|
|
1504
|
-
blurScope: 'global' as const,
|
|
1505
|
-
},
|
|
1444
|
+
emphasis: EMPHASIS_SELF,
|
|
1506
1445
|
},
|
|
1507
1446
|
],
|
|
1508
1447
|
};
|
|
@@ -1518,7 +1457,8 @@ function buildMultiLineOption(
|
|
|
1518
1457
|
gridOpacity: number,
|
|
1519
1458
|
colors: string[],
|
|
1520
1459
|
titleConfig: EChartsOption['title'],
|
|
1521
|
-
tooltipTheme: Record<string, unknown
|
|
1460
|
+
tooltipTheme: Record<string, unknown>,
|
|
1461
|
+
chartWidth?: number
|
|
1522
1462
|
): EChartsOption {
|
|
1523
1463
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1524
1464
|
const seriesNames = parsed.seriesNames ?? [];
|
|
@@ -1537,16 +1477,12 @@ function buildMultiLineOption(
|
|
|
1537
1477
|
symbolSize: 8,
|
|
1538
1478
|
lineStyle: { color, width: 3 },
|
|
1539
1479
|
itemStyle: { color },
|
|
1540
|
-
emphasis:
|
|
1541
|
-
focus: 'self' as const,
|
|
1542
|
-
blurScope: 'global' as const,
|
|
1543
|
-
},
|
|
1480
|
+
emphasis: EMPHASIS_SELF,
|
|
1544
1481
|
};
|
|
1545
1482
|
});
|
|
1546
1483
|
|
|
1547
1484
|
return {
|
|
1548
|
-
|
|
1549
|
-
animation: false,
|
|
1485
|
+
...CHART_BASE,
|
|
1550
1486
|
title: titleConfig,
|
|
1551
1487
|
tooltip: {
|
|
1552
1488
|
trigger: 'axis',
|
|
@@ -1558,14 +1494,8 @@ function buildMultiLineOption(
|
|
|
1558
1494
|
bottom: 10,
|
|
1559
1495
|
textStyle: { color: textColor },
|
|
1560
1496
|
},
|
|
1561
|
-
grid: {
|
|
1562
|
-
|
|
1563
|
-
right: '4%',
|
|
1564
|
-
bottom: '15%',
|
|
1565
|
-
top: parsed.title ? '15%' : '5%',
|
|
1566
|
-
containLabel: true,
|
|
1567
|
-
},
|
|
1568
|
-
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels),
|
|
1497
|
+
grid: makeChartGrid({ xLabel, yLabel, hasTitle: !!parsed.title, hasLegend: true }),
|
|
1498
|
+
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels, undefined, chartWidth),
|
|
1569
1499
|
yAxis: makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, yLabel),
|
|
1570
1500
|
series,
|
|
1571
1501
|
};
|
|
@@ -1581,7 +1511,8 @@ function buildAreaOption(
|
|
|
1581
1511
|
splitLineColor: string,
|
|
1582
1512
|
gridOpacity: number,
|
|
1583
1513
|
titleConfig: EChartsOption['title'],
|
|
1584
|
-
tooltipTheme: Record<string, unknown
|
|
1514
|
+
tooltipTheme: Record<string, unknown>,
|
|
1515
|
+
chartWidth?: number
|
|
1585
1516
|
): EChartsOption {
|
|
1586
1517
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1587
1518
|
const lineColor = parsed.color ?? parsed.seriesNameColors?.[0] ?? palette.primary;
|
|
@@ -1589,22 +1520,15 @@ function buildAreaOption(
|
|
|
1589
1520
|
const values = parsed.data.map((d) => d.value);
|
|
1590
1521
|
|
|
1591
1522
|
return {
|
|
1592
|
-
|
|
1593
|
-
animation: false,
|
|
1523
|
+
...CHART_BASE,
|
|
1594
1524
|
title: titleConfig,
|
|
1595
1525
|
tooltip: {
|
|
1596
1526
|
trigger: 'axis',
|
|
1597
1527
|
...tooltipTheme,
|
|
1598
1528
|
axisPointer: { type: 'line' },
|
|
1599
1529
|
},
|
|
1600
|
-
grid: {
|
|
1601
|
-
|
|
1602
|
-
right: '4%',
|
|
1603
|
-
bottom: xLabel ? '10%' : '3%',
|
|
1604
|
-
top: parsed.title ? '15%' : '5%',
|
|
1605
|
-
containLabel: true,
|
|
1606
|
-
},
|
|
1607
|
-
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels),
|
|
1530
|
+
grid: makeChartGrid({ xLabel, yLabel, hasTitle: !!parsed.title }),
|
|
1531
|
+
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels, undefined, chartWidth),
|
|
1608
1532
|
yAxis: makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, yLabel),
|
|
1609
1533
|
series: [
|
|
1610
1534
|
{
|
|
@@ -1615,10 +1539,7 @@ function buildAreaOption(
|
|
|
1615
1539
|
lineStyle: { color: lineColor, width: 3 },
|
|
1616
1540
|
itemStyle: { color: lineColor },
|
|
1617
1541
|
areaStyle: { opacity: 0.25 },
|
|
1618
|
-
emphasis:
|
|
1619
|
-
focus: 'self' as const,
|
|
1620
|
-
blurScope: 'global' as const,
|
|
1621
|
-
},
|
|
1542
|
+
emphasis: EMPHASIS_SELF,
|
|
1622
1543
|
},
|
|
1623
1544
|
],
|
|
1624
1545
|
};
|
|
@@ -1652,8 +1573,7 @@ function buildPieOption(
|
|
|
1652
1573
|
}));
|
|
1653
1574
|
|
|
1654
1575
|
return {
|
|
1655
|
-
|
|
1656
|
-
animation: false,
|
|
1576
|
+
...CHART_BASE,
|
|
1657
1577
|
title: titleConfig,
|
|
1658
1578
|
tooltip: {
|
|
1659
1579
|
trigger: 'item',
|
|
@@ -1671,10 +1591,7 @@ function buildPieOption(
|
|
|
1671
1591
|
fontFamily: FONT_FAMILY,
|
|
1672
1592
|
},
|
|
1673
1593
|
labelLine: { show: true },
|
|
1674
|
-
emphasis:
|
|
1675
|
-
focus: 'self' as const,
|
|
1676
|
-
blurScope: 'global' as const,
|
|
1677
|
-
},
|
|
1594
|
+
emphasis: EMPHASIS_SELF,
|
|
1678
1595
|
},
|
|
1679
1596
|
],
|
|
1680
1597
|
};
|
|
@@ -1701,8 +1618,7 @@ function buildRadarOption(
|
|
|
1701
1618
|
}));
|
|
1702
1619
|
|
|
1703
1620
|
return {
|
|
1704
|
-
|
|
1705
|
-
animation: false,
|
|
1621
|
+
...CHART_BASE,
|
|
1706
1622
|
title: titleConfig,
|
|
1707
1623
|
tooltip: {
|
|
1708
1624
|
trigger: 'item',
|
|
@@ -1744,10 +1660,7 @@ function buildRadarOption(
|
|
|
1744
1660
|
},
|
|
1745
1661
|
},
|
|
1746
1662
|
],
|
|
1747
|
-
emphasis:
|
|
1748
|
-
focus: 'self' as const,
|
|
1749
|
-
blurScope: 'global' as const,
|
|
1750
|
-
},
|
|
1663
|
+
emphasis: EMPHASIS_SELF,
|
|
1751
1664
|
},
|
|
1752
1665
|
],
|
|
1753
1666
|
};
|
|
@@ -1769,8 +1682,7 @@ function buildPolarAreaOption(
|
|
|
1769
1682
|
}));
|
|
1770
1683
|
|
|
1771
1684
|
return {
|
|
1772
|
-
|
|
1773
|
-
animation: false,
|
|
1685
|
+
...CHART_BASE,
|
|
1774
1686
|
title: titleConfig,
|
|
1775
1687
|
tooltip: {
|
|
1776
1688
|
trigger: 'item',
|
|
@@ -1789,10 +1701,7 @@ function buildPolarAreaOption(
|
|
|
1789
1701
|
fontFamily: FONT_FAMILY,
|
|
1790
1702
|
},
|
|
1791
1703
|
labelLine: { show: true },
|
|
1792
|
-
emphasis:
|
|
1793
|
-
focus: 'self' as const,
|
|
1794
|
-
blurScope: 'global' as const,
|
|
1795
|
-
},
|
|
1704
|
+
emphasis: EMPHASIS_SELF,
|
|
1796
1705
|
},
|
|
1797
1706
|
],
|
|
1798
1707
|
};
|
|
@@ -1808,7 +1717,8 @@ function buildBarStackedOption(
|
|
|
1808
1717
|
gridOpacity: number,
|
|
1809
1718
|
colors: string[],
|
|
1810
1719
|
titleConfig: EChartsOption['title'],
|
|
1811
|
-
tooltipTheme: Record<string, unknown
|
|
1720
|
+
tooltipTheme: Record<string, unknown>,
|
|
1721
|
+
chartWidth?: number
|
|
1812
1722
|
): EChartsOption {
|
|
1813
1723
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1814
1724
|
const isHorizontal = parsed.orientation === 'horizontal';
|
|
@@ -1835,22 +1745,21 @@ function buildBarStackedOption(
|
|
|
1835
1745
|
fontWeight: 'bold' as const,
|
|
1836
1746
|
fontFamily: FONT_FAMILY,
|
|
1837
1747
|
},
|
|
1838
|
-
emphasis:
|
|
1839
|
-
focus: 'self' as const,
|
|
1840
|
-
blurScope: 'global' as const,
|
|
1841
|
-
},
|
|
1748
|
+
emphasis: EMPHASIS_SELF,
|
|
1842
1749
|
};
|
|
1843
1750
|
});
|
|
1844
1751
|
|
|
1845
1752
|
const hCatGap = isHorizontal && yLabel
|
|
1846
1753
|
? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16)
|
|
1847
1754
|
: undefined;
|
|
1848
|
-
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
|
|
1849
|
-
|
|
1755
|
+
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap, !isHorizontal ? chartWidth : undefined);
|
|
1756
|
+
// For horizontal bars with a legend, use a smaller nameGap so the xlabel
|
|
1757
|
+
// stays close to the axis ticks rather than drifting toward the legend.
|
|
1758
|
+
const hValueGap = isHorizontal && xLabel ? 40 : undefined;
|
|
1759
|
+
const valueAxis = makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel, undefined, hValueGap);
|
|
1850
1760
|
|
|
1851
1761
|
return {
|
|
1852
|
-
|
|
1853
|
-
animation: false,
|
|
1762
|
+
...CHART_BASE,
|
|
1854
1763
|
title: titleConfig,
|
|
1855
1764
|
tooltip: {
|
|
1856
1765
|
trigger: 'axis',
|
|
@@ -1862,13 +1771,7 @@ function buildBarStackedOption(
|
|
|
1862
1771
|
bottom: 10,
|
|
1863
1772
|
textStyle: { color: textColor },
|
|
1864
1773
|
},
|
|
1865
|
-
grid: {
|
|
1866
|
-
left: yLabel ? '12%' : '3%',
|
|
1867
|
-
right: '4%',
|
|
1868
|
-
bottom: '15%',
|
|
1869
|
-
top: parsed.title ? '15%' : '5%',
|
|
1870
|
-
containLabel: true,
|
|
1871
|
-
},
|
|
1774
|
+
grid: makeChartGrid({ xLabel, yLabel, hasTitle: !!parsed.title, hasLegend: true }),
|
|
1872
1775
|
xAxis: isHorizontal ? valueAxis : categoryAxis,
|
|
1873
1776
|
yAxis: isHorizontal ? categoryAxis : valueAxis,
|
|
1874
1777
|
series,
|
|
@@ -1882,17 +1785,7 @@ function buildBarStackedOption(
|
|
|
1882
1785
|
const ECHART_EXPORT_WIDTH = 1200;
|
|
1883
1786
|
const ECHART_EXPORT_HEIGHT = 800;
|
|
1884
1787
|
|
|
1885
|
-
|
|
1886
|
-
'bar',
|
|
1887
|
-
'line',
|
|
1888
|
-
'multi-line',
|
|
1889
|
-
'area',
|
|
1890
|
-
'pie',
|
|
1891
|
-
'doughnut',
|
|
1892
|
-
'radar',
|
|
1893
|
-
'polar-area',
|
|
1894
|
-
'bar-stacked',
|
|
1895
|
-
]);
|
|
1788
|
+
import { STANDARD_CHART_TYPES } from './dgmo-router';
|
|
1896
1789
|
|
|
1897
1790
|
/**
|
|
1898
1791
|
* Renders an ECharts diagram to SVG using server-side rendering.
|
|
@@ -1919,7 +1812,7 @@ export async function renderEChartsForExport(
|
|
|
1919
1812
|
if (chartType && STANDARD_CHART_TYPES.has(chartType)) {
|
|
1920
1813
|
const parsed = parseChart(content, effectivePalette);
|
|
1921
1814
|
if (parsed.error) return '';
|
|
1922
|
-
option = buildEChartsOptionFromChart(parsed, effectivePalette, isDark);
|
|
1815
|
+
option = buildEChartsOptionFromChart(parsed, effectivePalette, isDark, ECHART_EXPORT_WIDTH);
|
|
1923
1816
|
} else {
|
|
1924
1817
|
const parsed = parseEChart(content, effectivePalette);
|
|
1925
1818
|
if (parsed.error) return '';
|