ECharts - 数据可视化图表库
ECharts是一个功能强大的数据可视化图表库,适合在Vue项目中集成使用。本文介绍了ECharts的核心概念、安装方法以及在Vue3中的基础使用模式,重点展示了折线图和柱状图两种常用图表类型的实现方式。折线图示例包含平滑曲线、区域填充和响应式布局等特性,适用于展示数据趋势;柱状图则适合数据对比分析。ECharts支持20+种图表类型,具备优秀的性能和丰富的交互功能,能够满足各种数据可视化需求,是
·
ECharts - 数据可视化图表库
学习目标
完成本章学习后,你将能够:
- 理解ECharts的核心概念和使用场景
- 在Vue 3项目中集成和使用ECharts
- 创建常用的图表类型(折线图、柱状图、饼图等)
- 实现响应式图表和数据动态更新
- 掌握图表交互和主题定制
前置知识
学习本章内容前,你需要掌握:
问题引入
实际场景
小李在开发一个数据分析后台时,产品经理提出了以下需求:
- 销售数据展示:需要用折线图展示最近30天的销售趋势
- 产品占比分析:需要用饼图展示各产品的销售占比
- 地区对比:需要用柱状图对比不同地区的销售额
- 实时更新:图表数据需要每5秒自动刷新
- 响应式布局:图表需要适配不同屏幕尺寸
- 交互功能:鼠标悬停显示详细数据,点击图例筛选数据
小李想知道:有没有成熟的图表库可以快速实现这些需求?
为什么选择ECharts
ECharts是百度开源的一个强大的数据可视化库,具有以下优势:
- 功能强大:支持20+种图表类型,满足各种数据展示需求
- 性能优秀:基于Canvas渲染,支持大数据量展示
- 交互丰富:内置缩放、拖拽、高亮等交互功能
- 主题丰富:提供多种内置主题,支持自定义
- 文档完善:中英文文档齐全,示例丰富
- 社区活跃:GitHub 60k+ stars,持续维护更新
- Vue集成简单:有官方的Vue组件封装
核心概念
概念1:ECharts基本结构
ECharts图表由以下核心部分组成:
1. 图表容器(Container)
需要一个DOM元素作为图表的容器:
<div id="chart" style="width: 600px; height: 400px;"></div>
2. 图表实例(Instance)
通过echarts.init()创建图表实例:
const chart = echarts.init(document.getElementById('chart'));
3. 配置项(Option)
配置项定义了图表的所有内容:
const option = {
title: { text: '销售趋势' }, // 标题
xAxis: { data: ['周一', '周二'] }, // X轴
yAxis: {}, // Y轴
series: [{ // 数据系列
type: 'line',
data: [120, 200]
}]
};
4. 渲染图表
使用setOption()方法渲染图表:
chart.setOption(option);
概念2:在Vue 3中使用ECharts
安装ECharts
# 使用pnpm(推荐)
pnpm add echarts
# 使用npm
npm install echarts
# 使用yarn
yarn add echarts
基础使用模式
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
let chartInstance = null;
onMounted(() => {
// 初始化图表
chartInstance = echarts.init(chartRef.value);
// 配置图表
const option = {
// 图表配置
};
chartInstance.setOption(option);
});
onUnmounted(() => {
// 销毁图表实例,释放资源
if (chartInstance) {
chartInstance.dispose();
}
});
</script>
<template>
<div ref="chartRef" style="width: 600px; height: 400px;"></div>
</template>
常用图表类型
1. 折线图(Line Chart)
作用:展示数据随时间或类别的变化趋势
使用场景:
- 销售趋势分析
- 温度变化曲线
- 股票价格走势
完整示例
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
let chartInstance = null;
// 模拟销售数据
const salesData = {
dates: ['1月', '2月', '3月', '4月', '5月', '6月', '7月'],
sales: [120, 200, 150, 280, 210, 300, 350],
target: [150, 180, 200, 250, 280, 320, 360]
};
onMounted(() => {
// 初始化图表
chartInstance = echarts.init(chartRef.value);
// 配置折线图
const option = {
// 标题
title: {
text: '2024年销售趋势',
left: 'center',
textStyle: {
fontSize: 18,
fontWeight: 'bold'
}
},
// 提示框
tooltip: {
trigger: 'axis', // 坐标轴触发
axisPointer: {
type: 'cross' // 十字准星指示器
}
},
// 图例
legend: {
data: ['实际销售', '目标销售'],
top: 30
},
// 网格
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true // 包含坐标轴标签
},
// X轴
xAxis: {
type: 'category',
data: salesData.dates,
boundaryGap: false // 坐标轴两边不留白
},
// Y轴
yAxis: {
type: 'value',
name: '销售额(万元)'
},
// 数据系列
series: [
{
name: '实际销售',
type: 'line',
data: salesData.sales,
smooth: true, // 平滑曲线
itemStyle: {
color: '#42b883' // 线条颜色
},
areaStyle: { // 区域填充
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(66, 184, 131, 0.3)' },
{ offset: 1, color: 'rgba(66, 184, 131, 0.05)' }
]
}
}
},
{
name: '目标销售',
type: 'line',
data: salesData.target,
smooth: true,
itemStyle: {
color: '#ff6b6b'
},
lineStyle: {
type: 'dashed' // 虚线
}
}
]
};
chartInstance.setOption(option);
// 响应式调整
window.addEventListener('resize', () => {
chartInstance.resize();
});
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
}
window.removeEventListener('resize', () => {});
});
</script>
<template>
<div class="chart-container">
<div ref="chartRef" class="chart"></div>
</div>
</template>
<style scoped>
.chart-container {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.chart {
width: 100%;
height: 400px;
}
</style>
运行结果:
- 显示两条平滑的折线(实际销售和目标销售)
- 实际销售线有渐变填充区域
- 目标销售线为虚线
- 鼠标悬停显示详细数据
- 窗口大小改变时图表自动调整
2. 柱状图(Bar Chart)
作用:对比不同类别的数据大小
使用场景:
- 地区销售对比
- 产品销量排名
- 年度数据对比
完整示例
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
let chartInstance = null;
// 模拟地区销售数据
const regionData = {
regions: ['北京', '上海', '广州', '深圳', '杭州', '成都'],
sales: [320, 380, 290, 410, 260, 310],
growth: [12, 18, -5, 22, 8, 15] // 增长率
};
onMounted(() => {
chartInstance = echarts.init(chartRef.value);
const option = {
title: {
text: '各地区销售额对比',
subtext: '2024年第一季度',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow' // 阴影指示器
},
formatter: (params) => {
// 自定义提示框内容
const region = params[0].name;
const sales = params[0].value;
const growth = regionData.growth[params[0].dataIndex];
return `
<div style="padding: 5px;">
<strong>${region}</strong><br/>
销售额:${sales}万元<br/>
增长率:<span style="color: ${growth > 0 ? '#42b883' : '#ff6b6b'}">
${growth > 0 ? '+' : ''}${growth}%
</span>
</div>
`;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: regionData.regions,
axisLabel: {
interval: 0, // 显示所有标签
rotate: 0 // 标签旋转角度
}
},
yAxis: {
type: 'value',
name: '销售额(万元)'
},
series: [
{
name: '销售额',
type: 'bar',
data: regionData.sales,
// 根据增长率设置柱子颜色
itemStyle: {
color: (params) => {
const growth = regionData.growth[params.dataIndex];
return growth > 0 ? '#42b883' : '#ff6b6b';
}
},
// 显示数值标签
label: {
show: true,
position: 'top',
formatter: '{c}万'
},
// 柱子宽度
barWidth: '50%'
}
]
};
chartInstance.setOption(option);
window.addEventListener('resize', () => {
chartInstance.resize();
});
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
}
window.removeEventListener('resize', () => {});
});
</script>
<template>
<div class="chart-container">
<div ref="chartRef" class="chart"></div>
</div>
</template>
<style scoped>
.chart-container {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.chart {
width: 100%;
height: 400px;
}
</style>
运行结果:
- 显示6个地区的销售额柱状图
- 增长为正的柱子显示绿色,负增长显示红色
- 柱子顶部显示具体数值
- 鼠标悬停显示详细信息(包含增长率)
3. 饼图(Pie Chart)
作用:展示各部分占总体的比例
使用场景:
- 产品销售占比
- 用户来源分析
- 预算分配展示
完整示例
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
let chartInstance = null;
// 模拟产品销售数据
const productData = [
{ name: '手机', value: 3500, color: '#5470c6' },
{ name: '电脑', value: 2800, color: '#91cc75' },
{ name: '平板', value: 1800, color: '#fac858' },
{ name: '耳机', value: 1200, color: '#ee6666' },
{ name: '其他', value: 700, color: '#73c0de' }
];
onMounted(() => {
chartInstance = echarts.init(chartRef.value);
const option = {
title: {
text: '产品销售占比',
subtext: '2024年第一季度',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c}万元 ({d}%)'
},
legend: {
orient: 'vertical', // 垂直布局
left: 'left',
top: 'middle'
},
series: [
{
name: '销售额',
type: 'pie',
radius: ['40%', '70%'], // 环形图(内外半径)
center: ['60%', '50%'], // 图表位置
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 10, // 圆角
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}\n{d}%' // 显示名称和百分比
},
emphasis: { // 高亮样式
label: {
show: true,
fontSize: 16,
fontWeight: 'bold'
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
data: productData.map(item => ({
value: item.value,
name: item.name,
itemStyle: { color: item.color }
}))
}
]
};
chartInstance.setOption(option);
window.addEventListener('resize', () => {
chartInstance.resize();
});
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
}
window.removeEventListener('resize', () => {});
});
</script>
<template>
<div class="chart-container">
<div ref="chartRef" class="chart"></div>
</div>
</template>
<style scoped>
.chart-container {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.chart {
width: 100%;
height: 400px;
}
</style>
运行结果:
- 显示环形饼图,展示各产品销售占比
- 左侧显示图例
- 每个扇区显示名称和百分比
- 鼠标悬停时扇区放大并显示详细信息
4. 动态更新图表
作用:实时更新图表数据
使用场景:
- 实时监控数据
- 定时刷新报表
- 用户交互更新
完整示例
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
let chartInstance = null;
let updateTimer = null;
// 初始数据
const data = ref({
time: [],
value: []
});
// 生成随机数据
const generateData = () => {
const now = new Date();
const timeStr = `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
const value = Math.floor(Math.random() * 100) + 50;
// 保留最近20个数据点
data.value.time.push(timeStr);
data.value.value.push(value);
if (data.value.time.length > 20) {
data.value.time.shift();
data.value.value.shift();
}
};
// 更新图表
const updateChart = () => {
if (!chartInstance) return;
const option = {
title: {
text: '实时数据监控',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: data.value.time,
boundaryGap: false
},
yAxis: {
type: 'value',
name: '数值',
min: 0,
max: 200
},
series: [
{
name: '实时数据',
type: 'line',
data: data.value.value,
smooth: true,
symbol: 'circle',
symbolSize: 6,
itemStyle: {
color: '#42b883'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(66, 184, 131, 0.5)' },
{ offset: 1, color: 'rgba(66, 184, 131, 0.1)' }
]
}
}
}
]
};
chartInstance.setOption(option);
};
// 开始自动更新
const startAutoUpdate = () => {
updateTimer = setInterval(() => {
generateData();
updateChart();
}, 2000); // 每2秒更新一次
};
// 停止自动更新
const stopAutoUpdate = () => {
if (updateTimer) {
clearInterval(updateTimer);
updateTimer = null;
}
};
onMounted(() => {
chartInstance = echarts.init(chartRef.value);
// 初始化一些数据
for (let i = 0; i < 10; i++) {
generateData();
}
updateChart();
startAutoUpdate();
window.addEventListener('resize', () => {
chartInstance.resize();
});
});
onUnmounted(() => {
stopAutoUpdate();
if (chartInstance) {
chartInstance.dispose();
}
window.removeEventListener('resize', () => {});
});
</script>
<template>
<div class="chart-container">
<div class="controls">
<button @click="startAutoUpdate" class="btn">▶️ 开始</button>
<button @click="stopAutoUpdate" class="btn">⏸️ 暂停</button>
<span class="status">
最新数值:{{ data.value[data.value.length - 1] || 0 }}
</span>
</div>
<div ref="chartRef" class="chart"></div>
</div>
</template>
<style scoped>
.chart-container {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.controls {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 15px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 5px;
background: #42b883;
color: white;
cursor: pointer;
font-size: 14px;
}
.btn:hover {
background: #35a372;
}
.status {
margin-left: auto;
font-weight: bold;
color: #42b883;
}
.chart {
width: 100%;
height: 400px;
}
</style>
运行结果:
- 图表每2秒自动更新一次
- 显示最近20个数据点
- 可以暂停和继续更新
- 实时显示最新数值
最佳实践
企业级应用场景
场景1:创建可复用的图表组件
在企业应用中,通常需要创建可复用的图表组件:
<!-- components/BaseChart.vue -->
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts';
/**
* 通用图表组件
* @param {Object} option - ECharts配置项
* @param {String} theme - 主题名称
* @param {Number} height - 图表高度
*/
const props = defineProps({
option: {
type: Object,
required: true
},
theme: {
type: String,
default: 'default'
},
height: {
type: Number,
default: 400
}
});
const chartRef = ref(null);
let chartInstance = null;
// 初始化图表
const initChart = () => {
if (!chartRef.value) return;
chartInstance = echarts.init(chartRef.value, props.theme);
chartInstance.setOption(props.option);
// 响应式调整
window.addEventListener('resize', handleResize);
};
// 处理窗口大小变化
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// 监听配置变化
watch(() => props.option, (newOption) => {
if (chartInstance) {
chartInstance.setOption(newOption, true); // true表示不合并
}
}, { deep: true });
onMounted(() => {
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
}
window.removeEventListener('resize', handleResize);
});
// 暴露图表实例,供父组件使用
defineExpose({
getInstance: () => chartInstance
});
</script>
<template>
<div
ref="chartRef"
class="base-chart"
:style="{ height: height + 'px' }"
></div>
</template>
<style scoped>
.base-chart {
width: 100%;
}
</style>
使用可复用组件
<script setup>
import { ref, computed } from 'vue';
import BaseChart from '@/components/BaseChart.vue';
const salesData = ref([120, 200, 150, 280, 210, 300, 350]);
// 计算图表配置
const chartOption = computed(() => ({
title: { text: '销售趋势' },
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月']
},
yAxis: { type: 'value' },
series: [{
type: 'line',
data: salesData.value,
smooth: true
}]
}));
// 更新数据
const updateData = () => {
salesData.value = salesData.value.map(() =>
Math.floor(Math.random() * 300) + 100
);
};
</script>
<template>
<div>
<button @click="updateData">更新数据</button>
<BaseChart :option="chartOption" :height="400" />
</div>
</template>
优势:
- 代码复用,减少重复
- 统一管理图表生命周期
- 自动处理响应式和销毁
- 易于维护和扩展
场景2:多图表联动
实现多个图表之间的数据联动:
<script setup>
import { ref, computed } from 'vue';
import BaseChart from '@/components/BaseChart.vue';
// 选中的地区
const selectedRegion = ref(null);
// 地区数据
const regionData = ref([
{ name: '北京', sales: 320, products: [80, 120, 60, 60] },
{ name: '上海', sales: 380, products: [100, 140, 80, 60] },
{ name: '广州', sales: 290, products: [70, 100, 70, 50] },
{ name: '深圳', sales: 410, products: [110, 150, 90, 60] }
]);
// 地区销售柱状图配置
const regionChartOption = computed(() => ({
title: { text: '各地区销售额' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: regionData.value.map(r => r.name)
},
yAxis: { type: 'value' },
series: [{
type: 'bar',
data: regionData.value.map(r => r.sales),
itemStyle: {
color: (params) => {
return params.name === selectedRegion.value ? '#42b883' : '#5470c6';
}
}
}]
}));
// 产品销售饼图配置
const productChartOption = computed(() => {
const region = regionData.value.find(r => r.name === selectedRegion.value);
const products = region ? region.products : [0, 0, 0, 0];
return {
title: {
text: selectedRegion.value ? `${selectedRegion.value}产品销售` : '请选择地区'
},
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
radius: '60%',
data: [
{ name: '手机', value: products[0] },
{ name: '电脑', value: products[1] },
{ name: '平板', value: products[2] },
{ name: '其他', value: products[3] }
]
}]
};
});
// 处理地区点击
const handleRegionClick = (region) => {
selectedRegion.value = region;
};
</script>
<template>
<div class="dashboard">
<div class="chart-row">
<div class="chart-card">
<BaseChart
:option="regionChartOption"
:height="300"
@click="handleRegionClick"
/>
</div>
<div class="chart-card">
<BaseChart
:option="productChartOption"
:height="300"
/>
</div>
</div>
<div class="region-buttons">
<button
v-for="region in regionData"
:key="region.name"
@click="handleRegionClick(region.name)"
:class="{ active: selectedRegion === region.name }"
>
{{ region.name }}
</button>
</div>
</div>
</template>
<style scoped>
.dashboard {
padding: 20px;
}
.chart-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
margin-bottom: 20px;
}
.chart-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.region-buttons {
display: flex;
gap: 10px;
justify-content: center;
}
.region-buttons button {
padding: 10px 20px;
border: 2px solid #ddd;
border-radius: 5px;
background: white;
cursor: pointer;
transition: all 0.3s;
}
.region-buttons button.active {
background: #42b883;
color: white;
border-color: #42b883;
}
</style>
优势:
- 多图表数据联动
- 交互体验流畅
- 数据关联清晰
常见陷阱
陷阱1:忘记销毁图表实例
错误示例:
<script setup>
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
onMounted(() => {
// ❌ 错误:没有保存实例引用,无法销毁
echarts.init(chartRef.value).setOption({
// 配置...
});
});
// 组件卸载时,图表实例没有被销毁,造成内存泄漏
</script>
正确做法:
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
let chartInstance = null; // ✅ 保存实例引用
onMounted(() => {
chartInstance = echarts.init(chartRef.value);
chartInstance.setOption({
// 配置...
});
});
onUnmounted(() => {
// ✅ 正确:销毁图表实例
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
</script>
原因分析:
- 不销毁图表会导致内存泄漏
- 特别是在单页应用中频繁切换页面时
- 必须在组件卸载时调用
dispose()
陷阱2:图表容器没有设置高度
错误示例:
<template>
<!-- ❌ 错误:容器没有高度,图表无法显示 -->
<div ref="chartRef"></div>
</template>
正确做法:
<template>
<!-- ✅ 正确:明确设置容器高度 -->
<div ref="chartRef" style="height: 400px;"></div>
<!-- 或使用CSS类 -->
<div ref="chartRef" class="chart-container"></div>
</template>
<style scoped>
.chart-container {
height: 400px;
}
</style>
原因分析:
- ECharts需要明确的容器尺寸
- 容器高度为0时,图表无法渲染
- 可以使用固定高度或百分比高度(父元素需要有高度)
陷阱3:数据更新后图表不刷新
错误示例:
<script setup>
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
const data = ref([120, 200, 150]);
onMounted(() => {
const chart = echarts.init(chartRef.value);
chart.setOption({
series: [{ type: 'line', data: data.value }]
});
// ❌ 错误:数据变化后没有更新图表
setTimeout(() => {
data.value = [200, 300, 250];
// 图表不会自动更新
}, 2000);
});
</script>
正确做法:
<script setup>
import { ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
const data = ref([120, 200, 150]);
let chartInstance = null;
onMounted(() => {
chartInstance = echarts.init(chartRef.value);
updateChart();
});
// ✅ 正确:监听数据变化,更新图表
watch(data, () => {
updateChart();
}, { deep: true });
const updateChart = () => {
if (!chartInstance) return;
chartInstance.setOption({
series: [{ type: 'line', data: data.value }]
});
};
// 更新数据
setTimeout(() => {
data.value = [200, 300, 250]; // 图表会自动更新
}, 2000);
</script>
原因分析:
- ECharts不会自动监听数据变化
- 需要手动调用
setOption()更新图表 - 使用
watch监听数据变化是最佳实践
性能优化建议
建议1:按需引入
// ❌ 不推荐:全量引入(体积大)
import * as echarts from 'echarts';
// ✅ 推荐:按需引入(减小体积)
import * as echarts from 'echarts/core';
import { LineChart, BarChart, PieChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
// 注册必需的组件
echarts.use([
LineChart,
BarChart,
PieChart,
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
CanvasRenderer
]);
建议2:大数据量优化
// 处理大数据量时的优化配置
const option = {
series: [{
type: 'line',
data: largeDataArray,
// 开启大数据量优化
large: true,
largeThreshold: 2000, // 数据量超过2000时启用优化
// 采样策略
sampling: 'lttb' // 使用LTTB采样算法
}]
};
建议3:使用防抖优化resize
<script setup>
import { useDebounceFn } from '@vueuse/core';
// 使用防抖优化resize事件
const debouncedResize = useDebounceFn(() => {
if (chartInstance) {
chartInstance.resize();
}
}, 200);
onMounted(() => {
window.addEventListener('resize', debouncedResize);
});
onUnmounted(() => {
window.removeEventListener('resize', debouncedResize);
});
</script>
实践练习
练习1:创建销售仪表盘(难度:中等)
需求描述
创建一个销售数据仪表盘,包含:
- 月度销售趋势折线图
- 产品销售占比饼图
- 地区销售对比柱状图
- 数据可以动态更新
- 图表响应式布局
实现提示
- 使用Grid布局组织多个图表
- 创建可复用的图表组件
- 使用computed计算图表配置
- 实现数据更新功能
<script setup>
import { ref, computed } from 'vue';
import BaseChart from '@/components/BaseChart.vue';
// 销售数据
const salesData = ref({
months: ['1月', '2月', '3月', '4月', '5月', '6月'],
sales: [120, 200, 150, 280, 210, 300],
products: [
{ name: '手机', value: 3500 },
{ name: '电脑', value: 2800 },
{ name: '平板', value: 1800 },
{ name: '其他', value: 900 }
],
regions: [
{ name: '北京', value: 320 },
{ name: '上海', value: 380 },
{ name: '广州', value: 290 },
{ name: '深圳', value: 410 }
]
});
// 折线图配置
const trendOption = computed(() => ({
title: { text: '月度销售趋势', left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: salesData.value.months
},
yAxis: { type: 'value', name: '销售额(万元)' },
series: [{
type: 'line',
data: salesData.value.sales,
smooth: true,
itemStyle: { color: '#42b883' },
areaStyle: {
color: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(66, 184, 131, 0.3)' },
{ offset: 1, color: 'rgba(66, 184, 131, 0.05)' }
]
}
}
}]
}));
// 饼图配置
const productOption = computed(() => ({
title: { text: '产品销售占比', left: 'center' },
tooltip: { trigger: 'item' },
legend: { orient: 'vertical', left: 'left' },
series: [{
type: 'pie',
radius: '60%',
data: salesData.value.products,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
}));
// 柱状图配置
const regionOption = computed(() => ({
title: { text: '地区销售对比', left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: salesData.value.regions.map(r => r.name)
},
yAxis: { type: 'value', name: '销售额(万元)' },
series: [{
type: 'bar',
data: salesData.value.regions.map(r => r.value),
itemStyle: { color: '#5470c6' },
label: { show: true, position: 'top' }
}]
}));
// 模拟数据更新
const refreshData = () => {
salesData.value.sales = salesData.value.sales.map(() =>
Math.floor(Math.random() * 300) + 100
);
salesData.value.products = salesData.value.products.map(p => ({
...p,
value: Math.floor(Math.random() * 3000) + 1000
}));
salesData.value.regions = salesData.value.regions.map(r => ({
...r,
value: Math.floor(Math.random() * 400) + 200
}));
};
</script>
<template>
<div class="dashboard">
<div class="header">
<h2>销售数据仪表盘</h2>
<button @click="refreshData" class="refresh-btn">
🔄 刷新数据
</button>
</div>
<div class="charts-grid">
<div class="chart-card full-width">
<BaseChart :option="trendOption" :height="300" />
</div>
<div class="chart-card">
<BaseChart :option="productOption" :height="300" />
</div>
<div class="chart-card">
<BaseChart :option="regionOption" :height="300" />
</div>
</div>
</div>
</template>
<style scoped>
.dashboard {
padding: 20px;
background: #f5f5f5;
min-height: 100vh;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.refresh-btn {
padding: 10px 20px;
background: #42b883;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.refresh-btn:hover {
background: #35a372;
}
.charts-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.chart-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.full-width {
grid-column: 1 / -1;
}
@media (max-width: 768px) {
.charts-grid {
grid-template-columns: 1fr;
}
}
</style>
答案解析
- 组件复用:使用BaseChart组件减少重复代码
- 响应式数据:使用computed自动更新图表配置
- 布局设计:使用Grid布局实现响应式
- 数据更新:点击按钮刷新所有图表数据
- 移动适配:小屏幕自动切换为单列布局
练习2:实现图表主题切换(难度:简单)
需求描述
为图表添加主题切换功能:
- 支持明亮和暗黑两种主题
- 主题切换时图表平滑过渡
- 主题设置保存到localStorage
实现提示
- 使用ECharts内置主题或自定义主题
- 使用useLocalStorage保存主题设置
- 监听主题变化,重新初始化图表
<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';
import { useLocalStorage } from '@vueuse/core';
import * as echarts from 'echarts';
const chartRef = ref(null);
let chartInstance = null;
// 主题设置(保存到localStorage)
const theme = useLocalStorage('chart-theme', 'light');
// 图表配置
const option = {
title: { text: '主题切换示例' },
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五']
},
yAxis: { type: 'value' },
series: [{
type: 'bar',
data: [120, 200, 150, 280, 210]
}]
};
// 初始化图表
const initChart = () => {
if (chartInstance) {
chartInstance.dispose();
}
// 使用指定主题初始化
chartInstance = echarts.init(chartRef.value, theme.value);
chartInstance.setOption(option);
};
// 切换主题
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
};
// 监听主题变化
watch(theme, () => {
initChart();
});
onMounted(() => {
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
}
});
</script>
<template>
<div :class="['container', theme]">
<div class="controls">
<button @click="toggleTheme">
{{ theme === 'light' ? '🌙 切换暗黑' : '☀️ 切换明亮' }}
</button>
</div>
<div ref="chartRef" class="chart"></div>
</div>
</template>
<style scoped>
.container {
padding: 20px;
min-height: 100vh;
transition: background 0.3s;
}
.container.light {
background: #ffffff;
}
.container.dark {
background: #1a1a1a;
}
.controls {
margin-bottom: 20px;
}
.controls button {
padding: 10px 20px;
border: none;
border-radius: 5px;
background: #42b883;
color: white;
cursor: pointer;
}
.chart {
width: 100%;
height: 400px;
}
</style>
答案解析
- 主题保存:使用useLocalStorage持久化主题设置
- 主题切换:监听主题变化,重新初始化图表
- 平滑过渡:使用CSS transition实现背景色过渡
- 用户体验:刷新页面后主题保持不变
进阶阅读
- ECharts官方文档 - 完整的API文档
- ECharts示例 - 丰富的图表示例
- ECharts配置项手册 - 详细的配置说明
- Vue-ECharts - Vue 3的ECharts组件封装
下一步
恭喜你完成了ECharts的学习!
接下来你可以:
- 学习表单验证 → Vuelidate验证库
- 学习国际化 → Vue I18n
- 学习日期处理 → Dayjs日期库
- 学习HTTP请求 → Axios拦截器
更多推荐
所有评论(0)