feat: X轴轴名+刻度倾斜角度可调(默认水平) + 双Y轴图例按左右轴分组放到对应轴边上 v1.17.0
This commit is contained in:
@@ -291,6 +291,8 @@ function updateChart() {
|
|||||||
const dualAxis = document.getElementById('dualAxis').checked;
|
const dualAxis = document.getElementById('dualAxis').checked;
|
||||||
const leftAxisName = document.getElementById('leftAxisName').value;
|
const leftAxisName = document.getElementById('leftAxisName').value;
|
||||||
const rightAxisName = document.getElementById('rightAxisName').value;
|
const rightAxisName = document.getElementById('rightAxisName').value;
|
||||||
|
const xAxisName = document.getElementById('xAxisName').value;
|
||||||
|
const xLabelRotate = parseInt(document.getElementById('xLabelRotate').value) || 0;
|
||||||
|
|
||||||
// 饼图/雷达图:走专用构建逻辑(无坐标轴/网格,不支持双轴/堆叠/分割)
|
// 饼图/雷达图:走专用构建逻辑(无坐标轴/网格,不支持双轴/堆叠/分割)
|
||||||
if (chartType === 'pie' || chartType === 'radar') {
|
if (chartType === 'pie' || chartType === 'radar') {
|
||||||
@@ -460,7 +462,46 @@ function updateChart() {
|
|||||||
window._splitConfig = null;
|
window._splitConfig = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图表配置
|
// 图表配置:双Y轴图例按左右轴分组放到对应轴边上,grid 相应让位
|
||||||
|
let legendConfig, gridLeftCfg, gridRightCfg;
|
||||||
|
if (dualAxis && showLegend) {
|
||||||
|
const left = [], right = [];
|
||||||
|
seriesOrder.forEach(origIdx => {
|
||||||
|
const name = parsedData.seriesNames[origIdx];
|
||||||
|
(seriesAxis[origIdx] === 1 ? right : left).push(name);
|
||||||
|
});
|
||||||
|
legendConfig = [
|
||||||
|
{
|
||||||
|
show: left.length > 0,
|
||||||
|
orient: 'vertical', left: 8,
|
||||||
|
top: title ? 56 : 24,
|
||||||
|
data: left,
|
||||||
|
title: left.length > 1 ? { text: '左轴', textStyle: { color: textColor, fontSize: 11, fontWeight: 600 } } : undefined,
|
||||||
|
textStyle: { color: textColor, fontSize: 12 }, itemGap: 10, icon: 'roundRect'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
show: right.length > 0,
|
||||||
|
orient: 'vertical', right: 8,
|
||||||
|
top: title ? 56 : 24,
|
||||||
|
data: right,
|
||||||
|
title: right.length > 1 ? { text: '右轴', textStyle: { color: textColor, fontSize: 11, fontWeight: 600 } } : undefined,
|
||||||
|
textStyle: { color: textColor, fontSize: 12 }, itemGap: 10, icon: 'roundRect'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
gridLeftCfg = measureLegendWidth(left) + 30;
|
||||||
|
gridRightCfg = measureLegendWidth(right) + 30;
|
||||||
|
} else {
|
||||||
|
legendConfig = showLegend ? {
|
||||||
|
show: true,
|
||||||
|
top: title ? 50 : 15,
|
||||||
|
textStyle: { color: textColor, fontSize: 12 },
|
||||||
|
itemGap: 20,
|
||||||
|
icon: 'roundRect'
|
||||||
|
} : { show: false };
|
||||||
|
gridLeftCfg = '3%';
|
||||||
|
gridRightCfg = '4%';
|
||||||
|
}
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
backgroundColor: bgColor,
|
backgroundColor: bgColor,
|
||||||
title: title ? {
|
title: title ? {
|
||||||
@@ -494,29 +535,26 @@ function updateChart() {
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
legend: showLegend ? {
|
legend: legendConfig,
|
||||||
show: true,
|
|
||||||
top: title ? 50 : 15,
|
|
||||||
textStyle: { color: textColor, fontSize: 12 },
|
|
||||||
itemGap: 20,
|
|
||||||
icon: 'roundRect'
|
|
||||||
} : { show: false },
|
|
||||||
grid: {
|
grid: {
|
||||||
left: '3%',
|
left: gridLeftCfg,
|
||||||
right: '4%',
|
right: gridRightCfg,
|
||||||
bottom: '3%',
|
bottom: '3%',
|
||||||
top: showLegend ? (title ? 90 : 60) : (title ? 60 : 30),
|
top: dualAxis ? (title ? 52 : 24) : (showLegend ? (title ? 90 : 60) : (title ? 60 : 30)),
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
|
name: xAxisName,
|
||||||
|
nameGap: 20,
|
||||||
|
nameTextStyle: { color: textColor, fontSize: 12, fontWeight: 600 },
|
||||||
data: parsedData.categories,
|
data: parsedData.categories,
|
||||||
axisLine: { lineStyle: { color: axisLineColor } },
|
axisLine: { lineStyle: { color: axisLineColor } },
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
interval: 0,
|
interval: 0,
|
||||||
rotate: parsedData.categories.length > 10 ? 30 : 0
|
rotate: xLabelRotate
|
||||||
},
|
},
|
||||||
axisTick: { show: false }
|
axisTick: { show: false }
|
||||||
},
|
},
|
||||||
@@ -752,6 +790,18 @@ function updateSeriesAxis(origIdx, axis) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ===== 工具函数 =====
|
// ===== 工具函数 =====
|
||||||
|
// 测量图例文字宽度(用于双轴侧边图例的 grid 让位)
|
||||||
|
function measureLegendWidth(names, fontSize) {
|
||||||
|
fontSize = fontSize || 12;
|
||||||
|
if (!names || !names.length) return 0;
|
||||||
|
const c = document.createElement('canvas');
|
||||||
|
const ctx = c.getContext('2d');
|
||||||
|
ctx.font = `${fontSize}px sans-serif`;
|
||||||
|
let max = 0;
|
||||||
|
names.forEach(n => { const w = ctx.measureText(n).width; if (w > max) max = w; });
|
||||||
|
return Math.ceil(max + 44); // 图标(14)+间距(6)+边距(24)
|
||||||
|
}
|
||||||
|
|
||||||
function adjustColor(hex, amount) {
|
function adjustColor(hex, amount) {
|
||||||
hex = hex.replace('#', '');
|
hex = hex.replace('#', '');
|
||||||
const num = parseInt(hex, 16);
|
const num = parseInt(hex, 16);
|
||||||
@@ -1186,7 +1236,8 @@ function buildCombineChartOption(dataText, cfg) {
|
|||||||
title = '', chartType = 'bar', theme = 'default',
|
title = '', chartType = 'bar', theme = 'default',
|
||||||
showLegend = true, showGrid = true, showLabel = false,
|
showLegend = true, showGrid = true, showLabel = false,
|
||||||
stackMode = false, smoothLine = true,
|
stackMode = false, smoothLine = true,
|
||||||
dualYAxis = false, leftAxisName = '', rightAxisName = '', seriesConfig = null
|
dualYAxis = false, leftAxisName = '', rightAxisName = '', seriesConfig = null,
|
||||||
|
xAxisName = '', xLabelRotate = 0
|
||||||
} = cfg;
|
} = cfg;
|
||||||
|
|
||||||
const bgColor = theme === 'dark' ? '#1a1a2e' : '#ffffff';
|
const bgColor = theme === 'dark' ? '#1a1a2e' : '#ffffff';
|
||||||
@@ -1254,10 +1305,11 @@ function buildCombineChartOption(dataText, cfg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scMap = {};
|
||||||
|
(Array.isArray(seriesConfig) ? seriesConfig : []).forEach(s => { if (s && s.name) scMap[s.name] = s; });
|
||||||
|
|
||||||
const series = parsed.seriesNames.map((name, idx) => {
|
const series = parsed.seriesNames.map((name, idx) => {
|
||||||
const color = palette[idx % palette.length];
|
const color = palette[idx % palette.length];
|
||||||
const scMap = {};
|
|
||||||
(Array.isArray(seriesConfig) ? seriesConfig : []).forEach(s => { if (s && s.name) scMap[s.name] = s; });
|
|
||||||
const sc = scMap[name] || {};
|
const sc = scMap[name] || {};
|
||||||
let type = chartType === 'bar-line' ? (idx % 2 === 0 ? 'bar' : 'line') : chartType;
|
let type = chartType === 'bar-line' ? (idx % 2 === 0 ? 'bar' : 'line') : chartType;
|
||||||
if (sc.type && sc.type !== 'auto') type = sc.type;
|
if (sc.type && sc.type !== 'auto') type = sc.type;
|
||||||
@@ -1309,6 +1361,46 @@ function buildCombineChartOption(dataText, cfg) {
|
|||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 双Y轴图例按左右轴分组放到对应轴边上,grid 相应让位
|
||||||
|
let legendCfg, gridLeftCfg, gridRightCfg;
|
||||||
|
if (dualYAxis && showLegend) {
|
||||||
|
const left = [], right = [];
|
||||||
|
parsed.seriesNames.forEach(name => {
|
||||||
|
const sc = scMap[name] || {};
|
||||||
|
((sc.axis === 1 || sc.axis === '1') ? right : left).push(name);
|
||||||
|
});
|
||||||
|
legendCfg = [
|
||||||
|
{
|
||||||
|
show: left.length > 0,
|
||||||
|
orient: 'vertical', left: 6,
|
||||||
|
top: title ? 44 : 12,
|
||||||
|
data: left,
|
||||||
|
title: left.length > 1 ? { text: '左轴', textStyle: { color: textColor, fontSize: 10, fontWeight: 600 } } : undefined,
|
||||||
|
textStyle: { color: textColor, fontSize: 11 }, itemGap: 8, icon: 'roundRect'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
show: right.length > 0,
|
||||||
|
orient: 'vertical', right: 6,
|
||||||
|
top: title ? 44 : 12,
|
||||||
|
data: right,
|
||||||
|
title: right.length > 1 ? { text: '右轴', textStyle: { color: textColor, fontSize: 10, fontWeight: 600 } } : undefined,
|
||||||
|
textStyle: { color: textColor, fontSize: 11 }, itemGap: 8, icon: 'roundRect'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
gridLeftCfg = Math.min(measureLegendWidth(left, 11) + 24, 200);
|
||||||
|
gridRightCfg = Math.min(measureLegendWidth(right, 11) + 24, 200);
|
||||||
|
} else {
|
||||||
|
legendCfg = showLegend ? {
|
||||||
|
show: true,
|
||||||
|
top: title ? 40 : 10,
|
||||||
|
textStyle: { color: textColor, fontSize: 12 },
|
||||||
|
itemGap: 20,
|
||||||
|
icon: 'roundRect'
|
||||||
|
} : { show: false };
|
||||||
|
gridLeftCfg = '3%';
|
||||||
|
gridRightCfg = '4%';
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
backgroundColor: bgColor,
|
backgroundColor: bgColor,
|
||||||
title: title ? {
|
title: title ? {
|
||||||
@@ -1323,23 +1415,20 @@ function buildCombineChartOption(dataText, cfg) {
|
|||||||
borderColor: theme === 'dark' ? '#555' : '#eee',
|
borderColor: theme === 'dark' ? '#555' : '#eee',
|
||||||
textStyle: { color: textColor }
|
textStyle: { color: textColor }
|
||||||
},
|
},
|
||||||
legend: showLegend ? {
|
legend: legendCfg,
|
||||||
show: true,
|
|
||||||
top: title ? 40 : 10,
|
|
||||||
textStyle: { color: textColor, fontSize: 12 },
|
|
||||||
itemGap: 20,
|
|
||||||
icon: 'roundRect'
|
|
||||||
} : { show: false },
|
|
||||||
grid: {
|
grid: {
|
||||||
left: '3%', right: '4%', bottom: '3%',
|
left: gridLeftCfg, right: gridRightCfg, bottom: '3%',
|
||||||
top: showLegend ? (title ? 70 : 45) : (title ? 50 : 30),
|
top: dualYAxis ? (title ? 44 : 16) : (showLegend ? (title ? 70 : 45) : (title ? 50 : 30)),
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
|
name: xAxisName,
|
||||||
|
nameGap: 16,
|
||||||
|
nameTextStyle: { color: textColor, fontSize: 11, fontWeight: 600 },
|
||||||
data: parsed.categories,
|
data: parsed.categories,
|
||||||
axisLine: { lineStyle: { color: axisLineColor } },
|
axisLine: { lineStyle: { color: axisLineColor } },
|
||||||
axisLabel: { color: textColor, fontSize: 11, interval: 0, rotate: parsed.categories.length > 10 ? 30 : 0 },
|
axisLabel: { color: textColor, fontSize: 11, interval: 0, rotate: xLabelRotate },
|
||||||
axisTick: { show: false }
|
axisTick: { show: false }
|
||||||
},
|
},
|
||||||
yAxis: dualYAxis ? [
|
yAxis: dualYAxis ? [
|
||||||
@@ -1386,7 +1475,7 @@ function measureCombineChart(dataText, rowsAsSeries) {
|
|||||||
let combineCharts = [];
|
let combineCharts = [];
|
||||||
|
|
||||||
function defaultCombineChart() {
|
function defaultCombineChart() {
|
||||||
return { title: '', type: 'bar', theme: 'default', legend: true, grid: true, label: false, stack: false, data: '', rowsAsSeries: false, dualYAxis: false, leftAxisName: '', rightAxisName: '', seriesConfig: [] };
|
return { title: '', type: 'bar', theme: 'default', legend: true, grid: true, label: false, stack: false, data: '', rowsAsSeries: false, dualYAxis: false, leftAxisName: '', rightAxisName: '', seriesConfig: [], xAxisName: '', xLabelRotate: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
function initCombineCharts() {
|
function initCombineCharts() {
|
||||||
@@ -1453,6 +1542,15 @@ function renderCombineCharts() {
|
|||||||
<input type="text" value="${escHtml(c.rightAxisName)}" placeholder="右轴,如:增长率(%)" oninput="updateCombineChart(${i},'rightAxisName',this.value)">
|
<input type="text" value="${escHtml(c.rightAxisName)}" placeholder="右轴,如:增长率(%)" oninput="updateCombineChart(${i},'rightAxisName',this.value)">
|
||||||
</div>
|
</div>
|
||||||
</div>` : ''}
|
</div>` : ''}
|
||||||
|
<div class="config-group">
|
||||||
|
<label>X轴设置(轴名 · 刻度倾斜°)</label>
|
||||||
|
<div class="res-input-row">
|
||||||
|
<input type="text" value="${escHtml(c.xAxisName || '')}" placeholder="轴名,如:月份" oninput="updateCombineChart(${i},'xAxisName',this.value)">
|
||||||
|
<span class="res-times">·</span>
|
||||||
|
<input type="number" min="0" max="90" step="5" value="${c.xLabelRotate || 0}" style="width:64px" title="刻度文字倾斜角度(0-90°)" oninput="updateCombineChart(${i},'xLabelRotate',this.value)">
|
||||||
|
<span class="res-times">°</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="config-group">
|
<div class="config-group">
|
||||||
<label>系列图表类型 / 坐标轴</label>
|
<label>系列图表类型 / 坐标轴</label>
|
||||||
<div class="combine-series-cfg" id="combineSeriesCfg${i}">${renderCombineSeriesConfigHTML(i)}</div>
|
<div class="combine-series-cfg" id="combineSeriesCfg${i}">${renderCombineSeriesConfigHTML(i)}</div>
|
||||||
@@ -1587,6 +1685,7 @@ function generateCombine() {
|
|||||||
showLegend: sharedLegend ? false : c.legend, showGrid: c.grid, showLabel: c.label, stackMode: c.stack,
|
showLegend: sharedLegend ? false : c.legend, showGrid: c.grid, showLabel: c.label, stackMode: c.stack,
|
||||||
rowsAsSeries: !!c.rowsAsSeries,
|
rowsAsSeries: !!c.rowsAsSeries,
|
||||||
dualYAxis: !!c.dualYAxis, leftAxisName: c.leftAxisName || '', rightAxisName: c.rightAxisName || '',
|
dualYAxis: !!c.dualYAxis, leftAxisName: c.leftAxisName || '', rightAxisName: c.rightAxisName || '',
|
||||||
|
xAxisName: c.xAxisName || '', xLabelRotate: parseInt(c.xLabelRotate) || 0,
|
||||||
seriesConfig: c.seriesConfig
|
seriesConfig: c.seriesConfig
|
||||||
});
|
});
|
||||||
if (!option) { reject(new Error('数据格式错误')); return; }
|
if (!option) { reject(new Error('数据格式错误')); return; }
|
||||||
@@ -1755,6 +1854,8 @@ function collectChartConfig() {
|
|||||||
dualAxis: document.getElementById('dualAxis').checked,
|
dualAxis: document.getElementById('dualAxis').checked,
|
||||||
leftAxisName: document.getElementById('leftAxisName').value,
|
leftAxisName: document.getElementById('leftAxisName').value,
|
||||||
rightAxisName: document.getElementById('rightAxisName').value,
|
rightAxisName: document.getElementById('rightAxisName').value,
|
||||||
|
xAxisName: document.getElementById('xAxisName').value,
|
||||||
|
xLabelRotate: parseInt(document.getElementById('xLabelRotate').value) || 0,
|
||||||
enableSplit: document.getElementById('enableSplit').checked,
|
enableSplit: document.getElementById('enableSplit').checked,
|
||||||
splitIndex: parseInt(document.getElementById('splitIndex').value) || 3,
|
splitIndex: parseInt(document.getElementById('splitIndex').value) || 3,
|
||||||
leftLabel: document.getElementById('leftLabel').value,
|
leftLabel: document.getElementById('leftLabel').value,
|
||||||
@@ -1961,6 +2062,8 @@ function applyFavoriteConfig(fav) {
|
|||||||
document.getElementById('dualAxis').checked = !!cfg.dualAxis;
|
document.getElementById('dualAxis').checked = !!cfg.dualAxis;
|
||||||
document.getElementById('leftAxisName').value = cfg.leftAxisName || '';
|
document.getElementById('leftAxisName').value = cfg.leftAxisName || '';
|
||||||
document.getElementById('rightAxisName').value = cfg.rightAxisName || '';
|
document.getElementById('rightAxisName').value = cfg.rightAxisName || '';
|
||||||
|
document.getElementById('xAxisName').value = cfg.xAxisName || '';
|
||||||
|
document.getElementById('xLabelRotate').value = cfg.xLabelRotate || 0;
|
||||||
document.getElementById('enableSplit').checked = !!cfg.enableSplit;
|
document.getElementById('enableSplit').checked = !!cfg.enableSplit;
|
||||||
document.getElementById('splitIndex').value = cfg.splitIndex || 3;
|
document.getElementById('splitIndex').value = cfg.splitIndex || 3;
|
||||||
document.getElementById('leftLabel').value = cfg.leftLabel || '';
|
document.getElementById('leftLabel').value = cfg.leftLabel || '';
|
||||||
|
|||||||
+11
@@ -80,6 +80,17 @@ C, 20, 30, 40</pre>
|
|||||||
<input type="text" id="chartTitle" placeholder="图表标题" oninput="updateChart()">
|
<input type="text" id="chartTitle" placeholder="图表标题" oninput="updateChart()">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="config-group">
|
||||||
|
<label>X轴设置(轴名 · 刻度倾斜角度)</label>
|
||||||
|
<div class="res-input-row">
|
||||||
|
<input type="text" id="xAxisName" placeholder="横坐标轴名,如:月份" oninput="updateChart()">
|
||||||
|
<span class="res-times">·</span>
|
||||||
|
<input type="number" id="xLabelRotate" min="0" max="90" step="5" value="0" style="width:70px" title="刻度文字倾斜角度(0-90°)" oninput="updateChart()">
|
||||||
|
<span class="res-times">°</span>
|
||||||
|
</div>
|
||||||
|
<p class="hint-text">轴名可留空不显示;刻度方向:0°=水平(默认),45°=右上倾斜,90°=竖直,实时生效</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="config-group">
|
<div class="config-group">
|
||||||
<label>主题风格</label>
|
<label>主题风格</label>
|
||||||
<select id="themeStyle" onchange="syncThemeButtons(this.value); updateChart()">
|
<select id="themeStyle" onchange="syncThemeButtons(this.value); updateChart()">
|
||||||
|
|||||||
Reference in New Issue
Block a user