Echarts图表实战教程 从零学会柱状图折线图饼图 解决数据为空显示异常渐变配置数据大屏等常见问题
为什么我们总是被Echarts劝退
说实话,第一次接触Echarts的时候,我也懵过。打开官网,看到满屏的配置项,什么xAxis、yAxis、series、data……密密麻麻的配置让人头大。试了几次,数据对了但样式丑得没法看,或者数据为空的时候图表直接报错,整个页面白茫茫一片,用户看着直皱眉。
其实Echarts没那么可怕。它就是个”积木玩具”——你把正确的模块拼在一起,自然就出效果了。今天我把这些年踩过的坑、调过的配置、改过的bug,全部摊开来讲,保证你看完能独立搞定一套完整的数据大屏。
先说结论:我们这篇文章不讲官方文档的复述,只讲实战里真正有用的东西。每一个配置项都带着原因说明,每一个案例都跑过真实场景。
环境准备:别再问我CDN怎么选
先把环境搭好。很多新手在这里就卡住了——npm装包、CDN引入、模块打包,选哪种?
我给你一个最稳妥的方案:直接用CDN引入,零配置,复制粘贴就能跑。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Echarts实战教程</title>
<!-- 引入Echarts CDN -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0e27; font-family: 'PingFang SC', sans-serif; }
.chart-container {
width: 100%;
height: 400px;
background: rgba(255,255,255,0.03);
border-radius: 12px;
margin: 20px 0;
}
</style>
</head>
<body>
<div id="chart" class="chart-container"></div>
<script>
// 你的代码写在这里
</script>
</body>
</html>
为什么要用CDN?因为你不需要装node_modules,不需要配webpack,不需要懂模块化。一个HTML文件就能跑,调试最快。等你项目大了再切到npm引入,逻辑一样。
常见错误提醒:CDN地址一定要加版本号,不然自动升级可能破坏你的样式。echarts@5.4.3 表示锁定5.4.3版本,稳定可靠。
柱状图:不只是画柱子那么简单
柱状图是Echarts里用得最多的图表,没有之一。但很多人用它的时候,就是直接复制粘贴官网示例,改改数据就上线了。结果上线后出问题:数据为空的时候柱子直接消失,颜色看着很脏,tooltip显示不完整……
我们先从最基础的柱状图开始,然后一步步叠加功能。
最简柱状图:先让东西跑起来
// 1. 初始化echarts实例
const chartDom = document.getElementById('chart');
const myChart = echarts.init(chartDom);
// 2. 配置项
const option = {
// 标题
title: {
text: '2024年季度销售额',
textStyle: { color: '#fff', fontSize: 18 },
left: 'center',
top: 20
},
// 提示框组件
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(10,14,39,0.9)',
borderColor: '#333',
textStyle: { color: '#fff' },
// 自定义格式化
formatter: function(params) {
return `${params[0].name}<br/>销售额:<b>${params[0].value}万</b>`;
}
},
// 图例
legend: {
data: ['销售额'],
textStyle: { color: '#aaa' },
top: 50
},
// X轴
xAxis: {
type: 'category',
data: ['Q1', 'Q2', 'Q3', 'Q4'],
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' }
},
// Y轴
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { color: '#222' } }
},
// 系列
series: [{
name: '销售额',
type: 'bar',
data: [120, 200, 150, 80],
// 柱子宽度
barWidth: '60%',
// 柱子样式
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 0.5, color: '#188df0' },
{ offset: 1, color: '#188df0' }
])
}
}]
};
// 3. 设置配置项
myChart.setOption(option);
这就是一个完整的最小可用柱状图。别嫌长,每一个配置项后面都有它存在的理由。
关键点解读:
trigger: 'axis':tooltip在坐标轴级别触发,鼠标移上去整条轴线都有提示,比'item'(只在单个点上触发)更实用。formatter:自定义tooltip内容,params[0].name是X轴值,params[0].value是Y轴值。barWidth: '60%':柱子宽度占类别宽度的60%,太宽了挤在一起,太窄了看着稀疏。LinearGradient:柱子的渐变色,从上到下由浅蓝渐变到深蓝,视觉上更有层次感。
多系列柱状图:对比分析必备
实际业务中,你往往需要同时展示多组数据做对比。比如不同产品的月度销量对比。
const option = {
title: { text: '各产品月度销量对比', textStyle: { color: '#fff' }, left: 'center' },
tooltip: { trigger: 'axis', backgroundColor: 'rgba(10,14,39,0.9)', textStyle: { color: '#fff' } },
legend: {
data: ['产品A', '产品B', '产品C'],
textStyle: { color: '#aaa' },
top: 40
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { color: '#222' } }
},
series: [
{
name: '产品A',
type: 'bar',
data: [120, 132, 101, 134, 90, 230],
itemStyle: { color: '#83bff6' },
// 柱子圆角
borderRadius: [4, 4, 0, 0]
},
{
name: '产品B',
type: 'bar',
data: [220, 182, 191, 234, 290, 330],
itemStyle: { color: '#ffb983' },
borderRadius: [4, 4, 0, 0]
},
{
name: '产品C',
type: 'bar',
data: [150, 232, 201, 154, 190, 330],
itemStyle: { color: '#91d5ff' },
borderRadius: [4, 4, 0, 0]
}
]
};
注意:borderRadius: [4, 4, 0, 0] 是Echarts 5.x支持的新特性,让柱子的顶部变圆角,视觉效果更现代。四个值分别对应左上、右上、右下、左下。
数据为空时的柱状图:这是个大坑
这是本文重点要解决的问题之一。
想象一下这个场景:你的柱状图数据来自后端接口,某个月的数据还没生成,接口返回[120, null, 101, null, 90]。默认情况下,Echarts会把null值的地方留空,柱子断掉。在数据大屏上,这看着非常不专业,用户会觉得”是不是出bug了”。
解决方案有三个层级,从简单到复杂:
方案一:用0替代null(最简单但不够好)
// 数据处理阶段
const rawData = [120, null, 101, null, 90];
const processedData = rawData.map(v => v === null ? 0 : v);
// 然后直接用processedData作为series.data
缺点:如果Y轴有0的刻度和有意义的0数据混在一起,容易混淆。
方案二:自定义formatter让空数据显示”暂无数据”
series: [{
name: '销量',
type: 'bar',
data: [120, null, 101, null, 90],
// 关键:在tooltip中处理null值
tooltip: {
formatter: function(params) {
if (params.value === null || params.value === undefined) {
return `${params.name}<br/>数据:<span style="color:#f56c6c">暂无数据</span>`;
}
return `${params.name}<br/>数据:<b>${params.value}</b>`;
}
}
}]
方案三(推荐):空数据用特殊样式标记
series: [{
name: '销量',
type: 'bar',
data: [
{ value: 120, itemStyle: { color: '#83bff6' } },
{ value: null, itemStyle: { color: 'transparent' }, label: { show: true, formatter: '暂无数据', color: '#888' } },
{ value: 101, itemStyle: { color: '#83bff6' } },
{ value: null, itemStyle: { color: 'transparent' }, label: { show: true, formatter: '暂无数据', color: '#888' } },
{ value: 90, itemStyle: { color: '#83bff6' } }
],
// 让柱子在空数据处显示为虚线或占位
connectNulls: false // 不连接null点
}]
方案四(最专业):空数据时用背景色或特殊标记
series: [{
name: '销量',
type: 'bar',
data: [120, null, 101, null, 90],
// 用visualMap处理null值
visualMap: {
show: false,
min: 0,
max: 300,
inRange: {
color: ['#83bff6', '#188df0']
},
// 关键配置:null值映射为透明
outOfRange: { color: 'transparent' }
}
}]
visualMap 的 outOfRange 属性专门处理超出范围的值(包括null),将其映射为透明色。这样柱子不会断掉,只是空数据的地方看不见,视觉上更平滑。
实际业务中的最佳实践:我一般用方案四+方案二的组合。图表上null值透明处理,tooltip里显示”暂无数据”的提示。这样既美观又友好。
// 完整的数据处理函数
function processBarData(rawData) {
return rawData.map(value => {
if (value === null || value === undefined || value === '') {
return {
value: 0, // 用0占位,保证柱子连续
itemStyle: { color: 'transparent' }, // 透明
label: { show: true, formatter: '暂无', color: '#888' }
};
}
return value;
});
}
折线图:曲线里的细节决定成败
折线图和柱状图的区别在于:折线图强调趋势,柱状图强调大小对比。配置项上也有差异,特别是平滑曲线、面积图、数据点这些。
基础折线图
const option = {
title: { text: '近7日访问量趋势', textStyle: { color: '#fff' }, left: 'center' },
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(10,14,39,0.9)',
textStyle: { color: '#fff' }
},
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { color: '#222' } }
},
series: [{
name: '访问量',
type: 'line',
data: [1200, 1320, 1010, 1340, 900, 2300, 2100],
// 平滑曲线
smooth: true,
// 线条样式
lineStyle: {
color: '#83bff6',
width: 3,
// 虚线可选
// type: 'dashed'
},
// 数据点样式
symbol: 'circle',
symbolSize: 8,
itemStyle: { color: '#83bff6' },
// 区域填充
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(131,191,246,0.4)' },
{ offset: 1, color: 'rgba(131,191,246,0)' }
])
}
}]
};
关键配置解读:
smooth: true:把折线变成平滑曲线。业务场景中,如果数据波动大,建议开启;如果强调精确趋势,关闭。areaStyle:区域填充,用渐变色从上到下由浓到淡,视觉上有”面积感”,比纯线条更饱满。symbol和symbolSize:数据点的形状和大小。默认是空心的圆,可以改成'diamond'(菱形)、'rect'(方形)等。
多折线图:别再用同样的颜色了
series: [
{
name: '页面A',
type: 'line',
data: [120, 132, 101, 134, 90, 230, 210],
smooth: true,
lineStyle: { color: '#83bff6', width: 3 },
itemStyle: { color: '#83bff6' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(131,191,246,0.3)' },
{ offset: 1, color: 'rgba(131,191,246,0)' }
])
}
},
{
name: '页面B',
type: 'line',
data: [220, 182, 191, 234, 290, 330, 310],
smooth: true,
lineStyle: { color: '#ffb983', width: 3 },
itemStyle: { color: '#ffb983' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255,185,131,0.3)' },
{ offset: 1, color: 'rgba(255,185,131,0)' }
])
}
}
]
两条线用不同的渐变色,视觉上区分明显。注意areaStyle的透明度要低一些,不然两条线的区域会叠在一起看不清。
折线图的空数据问题
折线图的空数据问题比柱状图更复杂。默认情况下,null值的地方会断开线条,这在数据大屏上非常难看。
series: [{
name: '访问量',
type: 'line',
data: [1200, null, 1010, null, 900, 2300, 2100],
smooth: true,
// 关键:连接null点
connectNulls: true,
lineStyle: { color: '#83bff6', width: 3 },
itemStyle: { color: '#83bff6' }
}]
connectNulls: true 是关键配置,它会跳过null值,直接连接前后的点。这样线条不会断,视觉上更连贯。
但注意:如果连续多个null,connectNulls 可能会产生不合理的连线。这时候需要后端配合,在数据层面做插值处理,或者前端用fillMissingData函数。
// 数据插值处理
function fillMissingData(data) {
const filled = [];
let lastValid = null;
for (let i = 0; i < data.length; i++) {
if (data[i] !== null && data[i] !== undefined) {
lastValid = data[i];
filled.push(data[i]);
} else if (lastValid !== null) {
// 用最后一个有效值填充
filled.push(lastValid);
} else {
filled.push(0);
}
}
return filled;
}
const rawData = [1200, null, null, 1010, null, 900, 2300];
const processedData = fillMissingData(rawData);
饼图:环形图的优雅写法
饼图在Echarts里用得相对少一些,但在占比分析场景不可替代。不过要注意:饼图的数据条数不要太多,超过8个就看不清了。
基础饼图
const option = {
title: {
text: '流量来源分布',
textStyle: { color: '#fff' },
left: 'center'
},
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(10,14,39,0.9)',
textStyle: { color: '#fff' },
// 格式化显示百分比
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 50,
top: 'center',
textStyle: { color: '#aaa' },
itemGap: 20
},
series: [{
name: '流量来源',
type: 'pie',
radius: ['40%', '70%'], // 环形图:内半径和外半径
center: ['35%', '50%'], // 饼图中心位置
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#0a0e27',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}\n{d}%',
color: '#aaa'
},
labelLine: {
lineStyle: { color: '#555' }
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold',
color: '#fff'
}
},
data: [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' },
{ value: 484, name: '联盟广告' },
{ value: 300, name: '视频广告' }
]
}]
};
关键点:
radius: ['40%', '70%']:这是环形图的写法。如果改成'60%',就是普通饼图。环形图比饼图更现代,中间可以放文字或图标。center: ['35%', '50%']:饼图的中心位置。默认是['50%', '50%'],居中。这里改成['35%', '50%']是因为右边有图例,把饼图往左移。formatter: '{b}: {c} ({d}%)':tooltip的格式化,{b}是名称,{c}是数值,{d}是百分比。label里的formatter: '{b}\n{d}%':在标签上显示名称和百分比,\n是换行。
饼图排序问题
饼图默认按数据顺序排列,但有时候数据不是从大到小排的,视觉效果会很乱。
// 数据处理:按value降序排列
const rawData = [
{ value: 300, name: '视频广告' },
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 484, name: '联盟广告' },
{ value: 580, name: '邮件营销' }
];
// 排序
const sortedData = rawData.sort((a, b) => b.value - a.value);
// 使用排序后的数据
series: [{
data: sortedData,
// ...其他配置
}]
饼图空数据问题
饼图的空数据其实不太常见,因为饼图展示的是占比,数据为空意味着这个类别不存在。但有一种情况:某个类别的数据为0或null。
// 过滤掉空数据
const filteredData = rawData.filter(item =>
item.value !== null && item.value !== undefined && item.value > 0
);
渐变配置:让图表从”能用”变成”好看”
前面我们已经用过了简单的渐变色,但Echarts的渐变配置可以玩出很多花样。
线性渐变(LinearGradient)
// 垂直渐变:从上到下
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' }, // 顶部
{ offset: 0.5, color: '#188df0' }, // 中间
{ offset: 1, color: '#188df0' } // 底部
])
// 水平渐变:从左到右
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#83bff6' },
{ offset: 1, color: '#ffb983' }
])
// 对角渐变
new echarts.graphic.LinearGradient(0, 0, 1, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 1, color: '#ffb983' }
])
四个参数的含义:(x0, y0, x1, y1),表示从点(x0,y0)到点(x1,y1)的方向。
(0, 0, 0, 1):从上到下(0, 0, 1, 0):从左到右(0, 0, 1, 1):从左上到右下
径向渐变(RadialGradient)
// 径向渐变:从中心向外扩散
new echarts.graphic.RadialGradient(0.5, 0.5, 0.5, [
{ offset: 0, color: '#83bff6' },
{ offset: 1, color: '#188df0' }
])
三个参数:(x, y, r),表示圆心位置和半径。
实际案例:渐变柱状图
series: [{
name: '销售额',
type: 'bar',
data: [120, 200, 150, 80],
// 每个柱子用不同的渐变色
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 0.4, color: '#188df0' },
{ offset: 1, color: '#0d5a9e' }
])
}
}]
注意事项
渐变配置在数据大屏里很常用,但要注意性能。如果数据量大(超过500个点),大量使用渐变可能会导致渲染变慢。这时候可以考虑用纯色替代,或者只在hover状态使用渐变。
数据大屏:从0到1搭建完整大屏
数据大屏是Echarts最常见的应用场景之一。但很多新手做出来之后发现:图表之间对不齐、颜色不统一、响应式有问题、空数据到处乱跳……
下面我带你从0到1搭一个完整的数据大屏。
第一步:确定布局
数据大屏一般有固定的布局规范。我们采用经典的”上中下”三栏布局:
- 顶部:标题栏
- 左侧:2个图表(柱状图+饼图)
- 中间:1个大图表(折线图)
- 右侧:2个图表(柱状图+折线图)
- 底部:统计卡片
<div class="dashboard">
<!-- 顶部标题 -->
<div class="header">
<h1>数据监控中心</h1>
<span class="time" id="currentTime"></span>
</div>
<!-- 主体内容 -->
<div class="main">
<!-- 左侧 -->
<div class="left">
<div class="chart-card" id="barChart"></div>
<div class="chart-card" id="pieChart"></div>
</div>
<!-- 中间 -->
<div class="center">
<div class="chart-card large" id="lineChart"></div>
</div>
<!-- 右侧 -->
<div class="right">
<div class="chart-card" id="barChart2"></div>
<div class="chart-card" id="lineChart2"></div>
</div>
</div>
<!-- 底部统计 -->
<div class="footer">
<div class="stat-item">
<span class="stat-value">12,345</span>
<span class="stat-label">今日访问</span>
</div>
<div class="stat-item">
<span class="stat-value">8,921</span>
<span class="stat-label">新增用户</span>
</div>
<div class="stat-item">
<span class="stat-value">96.8%</span>
<span class="stat-label">系统可用率</span>
</div>
</div>
</div>
第二步:CSS样式
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #0a0e27;
color: #fff;
font-family: 'PingFang SC', sans-serif;
min-height: 100vh;
}
.dashboard {
width: 100%;
min-height: 100vh;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
.header {
text-align: center;
padding: 20px;
background: linear-gradient(90deg, transparent, rgba(131,191,246,0.1), transparent);
border-bottom: 1px solid rgba(131,191,246,0.3);
}
.header h1 {
font-size: 32px;
letter-spacing: 8px;
background: linear-gradient(90deg, #83bff6, #ffb983);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header .time {
display: block;
margin-top: 10px;
color: #888;
font-size: 14px;
}
.main {
display: flex;
gap: 20px;
flex: 1;
}
.left, .right {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
.center {
flex: 2;
}
.chart-card {
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.05);
border-radius: 12px;
padding: 15px;
position: relative;
overflow: hidden;
}
.chart-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #83bff6, transparent);
}
.chart-card.large {
flex: 1;
}
.chart-card h3 {
font-size: 16px;
color: #aaa;
margin-bottom: 10px;
padding-left: 10px;
border-left: 3px solid #83bff6;
}
.chart-container {
width: 100%;
height: 250px;
}
.chart-card.large .chart-container {
height: 100%;
}
.footer {
display: flex;
justify-content: center;
gap: 60px;
padding: 20px;
background: rgba(255,255,255,0.03);
border-radius: 12px;
}
.stat-item {
text-align: center;
}
.stat-value {
display: block;
font-size: 36px;
font-weight: bold;
background: linear-gradient(180deg, #83bff6, #188df0);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
display: block;
font-size: 14px;
color: #888;
margin-top: 5px;
}
第三步:JavaScript初始化
// 获取所有图表容器
const chartContainers = [
{ id: 'barChart', type: 'bar' },
{ id: 'pieChart', type: 'pie' },
{ id: 'lineChart', type: 'line' },
{ id: 'barChart2', type: 'bar' },
{ id: 'lineChart2', type: 'line' }
];
// 初始化所有图表
const charts = {};
chartContainers.forEach(({ id, type }) => {
const dom = document.getElementById(id);
charts[id] = echarts.init(dom);
});
// 通用配置模板
function getCommonOption(title) {
return {
title: { text: title, textStyle: { color: '#aaa', fontSize: 14 } },
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(10,14,39,0.9)',
textStyle: { color: '#fff' }
},
grid: {
top: '15%',
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
}
};
}
// 柱状图配置
function getBarOption(title, data, xAxisData) {
return {
...getCommonOption(title),
xAxis: {
type: 'category',
data: xAxisData,
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#888', fontSize: 12 }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#888', fontSize: 12 },
splitLine: { lineStyle: { color: '#222' } }
},
series: [{
name: title,
type: 'bar',
data: data,
barWidth: '50%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 1, color: '#188df0' }
]),
borderRadius: [4, 4, 0, 0]
}
}]
};
}
// 饼图配置
function getPieOption(title, data) {
return {
title: { text: title, textStyle: { color: '#aaa', fontSize: 14 } },
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(10,14,39,0.9)',
textStyle: { color: '#fff' },
formatter: '{b}: {c} ({d}%)'
},
series: [{
name: title,
type: 'pie',
radius: ['30%', '70%'],
center: ['50%', '55%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 8,
borderColor: '#0a0e27',
borderWidth: 2
},
label: { show: false },
labelLine: { show: false },
data: data
}]
};
}
// 折线图配置
function getLineOption(title, data, xAxisData) {
return {
...getCommonOption(title),
xAxis: {
type: 'category',
data: xAxisData,
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#888', fontSize: 12 }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#888', fontSize: 12 },
splitLine: { lineStyle: { color: '#222' } }
},
series: [{
name: title,
type: 'line',
data: data,
smooth: true,
connectNulls: true,
lineStyle: { color: '#ffb983', width: 3 },
itemStyle: { color: '#ffb983' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255,185,131,0.3)' },
{ offset: 1, color: 'rgba(255,185,131,0)' }
])
}
}]
};
}
// 渲染图表
charts['barChart'].setOption(getBarOption('月度销售额', [120, 200, 150, 80, 180, 220], ['1月','2月','3月','4月','5月','6月']));
charts['pieChart'].setOption(getPieOption('流量来源', [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' },
{ value: 484, name: '联盟广告' },
{ value: 300, name: '视频广告' }
]));
charts['lineChart'].setOption(getLineOption('近7日访问量', [1200, 1320, 1010, 1340, 900, 2300, 2100], ['周一','周二','周三','周四','周五','周六','周日']));
charts['barChart2'].setOption(getBarOption('产品销量', [320, 332, 401, 434, 290, 330], ['产品A','产品B','产品C','产品D','产品E','产品F']));
charts['lineChart2'].setOption(getLineOption('转化率', [2.1, 2.3, 2.0, 2.5, 2.8, 2.6, 2.9], ['周一','周二','周三','周四','周五','周六','周日']));
// 响应式处理
window.addEventListener('resize', () => {
Object.values(charts).forEach(chart => chart.resize());
});
// 实时更新时间
function updateTime() {
const now = new Date();
document.getElementById('currentTime').textContent =
now.toLocaleString('zh-CN', { hour12: false });
}
setInterval(updateTime, 1000);
updateTime();
数据大屏的常见问题
问题一:图表高度不对齐
解决方法:给所有.chart-card设置固定的高度,或者用CSS Grid/Flexbox强制等高。
问题二:响应式错位
解决方法:在resize事件里调用每个图表的resize()方法。上面代码已经处理了。
问题三:数据为空时图表空白
解决方法:用前面讲过的方案四(visualMap的outOfRange),或者在数据层面做预处理。
问题四:颜色不统一
解决方法:定义一套设计规范,所有图表的颜色都从同一个色板里取。可以用CSS变量管理:
:root {
--primary: #83bff6;
--secondary: #ffb983;
--bg-dark: #0a0e27;
--text-muted: #888;
}
然后在JS里引用:
const colors = {
primary: '#83bff6',
secondary: '#ffb983'
};
数据大屏的最佳实践总结
做数据大屏,有几个原则一定要记住:
第一,统一色系。整个大屏的颜色不能超过5种,否则看起来像色卡展览馆。建议主色不超过2种,辅助色不超过3种。
第二,留白要足。图表周围不要塞满,至少留20%的边距。数据大屏最容易犯的错误就是把每个格子都塞满,看起来非常拥挤。
第三,动画要克制。Echarts默认有入场动画,但数据刷新时不要每次都重新渲染整个图表,只做数据更新就好:
// 错误做法:每次都init再setOption
const chart = echarts.init(dom);
chart.setOption(option);
// 正确做法:只更新数据
chart.setOption({
series: [{ data: newData }]
});
第四,空数据要有态度的处理。不要让用户看到空白,至少显示”暂无数据”的提示。
// 封装一个统一的数据处理函数
function processData(data, defaultValue = 0) {
return data.map(item => {
if (item === null || item === undefined || item === '') {
return { value: defaultValue, itemStyle: { color: '#333' } };
}
return item;
});
}
一些容易被忽略的细节
图表尺寸问题
很多新手发现图表显示不完整,或者被截断。原因通常是容器高度没有正确设置。
.chart-container {
height: 400px; /* 必须有固定高度 */
width: 100%;
}
Echarts的图表需要容器有明确的高度,否则默认高度为0,什么都看不见。
tooltip显示不完整
tooltip的内容太多时,可能会被截断或超出屏幕。解决方法:
tooltip: {
confine: true, // 限制在容器内
extraCssText: 'box-shadow: 0 0 10px rgba(0,0,0,0.5);' // 加阴影提升可读性
}
图例位置不当
图例默认在顶部,但有时候会遮挡标题。可以调整位置:
legend: {
top: '5%',
right: '5%',
orient: 'horizontal' // 水平排列
}
图表刷新频率
数据大屏通常需要实时刷新。但刷新频率不要太高,建议30秒~1分钟一次。太频繁会:
- 增加服务器压力
- 页面闪烁,影响观感
- 用户看起来晕
// 使用setInterval定时刷新
setInterval(() => {
fetch('/api/data')
.then(res => res.json())
.then(data => {
chart.setOption({
series: [{ data: data.values }]
});
});
}, 30000); // 30秒刷新一次
总结:从”会用”到”用好”
这篇文章讲了很多内容,但核心就三点:
第一,理解配置项的意义。不要死记硬背,要理解每个配置项为什么存在。比如connectNulls是为了让折线不断开,visualMap.outOfRange是为了处理空数据。
第二,封装通用函数。把重复的配置抽成函数,减少重复代码。比如getBarOption、getLineOption。
第三,处理边界情况。空数据、大数据量、响应式、性能……这些是区分”能用”和”好用”的关键。
Echarts不难,难的是把这些细节都考虑到。这篇文章把常见的坑都填了,你照着做,至少能搞出一个专业的数据大屏。
最后送一句话:好的图表不是配置出来的,是设计出来的。先想清楚要展示什么数据、给谁看、想传达什么信息,然后再动手写代码。配置项只是工具,思路才是核心。
