从画第一个柱状图到独立完成数据大屏:ECharts零基础入门到实战项目全流程
嘿,朋友!今天咱们来聊聊ECharts这件事儿。你可能会想,”又是一个ECharts教程?”别急着划走,这篇不一样。我是那个从对着空白页面发愁、到现在能独立做出炫酷数据大屏的老学长。这篇教程,就是把我的血泪经验和实战心得,掰开了揉碎了讲给你听。
一、初识ECharts:为什么要学它?
先说个真实场景。我之前在公司做项目,老板丢给我一堆数据,说”你给我整点能看的图表”。我当时啥都不会,只会用HTML和CSS,硬撑了两天,做出来的东西惨不忍睹。后来同事甩给我一个链接——ECharts,Apache开源的一个图表库。从那天起,我的世界变了。
ECharts到底牛在哪?我总结几点:
- 国产之光:百度开源,文档全中文,社区活跃,出了问题很容易找到解决方案
- 图表丰富:柱状图、折线图、饼图、散点图、K线图、地图、热力图…30+种图表类型
- 渲染引擎多样:支持Canvas和SVG,大屏项目推荐Canvas,性能好
- 跨平台:浏览器、移动端都能用
- 生态成熟:配合DataV、AntV等阿里系工具,做数据大屏一条龙
二、环境搭建:从Hello World开始
别急,咱们先从最简单的开始。创建一个HTML文件,引入ECharts,画你的第一个柱状图。
2.1 引入ECharts
有两种方式:
方式一:CDN引入(适合快速上手)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>ECharts 第一个柱状图</title>
<!-- 引入 ECharts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
</head>
<body>
<!-- 为 ECharts 准备一个定义了宽高的 DOM -->
<div id="main" style="width: 600px; height: 400px;"></div>
<script>
// 基于准备好的 DOM,初始化 ECharts 实例
var myChart = echarts.init(document.getElementById('main'));
// 指定配置项和数据
var option = {
title: {
text: 'ECharts 第一个柱状图'
},
tooltip: {},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
};
// 使用刚指定的配置项和数据显示图表
myChart.setOption(option);
</script>
</body>
</html>
这段代码简单到离谱对吧?我就直接告诉你每个部分在干什么:
echarts.init()—— 告诉ECharts”我要在哪个div里画图”option—— 图表的”配置说明书”,所有参数都在这setOption()—— 把配置应用上去,图表就出来了
2.2 本地安装(适合正式项目)
如果你要做正式项目,建议用npm安装:
npm install echarts --save
然后在项目里引入:
import * as echarts from 'echarts';
// 或者按需引入,减小体积
import echarts from 'echarts/lib/echarts';
import 'echarts/lib/chart/bar';
import 'echarts/lib/component/title';
import 'echarts/lib/component/tooltip';
import 'echarts/lib/component/grid';
三、核心概念:option配置全解析
ECharts的核心就是option配置对象。别怕,我把它拆成几个板块来讲,保证你能看懂。
3.1 组件总览
option = {
title: {}, // 标题
tooltip: {}, // 提示框
legend: {}, // 图例
xAxis: {}, // 直角坐标系 - X轴
yAxis: {}, // 直角坐标系 - Y轴
grid: {}, // 网格
series: [], // 系列列表(真正的图表数据)
// 还有:dataZoom, visualMap, toolbox, brush 等组件
}
3.2 标题组件(title)
title: {
text: '2024年各季度销售额', // 主标题
subtext: '数据来源:公司财务系统', // 副标题
textStyle: { // 主标题样式
fontSize: 20,
color: '#333',
fontWeight: 'bold'
},
subtextStyle: { // 副标题样式
fontSize: 12,
color: '#999'
},
left: 'center', // 水平位置:left / center / right
top: 'top', // 垂直位置:top / middle / bottom
padding: [10, 0, 0, 0], // 内边距
backgroundColor: '#fff', // 背景色
borderWidth: 0, // 边框宽度
borderColor: '#ccc', // 边框颜色
}
3.3 提示框组件(tooltip)
tooltip: {
trigger: 'axis', // 触发类型:'axis'(坐标轴)/ 'item'(数据项)
show: true, // 是否显示
confine: true, // 是否限制在图形区域
backgroundColor: 'rgba(50,50,50,0.7)', // 背景色
borderColor: '#333', // 边框颜色
borderWidth: 1, // 边框宽度
textStyle: { // 文本样式
color: '#fff',
fontSize: 14
},
// 自定义提示框内容
formatter: function(params) {
// params 是数组,包含多个系列的数据
var result = `<div style="font-weight:bold">${params[0].name}</div>`;
params.forEach(function(item) {
result += `<div>${item.marker} ${item.seriesName}: ${item.value}</div>`;
});
return result;
}
}
3.4 图例组件(legend)
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
orient: 'horizontal', // 布局方向:'horizontal' / 'vertical'
left: 'center', // 水平位置
top: 'top', // 垂直位置
textStyle: {
color: '#333',
fontSize: 12
},
icon: 'circle', // 图例标记的图形:'circle' / 'rect' / 'roundRect' / 'triangle' / 'diamond' / 'pin' / 'arrow' / 'none'
itemWidth: 14, // 图例标记的图形宽度
itemHeight: 14, // 图例标记的图形高度
padding: [5, 10, 5, 10], // 内边距
}
3.5 直角坐标系(X轴和Y轴)
// X轴
xAxis: {
type: 'category', // 轴类型:'value' / 'category' / 'time' / 'log'
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
position: 'bottom', // 位置:'top' / 'bottom'
axisLine: { // 坐标轴线
lineStyle: {
color: '#333',
width: 1,
type: 'solid'
}
},
axisLabel: { // 坐标轴标签
color: '#333',
fontSize: 12,
rotate: 0, // 旋转角度
formatter: function(value) {
// 可以自定义标签内容
return value;
}
},
splitLine: { // 分隔线
show: true,
lineStyle: {
color: '#eee',
type: 'solid'
}
},
axisTick: { // 刻度
show: true,
length: 5,
lineStyle: {
color: '#333'
}
},
// 支持多条X轴,用数组
// polar: {}, // 极坐标系的类目轴
},
// Y轴
yAxis: {
type: 'value', // 值轴
position: 'left', // 位置:'left' / 'right'
axisLine: {
lineStyle: {
color: '#333'
}
},
axisLabel: {
color: '#333',
fontSize: 12,
formatter: '{value} 件' // 单位
},
splitLine: {
show: true,
lineStyle: {
color: '#eee',
type: 'dashed'
}
},
// 支持多条Y轴
},
四、常用图表类型详解
4.1 柱状图/条形图
// 基础柱状图
option = {
xAxis: { type: 'category', data: ['A', 'B', 'C', 'D'] },
yAxis: { type: 'value' },
series: [{
type: 'bar',
data: [120, 200, 150, 80],
itemStyle: {
color: function(params) {
// 渐变色
var colorList = ['#5470c6', '#91cc75', '#fac858', '#ee6666'];
return colorList[params.dataIndex];
}
},
// 标签
label: {
show: true,
position: 'top',
formatter: '{c}'
}
}]
};
// 堆叠柱状图
option = {
legend: { data: ['邮件营销', '联盟广告', '视频广告'] },
xAxis: { type: 'category', data: ['周一', '周二', '周三', '周四', '周五'] },
yAxis: { type: 'value' },
series: [
{ name: '邮件营销', type: 'bar', stack: 'total', data: [120, 132, 101, 134, 90] },
{ name: '联盟广告', type: 'bar', stack: 'total', data: [220, 182, 191, 234, 290] },
{ name: '视频广告', type: 'bar', stack: 'total', data: [150, 232, 201, 154, 190] }
]
};
// 分组柱状图
option = {
legend: { data: ['2023年', '2024年'] },
xAxis: { type: 'category', data: ['一月', '二月', '三月', '四月'] },
yAxis: { type: 'value' },
series: [
{ name: '2023年', type: 'bar', data: [820, 932, 901, 934] },
{ name: '2024年', type: 'bar', data: [1290, 1332, 1301, 1334] }
]
};
4.2 折线图
option = {
xAxis: {
type: 'category',
data: ['一月', '二月', '三月', '四月', '五月', '六月']
},
yAxis: { type: 'value' },
series: [{
type: 'line',
data: [820, 932, 901, 934, 1290, 1330],
smooth: true, // 平滑曲线
symbol: 'circle', // 拐点标记
symbolSize: 8,
lineStyle: {
width: 3,
color: '#5470c6'
},
areaStyle: { // 面积图
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(84,112,198,0.5)' },
{ offset: 1, color: 'rgba(84,112,198,0.05)' }
])
}
}]
};
4.3 饼图/环形图
option = {
series: [{
type: 'pie',
radius: ['40%', '70%'], // 环形图
center: ['50%', '50%'],
roseType: 'area', // 南丁格尔图
itemStyle: {
borderRadius: 8 // 圆角
},
label: {
formatter: '{b}: {d}%' // 显示名称和百分比
},
data: [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' },
{ value: 484, name: '联盟广告' },
{ value: 300, name: '视频广告' }
]
}]
};
4.4 散点图
option = {
xAxis: {},
yAxis: {},
series: [{
type: 'scatter',
data: [
[10.0, 8.04],
[8.0, 6.95],
[13.0, 7.58],
[9.0, 8.81],
[11.0, 8.33],
[14.0, 7.66],
[13.0, 8.72],
[9.0, 7.56],
[11.0, 9.32],
[14.0, 8.97],
[6.0, 7.24],
[4.0, 4.26]
],
symbolSize: function(data) {
return data[2] * 5; // 气泡大小
}
}]
};
4.5 仪表盘
option = {
series: [{
type: 'gauge',
startAngle: 180,
endAngle: 0,
min: 0,
max: 100,
splitNumber: 10,
itemStyle: {
color: '#5470c6'
},
progress: {
show: true,
width: 18
},
pointer: {
icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
length: '12%',
width: 20,
offsetCenter: [0, '-60%'],
itemStyle: { color: 'auto' }
},
axisLine: {
lineStyle: {
width: 18,
color: [[0.3, '#67e0e3'], [0.7, '#e6f639'], [1, '#f94d3e']]
}
},
axisTick: { show: false },
splitLine: { length: 15, lineStyle: { width: 2, color: '#999' } },
axisLabel: { distance: 25, color: '#999', fontSize: 12 },
anchor: { show: true, size: 20, itemStyle: { color: 'auto' } },
title: {
offsetCenter: [0, '-40%'],
fontSize: 14
},
detail: {
valueAnimation: true,
fontSize: 30,
offsetCenter: [0, '10%'],
formatter: '{value}%'
},
data: [{ value: 70, name: '健康指数' }]
}]
};
五、进阶技巧:让你的图表更专业
5.1 自适应窗口大小
var myChart = echarts.init(document.getElementById('main'));
// ... 设置option ...
// 监听窗口变化,自动调整图表大小
window.addEventListener('resize', function() {
myChart.resize();
});
5.2 数据动态更新
// 模拟实时数据更新
setInterval(function() {
var data = [];
for (var i = 0; i < 12; i++) {
data.push(Math.round(Math.random() * 500 + 200));
}
myChart.setOption({
series: [{
data: data
}]
});
}, 2000);
5.3 多图联动
// 点击一个图表,联动其他图表
var chart1 = echarts.init(document.getElementById('chart1'));
var chart2 = echarts.init(document.getElementById('chart2'));
chart1.on('click', function(params) {
// 根据点击的类目,更新另一个图表的数据
chart2.setOption({
series: [{
data: getRelatedData(params.name)
}]
});
});
5.4 事件监听
myChart.on('click', function(params) {
console.log(params);
// params包含:
// - componentType: 'series' / 'markLine' / 'markPoint' / 'markArea'
// - seriesType: 系列类型
// - seriesIndex: 系列索引
// - seriesName: 系列名称
// - name: 数据项名称
// - dataIndex: 数据项索引
// - data: 数据
// - value: 数值
// - color: 颜色
});
// 其他常用事件
myChart.on('highlight', function(e) { console.log('highlight'); });
myChart.on('downplay', function(e) { console.log('downplay'); });
myChart.on('legendselectchanged', function(e) { console.log('legend'); });
myChart.on('datazoom', function(e) { console.log('datazoom'); });
myChart.on('datarangeselected', function(e) { console.log('range'); });
5.5 主题和颜色
// 自定义主题
var customTheme = {
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272'],
backgroundColor: 'rgba(0,0,0,0)',
textStyle: {
color: '#333'
},
// ... 更多配置
};
// 注册主题
echarts.registerTheme('myTheme', customTheme);
// 使用主题
var myChart = echarts.init(document.getElementById('main'), 'myTheme');
六、实战项目:搭建数据大屏
好了,基础概念都搞清楚了,咱们来点真正的实战。做一个数据大屏,这才是ECharts的终极战场。
6.1 大屏需求分析
假设你要做一个智慧园区数据大屏,需要展示:
- 园区入驻企业数量及趋势
- 各行业分布
- 实时访客数据
- 能源消耗监测
- 安全预警信息
- 地理位置分布
6.2 项目结构
data-dashboard/
├── index.html
├── css/
│ └── style.css
├── js/
│ ├── echarts.min.js
│ └── main.js
└── data/
└── mock.json
6.3 HTML结构
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>智慧园区数据大屏</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="dashboard">
<!-- 顶部标题栏 -->
<header class="header">
<div class="logo">🏢 智慧园区数据中台</div>
<div class="time" id="currentTime">2024-01-01 00:00:00</div>
</header>
<!-- 主内容区 -->
<main class="main">
<!-- 左侧 -->
<div class="left">
<!-- 入驻企业数量 -->
<div class="card company-count">
<h3>入驻企业数量</h3>
<div class="number" id="companyCount">0</div>
<div class="trend">↑ 12.5% 较去年</div>
</div>
<!-- 各行业分布 -->
<div class="card industry-chart">
<h3>行业分布</h3>
<div id="industryChart" class="chart"></div>
</div>
<!-- 企业入驻趋势 -->
<div class="card trend-chart">
<h3>企业入驻趋势</h3>
<div id="trendChart" class="chart"></div>
</div>
<!-- 访客数据 -->
<div class="card visitor-chart">
<h3>实时访客数据</h3>
<div id="visitorChart" class="chart"></div>
</div>
</div>
<!-- 中间 -->
<div class="center">
<!-- 核心指标 -->
<div class="core-metrics">
<div class="metric">
<div class="label">今日访客</div>
<div class="value" id="todayVisitor">12,580</div>
</div>
<div class="metric">
<div class="label">今日营收</div>
<div class="value" id="todayRevenue">¥580,000</div>
</div>
<div class="metric">
<div class="label">安全评分</div>
<div class="value" id="safetyScore">98.5</div>
</div>
<div class="metric">
<div class="label">能源效率</div>
<div class="value" id="energyEfficiency">92%</div>
</div>
</div>
<!-- 园区地图 -->
<div class="card map-chart">
<div id="mapChart" class="chart"></div>
</div>
</div>
<!-- 右侧 -->
<div class="right">
<!-- 能源消耗 -->
<div class="card energy-chart">
<h3>能源消耗监测</h3>
<div id="energyChart" class="chart"></div>
</div>
<!-- 安全预警 -->
<div class="card alert-chart">
<h3>安全预警信息</h3>
<div id="alertChart" class="chart"></div>
</div>
<!-- 设备状态 -->
<div class="card device-chart">
<h3>设备运行状态</h3>
<div id="deviceChart" class="chart"></div>
</div>
<!-- 排名数据 -->
<div class="card rank-chart">
<h3>企业效益排名</h3>
<div id="rankChart" class="chart"></div>
</div>
</div>
</main>
<!-- 底部信息栏 -->
<footer class="footer">
<span>数据来源:园区管理系统</span>
<span>更新时间:<span id="updateTime">2024-01-01 00:00:00</span></span>
</footer>
</div>
<script src="js/echarts.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
6.4 CSS样式
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: linear-gradient(135deg, #0a1628 0%, #1a2a4a 50%, #0a1628 100%);
color: #fff;
min-height: 100vh;
overflow-x: hidden;
}
.dashboard {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
padding: 10px;
}
/* 顶部标题栏 */
.header {
height: 60px;
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(90deg, transparent, rgba(0,150,255,0.3), transparent);
border-bottom: 2px solid rgba(0,150,255,0.5);
padding: 0 30px;
}
.header .logo {
font-size: 24px;
font-weight: bold;
background: linear-gradient(90deg, #00f2fe, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 20px rgba(0,242,254,0.5);
}
.header .time {
font-size: 16px;
color: #00f2fe;
}
/* 主内容区 */
.main {
flex: 1;
display: flex;
gap: 15px;
margin-top: 15px;
}
.left, .center, .right {
display: flex;
flex-direction: column;
gap: 15px;
}
.left, .right {
width: 25%;
}
.center {
width: 50%;
}
/* 卡片样式 */
.card {
background: rgba(0, 30, 60, 0.6);
border: 1px solid rgba(0, 150, 255, 0.3);
border-radius: 10px;
padding: 15px;
flex: 1;
position: relative;
overflow: hidden;
}
.card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #00f2fe, transparent);
}
.card h3 {
font-size: 14px;
color: #00f2fe;
margin-bottom: 10px;
padding-left: 10px;
border-left: 3px solid #00f2fe;
}
.chart {
width: 100%;
height: calc(100% - 30px);
}
/* 入驻企业数量卡片 */
.company-count {
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
}
.company-count .number {
font-size: 48px;
font-weight: bold;
background: linear-gradient(180deg, #00f2fe, #0084ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.company-count .trend {
font-size: 14px;
color: #66bb6a;
margin-top: 10px;
}
/* 核心指标 */
.core-metrics {
display: flex;
justify-content: space-around;
padding: 15px;
background: rgba(0, 30, 60, 0.6);
border: 1px solid rgba(0, 150, 255, 0.3);
border-radius: 10px;
}
.core-metrics .metric {
text-align: center;
}
.core-metrics .metric .label {
font-size: 12px;
color: #aaa;
margin-bottom: 5px;
}
.core-metrics .metric .value {
font-size: 28px;
font-weight: bold;
color: #00f2fe;
}
/* 地图卡片 */
.map-chart {
flex: 1;
}
.map-chart .chart {
height: 100%;
}
/* 底部 */
.footer {
height: 30px;
display: flex;
justify-content: center;
align-items: center;
gap: 50px;
font-size: 12px;
color: #666;
border-top: 1px solid rgba(0, 150, 255, 0.2);
margin-top: 10px;
}
6.5 JavaScript主逻辑
/**
* 智慧园区数据大屏 - 主逻辑
* @author: 你的名字
* @date: 2024-01-01
*/
// 模拟数据
const mockData = {
companyCount: 1286,
industryData: [
{ value: 335, name: '信息技术' },
{ value: 310, name: '金融服务' },
{ value: 234, name: '文化创意' },
{ value: 180, name: '生物医药' },
{ value: 148, name: '教育培训' },
{ value: 95, name: '其他' }
],
trendData: {
categories: ['2019', '2020', '2021', '2022', '2023', '2024'],
newCompanies: [280, 320, 390, 450, 520, 1286],
exitCompanies: [50, 60, 45, 38, 42, 12]
},
visitorData: {
hours: ['08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00'],
today: [120, 580, 920, 850, 760, 650, 320],
yesterday: [100, 520, 850, 780, 690, 580, 280]
},
energyData: {
categories: ['1月', '2月', '3月', '4月', '5月', '6月'],
electricity: [85, 82, 78, 75, 80, 88],
water: [45, 42, 40, 38, 42, 48],
gas: [30, 28, 25, 22, 25, 32]
},
deviceData: {
total: 1580,
online: 1520,
offline: 35,
alarm: 25
},
alertData: [
{ name: '消防预警', value: 3, level: 'high' },
{ name: '设备故障', value: 8, level: 'medium' },
{ name: '能源异常', value: 5, level: 'low' },
{ name: '安防告警', value: 2, level: 'medium' },
{ name: '环境异常', value: 1, level: 'low' }
],
rankData: [
{ name: '科技创新有限公司', value: 9850 },
{ name: '金融服务有限公司', value: 8720 },
{ name: '文化创意有限公司', value: 7650 },
{ name: '生物医药有限公司', value: 6580 },
{ name: '教育科技有限公司', value: 5420 }
]
};
/**
* 初始化所有图表
*/
function initDashboard() {
// 初始化各个图表
initCompanyCount();
initIndustryChart();
initTrendChart();
initVisitorChart();
initMapChart();
initEnergyChart();
initAlertChart();
initDeviceChart();
initRankChart();
// 启动实时更新
startRealTimeUpdate();
// 更新时间显示
updateTime();
setInterval(updateTime, 1000);
}
/**
* 入驻企业数量 - 数字滚动动画
*/
function initCompanyCount() {
const target = mockData.companyCount;
const element = document.getElementById('companyCount');
let current = 0;
const step = Math.ceil(target / 60);
const timer = setInterval(() => {
current += step;
if (current >= target) {
current = target;
clearInterval(timer);
}
element.textContent = current.toLocaleString();
}, 30);
}
/**
* 行业分布 - 饼图
*/
function initIndustryChart() {
const chart = echarts.init(document.getElementById('industryChart'));
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c}家 ({d}%)'
},
legend: {
orient: 'vertical',
right: '5%',
top: 'center',
textStyle: {
color: '#aaa',
fontSize: 11
}
},
series: [{
type: 'pie',
radius: ['35%', '65%'],
center: ['40%', '50%'],
roseType: 'area',
itemStyle: {
borderRadius: 5
},
label: {
show: false
},
data: mockData.industryData,
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272']
}]
};
chart.setOption(option);
// 响应式
window.addEventListener('resize', () => chart.resize());
}
/**
* 企业入驻趋势 - 折线图
*/
function initTrendChart() {
const chart = echarts.init(document.getElementById('trendChart'));
const option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['新增企业', '退出企业'],
textStyle: { color: '#aaa' },
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: mockData.trendData.categories,
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: mockData.trendData.newCompanies,
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#5470c6', width: 2 },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(84,112,198,0.3)' },
{ offset: 1, color: 'rgba(84,112,198,0)' }
])
}
},
{
name: '退出企业',
type: 'line',
data: mockData.trendData.exitCompanies,
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#ee6666', width: 2 },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(238,102,102,0.3)' },
{ offset: 1, color: 'rgba(238,102,102,0)' }
])
}
}
]
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
}
/**
* 实时访客数据 - 柱状图
*/
function initVisitorChart() {
const chart = echarts.init(document.getElementById('visitorChart'));
const option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['今日', '昨日'],
textStyle: { color: '#aaa' },
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: mockData.visitorData.hours,
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa', fontSize: 10 }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { color: '#222' } }
},
series: [
{
name: '今日',
type: 'bar',
data: mockData.visitorData.today,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#00f2fe' },
{ offset: 1, color: '#0084ff' }
])
}
},
{
name: '昨日',
type: 'bar',
data: mockData.visitorData.yesterday,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(0,242,254,0.5)' },
{ offset: 1, color: 'rgba(0,132,255,0.2)' }
])
}
}
]
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
}
/**
* 园区地图 - 散点/气泡图
* 注意:实际项目需要引入地图JSON数据
*/
function initMapChart() {
const chart = echarts.init(document.getElementById('mapChart'));
// 模拟园区建筑位置
const buildingData = [
{ name: 'A栋', value: [20, 80], count: 156 },
{ name: 'B栋', value: [35, 65], count: 203 },
{ name: 'C栋', value: [50, 75], count: 178 },
{ name: 'D栋', value: [65, 60], count: 245 },
{ name: 'E栋', value: [80, 70], count: 132 },
{ name: 'F栋', value: [30, 40], count: 189 },
{ name: 'G栋', value: [55, 45], count: 167 },
{ name: 'H栋', value: [75, 50], count: 145 }
];
const option = {
tooltip: {
trigger: 'item',
formatter: function(params) {
if (params.componentType === 'series') {
return `${params.name}<br/>入驻企业:${params.data.count}家`;
}
return params.name;
}
},
grid: {
left: '5%',
right: '5%',
top: '10%',
bottom: '5%',
containLabel: true
},
xAxis: {
show: false,
min: 0,
max: 100
},
yAxis: {
show: false,
min: 0,
max: 100
},
series: [
// 建筑位置
{
type: 'scatter',
symbolSize: function(data) {
return Math.sqrt(data.count) * 3;
},
data: buildingData,
itemStyle: {
color: '#00f2fe',
shadowBlur: 10,
shadowColor: '#00f2fe'
},
label: {
show: true,
formatter: '{b}',
position: 'top',
color: '#fff',
fontSize: 10
}
},
// 连接线条(模拟道路)
{
type: 'lines',
coordinateSystem: 'cartesian2d',
polyline: false,
lineStyle: {
color: '#00f2fe',
width: 1,
opacity: 0.3
},
data: [
[{coord: [20, 80]}, {coord: [35, 65]}],
[{coord: [35, 65]}, {coord: [50, 75]}],
[{coord: [50, 75]}, {coord: [65, 60]}],
[{coord: [65, 60]}, {coord: [80, 70]}],
[{coord: [30, 40]}, {coord: [55, 45]}],
[{coord: [55, 45]}, {coord: [75, 50]}],
[{coord: [35, 65]}, {coord: [30, 40]}],
[{coord: [50, 75]}, {coord: [55, 45]}],
[{coord: [65, 60]}, {coord: [75, 50]}]
]
}
]
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
}
/**
* 能源消耗 - 堆叠柱状图
*/
function initEnergyChart() {
const chart = echarts.init(document.getElementById('energyChart'));
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
legend: {
data: ['电力', '水务', '燃气'],
textStyle: { color: '#aaa' },
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: mockData.energyData.categories,
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { color: '#222' } }
},
series: [
{
name: '电力',
type: 'bar',
stack: 'total',
data: mockData.energyData.electricity,
itemStyle: { color: '#5470c6' }
},
{
name: '水务',
type: 'bar',
stack: 'total',
data: mockData.energyData.water,
itemStyle: { color: '#91cc75' }
},
{
name: '燃气',
type: 'bar',
stack: 'total',
data: mockData.energyData.gas,
itemStyle: { color: '#fac858' }
}
]
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
}
/**
* 安全预警 - 横向柱状图
*/
function initAlertChart() {
const chart = echarts.init(document.getElementById('alertChart'));
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
grid: {
left: '3%',
right: '15%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { color: '#222' } }
},
yAxis: {
type: 'category',
data: mockData.alertData.map(item => item.name).reverse(),
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa' }
},
series: [{
type: 'bar',
data: mockData.alertData.map(item => item.value).reverse(),
itemStyle: {
color: function(params) {
const colors = ['#ee6666', '#fac858', '#91cc75'];
return colors[params.dataIndex % 3];
}
},
label: {
show: true,
position: 'right',
color: '#fff'
}
}]
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
}
/**
* 设备状态 - 仪表盘
*/
function initDeviceChart() {
const chart = echarts.init(document.getElementById('deviceChart'));
const onlineRate = (mockData.deviceData.online / mockData.deviceData.total * 100).toFixed(1);
const option = {
series: [
{
type: 'gauge',
startAngle: 180,
endAngle: 0,
min: 0,
max: 100,
splitNumber: 5,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#ee6666' },
{ offset: 0.5, color: '#fac858' },
{ offset: 1, color: '#91cc75' }
])
},
progress: {
show: true,
width: 18
},
pointer: {
icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
length: '60%',
width: 10,
offsetCenter: [0, '-60%'],
itemStyle: { color: 'auto' }
},
axisLine: {
lineStyle: { width: 18 }
},
axisTick: { show: false },
splitLine: { length: 10, lineStyle: { width: 2, color: '#999' } },
axisLabel: { distance: 20, color: '#999', fontSize: 10 },
anchor: {
show: true,
size: 15,
itemStyle: { color: 'auto' }
},
title: {
offsetCenter: [0, '-30%'],
fontSize: 12,
color: '#aaa'
},
detail: {
valueAnimation: true,
fontSize: 24,
offsetCenter: [0, '10%'],
formatter: '{value}%',
color: '#00f2fe'
},
data: [{
value: onlineRate,
name: '在线率'
}]
},
// 辅助信息
{
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '75%'],
avoidLabelOverlap: false,
label: { show: false },
emphasis: { label: { show: false } },
data: [
{ value: mockData.deviceData.online, name: '在线', itemStyle: { color: '#91cc75' } },
{ value: mockData.deviceData.offline, name: '离线', itemStyle: { color: '#aaa' } },
{ value: mockData.deviceData.alarm, name: '告警', itemStyle: { color: '#ee6666' } }
]
}
]
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
}
/**
* 企业效益排名 - 横向柱状图
*/
function initRankChart() {
const chart = echarts.init(document.getElementById('rankChart'));
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
grid: {
left: '3%',
right: '15%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa', formatter: '{value}万' },
splitLine: { lineStyle: { color: '#222' } }
},
yAxis: {
type: 'category',
data: mockData.rankData.map(item => item.name).reverse(),
axisLine: { lineStyle: { color: '#333' } },
axisLabel: { color: '#aaa', fontSize: 10 }
},
series: [{
type: 'bar',
data: mockData.rankData.map(item => item.value).reverse(),
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#0084ff' },
{ offset: 1, color: '#00f2fe' }
])
},
label: {
show: true,
position: 'right',
color: '#fff',
formatter: '{c}万'
}
}]
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
}
/**
* 更新时间显示
*/
function updateTime() {
const now = new Date();
const timeStr = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
document.getElementById('currentTime').textContent = timeStr;
document.getElementById('updateTime').textContent = timeStr;
}
/**
* 启动实时更新
*/
function startRealTimeUpdate() {
// 每秒更新访客数据
setInterval(() => {
const visitorChart = echarts.getInstanceByDom(document.getElementById('visitorChart'));
if (visitorChart) {
const newData = mockData.visitorData.today.map(v =>
Math.max(0, v + Math.round((Math.random() - 0.5) * 20))
);
visitorChart.setOption({
series: [{ data: newData }]
});
}
}, 3000);
// 每分钟更新安全评分
setInterval(() => {
const score = (95 + Math.random() * 5).toFixed(1);
document.getElementById('safetyScore').textContent = score;
}, 60000);
}
/**
* 页面加载完成后初始化
*/
document.addEventListener('DOMContentLoaded', initDashboard);
6.6 数据接口对接
实际项目中,数据来自后端API。下面是数据请求的封装:
/**
* 数据服务层
*/
const DataService = {
// 基础URL
baseUrl: '/api/dashboard',
// 请求方法封装
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const defaultOptions = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
};
try {
const response = await fetch(url, { ...defaultOptions, ...options });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('数据请求失败:', error);
throw error;
}
},
// 获取园区概览数据
async getOverview() {
return this.request('/overview');
},
// 获取企业数据
async getCompanies() {
return this.request('/companies');
},
// 获取访客数据
async getVisitors(params = {}) {
const query = new URLSearchParams(params).toString();
return this.request(`/visitors?${query}`);
},
// 获取能源数据
async getEnergy(params = {}) {
const query = new URLSearchParams(params).toString();
return this.request(`/energy?${query}`);
},
// 获取设备数据
async getDevices() {
return this.request('/devices');
},
// 获取预警数据
async getAlerts() {
return this.request('/alerts');
},
// 获取排名数据
async getRanking(params = {}) {
const query = new URLSearchParams(params).toString();
return this.request(`/ranking?${query}`);
}
};
/**
* 使用数据服务更新图表
*/
async function loadDataAndRefresh() {
try {
// 并发请求,提高效率
const [overview, companies, visitors, energy, devices, alerts, ranking] = await Promise.all([
DataService.getOverview(),
DataService.getCompanies(),
DataService.getVisitors({ hours: 24 }),
DataService.getEnergy({ period: 'month' }),
DataService.getDevices(),
DataService.getAlerts(),
DataService.getRanking({ limit: 10 })
]);
// 更新图表
updateCompanyCount(overview.companyCount);
updateIndustryChart(companies.industryDistribution);
updateVisitorChart(visitors.hourlyData);
updateEnergyChart(energy.monthlyData);
updateDeviceChart(devices.status);
updateAlertChart(alerts.list);
updateRankChart(ranking.list);
} catch (error) {
console.error('加载数据失败:', error);
// 显示错误提示
showError('数据加载失败,请稍后重试');
}
}
七、常见问题与解决方案
7.1 图表不显示
症状:页面渲染了,但图表区域空白。
排查步骤:
// 1. 检查DOM是否存在
console.log(document.getElementById('main'));
// 2. 检查尺寸
const dom = document.getElementById('main');
console.log(dom.offsetWidth, dom.offsetHeight);
// 3. 检查初始化
var chart = echarts.init(dom);
console.log(chart);
// 4. 检查option
chart.setOption(option);
常见原因:
- 容器没有设置宽高
- DOM元素未渲染完成就初始化
- ECharts未正确引入
7.2 图表自适应
症状:窗口大小改变后,图表不变。
// 方法一:监听resize事件
window.addEventListener('resize', () => {
chart.resize();
});
// 方法二:使用ResizeObserver(更现代)
const resizeObserver = new ResizeObserver(() => {
chart.resize();
});
resizeObserver.observe(chartDom);
// 方法三:在ECharts 5.x中,可以在init时传入{resize: true}
var chart = echarts.init(dom, null, { resize: true });
7.3 性能优化
症状:数据量大时,图表卡顿。
优化策略:
// 1. 减少数据量 - 采样
function sampleData(data, step) {
return data.filter((_, index) => index % step === 0);
}
// 2. 使用dataZoom组件
option = {
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
},
{
start: 0,
end: 100
}
]
};
// 3. 关闭不必要的动画
option = {
animation: false, // 关闭全局动画
// 或针对单个系列
series: [{
animation: false
}]
};
// 4. 按需加载
import 'echarts/lib/chart/bar';
import 'echarts/lib/component/tooltip';
// 不引入用不到的模块
// 5. 使用Canvas代替SVG
var chart = echarts.init(dom, null, { renderer: 'canvas' });
7.4 主题切换
// 自定义主题
const darkTheme = {
backgroundColor: '#1a1a2e',
textStyle: { color: '#fff' },
// ... 更多配置
};
echarts.registerTheme('dark', darkTheme);
// 切换主题
chart.dispose(); // 销毁旧实例
chart = echarts.init(dom, 'dark'); // 用新主题重新初始化
八、进阶玩法:让你的大屏更上一层楼
8.1 3D地球
// 需要引入 echarts-gl
import 'echarts-gl';
const option = {
globe: {
shading: 'lambert',
light: {
ambient: { intensity: 0.3 },
main: { intensity: 1.2 }
},
viewControl: {
autoRotate: true,
distance: 150
}
},
series: [{
type: 'lines3D',
data: [
{ coords: [[116.4, 39.9], [121.4, 31.2]] },
{ coords: [[116.4, 39.9], [113.2, 23.1]] }
],
lineStyle: {
width: 2,
color: '#00f2fe'
}
}]
};
8.2 动态效果
// 流光效果
series: [{
type: 'lines',
effect: {
show: true,
period: 4,
trailLength: 0.1,
symbol: 'arrow',
symbolSize: 5,
color: '#00f2fe'
},
lineStyle: {
color: '#00f2fe',
width: 1,
opacity: 0.4
},
data: [...]
}]
// 波纹效果
series: [{
type: 'effectScatter',
symbolSize: 20,
rippleEffect: {
period: 4,
brushType: 'stroke',
scale: 3
},
data: [...]
}]
8.3 与后端数据对接的完整流程
/**
* 完整的数据流管理
*/
class DashboardController {
constructor() {
this.charts = {};
this.refreshInterval = null;
this.ws = null;
}
init() {
this.initCharts();
this.loadData();
this.connectWebSocket();
this.setupEventListeners();
}
initCharts() {
// 初始化所有图表
this.charts.industry = echarts.init(document.getElementById('industryChart'));
this.charts.trend = echarts.init(document.getElementById('trendChart'));
// ... 其他图表
}
async loadData() {
try {
const data = await DataService.getOverview();
this.updateCharts(data);
} catch (error) {
console.error('数据加载失败', error);
}
}
connectWebSocket() {
this.ws = new WebSocket('wss://api.example.com/dashboard');
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleRealTimeData(data);
};
this.ws.onclose = () => {
// 断线重连
setTimeout(() => this.connectWebSocket(), 3000);
};
}
handleRealTimeData(data) {
if (data.type === 'visitor') {
this.updateVisitorChart(data.value);
} else if (data.type === 'alert') {
this.showAlertNotification(data.message);
}
}
showAlertNotification(message) {
// 显示通知
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
updateCharts(data) {
// 更新各个图表
this.charts.industry.setOption(this.getIndustryOption(data.industry));
this.charts.trend.setOption(this.getTrendOption(data.trend));
// ...
}
setupEventListeners() {
// 监听窗口变化
window.addEventListener('resize', () => {
Object.values(this.charts).forEach(chart => chart.resize());
});
// 监听页面可见性变化,节省资源
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.pauseRefresh();
} else {
this.resumeRefresh();
}
});
}
pauseRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
}
resumeRefresh() {
this.loadData();
this.refreshInterval = setInterval(() => this.loadData(), 60000);
}
}
// 启动
const controller = new DashboardController();
controller.init();
九、总结:从入门到精通的路径
好了,这篇教程到这里就差不多了。我帮你梳理一下学习路径:
第一阶段:基础入门
- 学会引入ECharts
- 理解option配置结构
- 掌握3-5种基础图表
第二阶段:进阶提升
- 深入理解各个组件的配置
- 学会事件监听和交互
- 掌握数据动态更新
第三阶段:实战项目
- 独立完成一个数据大屏
- 学会与后端对接数据
- 掌握性能优化技巧
第四阶段:高级应用
- 3D可视化
- 自定义主题和样式
- 复杂交互设计
记住,学习ECharts最好的方式就是动手做项目。不要只看教程,要自己去写代码,去调试,去犯错,然后解决问题。我的经验告诉我,做三个完整的大屏项目,你就基本可以称之为”ECharts高手”了。
最后送你一句话:好的数据可视化,不只是让数据好看,更是让数据会说话。 希望这篇教程能帮你迈出第一步!
如果你在学习过程中遇到问题,别不好意思,多去ECharts官方文档看看,社区的力量是无穷的。加油!
