@diagrammo/dgmo 0.3.1 → 0.3.2
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/dist/cli.cjs +134 -135
- package/dist/index.cjs +123 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +123 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/d3.ts +106 -46
- package/src/echarts.ts +54 -21
package/package.json
CHANGED
package/src/d3.ts
CHANGED
|
@@ -1074,6 +1074,29 @@ function tokenizeFreeformText(text: string): WordCloudWord[] {
|
|
|
1074
1074
|
// Slope Chart Renderer
|
|
1075
1075
|
// ============================================================
|
|
1076
1076
|
|
|
1077
|
+
/**
|
|
1078
|
+
* Resolves vertical label collisions by nudging overlapping items apart.
|
|
1079
|
+
* Takes items with a naturalY (center) and height, returns adjusted center Y positions.
|
|
1080
|
+
*/
|
|
1081
|
+
function resolveVerticalCollisions(
|
|
1082
|
+
items: { naturalY: number; height: number }[],
|
|
1083
|
+
minGap: number
|
|
1084
|
+
): number[] {
|
|
1085
|
+
if (items.length === 0) return [];
|
|
1086
|
+
const sorted = items
|
|
1087
|
+
.map((it, i) => ({ ...it, idx: i }))
|
|
1088
|
+
.sort((a, b) => a.naturalY - b.naturalY);
|
|
1089
|
+
const adjustedY = new Array<number>(items.length);
|
|
1090
|
+
let prevBottom = -Infinity;
|
|
1091
|
+
for (const item of sorted) {
|
|
1092
|
+
const halfH = item.height / 2;
|
|
1093
|
+
const top = Math.max(item.naturalY - halfH, prevBottom + minGap);
|
|
1094
|
+
adjustedY[item.idx] = top + halfH;
|
|
1095
|
+
prevBottom = top + item.height;
|
|
1096
|
+
}
|
|
1097
|
+
return adjustedY;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1077
1100
|
const SLOPE_MARGIN = { top: 80, bottom: 40, left: 80 };
|
|
1078
1101
|
const SLOPE_LABEL_FONT_SIZE = 14;
|
|
1079
1102
|
const SLOPE_CHAR_WIDTH = 8; // approximate px per character at 14px
|
|
@@ -1205,28 +1228,83 @@ export function renderSlopeChart(
|
|
|
1205
1228
|
.x((_d, i) => xScale(periods[i])!)
|
|
1206
1229
|
.y((d) => yScale(d));
|
|
1207
1230
|
|
|
1208
|
-
//
|
|
1209
|
-
data.
|
|
1231
|
+
// Pre-compute per-series data for label collision resolution
|
|
1232
|
+
const seriesInfo = data.map((item, idx) => {
|
|
1210
1233
|
const color = item.color ?? colors[idx % colors.length];
|
|
1211
|
-
|
|
1212
|
-
// Wrap each series in a group with data-line-number for sync adapter
|
|
1213
|
-
const seriesG = g
|
|
1214
|
-
.append('g')
|
|
1215
|
-
.attr('class', 'slope-series')
|
|
1216
|
-
.attr('data-line-number', String(item.lineNumber));
|
|
1217
|
-
|
|
1218
|
-
// Tooltip content – overall change for this series
|
|
1219
1234
|
const firstVal = item.values[0];
|
|
1220
1235
|
const lastVal = item.values[item.values.length - 1];
|
|
1221
1236
|
const absChange = lastVal - firstVal;
|
|
1222
1237
|
const pctChange = firstVal !== 0 ? (absChange / firstVal) * 100 : null;
|
|
1223
1238
|
const sign = absChange > 0 ? '+' : '';
|
|
1224
|
-
const
|
|
1225
|
-
pctChange !== null ? ` (${sign}${pctChange.toFixed(1)}%)` : '';
|
|
1226
|
-
const tipLines = [`${sign}${absChange}`];
|
|
1239
|
+
const tipLines = [`${sign}${parseFloat(absChange.toFixed(2))}`];
|
|
1227
1240
|
if (pctChange !== null) tipLines.push(`${sign}${pctChange.toFixed(1)}%`);
|
|
1228
1241
|
const tipHtml = tipLines.join('<br>');
|
|
1229
1242
|
|
|
1243
|
+
// Compute right-side label text and wrapping info
|
|
1244
|
+
const lastX = xScale(periods[periods.length - 1])!;
|
|
1245
|
+
const labelText = `${lastVal} — ${item.label}`;
|
|
1246
|
+
const availableWidth = rightMargin - 15;
|
|
1247
|
+
const maxChars = Math.floor(availableWidth / SLOPE_CHAR_WIDTH);
|
|
1248
|
+
|
|
1249
|
+
let labelLineCount = 1;
|
|
1250
|
+
let wrappedLines: string[] | null = null;
|
|
1251
|
+
if (labelText.length > maxChars) {
|
|
1252
|
+
const words = labelText.split(/\s+/);
|
|
1253
|
+
const lines: string[] = [];
|
|
1254
|
+
let current = '';
|
|
1255
|
+
for (const word of words) {
|
|
1256
|
+
const test = current ? `${current} ${word}` : word;
|
|
1257
|
+
if (test.length > maxChars && current) {
|
|
1258
|
+
lines.push(current);
|
|
1259
|
+
current = word;
|
|
1260
|
+
} else {
|
|
1261
|
+
current = test;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
if (current) lines.push(current);
|
|
1265
|
+
labelLineCount = lines.length;
|
|
1266
|
+
wrappedLines = lines;
|
|
1267
|
+
}
|
|
1268
|
+
const lineHeight = SLOPE_LABEL_FONT_SIZE * 1.2;
|
|
1269
|
+
const labelHeight = labelLineCount === 1
|
|
1270
|
+
? SLOPE_LABEL_FONT_SIZE
|
|
1271
|
+
: labelLineCount * lineHeight;
|
|
1272
|
+
|
|
1273
|
+
return {
|
|
1274
|
+
item, idx, color, firstVal, lastVal, tipHtml,
|
|
1275
|
+
lastX, labelText, maxChars, wrappedLines, labelHeight,
|
|
1276
|
+
};
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
// --- Resolve left-side label collisions per non-last period column ---
|
|
1280
|
+
const leftLabelHeight = 20; // 16px font needs ~20px to avoid glyph overlap
|
|
1281
|
+
const leftLabelCollisions: Map<number, number[]> = new Map();
|
|
1282
|
+
for (let pi = 0; pi < periods.length - 1; pi++) {
|
|
1283
|
+
const entries = data.map((item) => ({
|
|
1284
|
+
naturalY: yScale(item.values[pi]),
|
|
1285
|
+
height: leftLabelHeight,
|
|
1286
|
+
}));
|
|
1287
|
+
leftLabelCollisions.set(pi, resolveVerticalCollisions(entries, 4));
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// --- Resolve right-side label collisions ---
|
|
1291
|
+
const rightEntries = seriesInfo.map((si) => ({
|
|
1292
|
+
naturalY: yScale(si.lastVal),
|
|
1293
|
+
height: Math.max(si.labelHeight, SLOPE_LABEL_FONT_SIZE * 1.4),
|
|
1294
|
+
}));
|
|
1295
|
+
const rightAdjustedY = resolveVerticalCollisions(rightEntries, 4);
|
|
1296
|
+
|
|
1297
|
+
// Render each data series
|
|
1298
|
+
data.forEach((item, idx) => {
|
|
1299
|
+
const si = seriesInfo[idx];
|
|
1300
|
+
const color = si.color;
|
|
1301
|
+
|
|
1302
|
+
// Wrap each series in a group with data-line-number for sync adapter
|
|
1303
|
+
const seriesG = g
|
|
1304
|
+
.append('g')
|
|
1305
|
+
.attr('class', 'slope-series')
|
|
1306
|
+
.attr('data-line-number', String(item.lineNumber));
|
|
1307
|
+
|
|
1230
1308
|
// Line
|
|
1231
1309
|
seriesG.append('path')
|
|
1232
1310
|
.datum(item.values)
|
|
@@ -1244,10 +1322,10 @@ export function renderSlopeChart(
|
|
|
1244
1322
|
.attr('d', lineGen)
|
|
1245
1323
|
.style('cursor', onClickItem ? 'pointer' : 'default')
|
|
1246
1324
|
.on('mouseenter', (event: MouseEvent) =>
|
|
1247
|
-
showTooltip(tooltip, tipHtml, event)
|
|
1325
|
+
showTooltip(tooltip, si.tipHtml, event)
|
|
1248
1326
|
)
|
|
1249
1327
|
.on('mousemove', (event: MouseEvent) =>
|
|
1250
|
-
showTooltip(tooltip, tipHtml, event)
|
|
1328
|
+
showTooltip(tooltip, si.tipHtml, event)
|
|
1251
1329
|
)
|
|
1252
1330
|
.on('mouseleave', () => hideTooltip(tooltip))
|
|
1253
1331
|
.on('click', () => {
|
|
@@ -1269,10 +1347,10 @@ export function renderSlopeChart(
|
|
|
1269
1347
|
.attr('stroke-width', 1.5)
|
|
1270
1348
|
.style('cursor', onClickItem ? 'pointer' : 'default')
|
|
1271
1349
|
.on('mouseenter', (event: MouseEvent) =>
|
|
1272
|
-
showTooltip(tooltip, tipHtml, event)
|
|
1350
|
+
showTooltip(tooltip, si.tipHtml, event)
|
|
1273
1351
|
)
|
|
1274
1352
|
.on('mousemove', (event: MouseEvent) =>
|
|
1275
|
-
showTooltip(tooltip, tipHtml, event)
|
|
1353
|
+
showTooltip(tooltip, si.tipHtml, event)
|
|
1276
1354
|
)
|
|
1277
1355
|
.on('mouseleave', () => hideTooltip(tooltip))
|
|
1278
1356
|
.on('click', () => {
|
|
@@ -1283,59 +1361,41 @@ export function renderSlopeChart(
|
|
|
1283
1361
|
const isFirst = i === 0;
|
|
1284
1362
|
const isLast = i === periods.length - 1;
|
|
1285
1363
|
if (!isLast) {
|
|
1364
|
+
const adjustedY = leftLabelCollisions.get(i)![idx];
|
|
1286
1365
|
seriesG.append('text')
|
|
1287
1366
|
.attr('x', isFirst ? x - 10 : x)
|
|
1288
|
-
.attr('y',
|
|
1367
|
+
.attr('y', adjustedY)
|
|
1289
1368
|
.attr('dy', '0.35em')
|
|
1290
1369
|
.attr('text-anchor', isFirst ? 'end' : 'middle')
|
|
1291
|
-
.attr('fill',
|
|
1370
|
+
.attr('fill', color)
|
|
1292
1371
|
.attr('font-size', '16px')
|
|
1293
1372
|
.text(val.toString());
|
|
1294
1373
|
}
|
|
1295
1374
|
});
|
|
1296
1375
|
|
|
1297
1376
|
// Series label with value at end of line — wraps if it exceeds available space
|
|
1298
|
-
const
|
|
1299
|
-
const lastY = yScale(lastVal);
|
|
1300
|
-
const labelText = `${lastVal} — ${item.label}`;
|
|
1301
|
-
const availableWidth = rightMargin - 15;
|
|
1302
|
-
const maxChars = Math.floor(availableWidth / SLOPE_CHAR_WIDTH);
|
|
1377
|
+
const adjustedLastY = rightAdjustedY[idx];
|
|
1303
1378
|
|
|
1304
1379
|
const labelEl = seriesG
|
|
1305
1380
|
.append('text')
|
|
1306
|
-
.attr('x', lastX + 10)
|
|
1307
|
-
.attr('y',
|
|
1381
|
+
.attr('x', si.lastX + 10)
|
|
1382
|
+
.attr('y', adjustedLastY)
|
|
1308
1383
|
.attr('text-anchor', 'start')
|
|
1309
1384
|
.attr('fill', color)
|
|
1310
1385
|
.attr('font-size', `${SLOPE_LABEL_FONT_SIZE}px`)
|
|
1311
1386
|
.attr('font-weight', '500');
|
|
1312
1387
|
|
|
1313
|
-
if (
|
|
1314
|
-
labelEl.attr('dy', '0.35em').text(labelText);
|
|
1388
|
+
if (!si.wrappedLines) {
|
|
1389
|
+
labelEl.attr('dy', '0.35em').text(si.labelText);
|
|
1315
1390
|
} else {
|
|
1316
|
-
// Wrap into lines that fit the available width
|
|
1317
|
-
const words = labelText.split(/\s+/);
|
|
1318
|
-
const lines: string[] = [];
|
|
1319
|
-
let current = '';
|
|
1320
|
-
for (const word of words) {
|
|
1321
|
-
const test = current ? `${current} ${word}` : word;
|
|
1322
|
-
if (test.length > maxChars && current) {
|
|
1323
|
-
lines.push(current);
|
|
1324
|
-
current = word;
|
|
1325
|
-
} else {
|
|
1326
|
-
current = test;
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
if (current) lines.push(current);
|
|
1330
|
-
|
|
1331
1391
|
const lineHeight = SLOPE_LABEL_FONT_SIZE * 1.2;
|
|
1332
|
-
const totalHeight = (
|
|
1392
|
+
const totalHeight = (si.wrappedLines.length - 1) * lineHeight;
|
|
1333
1393
|
const startDy = -totalHeight / 2;
|
|
1334
1394
|
|
|
1335
|
-
|
|
1395
|
+
si.wrappedLines.forEach((line, li) => {
|
|
1336
1396
|
labelEl
|
|
1337
1397
|
.append('tspan')
|
|
1338
|
-
.attr('x', lastX + 10)
|
|
1398
|
+
.attr('x', si.lastX + 10)
|
|
1339
1399
|
.attr(
|
|
1340
1400
|
'dy',
|
|
1341
1401
|
li === 0
|
package/src/echarts.ts
CHANGED
|
@@ -1309,21 +1309,45 @@ function makeGridAxis(
|
|
|
1309
1309
|
gridOpacity: number,
|
|
1310
1310
|
label?: string,
|
|
1311
1311
|
data?: string[],
|
|
1312
|
-
nameGapOverride?: number
|
|
1312
|
+
nameGapOverride?: number,
|
|
1313
|
+
chartWidthHint?: number
|
|
1313
1314
|
): Record<string, unknown> {
|
|
1314
1315
|
const defaultGap = type === 'value' ? 75 : 40;
|
|
1316
|
+
|
|
1317
|
+
// Compute category label sizing: font size and width constraint
|
|
1318
|
+
let catFontSize = 16;
|
|
1319
|
+
let catLabelExtras: Record<string, unknown> = {};
|
|
1320
|
+
if (type === 'category' && data && data.length > 0) {
|
|
1321
|
+
const maxLabelLen = Math.max(...data.map((l) => l.length));
|
|
1322
|
+
const count = data.length;
|
|
1323
|
+
// Reduce font size based on density and label length
|
|
1324
|
+
if (count > 10 || maxLabelLen > 20) catFontSize = 10;
|
|
1325
|
+
else if (count > 5 || maxLabelLen > 14) catFontSize = 11;
|
|
1326
|
+
else if (maxLabelLen > 8) catFontSize = 12;
|
|
1327
|
+
|
|
1328
|
+
// Constrain labels to their allotted slot width so ECharts wraps instead of hiding
|
|
1329
|
+
if (chartWidthHint && count > 0) {
|
|
1330
|
+
const availPerLabel = Math.floor((chartWidthHint * 0.85) / count);
|
|
1331
|
+
catLabelExtras = {
|
|
1332
|
+
width: availPerLabel,
|
|
1333
|
+
overflow: 'break',
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1315
1338
|
return {
|
|
1316
1339
|
type,
|
|
1317
1340
|
...(data && { data }),
|
|
1318
1341
|
axisLine: { lineStyle: { color: axisLineColor } },
|
|
1319
1342
|
axisLabel: {
|
|
1320
1343
|
color: textColor,
|
|
1321
|
-
fontSize: type === 'category' && data ?
|
|
1344
|
+
fontSize: type === 'category' && data ? catFontSize : 16,
|
|
1322
1345
|
fontFamily: FONT_FAMILY,
|
|
1323
1346
|
...(type === 'category' && {
|
|
1324
1347
|
interval: 0,
|
|
1325
1348
|
formatter: (value: string) =>
|
|
1326
|
-
value.replace(/([a-z])([A-Z])/g, '$1\n$2')
|
|
1349
|
+
value.replace(/([a-z])([A-Z])/g, '$1\n$2'),
|
|
1350
|
+
...catLabelExtras,
|
|
1327
1351
|
}),
|
|
1328
1352
|
},
|
|
1329
1353
|
splitLine: { lineStyle: { color: splitLineColor, opacity: gridOpacity } },
|
|
@@ -1343,7 +1367,8 @@ function makeGridAxis(
|
|
|
1343
1367
|
export function buildEChartsOptionFromChart(
|
|
1344
1368
|
parsed: ParsedChart,
|
|
1345
1369
|
palette: PaletteColors,
|
|
1346
|
-
isDark: boolean
|
|
1370
|
+
isDark: boolean,
|
|
1371
|
+
chartWidth?: number
|
|
1347
1372
|
): EChartsOption {
|
|
1348
1373
|
if (parsed.error) return {};
|
|
1349
1374
|
|
|
@@ -1375,15 +1400,15 @@ export function buildEChartsOptionFromChart(
|
|
|
1375
1400
|
|
|
1376
1401
|
switch (parsed.type) {
|
|
1377
1402
|
case 'bar':
|
|
1378
|
-
return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme);
|
|
1403
|
+
return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
|
|
1379
1404
|
case 'bar-stacked':
|
|
1380
|
-
return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme);
|
|
1405
|
+
return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
|
|
1381
1406
|
case 'line':
|
|
1382
1407
|
return parsed.seriesNames
|
|
1383
|
-
? buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme)
|
|
1384
|
-
: buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme);
|
|
1408
|
+
? buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth)
|
|
1409
|
+
: buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
|
|
1385
1410
|
case 'area':
|
|
1386
|
-
return buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme);
|
|
1411
|
+
return buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
|
|
1387
1412
|
case 'pie':
|
|
1388
1413
|
return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme, false);
|
|
1389
1414
|
case 'doughnut':
|
|
@@ -1405,7 +1430,8 @@ function buildBarOption(
|
|
|
1405
1430
|
gridOpacity: number,
|
|
1406
1431
|
colors: string[],
|
|
1407
1432
|
titleConfig: EChartsOption['title'],
|
|
1408
|
-
tooltipTheme: Record<string, unknown
|
|
1433
|
+
tooltipTheme: Record<string, unknown>,
|
|
1434
|
+
chartWidth?: number
|
|
1409
1435
|
): EChartsOption {
|
|
1410
1436
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1411
1437
|
const isHorizontal = parsed.orientation === 'horizontal';
|
|
@@ -1420,7 +1446,7 @@ function buildBarOption(
|
|
|
1420
1446
|
const hCatGap = isHorizontal && yLabel
|
|
1421
1447
|
? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16)
|
|
1422
1448
|
: undefined;
|
|
1423
|
-
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
|
|
1449
|
+
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap, !isHorizontal ? chartWidth : undefined);
|
|
1424
1450
|
const valueAxis = makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
|
|
1425
1451
|
|
|
1426
1452
|
// xAxis is always the bottom axis, yAxis is always the left axis in ECharts
|
|
@@ -1466,7 +1492,8 @@ function buildLineOption(
|
|
|
1466
1492
|
splitLineColor: string,
|
|
1467
1493
|
gridOpacity: number,
|
|
1468
1494
|
titleConfig: EChartsOption['title'],
|
|
1469
|
-
tooltipTheme: Record<string, unknown
|
|
1495
|
+
tooltipTheme: Record<string, unknown>,
|
|
1496
|
+
chartWidth?: number
|
|
1470
1497
|
): EChartsOption {
|
|
1471
1498
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1472
1499
|
const lineColor = parsed.color ?? parsed.seriesNameColors?.[0] ?? palette.primary;
|
|
@@ -1489,7 +1516,7 @@ function buildLineOption(
|
|
|
1489
1516
|
top: parsed.title ? '15%' : '5%',
|
|
1490
1517
|
containLabel: true,
|
|
1491
1518
|
},
|
|
1492
|
-
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels),
|
|
1519
|
+
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels, undefined, chartWidth),
|
|
1493
1520
|
yAxis: makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, yLabel),
|
|
1494
1521
|
series: [
|
|
1495
1522
|
{
|
|
@@ -1518,7 +1545,8 @@ function buildMultiLineOption(
|
|
|
1518
1545
|
gridOpacity: number,
|
|
1519
1546
|
colors: string[],
|
|
1520
1547
|
titleConfig: EChartsOption['title'],
|
|
1521
|
-
tooltipTheme: Record<string, unknown
|
|
1548
|
+
tooltipTheme: Record<string, unknown>,
|
|
1549
|
+
chartWidth?: number
|
|
1522
1550
|
): EChartsOption {
|
|
1523
1551
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1524
1552
|
const seriesNames = parsed.seriesNames ?? [];
|
|
@@ -1565,7 +1593,7 @@ function buildMultiLineOption(
|
|
|
1565
1593
|
top: parsed.title ? '15%' : '5%',
|
|
1566
1594
|
containLabel: true,
|
|
1567
1595
|
},
|
|
1568
|
-
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels),
|
|
1596
|
+
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels, undefined, chartWidth),
|
|
1569
1597
|
yAxis: makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, yLabel),
|
|
1570
1598
|
series,
|
|
1571
1599
|
};
|
|
@@ -1581,7 +1609,8 @@ function buildAreaOption(
|
|
|
1581
1609
|
splitLineColor: string,
|
|
1582
1610
|
gridOpacity: number,
|
|
1583
1611
|
titleConfig: EChartsOption['title'],
|
|
1584
|
-
tooltipTheme: Record<string, unknown
|
|
1612
|
+
tooltipTheme: Record<string, unknown>,
|
|
1613
|
+
chartWidth?: number
|
|
1585
1614
|
): EChartsOption {
|
|
1586
1615
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1587
1616
|
const lineColor = parsed.color ?? parsed.seriesNameColors?.[0] ?? palette.primary;
|
|
@@ -1604,7 +1633,7 @@ function buildAreaOption(
|
|
|
1604
1633
|
top: parsed.title ? '15%' : '5%',
|
|
1605
1634
|
containLabel: true,
|
|
1606
1635
|
},
|
|
1607
|
-
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels),
|
|
1636
|
+
xAxis: makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, xLabel, labels, undefined, chartWidth),
|
|
1608
1637
|
yAxis: makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, yLabel),
|
|
1609
1638
|
series: [
|
|
1610
1639
|
{
|
|
@@ -1808,7 +1837,8 @@ function buildBarStackedOption(
|
|
|
1808
1837
|
gridOpacity: number,
|
|
1809
1838
|
colors: string[],
|
|
1810
1839
|
titleConfig: EChartsOption['title'],
|
|
1811
|
-
tooltipTheme: Record<string, unknown
|
|
1840
|
+
tooltipTheme: Record<string, unknown>,
|
|
1841
|
+
chartWidth?: number
|
|
1812
1842
|
): EChartsOption {
|
|
1813
1843
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1814
1844
|
const isHorizontal = parsed.orientation === 'horizontal';
|
|
@@ -1845,8 +1875,11 @@ function buildBarStackedOption(
|
|
|
1845
1875
|
const hCatGap = isHorizontal && yLabel
|
|
1846
1876
|
? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16)
|
|
1847
1877
|
: undefined;
|
|
1848
|
-
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
|
|
1849
|
-
|
|
1878
|
+
const categoryAxis = makeGridAxis('category', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap, !isHorizontal ? chartWidth : undefined);
|
|
1879
|
+
// For horizontal bars with a legend, use a smaller nameGap so the xlabel
|
|
1880
|
+
// stays close to the axis ticks rather than drifting toward the legend.
|
|
1881
|
+
const hValueGap = isHorizontal && xLabel ? 40 : undefined;
|
|
1882
|
+
const valueAxis = makeGridAxis('value', textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel, undefined, hValueGap);
|
|
1850
1883
|
|
|
1851
1884
|
return {
|
|
1852
1885
|
backgroundColor: 'transparent',
|
|
@@ -1919,7 +1952,7 @@ export async function renderEChartsForExport(
|
|
|
1919
1952
|
if (chartType && STANDARD_CHART_TYPES.has(chartType)) {
|
|
1920
1953
|
const parsed = parseChart(content, effectivePalette);
|
|
1921
1954
|
if (parsed.error) return '';
|
|
1922
|
-
option = buildEChartsOptionFromChart(parsed, effectivePalette, isDark);
|
|
1955
|
+
option = buildEChartsOptionFromChart(parsed, effectivePalette, isDark, ECHART_EXPORT_WIDTH);
|
|
1923
1956
|
} else {
|
|
1924
1957
|
const parsed = parseEChart(content, effectivePalette);
|
|
1925
1958
|
if (parsed.error) return '';
|