feat: 新增饼图/雷达图图表类型
- 单图模式图表类型新增:饼图(环形占比,第一列=名称/第一系列=数值)、雷达图(多维度对比,第一列=维度/每系列=多边形) - 多图合并的每张图也可选饼图/雷达图 - 后端 /api/chart 与 /api/combine 的 chartType 支持 pie/radar - 饼图/雷达图自动隐藏坐标轴/网格/双轴等不适用配置
This commit is contained in:
@@ -157,6 +157,91 @@ function initChart() {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== 饼图/雷达图 option 构建 =====
|
||||
function buildPieRadarOption(chartType) {
|
||||
if (!parsedData || parsedData.seriesNames.length === 0) return null;
|
||||
const title = document.getElementById('chartTitle').value;
|
||||
const theme = document.getElementById('themeStyle').value;
|
||||
const showLegend = document.getElementById('showLegend').checked;
|
||||
const showLabel = document.getElementById('showLabel').checked;
|
||||
const bgColor = theme === 'dark' ? '#1a1a2e' : '#ffffff';
|
||||
const textColor = theme === 'dark' ? '#e0e0e0' : '#333333';
|
||||
const palette = colorPalettes[theme] || colorPalettes.default;
|
||||
const colors = seriesOrder.map((origIdx, di) => seriesColors[origIdx] || palette[di % palette.length]);
|
||||
|
||||
const baseTitle = title ? {
|
||||
text: title, left: 'center', top: 10,
|
||||
textStyle: { color: textColor, fontSize: 18, fontWeight: 600 }
|
||||
} : undefined;
|
||||
|
||||
if (chartType === 'pie') {
|
||||
// 饼图:第一列=名称,第一个系列=数值
|
||||
const sName = parsedData.seriesNames[0];
|
||||
const data = parsedData.seriesData[sName];
|
||||
const pieData = parsedData.categories.map((c, i) => ({ name: c, value: data[i] || 0 }));
|
||||
return {
|
||||
backgroundColor: bgColor,
|
||||
title: baseTitle,
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
||||
legend: showLegend ? {
|
||||
orient: 'vertical', left: 'left', top: title ? 50 : 20,
|
||||
textStyle: { color: textColor, fontSize: 12 }
|
||||
} : { show: false },
|
||||
color: colors,
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['38%', '68%'],
|
||||
center: ['52%', '55%'],
|
||||
avoidLabelOverlap: true,
|
||||
itemStyle: { borderRadius: 6, borderColor: bgColor, borderWidth: 2 },
|
||||
label: { show: showLabel, formatter: '{b}: {d}%', color: textColor },
|
||||
labelLine: { show: showLabel },
|
||||
data: pieData
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
// 雷达图:第一列=维度名,每个系列=一个雷达多边形
|
||||
const indicator = parsedData.categories.map(c => {
|
||||
let max = 0;
|
||||
parsedData.seriesNames.forEach(n => {
|
||||
parsedData.seriesData[n].forEach(v => { if (v > max) max = v; });
|
||||
});
|
||||
return { name: c, max: Math.ceil(max * 1.2) || 100 };
|
||||
});
|
||||
return {
|
||||
backgroundColor: bgColor,
|
||||
title: baseTitle,
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: showLegend ? {
|
||||
orient: 'horizontal', left: 'center', top: title ? 48 : 15,
|
||||
textStyle: { color: textColor, fontSize: 12 }
|
||||
} : { show: false },
|
||||
color: colors,
|
||||
radar: {
|
||||
indicator,
|
||||
radius: '62%',
|
||||
center: ['50%', '55%'],
|
||||
splitNumber: 5,
|
||||
axisName: { color: textColor, fontSize: 12 },
|
||||
splitLine: { lineStyle: { color: theme === 'dark' ? '#444' : '#ddd' } },
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: theme === 'dark' ? ['rgba(79,195,247,0.03)', 'rgba(79,195,247,0.06)'] : ['rgba(79,70,229,0.03)', 'rgba(79,70,229,0.06)']
|
||||
}
|
||||
},
|
||||
axisLine: { lineStyle: { color: theme === 'dark' ? '#444' : '#ddd' } }
|
||||
},
|
||||
series: parsedData.seriesNames.map(n => ({
|
||||
type: 'radar',
|
||||
name: n,
|
||||
data: [{ value: parsedData.seriesData[n], name: n }],
|
||||
symbolSize: 4,
|
||||
areaStyle: { opacity: 0.15 }
|
||||
}))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 更新图表 =====
|
||||
function updateChart() {
|
||||
if (!parsedData || !chartInstance) return;
|
||||
@@ -174,6 +259,16 @@ function updateChart() {
|
||||
const leftAxisName = document.getElementById('leftAxisName').value;
|
||||
const rightAxisName = document.getElementById('rightAxisName').value;
|
||||
|
||||
// 饼图/雷达图:走专用构建逻辑(无坐标轴/网格,不支持双轴/堆叠/分割)
|
||||
if (chartType === 'pie' || chartType === 'radar') {
|
||||
const option = buildPieRadarOption(chartType);
|
||||
if (option) {
|
||||
chartInstance.setOption(option, true);
|
||||
refreshPreview();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示/隐藏分割配置
|
||||
document.getElementById('splitConfig').style.display = enableSplit ? 'block' : 'none';
|
||||
|
||||
@@ -1058,6 +1153,66 @@ function buildCombineChartOption(dataText, cfg) {
|
||||
const axisLineColor = theme === 'dark' ? '#444' : '#ddd';
|
||||
const palette = colorPalettes[theme] || colorPalettes.default;
|
||||
|
||||
// 饼图/雷达图:专用构建逻辑
|
||||
if (chartType === 'pie' || chartType === 'radar') {
|
||||
if (chartType === 'pie') {
|
||||
const sName = parsed.seriesNames[0];
|
||||
const data = parsed.seriesData[sName];
|
||||
const pieData = parsed.categories.map((c, i) => ({ name: c, value: data[i] || 0 }));
|
||||
return {
|
||||
backgroundColor: bgColor,
|
||||
title: title ? { text: title, left: 'center', top: 10, textStyle: { color: textColor, fontSize: 16, fontWeight: 600 } } : undefined,
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
||||
legend: showLegend ? { orient: 'vertical', left: 'left', top: title ? 40 : 10, textStyle: { color: textColor, fontSize: 12 } } : { show: false },
|
||||
color: palette,
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['38%', '68%'],
|
||||
center: ['55%', '55%'],
|
||||
itemStyle: { borderRadius: 6, borderColor: bgColor, borderWidth: 2 },
|
||||
label: { show: showLabel, formatter: '{b}: {d}%', color: textColor },
|
||||
labelLine: { show: showLabel },
|
||||
data: pieData
|
||||
}],
|
||||
animation: false
|
||||
};
|
||||
} else {
|
||||
const indicator = parsed.categories.map(c => {
|
||||
let max = 0;
|
||||
parsed.seriesNames.forEach(n => { parsed.seriesData[n].forEach(v => { if (v > max) max = v; }); });
|
||||
return { name: c, max: Math.ceil(max * 1.2) || 100 };
|
||||
});
|
||||
return {
|
||||
backgroundColor: bgColor,
|
||||
title: title ? { text: title, left: 'center', top: 10, textStyle: { color: textColor, fontSize: 16, fontWeight: 600 } } : undefined,
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: showLegend ? { orient: 'horizontal', left: 'center', top: title ? 38 : 10, textStyle: { color: textColor, fontSize: 12 } } : { show: false },
|
||||
color: palette,
|
||||
radar: {
|
||||
indicator,
|
||||
radius: '62%',
|
||||
center: ['50%', '55%'],
|
||||
splitNumber: 5,
|
||||
axisName: { color: textColor, fontSize: 11 },
|
||||
splitLine: { lineStyle: { color: theme === 'dark' ? '#444' : '#ddd' } },
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: theme === 'dark' ? ['rgba(79,195,247,0.03)', 'rgba(79,195,247,0.06)'] : ['rgba(79,70,229,0.03)', 'rgba(79,70,229,0.06)']
|
||||
}
|
||||
},
|
||||
axisLine: { lineStyle: { color: theme === 'dark' ? '#444' : '#ddd' } }
|
||||
},
|
||||
series: parsed.seriesNames.map(n => ({
|
||||
type: 'radar', name: n,
|
||||
data: [{ value: parsed.seriesData[n], name: n }],
|
||||
symbolSize: 4,
|
||||
areaStyle: { opacity: 0.15 }
|
||||
})),
|
||||
animation: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const series = parsed.seriesNames.map((name, idx) => {
|
||||
const color = palette[idx % palette.length];
|
||||
let type = chartType === 'bar-line' ? (idx % 2 === 0 ? 'bar' : 'line') : chartType;
|
||||
@@ -1177,7 +1332,7 @@ function initCombineCharts() {
|
||||
renderCombineCharts();
|
||||
}
|
||||
|
||||
const COMBINE_TYPES = [['bar', '柱状图'], ['line', '折线图'], ['bar-line', '柱状图+折线图混合']];
|
||||
const COMBINE_TYPES = [['bar', '柱状图'], ['line', '折线图'], ['bar-line', '柱状图+折线图混合'], ['pie', '饼图'], ['radar', '雷达图']];
|
||||
const COMBINE_THEMES = [['default', '默认'], ['dark', '深色'], ['macarons', '马卡龙'], ['gradient', '渐变'], ['retro', '复古'], ['ocean', '海洋'], ['forest', '森林'], ['sunset', '日落'], ['lavender', '薰衣草'], ['minimal', '极简'], ['cherry', '樱花'], ['midnight', '午夜'], ['gold', '金色'], ['coral', '珊瑚'], ['mint', '薄荷'], ['slate', '石板灰'], ['sky', '天空蓝'], ['rose', '玫瑰红'], ['amber', '琥珀黄'], ['emerald', '翡翠绿'], ['indigo', '靛蓝色'], ['stone', '石灰白']];
|
||||
|
||||
function escHtml(str) {
|
||||
|
||||
Reference in New Issue
Block a user