做数据可视化这行久了,你会发现一个有趣的现象:很多看似专业的图表,其实踩了十几个基础坑。ECharts 作为国内最流行的开源可视化库,文档虽然全,但“怎么用好”和“怎么画对”是两码事。
今天不聊深奥的渲染原理,咱们像老朋友聊天一样,把那些在项目中真金白银摔出来的坑,从柱状图到折线图,掰开了揉碎了讲清楚。
一、 柱状图:选型与变形的艺术
很多人觉得柱状图(Bar Chart)最简单,甚至有点瞧不起它。但恰恰是这种“简单”,最容易画出低级错误。
1.1 条形图 vs 柱状图:别搞混了方向
先澄清一个概念:在 ECharts 里,bar 系列默认是垂直的(我们习惯叫柱状图),而 bar + yAxis.type: 'category' 配合 xAxis 为数值轴时,才是水平的条形图(Bar chart)。
避坑指南:
- 分类多、标签长时,果断转条形图。如果你有一组分类名称特别长(比如“2023年第一季度华东区销售额”),用垂直柱状图,X轴标签要么重叠,要么旋转45度,看着都累。这时候水平条形图能让人自然从左读到右,阅读体验流畅得多。
- 对比维度少,看高度差异明显时,用垂直柱状图。人眼对“高度”的感知比对“长度”更敏感,尤其是当数值差异较小时,垂直柱子的高低对比更直观。
1.2 堆叠柱状图的陷阱:起点不是0
堆叠柱状图(Stacked Bar)是用来展示“总量构成”的利器,但很多人画错了。
错误示范:
option = {
series: [
{ type: 'bar', stack: 'total', data: [10, 20, 30] },
{ type: 'bar', stack: 'total', data: [15, 25, 35] }
]
};
如果 yAxis 的 min 不是 0,而是 10,那么第一根柱子看起来像是从0开始,但视觉上它的“底座”被截断了。这会导致视觉上的误导,用户会错误地比较柱子顶部的高度差,而忽略了基础值的差异。
正确做法:
堆叠柱状图必须从0开始,且不要随意设置 yAxis.min,除非你有非常明确的业务理由。同时,记得开启 label 显示具体数值,因为堆叠后,中间层的具体数值很难从视觉上直接读出。
option = {
yAxis: { type: 'value', min: 0 }, // 必须从0开始
series: [
{
type: 'bar',
stack: 'total',
data: [10, 20, 30],
label: { show: true, position: 'inside' } // 内部显示数值
},
{
type: 'bar',
stack: 'total',
data: [15, 25, 35],
label: { show: true, position: 'inside' }
}
]
};
1.3 分组柱状图:颜色不要超过5种
分组柱状图(Grouped Bar)用于对比多个维度。比如“2022年 vs 2023年各月销售额”。
常见错误:
- 使用高饱和度的原色(红、黄、蓝),造成视觉疲劳。
- 颜色对比度太低,难以区分。
- 分类超过5组,图表变得像彩虹糖一样混乱。
专业建议:
- 使用中性色调或灰度系列搭配一个强调色。例如,大部分柱子用浅灰色,对比年份用深蓝色。
- 如果必须用多色,选择色盲友好的配色方案,如
Tableau 10或Viridis。 - 关键:添加图例(legend),并确保图例与柱子颜色一一对应,且顺序与X轴分组顺序一致。
// 色盲友好的配色示例
const colorPalette = ['#4BACC6', '#F09199', '#84DE2D', '#FFC05E', '#AE91FF'];
option = {
legend: { data: ['2022年', '2023年', '2024年'] },
color: colorPalette, // 全局配色
series: [
{ name: '2022年', type: 'bar', data: [120, 200, 150, 80, 70] },
{ name: '2023年', type: 'bar', data: [130, 210, 160, 90, 80] },
{ name: '2024年', type: 'bar', data: [140, 220, 170, 100, 90] }
]
};
二、 折线图:配色与平滑的玄机
折线图是趋势分析的核心,但也是最容易被“画丑”的图表。
2.1 平滑曲线(smooth):用与不用,差距巨大
smooth: true 会让折线变成光滑的贝塞尔曲线。这看起来很“现代”,但严重误导数据。
什么时候用 smooth?
- 数据本身是连续的物理量(如温度、股价),且采样点足够密集。
- 追求视觉美感,而非精确数据解读。
什么时候不用?
- 离散数据(如月度销售额、用户增长数)。
- 需要突出数据点(拐点)时。
- 数据量较少时,平滑曲线会制造出“虚假的趋势”,比如把两个点连成一条平滑的弧线,让人误以为中间有波动,实际上可能是直线。
专业建议:
默认 smooth: false,除非你有充分理由。如果必须平滑,考虑使用 lineSmooth: true(更保守的平滑)或手动控制拐点。
2.2 多折线配色:不要只用渐变,要用“逻辑色”
很多人喜欢用 ECharts 默认的多色渐变,看起来挺花哨,但问题是:用户记不住哪条线代表什么。
避坑指南:
- 保持颜色与语义一致。比如,如果“预算”一直是蓝色,“实际”一直是橙色,那么在整个图表中都要保持一致。不要在一个图里蓝色是预算,在另一个图里蓝色是实际。
- 同类数据用同色系不同深浅。比如展示三个地区的疫情趋势,可以用深蓝、中蓝、浅蓝,而不是蓝、绿、红。这样用户一眼就能看出它们是“同一类事物”。
- 避免使用黄色、浅绿色作为主线,因为这些颜色在白色背景上几乎不可见。
2.3 面积图(Area Chart):透明度的微妙平衡
面积图是折线图的变体,填充颜色可以增强趋势的视觉权重。
关键参数:areaStyle: { opacity: 0.5 }
错误示范:
areaStyle: { opacity: 1 } // 完全不透明,覆盖背景和其他元素,非常难看
正确做法:
- 使用半透明填充(opacity 0.3-0.6),这样多条面积图重叠时,可以看到彼此,形成丰富的层次感。
- 如果只有一条面积图,可以用稍高的不透明度(0.7-0.8)突出主体。
- 注意: 面积图的Y轴建议从0开始,否则面积的大小会误导用户对“总量”的感知。
series: [
{
type: 'line',
data: [820, 932, 901, 934, 1290, 1330, 1320],
areaStyle: {
opacity: 0.5, // 半透明,允许重叠查看
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(54, 162, 235, 0.8)' },
{ offset: 1, color: 'rgba(54, 162, 235, 0.1)' }
])
}
}
]
三、 通用避坑:让图表“说话”而不是“吵闹”
3.1 网格线(Grid Line):少即是多
新手常见错误:
- 开启所有方向的网格线,形成密密麻麻的格子。
- 网格线颜色过深,喧宾夺主。
专业建议:
- X轴网格线:如果分类较多,建议关闭 X 轴网格线(
splitLine.show: false),只保留 Y 轴网格线,帮助读者横向对齐数据点。 - Y轴网格线:使用极浅的灰色(如
#eee或rgba(0,0,0,0.05)),并设置为虚线(type: 'dashed'),让它们成为“辅助线”而非“主导线”。 - 只在需要时显示:如果数据点已经通过
label或markPoint标注,网格线可以进一步弱化甚至省略。
yAxis: {
splitLine: {
show: true,
lineStyle: {
color: '#eee',
type: 'dashed' // 虚线,更轻量
}
}
},
xAxis: {
splitLine: {
show: false // 关闭X轴网格线,减少干扰
}
}
3.2 数据标签(Label):智能显示,不要堆砌
错误示范:
label: { show: true } // 所有点都显示数值,图表变成数字森林
专业建议:
- 仅在高点/低点显示:使用
label: { show: true, position: 'top' }配合markPoint,只在关键数据点显示数值。 - 使用
formatter自定义:不要直接显示原始数字,加上单位、百分比或趋势符号。 - 避免重叠:对于密集数据,使用
rich文本或调整offset,确保标签不与柱体或线条重叠。
label: {
show: true,
position: 'top',
formatter: '{c} 万元', // 添加单位
color: '#333', // 深色,确保可读
fontSize: 10
}
3.3 交互体验:Tooltip 的优雅呈现
错误示范:
- Tooltip 默认配置,鼠标悬停时显示原始数据,无格式化,无排序。
- 多个系列同时显示,信息过载。
专业建议:
- 使用
trigger: 'axis':让 Tooltip 显示整个轴上的所有数据,方便对比。 - 自定义
formatter:按系列分组显示,每个系列一行,清晰明了。 - 添加排序:如果数据点很多,可以让 Tooltip 按数值大小排序,突出最大值。
- 延迟显示:设置
showDelay: 100,避免鼠标快速划过时 Tooltip 频繁闪烁。
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' }, // 十字准星,便于定位
formatter: function(params) {
// params 是数组,包含该轴上所有系列的数据
let res = `<div style="font-weight:bold">${params[0].axisValue}</div>`;
// 按数值排序,只显示前5个
params.sort((a, b) => b.value - a.value);
params.slice(0, 5).forEach(item => {
res += `<div>${item.marker} ${item.seriesName}: ${item.value}</div>`;
});
return res;
},
backgroundColor: 'rgba(255,255,255,0.95)',
borderColor: '#ccc',
textStyle: { color: '#333' }
}
四、 一个完整的专业案例
让我们把上述技巧整合到一个完整的例子中,展示如何制作一个“专业级”的柱状图+折线图混合图表。
”`javascript // 假设我们需要展示“月度销售额”(柱状图)和“同比增长率”(折线图) const option = { // 1. 全局配置:使用专业的配色方案 color: [‘#36A2EB’, ‘#FF6384’, ‘#4BC0C0’], // 蓝、红、青
// 2. 标题:简洁有力,说明图表内容 title: {
text: '2023年月度销售额与增长率',
left: 'center',
textStyle: { fontSize: 18, fontWeight: 'bold' }
},
// 3. 图例:放在顶部,方便识别 legend: {
data: ['销售额(万元)', '同比增长率(%)'],
top: 30,
right: 20
},
// 4. 提示框:自定义格式化,突出重点 tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' },
formatter: function(params) {
let res = `<div style="font-weight:bold">${params[0].axisValue}</div>`;
params.forEach(item => {
const unit = item.seriesName.includes('销售额') ? '万元' : '%';
res += `<div>${item.marker} ${item.seriesName}: ${item.value} ${unit}</div>`;
});
return res;
}
},
// 5. 坐标轴:优化网格线和标签 xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
axisLabel: {
color: '#666',
fontSize: 12
},
axisLine: { lineStyle: { color: '#ccc' } } // 浅灰色轴线
},
yAxis: [
{
type: 'value',
name: '销售额(万元)',
position: 'left',
axisLabel: { color: '#36A2EB' }, // 与柱子颜色一致
splitLine: { show: true, lineStyle: { color: '#eee', type: 'dashed' } }
},
{
type: 'value',
name: '同比增长率(%)',
position: 'right',
axisLabel: { color: '#FF6384' }, // 与折线颜色一致
splitLine: { show: false } // 右侧轴不显示网格线,减少干扰
}
],
// 6. 数据系列:柱状图 + 折线图,配合使用 series: [
{
name: '销售额(万元)',
type: 'bar',
data: [120, 132, 101, 134, 90, 230, 210, 180, 150, 170, 190, 220],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#36A2EB' }, // 顶部深蓝
{ offset: 1, color: '#87CEEB' } // 底部浅蓝
]),
borderRadius: [4, 4, 0, 0] // 柱顶圆角,更现代
},
label: {
show: true,
position: 'top',
formatter: '{c}',
color: '#36A2EB',
fontSize: 10
},
barWidth: '60%' // 控制柱子宽度,避免过宽或过细
},
{
name: '同比增长率(%)',
type: 'line',
yAxisIndex: 1, // 使用右侧Y轴
data: [5, 8, -2, 12, 3, 15, 10, 8, 6, 9, 11, 13],
smooth: false, // 不启用平滑,保持数据真实性
symbol: 'circle', // 数据点为圆形
symbolSize: 8,
lineStyle: {
width: 3,
color: '#FF6384'
},
itemStyle: {
color: '#FF6384',
borderWidth: 2,
borderColor: '#fff' // 白色边框,突出数据点
},
label: {
show: true,
position: 'top',
formatter: '{c}%',
color: '#FF6384',
fontSize: 10
},
// 添加区域填充,增强视觉层次
areaStyle: {
opacity: 0.1,
color: '#FF6384'
}
}
],
// 7. 缩放组件:允许用户交互 dataZoom:
