新手从零开始学Echarts教程视频入门制作动态图表实战从折线图柱状图到复杂图表配置问题解决数据可视化常见问题
第一章 初识 Echarts:你的数据可视化神器
嘿,你好呀!如果你是个刚接触前端图表库的小白,或者曾经被那些密密麻麻的配置项吓到过,这篇教程就是为你准备的。我们从头开始,一步步把 Echarts 摸透。
1.1 为什么选择 Echarts
Echarts 是百度开源的一套 JavaScript 可视化库,现在归 Apache 孵化了。它在国内用得非常广,文档齐全、示例丰富、社区活跃。相比其他方案(比如 D3.js),Echarts 上手更简单,配置逻辑更直观。
核心优势一句话概括:配置驱动,开箱即用。
你不需要懂 Canvas 底层,也不需要从零画一个饼图,只要把数据丢进 option 里,图表就出来了。
1.2 环境准备
首先,你需要一个基本的 HTML 文件:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Echarts 入门实战</title>
<style>
/* 图表容器必须有明确的高度,否则看不到 */
#main {
width: 800px;
height: 500px;
margin: 20px auto;
}
</style>
</head>
<body>
<div id="main"></div>
<!-- 引入 Echarts,推荐用 CDN -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script>
// 你的代码写在这里
</script>
</body>
</html>
💡 重要提醒:很多新手遇到的”图表不显示”问题,90% 是因为容器没有设置高度。Echarts 默认高度是 0,必须手动给父容器一个
height。
1.3 初始化图表
// 1. 获取 DOM 元素
const dom = document.getElementById('main');
// 2. 初始化 Echarts 实例
const myChart = echarts.init(dom);
// 3. 准备配置项
const option = {
title: {
text: '我的第一个 Echarts 图表'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar'
}]
};
// 4. 使用配置项显示图表
myChart.setOption(option);
运行这段代码,你就能看到一个简单的柱状图了。恭喜,你已经迈出了第一步!
第二章 折线图:时间序列数据的最佳伙伴
折线图是数据可视化中最基础也最常用的图表类型,特别适合展示数据随时间变化的趋势。
2.1 最简单的折线图
const option = {
title: {
text: '某城市每日最高气温变化',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
boundaryGap: false, // 折线图通常不设间距
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value',
name: '温度(℃)',
min: 0,
max: 40
},
series: [{
name: '最高气温',
type: 'line',
data: [22, 24, 26, 23, 27, 30, 28],
smooth: true, // 平滑曲线
areaStyle: {
// 填充区域颜色,让趋势更明显
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(54, 162, 235, 0.5)' },
{ offset: 1, color: 'rgba(54, 162, 235, 0.1)' }
])
}
}]
};
2.2 多条折线对比
当你需要对比多个维度的数据时,折线图同样胜任:
const option = {
title: { text: '两款产品月度销量对比' },
tooltip: { trigger: 'axis' },
legend: {
data: ['产品A', '产品B'],
top: 30
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: { type: 'value', name: '销量(件)' },
series: [
{
name: '产品A',
type: 'line',
data: [120, 132, 101, 134, 90, 230],
// 标记点,突出最大值
markPoint: {
data: [
{ type: 'max', name: '最大值' }
]
},
// 标记线,标注平均值
markLine: {
data: [
{ type: 'average', name: '平均值' }
]
}
},
{
name: '产品B',
type: 'line',
data: [220, 182, 191, 234, 290, 330],
markPoint: {
data: [
{ type: 'min', name: '最小值' }
]
}
}
]
};
📌 小技巧:
markPoint和markLine是 Echarts 提供的辅助标记功能,非常适合在图表上标注关键数据点,让你的图表更有”故事感”。
2.3 实时数据折线图
在很多监控场景中,数据是动态更新的。我们来看看如何实现实时刷新:
// 模拟实时数据
const option = {
title: { text: '实时服务器 CPU 使用率' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: [] // 初始为空
},
yAxis: {
type: 'value',
min: 0,
max: 100,
name: 'CPU 使用率(%)'
},
series: [{
name: 'CPU',
type: 'line',
data: [],
smooth: true,
areaStyle: {}
}]
};
const myChart = echarts.init(document.getElementById('main'));
myChart.setOption(option);
// 每分钟刷新一次数据
setInterval(function () {
const now = new Date();
const timeStr = now.getHours() + ':' +
String(now.getMinutes()).padStart(2, '0') + ':' +
String(now.getSeconds()).padStart(2, '0');
// 更新 X 轴
const xAxisData = myChart.getModel().getOption('xAxis')[0].data;
xAxisData.push(timeStr);
if (xAxisData.length > 20) {
xAxisData.shift(); // 保持最多20个数据点
}
// 更新 Series 数据
const newData = myChart.getModel().getOption('series')[0].data;
newData.push(Math.random() * 60 + 20); // 模拟 20~80 的随机值
if (newData.length > 20) {
newData.shift();
}
myChart.setOption({
xAxis: { data: xAxisData },
series: [{ data: newData }]
});
}, 1000);
这段代码展示了一个滑动窗口的实时折线图,数据每秒更新一次,旧数据自动滑出视野。这种模式在物联网监控、股票行情等场景中非常实用。
第三章 柱状图:分类对比的直观表达
柱状图适合展示不同类别之间的数值对比,是最容易理解的图表类型之一。
3.1 基础柱状图
const option = {
title: { text: '2024年各季度营收(万元)' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: ['Q1', 'Q2', 'Q3', 'Q4']
},
yAxis: {
type: 'value',
name: '营收(万元)'
},
series: [{
type: 'bar',
data: [1200, 1500, 1800, 2100],
// 自定义柱子颜色
itemStyle: {
color: '#5470c6'
},
// 柱子上显示数值
label: {
show: true,
position: 'top'
}
}]
};
3.2 堆叠柱状图
当需要展示”总量 + 各组成部分”时,堆叠柱状图非常合适:
const option = {
title: { text: '各产品线月度销售统计' },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { data: ['手机', '平板', '耳机', '手表'] },
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value',
name: '销售额(万元)'
},
series: [
{
name: '手机',
type: 'bar',
stack: 'total', // 关键:设置相同的 stack 值实现堆叠
data: [320, 302, 341, 374, 390, 450],
itemStyle: { color: '#5470c6' }
},
{
name: '平板',
type: 'bar',
stack: 'total',
data: [120, 132, 101, 134, 90, 230],
itemStyle: { color: '#91cc75' }
},
{
name: '耳机',
type: 'bar',
stack: 'total',
data: [220, 182, 191, 234, 290, 330],
itemStyle: { color: '#fac858' }
},
{
name: '手表',
type: 'bar',
stack: 'total',
data: [150, 212, 201, 154, 190, 330],
itemStyle: { color: '#ee6666' }
}
]
};
💡 堆叠原理:同一组
stack: 'total'的系列会在 Y 轴方向上叠加显示。不同 stack 名称的系列会分别堆叠,互不干扰。
3.3 分组柱状图
当需要同时对比多个类别和多个维度时,分组柱状图是个好选择:
const option = {
title: { text: '各城市男女平均工资对比' },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { data: ['男性', '女性'] },
xAxis: {
type: 'category',
data: ['北京', '上海', '广州', '深圳', '杭州']
},
yAxis: {
type: 'value',
name: '月薪(千元)'
},
series: [
{
name: '男性',
type: 'bar',
data: [18, 17, 14, 16, 15],
itemStyle: { color: '#5470c6' }
},
{
name: '女性',
type: 'bar',
data: [15, 14, 12, 13, 13],
itemStyle: { color: '#91cc75' }
}
]
};
分组柱状图和堆叠柱状图的区别在于:分组是并排显示,堆叠是上下叠加。根据你的分析目的选择合适的形式。
3.4 横向柱状图
横向柱状图在类别名称较长或者类别数量较多时特别有用:
const option = {
title: { text: '2024年全球科技公司市值排名' },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true // 确保标签不被裁切
},
xAxis: {
type: 'value',
name: '市值(亿美元)'
},
yAxis: {
type: 'category',
// 注意:categories 放在 yAxis 上,实现横向效果
data: ['苹果', '微软', '谷歌', '亚马逊', '英伟达', 'Meta', '特斯拉']
},
series: [{
type: 'bar',
data: [32000, 31000, 21000, 19000, 18000, 17000, 8000],
itemStyle: {
color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#83bff6' },
{ offset: 0.5, color: '#188df0' },
{ offset: 1, color: '#188df0' }
])
},
label: {
show: true,
position: 'right',
formatter: '{c} 亿$'
}
}]
};
把 xAxis 换成 value 类型,yAxis 换成 category 类型,柱状图就自动变成横向的了。这个转换非常简单。
第四章 进阶图表:从入门到进阶
掌握了基础图表之后,我们来看看一些更复杂的图表类型。
4.1 饼图:占比关系的经典表达
const option = {
title: { text: '公司部门人员分布', subtext: '共计 120 人', left: 'center' },
tooltip: {
trigger: 'item',
formatter: '{b}<br/>占比:{d}%' // {d} 表示百分比
},
legend: {
orient: 'vertical',
left: 'left',
top: 'center'
},
series: [
{
name: '人员分布',
type: 'pie',
radius: ['40%', '70%'], // 内半径和外半径,做成环形图
center: ['50%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}\n{d}%'
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold'
}
},
data: [
{ value: 35, name: '研发部' },
{ value: 25, name: '市场部' },
{ value: 20, name: '运营部' },
{ value: 15, name: '销售部' },
{ value: 10, name: '其他' }
]
}
]
};
📌 环形图 vs 普通饼图:将
radius设置为['40%', '70%']即可创建环形图,比传统饼图更现代,也更美观。中心可以放置总计数字或标签。
4.2 散点图:寻找数据间的关系
const option = {
title: { text: '身高体重散点图(示例数据)' },
tooltip: {
trigger: 'item',
formatter: function(params) {
return '身高: ' + params.value[0] + 'cm<br/>体重: ' + params.value[1] + 'kg';
}
},
xAxis: {
type: 'value',
name: '身高(cm)',
min: 150,
max: 200
},
yAxis: {
type: 'value',
name: '体重(kg)',
min: 40,
max: 100
},
series: [{
type: 'scatter',
// 每个点用 [x, y, size] 表示,size 控制气泡大小
data: [
[165, 55, 10],
[170, 62, 12],
[175, 70, 15],
[180, 75, 14],
[185, 82, 16],
[160, 50, 8],
[168, 58, 11],
[172, 65, 13],
[178, 72, 15],
[183, 78, 17]
],
symbolSize: function(val) {
// 根据数据值动态调整气泡大小
return val[2] * 2;
},
itemStyle: {
color: function(params) {
// 根据数据值设置颜色
const colors = ['#5470c6', '#91cc75', '#fac858', '#ee6666'];
return colors[params.dataIndex % colors.length];
}
}
}]
};
散点图最经典的应用是波士顿房价数据集的可视化——用房间数量预测房价。在这里,横轴可以代表房间数,纵轴代表房价,气泡大小可以代表房屋面积。
4.3 雷达图:多维度综合分析
const option = {
title: { text: '员工能力评估雷达图' },
tooltip: {},
legend: {
data: ['评估者A', '评估者B']
},
radar: {
// indicator 定义各维度
indicator: [
{ name: '技术能力', max: 100 },
{ name: '沟通能力', max: 100 },
{ name: '项目管理', max: 100 },
{ name: '团队协作', max: 100 },
{ name: '创新能力', max: 100 },
{ name: '执行力', max: 100 }
],
shape: 'polygon', // 可选 'polygon' 或 'circle'
splitNumber: 5,
axisName: {
color: '#333',
fontSize: 12
}
},
series: [{
name: '能力评估',
type: 'radar',
data: [
{
value: [85, 72, 90, 88, 75, 82],
name: '评估者A',
itemStyle: { color: '#5470c6' },
areaStyle: { opacity: 0.3 }
},
{
value: [78, 85, 80, 92, 88, 75],
name: '评估者B',
itemStyle: { color: '#91cc75' },
areaStyle: { opacity: 0.3 }
}
]
}]
};
雷达图在能力评估、产品对比、健康指标等场景下非常有用。它能同时展示多个维度的数据,让人一目了然地看出优势和短板。
4.4 漏斗图:转化流程可视化
const option = {
title: { text: '电商网站用户转化漏斗', left: 'center' },
tooltip: { trigger: 'item', formatter: '{b} : {c}人 ({d}%)' },
series: [
{
name: '转化漏斗',
type: 'funnel',
left: '10%',
top: 60,
bottom: 60,
width: '80%',
min: 0,
max: 10000,
minSize: '0%',
maxSize: '100%',
sort: 'descending', // 按值降序排列
gap: 2,
label: {
show: true,
position: 'inside'
},
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: 'solid'
}
},
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
emphasis: {
label: { fontSize: 20 }
},
data: [
{ value: 10000, name: '访问网站' },
{ value: 8000, name: '浏览商品' },
{ value: 5000, name: '加入购物车' },
{ value: 3000, name: '提交订单' },
{ value: 2000, name: '完成支付' }
]
}
]
};
漏斗图清晰展示了用户从访问到成交的每一步流失情况,是运营分析中不可或缺的图表类型。
4.5 热力图:数据密度直观呈现
// 需要额外引入 heatmap 扩展
// 在 Echarts 5 中,heatmap 已内置,无需额外引入
const option = {
title: { text: '一周内每小时网站访问量热力图' },
tooltip: {
position: 'top',
formatter: function(params) {
return params.name + ' ' + params.value[0] + '时<br/>访问量: ' + params.value[1];
}
},
grid: {
left: '10%',
right: '10%',
top: '15%',
bottom: '15%'
},
xAxis: {
type: 'category',
data: ['0时', '2时', '4时', '6时', '8时', '10时', '12时', '14时', '16时', '18时', '20时', '22时'],
splitArea: { show: true }
},
yAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
splitArea: { show: true }
},
visualMap: {
min: 0,
max: 1000,
show: true,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: '3%'
},
series: [{
type: 'heatmap',
data: [
[0, 0, 120], [0, 1, 90], [0, 2, 80], [0, 3, 70], [0, 4, 60], [0, 5, 50], [0, 6, 40],
[1, 0, 150], [1, 1, 110], [1, 2, 95], [1, 3, 80], [1, 4, 70], [1, 5, 60], [1, 6, 55],
[2, 0, 200], [2, 1, 160], [2, 2, 130], [2, 3, 110], [2, 4, 90], [2, 5, 80], [2, 6, 70],
[3, 0, 180], [3, 1, 140], [3, 2, 120], [3, 3, 100], [3, 4, 85], [3, 5, 75], [3, 6, 65],
[4, 0, 250], [4, 1, 200], [4, 2, 170], [4, 3, 150], [4, 4, 120], [4, 5, 100], [4, 6, 85],
[5, 0, 300], [5, 1, 250], [5, 2, 220], [5, 3, 200], [5, 4, 180], [5, 5, 160], [5, 6, 140],
[6, 0, 280], [6, 1, 230], [6, 2, 200], [6, 3, 180], [6, 4, 160], [6, 5, 150], [6, 6, 130]
],
label: {
show: true
}
}]
};
热力图在分析时间规律、地理分布、密集程度等场景下非常有用。通过颜色深浅直观地展示数值大小,比表格更易读。
第五章 动态交互:让图表”活”起来
静态图表只能展示数据,交互式图表才能让用户探索数据。Echarts 提供了丰富的交互能力。
5.1 点击事件响应
myChart.on('click', function(params) {
// params 包含点击的详细信息
console.log('点击了:', params.name);
console.log('数据:', params.value);
console.log('系列名称:', params.seriesName);
// 可以在点击后弹出详情,或跳转到新页面
alert('你点击了 ' + params.seriesName + ' 的 ' + params.name +
',数值为 ' + params.value);
});
// 双击事件
myChart.on('dblclick', function(params) {
console.log('双击了:', params.name);
});
5.2 图例筛选交互
const option = {
title: { text: '多产品线销售趋势' },
legend: {
data: ['产品A', '产品B', '产品C'],
selected: {
// 初始状态:隐藏产品B
'产品B': false
}
},
// ... 其他配置
};
用户点击图例可以显示或隐藏对应系列,这是最常用的交互之一。
5.3 数据区域缩放
const option = {
title: { text: '股票价格波动(可缩放)' },
tooltip: { trigger: 'axis' },
dataZoom: [
{
type: 'slider', // 滑动条类型
start: 0,
end: 100
},
{
type: 'inside', // 支持鼠标滚轮缩放
start: 0,
end: 100
}
],
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: { type: 'value' },
series: [{
type: 'line',
data: [120, 132, 101, 134, 90, 230, 210, 180, 160, 140, 150, 170]
}]
};
dataZoom 是最实用的交互组件之一,让用户可以自由缩放查看数据细节。
5.4 图表联动
当页面上有多个图表时,联动可以让它们同步交互:
// 创建两个图表实例
const chart1 = echarts.init(document.getElementById('chart1'));
const chart2 = echarts.init(document.getElementById('chart2'));
// 为两个图表分别设置配置(省略具体配置)
chart1.setOption(option1);
chart2.setOption(option2);
// 监听 chart1 的 highlight 事件
chart1.on('highlight', function(params) {
// 同步 chart2 的高亮状态
const action = {
type: params.batch ? 'downplay' : 'highlight',
batch: params.batch
};
chart2.dispatchAction(action);
});
// 监听 chart1 的 downplay 事件
chart1.on('downplay', function(params) {
chart2.dispatchAction({ type: 'downplay', batch: params.batch });
});
// 切换数据时也联动
chart1.on('dataZoom', function(params) {
chart2.dispatchAction({
type: 'dataZoom',
start: params.start,
end: params.end
});
});
图表联动在仪表盘、大屏展示等场景中非常常见,让多个图表形成有机整体。
第六章 复杂场景实战
6.1 从 API 获取数据并渲染
实际项目中,数据通常来自后端 API:
async function fetchAndRenderChart() {
try {
// 模拟 API 请求
const response = await fetch('/api/sales-data');
const data = await response.json();
// 处理数据
const categories = data.map(item => item.month);
const values = data.map(item => item.sales);
const option = {
title: { text: '月度销售数据' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: categories
},
yAxis: {
type: 'value',
name: '销售额(万元)'
},
series: [{
type: 'line',
data: values,
smooth: true,
areaStyle: {},
// 数据加载完成后显示动画
animationDuration: 1000
}]
};
myChart.setOption(option, true); // true 表示不合并,直接替换
} catch (error) {
console.error('数据加载失败:', error);
// 显示错误提示
myChart.showLoading({
text: '数据加载失败',
color: '#ee6666'
});
}
}
// 页面加载完成后获取数据
fetchAndRenderChart();
6.2 响应式图表
图表需要适应不同屏幕尺寸:
const myChart = echarts.init(document.getElementById('main'));
// 监听窗口大小变化
window.addEventListener('resize', function() {
myChart.resize();
});
// 使用 ResizeObserver 更精确地监听容器变化
const resizeObserver = new ResizeObserver(function() {
myChart.resize();
});
resizeObserver.observe(document.getElementById('main'));
响应式图表是必备的优化项,否则在移动端或窗口缩放时图表可能会错位或变形。
6.3 大数据量优化
当数据量超过几千条时,图表性能会明显下降:
const option = {
// 开启 dataZoom 自动分片
dataZoom: [
{ type: 'inside', start: 0, end: 100 }
],
series: [{
type: 'line',
data: largeData,
// 开启采样,避免渲染过多点
sampling: 'lttb', // LTTB 采样算法,保留数据特征
// 关闭动画,提升性能
animation: false
}]
};
大数据量的优化策略包括:采样、分片渲染、关闭动画、使用 WebGL 后端(Echarts GL)等。
第七章 常见问题与解决方案
7.1 图表不显示
问题表现:HTML 文件加载了,但页面是空白的。
排查步骤:
- 检查浏览器控制台是否有报错
- 确认 Echarts 脚本是否成功加载(Network 面板查看)
- 确认容器有明确的高度(最常见原因)
- 确认
init时 DOM 已经加载完成
// 错误写法:DOM 未加载完就初始化
// const myChart = echarts.init(document.getElementById('main'));
// 正确写法:确保 DOM 就绪
document.addEventListener('DOMContentLoaded', function() {
const myChart = echarts.init(document.getElementById('main'));
// ...
});
// 或者在 script 标签放在 body 末尾
7.2 图表尺寸异常
问题表现:图表只显示一小块,或者变形。
解决方案:
// 手动调用 resize
myChart.resize();
// 监听窗口变化
window.addEventListener('resize', () => myChart.resize());
7.3 中文显示乱码
// 确保 HTML 文件声明了 UTF-8 编码
<meta charset="UTF-8">
// 如果仍乱码,检查字体文件
// Echarts 默认使用系统字体,部分系统可能缺少中文字体
7.4 图表样式不生效
// 检查配置项层级是否正确
const option = {
series: [{
type: 'bar',
// itemStyle 是 series 级别
itemStyle: {
color: '#5470c6'
}
}]
};
7.5 动态数据更新闪烁
// 使用 setOption 时传入 true 可以避免合并导致的闪烁
myChart.setOption(option, true);
// 或者关闭动画
myChart.setOption({
series: [{
animation: false
}]
});
第八章 性能优化与最佳实践
8.1 避免频繁 setOption
// 错误:频繁创建和销毁实例
function updateData(newData) {
myChart.dispose(); // 销毁
myChart = echarts.init(dom); // 重新创建
myChart.setOption(option);
}
// 正确:复用实例,只更新数据
function updateData(newData) {
myChart.setOption({
series: [{ data: newData }]
});
}
8.2 按需引入
Echarts 核心包约 300KB,Gzip 后约 80KB。如果项目不需要所有图表类型,可以按需引入:
// 使用 Echarts 5 的按需引入
import * as echarts from 'echarts/core';
import { BarChart, LineChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
BarChart,
LineChart,
CanvasRenderer
]);
8.3 大数据量处理
const option = {
series: [{
type: 'line',
data: hugeData,
// 使用 LTTB 采样
sampling: 'lttb',
// 关闭逐点动画
animation: false,
// 使用渐进式渲染
progressive: 1000,
// 增量渲染阈值
progressiveThreshold: 3000
}]
};
第九章 项目实战:完整的电商数据看板
让我们把所有知识综合起来,做一个实际的电商数据看板:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>电商数据看板</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1a1a2e;
color: #fff;
font-family: 'Microsoft YaHei', sans-serif;
padding: 20px;
}
.dashboard-title {
text-align: center;
font-size: 28px;
margin-bottom: 20px;
color: #00d4ff;
text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
}
.stats-row {
display: flex;
justify-content: space-around;
margin-bottom: 20px;
}
.stat-card {
background: linear-gradient(135deg, #16213e, #0f3460);
border-radius: 10px;
padding: 20px 30px;
text-align: center;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.stat-value {
font-size: 32px;
font-weight: bold;
color: #00d4ff;
}
.stat-label {
font-size: 14px;
color: #888;
margin-top: 5px;
}
.charts-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.chart-container {
flex: 1;
background: #16213e;
border-radius: 10px;
padding: 15px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.chart-title {
font-size: 16px;
color: #00d4ff;
margin-bottom: 10px;
padding-left: 10px;
border-left: 3px solid #00d4ff;
}
.chart {
width: 100%;
height: 300px;
}
.bottom-row {
display: flex;
gap: 20px;
}
.bottom-row .chart-container {
flex: 2;
}
.bottom-row .chart-container:last-child {
flex: 1;
}
</style>
</head>
<body>
<h1 class="dashboard-title">📊 电商运营数据看板</h1>
<!-- 关键指标卡片 -->
<div class="stats-row">
<div class="stat-card">
<div class="stat-value" id="total-sales">128.5万</div>
<div class="stat-label">今日销售额</div>
</div>
<div class="stat-card">
<div class="stat-value" id="total-orders">3,562</div>
<div class="stat-label">今日订单</div>
</div>
<div class="stat-card">
<div class="stat-value" id="total-users">18,234</div>
<div class="stat-label">活跃用户</div>
</div>
<div class="stat-card">
<div class="stat-value" id="conversion-rate">4.2%</div>
<div class="stat-label">转化率</div>
</div>
</div>
<!-- 第一行图表 -->
<div class="charts-row">
<div class="chart-container">
<div class="chart-title">销售额趋势</div>
<div id="sales-trend" class="chart"></div>
</div>
<div class="chart-container">
<div class="chart-title">品类销售占比</div>
<div id="category-pie" class="chart"></div>
</div>
</div>
<!-- 第二行图表 -->
<div class="bottom-row">
<div class="chart-container">
<div class="chart-title">各渠道流量来源</div>
<div id="channel-bar" class="chart"></div>
</div>
<div class="chart-container">
<div class="chart-title">用户地域分布</div>
<div id="region-heat" class="chart"></div>
</div>
</div>
<script>
// ============ 销售额趋势图 ============
const salesTrend = echarts.init(document.getElementById('sales-trend'));
const salesTrendOption = {
tooltip: { trigger: 'axis' },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
axisLine: { lineStyle: { color: '#888' } },
axisLabel: { color: '#ccc' }
},
yAxis: {
type: 'value',
name: '万元',
axisLine: { lineStyle: { color: '#888' } },
axisLabel: { color: '#ccc' },
splitLine: { lineStyle: { color: '#333' } }
},
series: [{
name: '销售额',
type: 'line',
data: [8.2, 9.1, 10.5, 11.2, 12.8, 15.3, 14.6, 13.2, 16.8, 18.5, 20.2, 22.1],
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(0, 212, 255, 0.4)' },
{ offset: 1, color: 'rgba(0, 212, 255, 0.05)' }
])
},
lineStyle: { color: '#00d4ff', width: 2 },
itemStyle: { color: '#00d4ff' }
}]
};
salesTrend.setOption(salesTrendOption);
// ============ 品类销售占比图 ============
const categoryPie = echarts.init(document.getElementById('category-pie'));
const categoryPieOption = {
tooltip: { trigger: 'item', formatter: '{b}: {c}万 ({d}%)' },
legend: {
orient: 'vertical',
right: '5%',
top: 'center',
textStyle: { color: '#ccc' }
},
series: [{
name: '品类占比',
type: 'pie',
radius: ['35%', '65%'],
center: ['40%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 8,
borderColor: '#16213e',
borderWidth: 2
},
label: { show: false },
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold',
color: '#fff'
}
},
data: [
{ value: 35, name: '数码', itemStyle: { color: '#5470c6' } },
{ value: 28, name: '服装', itemStyle: { color: '#91cc75' } },
{ value: 18, name: '食品', itemStyle: { color: '#fac858' } },
{ value: 12, name: '美妆', itemStyle: { color: '#ee6666' } },
{ value: 7, name: '其他', itemStyle: { color: '#73c0de' } }
]
}]
};
categoryPie.setOption(categoryPieOption);
// ============ 各渠道流量来源 ============
const channelBar = echarts.init(document.getElementById('channel-bar'));
const channelBarOption = {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: {
data: ['直接访问', '搜索引擎', '社交媒体', '广告投放'],
textStyle: { color: '#ccc' },
top: 0
},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: ['1月','2月','3月','4月','5月','6月'],
axisLine: { lineStyle: { color: '#888' } },
axisLabel: { color: '#ccc' }
},
yAxis: {
type: 'value',
name: '访问量(万)',
axisLine: { lineStyle: { color: '#888' } },
axisLabel: { color: '#ccc' },
splitLine: { lineStyle: { color: '#333' } }
},
series: [
{
name: '直接访问',
type: 'bar',
stack: 'total',
data: [320, 302, 341, 374, 390, 450],
itemStyle: { color: '#5470c6' }
},
{
name: '搜索引擎',
type: 'bar',
stack: 'total',
data: [120, 132, 101, 134, 90, 230],
itemStyle: { color: '#91cc75' }
},
{
name: '社交媒体',
type: 'bar',
stack: 'total',
data: [220, 182, 191, 234, 290, 330],
itemStyle: { color: '#fac858' }
},
{
name: '广告投放',
type: 'bar',
stack: 'total',
data: [150, 212, 201, 154, 190, 330],
itemStyle: { color: '#ee6666' }
}
]
};
channelBar.setOption(channelBarOption);
// ============ 用户地域分布热力图 ============
const regionHeat = echarts.init(document.getElementById('region-heat'));
const regionHeatOption = {
tooltip: {
position: 'top',
formatter: function(p) {
return p.name + ':' + p.value[1] + '万用户';
}
},
visualMap: {
min: 0,
max: 50,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: 0,
textStyle: { color: '#ccc' }
},
series: [{
type: 'heatmap',
data: [
[0, 0, 35], [1, 0, 42], [2, 0, 28], [3, 0, 38],
[0, 1, 30], [1, 1, 45], [2, 1, 25], [3, 1, 32],
[0, 2, 22], [1, 2, 38], [2, 2, 20], [3, 2, 28],
[0, 3, 18], [1, 3, 25], [2, 3, 15], [3, 3, 22]
],
xAxis: {
type: 'category',
data: ['华东', '华南', '华北', '西南'],
axisLabel: { color: '#ccc' }
},
yAxis: {
type: 'category',
data: ['Q1', 'Q2', 'Q3', 'Q4'],
axisLabel: { color: '#ccc' }
},
itemStyle: {
borderColor: '#16213e',
borderWidth: 2
}
}]
};
regionHeat.setOption(regionHeatOption);
// ============ 响应式处理 ============
window.addEventListener('resize', function() {
salesTrend.resize();
categoryPie.resize();
channelBar.resize();
regionHeat.resize();
});
// ============ 模拟实时数据更新 ============
setInterval(function() {
// 更新销售额趋势的最后一个数据点
const newData = salesTrend.getModel().getOption('series')[0].data;
const lastValue = newData[newData.length - 1];
newData[newData.length - 1] = lastValue + (Math.random() - 0.5) * 2;
salesTrend.setOption({
series: [{ data: newData }]
});
}, 3000);
</script>
</body>
</html>
这个看板综合了折线图、饼图、堆叠柱状图和热力图,展示了电商运营的关键指标。所有图表都支持响应式和实时数据更新。
第十章 学习资源与进阶路径
10.1 推荐学习资源
官方文档:
- Echarts 官方文档:https://echarts.apache.org/zh/index.html
- 示例库:https://echarts.apache.org/examples/zh/index.html(这里能找到几乎所有图表类型的配置示例)
实战练习平台:
- CodePen / CodeSandbox 在线编辑
- 尝试把示例数据替换成自己的真实数据
10.2 进阶方向
- Echarts GL:三维可视化、地球仪、3D 地图
- BMapGL 集成:结合百度地图做地理位置可视化
- 自定义主题:使用 Apache ECharts Theme Builder 设计自己的主题
- React/Vue 封装:将 Echarts 封装成可复用的组件
10.3 避坑 checklist
- [ ] 容器有明确的高度
- [ ] 引入 Echarts 脚本成功(检查 Network)
- [ ] 中文显示正常(UTF-8 编码)
- [ ] 响应式处理(resize)
- [ ] 大数据量开启采样
- [ ] 按需引入减小包体积
- [ ] 避免频繁销毁和重建实例
结语
学完这篇教程,你已经掌握了 Echarts 的核心用法。从基础的折线图、柱状图,到进阶的雷达图、漏斗图、热力图,再到完整的数据看板实战,每一步都有代码示例和原理解释。
记住,Echarts 的学习曲线很平缓——配置驱动是它最大的特点,只要理解了 option 的结构,绝大多数图表都可以通过调整配置来实现。多去看官方示例,那是最好的老师。
如果你在实战中遇到问题,欢迎回来查看这篇教程,或者去 Echarts 官方文档和示例库寻找答案。数据可视化是一门艺术,也是一门技术,祝你在这条路上越走越远!
