说到 ECharts,很多人第一反应是“这玩意儿能画图表啊”,但实际上,它是目前前端可视化领域里最能打、也是最让人又爱又恨的存在。爱,是因为它文档全、社区大、插件多,基本上你能想到的图表它都能画;恨,是因为配置项实在太多了,有时候改一个颜色、调一个位置,都得翻半天文档,或者对着别人的代码猜它到底用了哪个属性。
我以前写项目的时候,也踩过不少坑。比如刚开始做3D地球的时候,怎么都调不对光照效果,或者柱状图叠在一起的时候标签全挤成一团。今天就把这些实战中总结出来的经验,连同那些容易踩坑的地方,一口气讲清楚。咱们不整那些虚头巴脑的理论,直接上干货,从最基础的柱状图一直聊到炫酷的3D场景,保证你看完能直接上手,少走弯路。
先从柱状图说起:那些你以为简单但容易翻车的细节
柱状图(Bar Chart)可能是大家最常用的图表了。刚开始我觉得它简单得不能再简单,数据一丢,type: 'bar' 就完事了。但等到真要美化、要交互、要处理复杂数据的时候,才发现里面门道不少。
基础配置与数据对接
先把最基础的架子搭起来。ECharts 的核心就是一个 option 对象,所有的配置都往里塞。对于柱状图,最关键的几个部分是 grid(控制图表区域的位置)、xAxis(x轴)、yAxis(y轴)和 series(数据系列)。
const option = {
// grid 控制图表离容器的距离,防止标签被切掉
grid: {
top: '10%',
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true // 这个属性很重要,确保标签不会被容器边界切掉
},
xAxis: {
type: 'category',
data: ['一月', '二月', '三月', '四月', '五月', '六月'],
axisLabel: {
interval: 0, // 强制显示所有标签,避免隔一个显示一个
rotate: 0 // 如果标签太长,可以设置旋转角度,比如30度
}
},
yAxis: {
type: 'value',
// 给y轴加点小尾巴,显得更精致
axisLine: { show: true },
splitLine: { show: true, lineStyle: { type: 'dashed' } }
},
series: [
{
name: '销量',
type: 'bar',
data: [120, 200, 150, 80, 70, 110],
// 给柱子加点圆角,看起来更现代
itemStyle: {
borderRadius: [4, 4, 0, 0]
},
// 柱子宽度如果默认太宽或太窄,可以手动调
barWidth: '60%'
}
]
};
这里有个小细节我想单独拎出来说说,就是 containLabel: true。很多新手在配置 grid 的时候,只设置了 top、left 这些距离,结果发现当 x 轴标签比较长,或者 y 轴数字很大的时候,标签会被容器切掉一半。加上 containLabel: true,ECharts 会自动计算标签占用的空间,给你预留出来,这样就不用手动去调那些烦人的像素值了。
堆叠柱状图与分组柱状图
当我们需要对比多组数据,或者看总量构成时,堆叠和分组就派上用场了。这两个长得有点像,但配置上有个关键区别,很多人容易搞混。
堆叠柱状图是把多个系列叠在一起,总和代表总量。配置上只需要把多个 series 的 stack 属性设为同一个值,比如 'total'。
series: [
{
name: '线上',
type: 'bar',
stack: 'total', // 关键:相同的stack值会让柱子堆叠
data: [320, 332, 301, 334, 390, 330]
},
{
name: '线下',
type: 'bar',
stack: 'total', // 和上面一样,才会堆在一起
data: [120, 132, 101, 134, 90, 230]
}
]
分组柱状图则是并排展示,方便直接对比每个类别的不同维度。这个不需要设 stack,默认就是分组的。但如果你想控制组与组之间的间距,或者柱子之间的间距,就得用到 barGap 和 barCategoryGap。
xAxis: {
type: 'category',
data: ['A', 'B', 'C', 'D']
},
series: [
{ name: '2023', type: 'bar', data: [12, 20, 30, 40] },
{ name: '2024', type: 'bar', data: [15, 25, 35, 45] }
],
// 控制柱子之间的空隙
barGap: '10%', // 同一组内不同柱子的间距,默认30%
// 控制组与组之间的空隙
barCategoryGap: '20%'
这里容易踩的一个坑是:如果你同时用了 stack 又想调整 barGap,你会发现 barGap 对堆叠柱子无效。因为堆叠的时候,柱子是上下叠的,左右间距由 barCategoryGap 控制,而 barGap 管的是并排柱子的间距。这一点要记清楚,不然调了半天没效果会很抓狂。
动态数据与异步加载
现在的项目很少有用死数据的,大部分都要从接口拿数据。ECharts 提供了 setOption 方法,可以在实例创建后动态更新数据。但这里有个性能问题要注意:如果你频繁调用 setOption,每次都会重新渲染整个图表,开销很大。
// 初始化
const chart = echarts.init(document.getElementById('main'));
chart.setOption({
xAxis: { type: 'category', data: [] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [] }]
});
// 异步加载数据
async function fetchData() {
const res = await fetch('/api/sales-data');
const data = await res.json();
// 只更新数据,不重设整个option,性能更好
chart.setOption({
xAxis: { data: data.categories },
series: [{ data: data.values }]
}, true); // 第二个参数为 true,表示不合并,直接替换
}
注意最后一个参数 true。默认情况下,setOption 是合并模式,会把新配置和旧配置合并。但如果你的数据维度变了(比如从3个类别变成10个),合并可能会导致奇怪的问题。传 true 强制不合并,会用新配置完全覆盖旧配置。不过要小心,如果你只是更新数据,最好还是用合并模式,并明确指定要更新的属性,这样能保留其他交互状态(比如高亮、缩放等)。
折线图:平滑、面积与异常值处理
折线图(Line Chart)和柱状图经常一起出现,用来展示趋势。它的配置和柱状图很类似,但有几个特有的属性需要关注,比如 smooth(平滑曲线)、areaStyle(面积填充)和 markPoint(标记点)。
平滑曲线与面积填充
series: [
{
name: '温度',
type: 'line',
smooth: true, // 设为true,折线变成平滑曲线
// 或者设为一个0-1之间的数,控制平滑程度
// smooth: 0.5,
areaStyle: {
// 给曲线下面填充颜色,可以用渐变
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)' }
])
},
data: [12, 15, 13, 18, 20, 19, 22, 25, 23, 20]
}
]
面积填充用 LinearGradient 可以做到从上到下颜色渐淡的效果,看起来很有层次感。不过要注意,如果数据波动特别大,面积图可能会显得非常杂乱。这时候可以配合 stack 属性,或者减少数据点的数量(通过 sampling: 'lttb' 进行采样)。
处理异常值与断点
实际业务数据里经常会有异常值,比如某个时间点传感器坏了,数据变成0或者NaN。直接画出来,折线会猛地掉下去或飞上去,非常难看。ECharts 提供了 silent 属性和 markLine、markPoint 来处理这种情况,但更常用的做法是在数据层面做预处理,或者用 connectNulls: false 让断点处断开。
series: [
{
type: 'line',
connectNulls: false, // 遇到null值时断开连线,而不是连过去
data: [12, 15, null, 18, 20, null, 22]
}
]
另外,如果你想在图表上标记出异常点,可以用 markPoint:
series: [
{
type: 'line',
data: [12, 15, 13, 18, 20],
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ coord: [2, 18], name: '异常点', itemStyle: { color: 'red' } }
]
}
}
]
markPoint 的 coord 可以精确指定位置,这样即使数据里没有这个点,也能在图表上标出来。这在展示关键事件(比如系统宕机、促销活动开始)时非常有用。
双Y轴的正确用法
有时候我们需要把两个量级完全不同的数据画在一起,比如“销售额(万元)”和“订单量(千个)”。这时候就要用双Y轴了。但双Y轴很容易画成“四不像”,关键是颜色、标签、刻度要对齐,不能让人看花眼。
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五']
},
yAxis: [
{
type: 'value',
name: '销售额(万元)',
position: 'left',
axisLine: { lineStyle: { color: '#5470c6' } },
axisLabel: { color: '#5470c6' }
},
{
type: 'value',
name: '订单量(千)',
position: 'right',
axisLine: { lineStyle: { color: '#91cc75' } },
axisLabel: { color: '#91cc75' },
// 右轴可以单独设置刻度,避免两个轴刻度重叠
splitNumber: 3
}
],
series: [
{
name: '销售额',
type: 'line',
yAxisIndex: 0, // 指定使用第一个y轴
data: [320, 332, 301, 334, 390],
itemStyle: { color: '#5470c6' }
},
{
name: '订单量',
type: 'line',
yAxisIndex: 1, // 指定使用第二个y轴
data: [12, 15, 13, 18, 20],
itemStyle: { color: '#91cc75' }
}
]
这里的关键是 yAxisIndex,它决定了每个 series 对应哪个 Y 轴。同时,Y 轴的颜色也要和对应 series 的颜色保持一致,这样用户一眼就能看出哪条线对应哪个轴。如果颜色对不上,读者会很困惑,甚至误解数据。
还有一个容易忽略的点:双轴的 splitNumber。默认情况下,两个轴的刻度数量可能不一样,导致网格线错乱。建议手动设置 splitNumber,让两个轴的网格线尽量对齐,视觉上更整洁。
饼图与环形图:比例关系的优雅表达
饼图(Pie Chart)和环形图(Ring Chart)适合展示部分与整体的关系。它们的配置相对简单,但要想做得精致,还是有不少讲究。
基础饼图与 labels
series: [
{
type: 'pie',
radius: ['40%', '70%'], // 内半径和外半径,形成环形
center: ['50%', '60%'], // 圆心位置,可以偏移
data: [
{ value: 335, name: '直接访问' },
{ value: 310, name: '邮件营销' },
{ value: 234, name: '联盟广告' },
{ value: 135, name: '视频广告' },
{ value: 1548, name: '搜索引擎' }
],
// 标签配置
label: {
show: true,
formatter: '{b}: {c} ({d}%)', // b是名称,c是值,d是百分比
// 或者用自定义函数
// formatter: function(params) { return params.name + '\n' + params.percent + '%'; }
},
// 引导线
labelLine: {
show: true,
length: 15, // 第一段引导线长度
length2: 10 // 第二段引导线长度
}
}
]
formatter 是饼图标签的核心。默认情况下,它只显示名称和值。但加上 {d} 可以显示百分比,非常实用。如果你想要更个性化的展示,比如把百分比和值分开显示,或者加上图标,可以用自定义函数。
label: {
formatter: function(params) {
if (params.percent < 5) {
// 百分比太小的时候,把标签放到图形外面
return params.name + ': ' + params.value;
}
return params.percent + '%';
}
}
南丁格尔图(玫瑰图)
玫瑰图和普通饼图的区别在于,它的半径不是固定的,而是根据数值大小变化的。这样视觉效果更强烈,适合展示差异较大的数据。
series: [
{
type: 'pie',
radius: [20, 140], // 内径和外径
center: ['50%', '50%'],
roseType: 'area', // 或者 'radius',area模式下面积随数值变化,radius模式下只有半径变化
itemStyle: {
borderRadius: 5 // 给扇区加点圆角
},
data: [
{ value: 40, name: 'rose 1' },
{ value: 38, name: 'rose 2' },
{ value: 32, name: 'rose 3' },
{ value: 30, name: 'rose 4' },
{ value: 28, name: 'rose 5' },
{ value: 26, name: 'rose 6' },
{ value: 22, name: 'rose 7' },
{ value: 18, name: 'rose 8' }
]
}
]
roseType: 'area' 会让扇区的面积随数值变化,视觉上更夸张;roseType: 'radius' 只改变半径,面积还是按比例。根据你的数据特点选择合适的模式。如果数据差异很大,用 radius 可能更清晰;如果数据差异小,想用视觉冲击力,就用 area。
饼图交互优化
饼图默认的交互是鼠标悬停时扇区会弹出(emphasis: { scaleSize: 10 })。但这个弹出效果有时候会和 tooltip 冲突,导致 tooltip 显示不正常。解决方法是手动控制 emphasis 的触发方式。
series: [
{
type: 'pie',
data: [...],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
// 禁用默认的弹出效果,改用阴影
selectedMode: false // 或者设为 'single' 允许单选高亮
}
]
另外,饼图的图例(legend)默认是平铺的,如果数据项很多,会非常占空间。可以用 legend 的 orient: 'vertical' 改为垂直排列,或者放到图表外面。
legend: {
orient: 'vertical',
right: '10%',
top: 'center',
// 限制图例高度,超出部分滚动
textStyle: {
overflow: 'truncate',
width: 100
}
}
