feat: 数据标签颜色默认同系列+可自定义;Y轴最小/最大值手动指定 v1.20.0

- 数据标签颜色:默认自动取系列颜色(柱状/折线色),也可选自定义颜色手动指定
- Y轴范围:默认自动计算,可手动指定最小/最大值;双Y轴时左/右轴可分别指定
- 主图 + 多图合并 + 服务端API 三处统一;收藏保存/恢复含两项设置
- API 新增参数 labelColorMode/labelColor, yAxisMin/yAxisMax, leftAxisMin/Max, rightAxisMin/Max
This commit is contained in:
2026-08-23 18:15:21 +08:00
parent 864c43ab99
commit 01c0536986
4 changed files with 185 additions and 8 deletions
+107 -4
View File
@@ -302,6 +302,25 @@ function updateChart() {
const xAxisNameLoc = document.getElementById('xAxisNameLoc').value;
const xLabelRotate = xLabelRotateValue();
// 数据标签颜色:auto=同系列颜色 / custom=手动指定
const labelColorMode = (document.getElementById('labelColorMode') || {}).value || 'auto';
const labelColor = (document.getElementById('labelColor') || {}).value || '#333333';
// Y轴范围(留空=自动)
const yAxisMin = (document.getElementById('yAxisMin') || {}).value;
const yAxisMax = (document.getElementById('yAxisMax') || {}).value;
const leftAxisMin = (document.getElementById('leftAxisMin') || {}).value;
const leftAxisMax = (document.getElementById('leftAxisMax') || {}).value;
const rightAxisMin = (document.getElementById('rightAxisMin') || {}).value;
const rightAxisMax = (document.getElementById('rightAxisMax') || {}).value;
// 显示/隐藏:数据标签颜色 + 单/双轴范围输入
const lcGroup = document.getElementById('labelColorGroup');
if (lcGroup) lcGroup.style.display = showLabel ? 'block' : 'none';
const singleR = document.getElementById('singleAxisRange');
const dualR = document.getElementById('dualAxisRange');
if (singleR) singleR.style.display = dualAxis ? 'none' : 'block';
if (dualR) dualR.style.display = dualAxis ? 'block' : 'none';
// 饼图/雷达图:走专用构建逻辑(无坐标轴/网格,不支持双轴/堆叠/分割)
if (chartType === 'pie' || chartType === 'radar') {
const option = buildPieRadarOption(chartType);
@@ -383,13 +402,13 @@ function updateChart() {
const styleOv = window.seriesStyleOverrides && window.seriesStyleOverrides[origIdx];
applySeriesStyle(seriesItem, type, color, styleOv);
// 数据标签
// 数据标签:颜色默认同系列颜色,可手动自定义
if (showLabel) {
seriesItem.label = {
show: true,
position: type === 'bar' ? 'top' : 'top',
fontSize: 11,
color: textColor,
color: labelColorMode === 'custom' ? labelColor : color,
formatter: (params) => {
if (params.value >= 10000) return (params.value / 10000).toFixed(1) + 'w';
if (params.value >= 1000) return (params.value / 1000).toFixed(1) + 'k';
@@ -599,6 +618,8 @@ function updateChart() {
{
type: 'value',
name: leftAxisName || '左轴',
min: axisNum(leftAxisMin),
max: axisNum(leftAxisMax),
nameLocation: leftAxisNameLoc === 'top' ? 'end' : 'middle',
nameRotate: leftAxisNameLoc === 'top' ? 0 : 90,
nameGap: leftAxisNameLoc === 'top' ? 12 : 32,
@@ -615,6 +636,8 @@ function updateChart() {
type: 'value',
name: rightAxisName || '右轴',
position: 'right',
min: axisNum(rightAxisMin),
max: axisNum(rightAxisMax),
nameLocation: rightAxisNameLoc === 'top' ? 'end' : 'middle',
nameRotate: rightAxisNameLoc === 'top' ? 0 : 90,
nameGap: rightAxisNameLoc === 'top' ? 12 : 32,
@@ -626,6 +649,8 @@ function updateChart() {
}
] : {
type: 'value',
min: axisNum(yAxisMin),
max: axisNum(yAxisMax),
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: textColor, fontSize: 11 },
@@ -844,6 +869,10 @@ function updateSeriesStyle(origIdx, style) {
function onDualAxisChange() {
const on = document.getElementById('dualAxis').checked;
document.getElementById('rightAxisGroup').style.display = on ? 'block' : 'none';
const singleR = document.getElementById('singleAxisRange');
const dualR = document.getElementById('dualAxisRange');
if (singleR) singleR.style.display = on ? 'none' : 'block';
if (dualR) dualR.style.display = on ? 'block' : 'none';
renderSeriesConfig(); // 显示/隐藏系列轴选择
updateChart();
}
@@ -954,6 +983,20 @@ function setXAxisNameDefault(firstField) {
}
}
// Y轴 min/max:空值或非法返回 undefined(走 ECharts 自动)
function axisNum(v) {
if (v === '' || v === undefined || v === null) return undefined;
const n = parseFloat(v);
return isNaN(n) ? undefined : n;
}
// 数据标签颜色模式切换:auto=同系列颜色 / custom=自定义色
function onLabelColorModeChange() {
const colorInput = document.getElementById('labelColor');
if (colorInput) colorInput.style.display = document.getElementById('labelColorMode').value === 'custom' ? 'block' : 'none';
updateChart();
}
function adjustColor(hex, amount) {
hex = hex.replace('#', '');
const num = parseInt(hex, 16);
@@ -1390,6 +1433,8 @@ function buildCombineChartOption(dataText, cfg) {
stackMode = false, smoothLine = true,
dualYAxis = false, leftAxisName = '', rightAxisName = '', seriesConfig = null,
leftAxisNameLoc = 'vertical', rightAxisNameLoc = 'vertical',
labelColorMode = 'auto', labelColor = '#333333',
yAxisMin = '', yAxisMax = '', leftAxisMin = '', leftAxisMax = '', rightAxisMin = '', rightAxisMax = '',
xAxisName = '', xAxisNameLoc = 'middle', xLabelRotate = 0, width = 600
} = cfg;
@@ -1505,7 +1550,7 @@ function buildCombineChartOption(dataText, cfg) {
show: true,
position: 'top',
fontSize: 11,
color: textColor,
color: labelColorMode === 'custom' ? labelColor : color,
formatter: (p) => {
if (p.value >= 10000) return (p.value / 10000).toFixed(1) + 'w';
if (p.value >= 1000) return (p.value / 1000).toFixed(1) + 'k';
@@ -1612,6 +1657,8 @@ function buildCombineChartOption(dataText, cfg) {
{
type: 'value',
name: leftAxisName || '左轴',
min: axisNum(leftAxisMin),
max: axisNum(leftAxisMax),
nameLocation: leftAxisNameLoc === 'top' ? 'end' : 'middle',
nameRotate: leftAxisNameLoc === 'top' ? 0 : 90,
nameGap: leftAxisNameLoc === 'top' ? 12 : 30,
@@ -1625,6 +1672,8 @@ function buildCombineChartOption(dataText, cfg) {
type: 'value',
name: rightAxisName || '右轴',
position: 'right',
min: axisNum(rightAxisMin),
max: axisNum(rightAxisMax),
nameLocation: rightAxisNameLoc === 'top' ? 'end' : 'middle',
nameRotate: rightAxisNameLoc === 'top' ? 0 : 90,
nameGap: rightAxisNameLoc === 'top' ? 12 : 30,
@@ -1636,6 +1685,8 @@ function buildCombineChartOption(dataText, cfg) {
}
] : {
type: 'value',
min: axisNum(yAxisMin),
max: axisNum(yAxisMax),
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: textColor, fontSize: 11 },
@@ -1658,7 +1709,7 @@ function measureCombineChart(dataText, rowsAsSeries) {
let combineCharts = [];
function defaultCombineChart() {
return { title: '', type: 'bar', theme: 'default', legend: true, grid: true, label: false, stack: false, data: '', rowsAsSeries: false, dualYAxis: false, leftAxisName: '', rightAxisName: '', leftAxisNameLoc: 'vertical', rightAxisNameLoc: 'vertical', seriesConfig: [], xAxisName: '', xAxisNameLoc: 'middle', xLabelRotate: 0 };
return { title: '', type: 'bar', theme: 'default', legend: true, grid: true, label: false, stack: false, data: '', rowsAsSeries: false, dualYAxis: false, leftAxisName: '', rightAxisName: '', leftAxisNameLoc: 'vertical', rightAxisNameLoc: 'vertical', labelColorMode: 'auto', labelColor: '#333333', yAxisMin: '', yAxisMax: '', leftAxisMin: '', leftAxisMax: '', rightAxisMin: '', rightAxisMax: '', seriesConfig: [], xAxisName: '', xAxisNameLoc: 'middle', xLabelRotate: 0 };
}
function initCombineCharts() {
@@ -1713,6 +1764,35 @@ function renderCombineCharts() {
<label><input type="checkbox" ${c.rowsAsSeries ? 'checked' : ''} onchange="updateCombineChart(${i},'rowsAsSeries',this.checked)"> 行=系列</label>
</div>
</div>
<div class="config-group">
<label>数据标签颜色(默认同系列颜色)</label>
<div class="res-input-row">
<select onchange="updateCombineChart(${i},'labelColorMode',this.value)" style="flex:1">
<option value="auto" ${(c.labelColorMode || 'auto') === 'auto' ? 'selected' : ''}>自动(同系列颜色)</option>
<option value="custom" ${c.labelColorMode === 'custom' ? 'selected' : ''}>自定义…</option>
</select>
<input type="color" value="${c.labelColor || '#333333'}" style="width:44px;height:32px" onchange="updateCombineChart(${i},'labelColor',this.value)">
</div>
</div>
<div class="config-group">
<label>Y轴范围(留空=自动)</label>
${c.dualYAxis ? `
<div class="res-input-row">
<input type="number" value="${escHtml(c.leftAxisMin)}" placeholder="左轴最小" oninput="updateCombineChart(${i},'leftAxisMin',this.value)">
<span class="res-times">~</span>
<input type="number" value="${escHtml(c.leftAxisMax)}" placeholder="左轴最大" oninput="updateCombineChart(${i},'leftAxisMax',this.value)">
</div>
<div class="res-input-row" style="margin-top:6px">
<input type="number" value="${escHtml(c.rightAxisMin)}" placeholder="右轴最小" oninput="updateCombineChart(${i},'rightAxisMin',this.value)">
<span class="res-times">~</span>
<input type="number" value="${escHtml(c.rightAxisMax)}" placeholder="右轴最大" oninput="updateCombineChart(${i},'rightAxisMax',this.value)">
</div>` : `
<div class="res-input-row">
<input type="number" value="${escHtml(c.yAxisMin)}" placeholder="最小值" oninput="updateCombineChart(${i},'yAxisMin',this.value)">
<span class="res-times">~</span>
<input type="number" value="${escHtml(c.yAxisMax)}" placeholder="最大值" oninput="updateCombineChart(${i},'yAxisMax',this.value)">
</div>`}
</div>
<div class="config-group">
<label><input type="checkbox" ${c.dualYAxis ? 'checked' : ''} onchange="updateCombineChart(${i},'dualYAxis',this.checked)"> 双Y轴(不同左右量度)</label>
</div>
@@ -1940,6 +2020,10 @@ function generateCombine() {
rowsAsSeries: !!c.rowsAsSeries,
dualYAxis: !!c.dualYAxis, leftAxisName: c.leftAxisName || '', rightAxisName: c.rightAxisName || '',
leftAxisNameLoc: c.leftAxisNameLoc || 'vertical', rightAxisNameLoc: c.rightAxisNameLoc || 'vertical',
labelColorMode: c.labelColorMode || 'auto', labelColor: c.labelColor || '#333333',
yAxisMin: c.yAxisMin !== undefined ? c.yAxisMin : '', yAxisMax: c.yAxisMax !== undefined ? c.yAxisMax : '',
leftAxisMin: c.leftAxisMin !== undefined ? c.leftAxisMin : '', leftAxisMax: c.leftAxisMax !== undefined ? c.leftAxisMax : '',
rightAxisMin: c.rightAxisMin !== undefined ? c.rightAxisMin : '', rightAxisMax: c.rightAxisMax !== undefined ? c.rightAxisMax : '',
xAxisName: c.xAxisName || '', xAxisNameLoc: c.xAxisNameLoc || 'middle', xLabelRotate: parseInt(c.xLabelRotate) || 0,
seriesConfig: c.seriesConfig, width: w
});
@@ -2111,6 +2195,14 @@ function collectChartConfig() {
rightAxisName: document.getElementById('rightAxisName').value,
leftAxisNameLoc: (document.getElementById('leftAxisNameLoc') || {}).value || 'vertical',
rightAxisNameLoc: (document.getElementById('rightAxisNameLoc') || {}).value || 'vertical',
labelColorMode: (document.getElementById('labelColorMode') || {}).value || 'auto',
labelColor: (document.getElementById('labelColor') || {}).value || '#333333',
yAxisMin: (document.getElementById('yAxisMin') || {}).value || '',
yAxisMax: (document.getElementById('yAxisMax') || {}).value || '',
leftAxisMin: (document.getElementById('leftAxisMin') || {}).value || '',
leftAxisMax: (document.getElementById('leftAxisMax') || {}).value || '',
rightAxisMin: (document.getElementById('rightAxisMin') || {}).value || '',
rightAxisMax: (document.getElementById('rightAxisMax') || {}).value || '',
xAxisName: document.getElementById('xAxisName').value,
xAxisNameLoc: document.getElementById('xAxisNameLoc').value,
xLabelRotate: xLabelRotateValue(),
@@ -2325,6 +2417,17 @@ function applyFavoriteConfig(fav) {
const rNameLoc = document.getElementById('rightAxisNameLoc');
if (lNameLoc) lNameLoc.value = cfg.leftAxisNameLoc || 'vertical';
if (rNameLoc) rNameLoc.value = cfg.rightAxisNameLoc || 'vertical';
const lcMode = document.getElementById('labelColorMode');
const lcColor = document.getElementById('labelColor');
if (lcMode) lcMode.value = cfg.labelColorMode || 'auto';
if (lcColor) lcColor.value = cfg.labelColor || '#333333';
if (lcColor) lcColor.style.display = (cfg.labelColorMode || 'auto') === 'custom' ? 'block' : 'none';
document.getElementById('yAxisMin').value = cfg.yAxisMin || '';
document.getElementById('yAxisMax').value = cfg.yAxisMax || '';
document.getElementById('leftAxisMin').value = cfg.leftAxisMin || '';
document.getElementById('leftAxisMax').value = cfg.leftAxisMax || '';
document.getElementById('rightAxisMin').value = cfg.rightAxisMin || '';
document.getElementById('rightAxisMax').value = cfg.rightAxisMax || '';
document.getElementById('xAxisName').value = cfg.xAxisName || '';
document.getElementById('xAxisNameLoc').value = cfg.xAxisNameLoc || 'middle';
xAxisNameTouched = true; // 收藏恢复时尊重已存轴名,不再自动覆盖