数据可视化:ECharts 集成
1. 为什么选 ECharts
中后台项目 90% 的图表需求用 ECharts 都能搞定:
- 成熟:百度 → Apache 顶级项目,10+ 年打磨
- 文档强:中英双语,配上"配置项手册 + 在线 demo + 示例库"三件套
- 图表全:30+ 种,还能组合(一个图里有柱状 + 折线 + 散点)
- 交互强:缩放、联动、数据区域选择,开箱即用
- 渲染器可换:Canvas(默认,性能好)/ SVG(清晰度高,适合打印)
什么时候不用 ECharts:
| 场景 | 推荐 |
|---|---|
| 简单图表,想要更小的包 | Chart.js(200KB vs ECharts ~400KB) |
| 需要高度定制化视觉效果 | D3.js(底层 API,写得多但无限自由) |
| 看板式大屏 | ECharts + echarts-liquidfill / echarts-wordcloud 等扩展 |
| 3D 图表 | ECharts GL |
in4vue 数据展示简单(AI 问答日志、笔记访问统计),用 ECharts 就行。
2. 两种用法:原生 vs vue-echarts
2.1 原生 ECharts
直接用 echarts.init(dom),完全原生,没有 Vue 绑定:
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import * as echarts from 'echarts'
const chartEl = ref<HTMLDivElement>()
let chart: echarts.ECharts | null = null
onMounted(() => {
chart = echarts.init(chartEl.value!)
chart.setOption({
xAxis: { type: 'category', data: ['周一', '周二', '周三'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [120, 200, 150] }],
})
})
onUnmounted(() => {
chart?.dispose() // 必须销毁,否则内存泄漏
})
</script>
<template>
<div ref="chartEl" class="w-full h-80" />
</template>
优点:零额外依赖,控制最细。 缺点:要手动管理生命周期(挂载/销毁/更新/resize),每个图都写一遍。
2.2 vue-echarts(推荐)
pnpm add echarts vue-echarts
<script setup lang="ts">
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { BarChart } from 'echarts/charts'
import { GridComponent, TooltipComponent } from 'echarts/components'
import VChart from 'vue-echarts'
// 按需注册(体积优化,见下一节)
use([CanvasRenderer, BarChart, GridComponent, TooltipComponent])
const option = {
xAxis: { type: 'category', data: ['周一', '周二', '周三'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [120, 200, 150] }],
}
</script>
<template>
<VChart :option="option" class="w-full h-80" autoresize />
</template>
vue-echarts 帮你做了:
- 组件卸载时自动
dispose autoresize属性自动监听容器尺寸变化option变化自动setOption(响应式)- 和 Vue SSR / Keep-Alive 兼容
推荐 vue-echarts,后面所有示例都基于它。
3. 按需引入:把 400KB 压到 80KB
import * as echarts 会把整个 ECharts(所有图表类型 + 所有组件)打进去。实际只用 2-3 种图表时,按需引入能省 300KB+:
// src/lib/echarts.ts —— 在一个地方集中注册
import { use } from 'echarts/core'
// 渲染器(二选一)
import { CanvasRenderer } from 'echarts/renderers'
// import { SVGRenderer } from 'echarts/renderers'
// 图表类型(用到哪些引哪些)
import { BarChart, LineChart, PieChart } from 'echarts/charts'
// 组件
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
DataZoomComponent,
} from 'echarts/components'
use([
CanvasRenderer,
BarChart,
LineChart,
PieChart,
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
DataZoomComponent,
])
// 导出组件,其他地方直接 import
export { default as VChart } from 'vue-echarts'
业务代码里:
<script setup lang="ts">
import { VChart } from '@/lib/echarts'
</script>
组件清单速查:
| 组件 | 干什么 |
|---|---|
GridComponent |
直角坐标系(bar/line/scatter 必需) |
PolarComponent |
极坐标系(雷达图、某些饼图变体) |
TitleComponent |
图表标题 |
TooltipComponent |
鼠标悬停提示框 |
LegendComponent |
图例(多 series 时的"柱状 / 折线"切换) |
DataZoomComponent |
缩放/滑动条 |
ToolboxComponent |
右上角工具箱(下载图片、数据切换等) |
MarkLineComponent |
辅助线(均值线、阈值线) |
Java 对照:类似 Spring Boot 的 starter —— 只引需要的,不要 <spring-boot-starter-parent> 全家桶。
4. 响应式数据:option 变了图表跟着变
vue-echarts 会 watch option 深层变化,自动 setOption。响应式数据直接扔进去:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { VChart } from '@/lib/echarts'
const salesData = ref([120, 200, 150, 80, 70, 110, 130])
const days = ref(['周一', '周二', '周三', '周四', '周五', '周六', '周日'])
const option = computed(() => ({
xAxis: { type: 'category', data: days.value },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: salesData.value, name: '访问量' }],
tooltip: { trigger: 'axis' },
}))
// 数据刷新
const refresh = () => {
salesData.value = salesData.value.map(() => Math.round(Math.random() * 200))
}
</script>
<template>
<el-button @click="refresh">刷新数据</el-button>
<VChart :option="option" class="w-full h-80" autoresize />
</template>
关键点:
option写成computed,依赖变了自动重算- vue-echarts 默认用
setOption(option, true)合并更新(不重绘整个图),切换 series 数量时流畅
5. 常见图表模板
5.1 折线图(趋势)
const option = {
title: { text: '最近 7 天访问量' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: ['5/6', '5/7', '5/8', '5/9', '5/10', '5/11', '5/12'],
boundaryGap: false, // 折线图常让 x 轴从第一个点开始
},
yAxis: { type: 'value' },
series: [
{
name: '访问量',
type: 'line',
smooth: true, // 曲线
data: [150, 230, 180, 320, 410, 380, 500],
areaStyle: {}, // 面积填充
},
],
}
5.2 柱状图(对比)
const option = {
title: { text: '各分类笔记数' },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
xAxis: { type: 'category', data: ['基础', 'Vue 核心', '生态', '样式', '实战'] },
yAxis: { type: 'value' },
series: [
{
type: 'bar',
data: [4, 8, 6, 6, 7],
itemStyle: {
color: (params: any) => {
const colors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de']
return colors[params.dataIndex]
},
},
},
],
}
5.3 饼图(占比)
const option = {
title: { text: '访问来源', left: 'center' },
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { orient: 'vertical', left: 'left' },
series: [
{
type: 'pie',
radius: ['40%', '70%'], // 圆环
data: [
{ value: 1048, name: '直接访问' },
{ value: 735, name: '搜索引擎' },
{ value: 580, name: '社交分享' },
{ value: 484, name: '外链引荐' },
],
label: { formatter: '{b}\n{d}%' },
},
],
}
5.4 多系列组合
const option = {
tooltip: { trigger: 'axis' },
legend: { data: ['访问量', '新增用户'] },
xAxis: { type: 'category', data: ['5/6','5/7','5/8','5/9','5/10','5/11','5/12'] },
// 双 y 轴
yAxis: [
{ type: 'value', name: '访问量' },
{ type: 'value', name: '新增用户' },
],
series: [
{ name: '访问量', type: 'bar', data: [150, 230, 180, 320, 410, 380, 500] },
{ name: '新增用户', type: 'line', yAxisIndex: 1, data: [12, 18, 15, 22, 30, 25, 40] },
],
}
6. 主题:暗色模式联动
ECharts 默认浅色。三种方式切换:
6.1 vue-echarts 的 theme 属性
<script setup lang="ts">
import { isDark } from '@/composables/useTheme'
import { computed } from 'vue'
const theme = computed(() => (isDark.value ? 'dark' : ''))
</script>
<template>
<VChart :option="option" :theme="theme" class="w-full h-80" autoresize />
</template>
ECharts 内置 dark 主题。切换 isDark 时图表自动重新生成。
6.2 注册自定义主题
// src/lib/echarts-theme.ts
import * as echarts from 'echarts/core'
echarts.registerTheme('in4vue-dark', {
backgroundColor: '#1f2937',
textStyle: { color: '#e5e7eb' },
title: { textStyle: { color: '#f9fafb' } },
// ... 完整配置看 ECharts 主题工具
})
用 ECharts 主题编辑器 拖颜色生成 JSON,导进来。
6.3 option 里直接配色
更灵活但要每个图写:
const option = computed(() => ({
backgroundColor: isDark.value ? '#1f2937' : '#fff',
textStyle: { color: isDark.value ? '#e5e7eb' : '#333' },
// ...
}))
7. 包装成通用图表组件
业务里一堆 <VChart :option="..."> 重复了标题/loading/空状态的逻辑。抽一个:
<!-- src/components/BaseChart.vue -->
<script setup lang="ts">
import { VChart } from '@/lib/echarts'
import { isDark } from '@/composables/useTheme'
import { computed } from 'vue'
const props = defineProps<{
option: any
loading?: boolean
height?: string
empty?: boolean
}>()
const theme = computed(() => (isDark.value ? 'dark' : ''))
</script>
<template>
<div class="relative" :style="{ height: height ?? '320px' }">
<el-empty v-if="empty" description="暂无数据" class="h-full" />
<VChart
v-else
:option="option"
:theme="theme"
:loading="loading"
:loading-options="{ text: '加载中...', color: '#3b82f6' }"
autoresize
class="h-full w-full"
/>
</div>
</template>
使用:
<BaseChart :option="option" :loading="loading" :empty="list.length === 0" height="400px" />
好处:
- 统一空态、loading、主题
- 调整整站图表样式改一个文件
- 业务代码只关心"数据怎么塞进 option"
8. 和接口数据对接
后端返回的格式很少正好是 ECharts 要的。写个小转换函数:
// src/utils/chart-adapter.ts
export interface DailyVisit {
date: string
visits: number
}
export function toLineOption(data: DailyVisit[]): any {
return {
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: data.map((d) => d.date) },
yAxis: { type: 'value' },
series: [
{
type: 'line',
smooth: true,
data: data.map((d) => d.visits),
},
],
}
}
组件里:
const { list: visits } = useApi(() => statsApi.daily())
const option = computed(() => toLineOption(visits.value))
Java 对照:这相当于 DTO → VO 的转换——后端给的业务模型和前端视图模型通常不一样,中间加一层适配器。
9. 性能:大数据量优化
9.1 数据量 > 1 万点
改用 dataset + sampling:
const option = {
dataset: {
source: bigArray, // [[date, value], [date, value], ...]
},
xAxis: { type: 'time' },
yAxis: {},
series: [{
type: 'line',
sampling: 'lttb', // 大数据量采样算法
encode: { x: 0, y: 1 },
}],
}
sampling: 'lttb' 是视觉友好的降采样算法,保留趋势形状。
9.2 关闭动画
const option = { animation: false, ... }
大数据量时初次动画会很卡,直接关。
9.3 渲染器换 Canvas
Canvas(默认)处理大数据比 SVG 快。只在要打印/导出矢量图时才用 SVG。
10. 事件监听
<script setup lang="ts">
const onChartClick = (params: any) => {
console.log('点击了', params.name, params.value)
// 跳转到详情
router.push(`/detail/${params.name}`)
}
</script>
<template>
<VChart :option="option" @click="onChartClick" autoresize class="h-80" />
</template>
常用事件:click / dblclick / mouseover / legendselectchanged / datazoom
11. 常见坑点
| 现象 | 原因 | 解法 |
|---|---|---|
| 图表不显示 | 容器没高度 | 父元素显式设 height 或 h-80 |
| 切换选项卡后图表变形 | 隐藏时 resize 没触发 | 用 v-show(保留 DOM)+ autoresize |
| 刷新数据图表一闪 | option 每次都返回新对象 |
用 computed 让引用稳定 |
| 图表很小但数据多卡 | 未按需引入 | 按需 use([...]) |
| 内存占用越来越大 | 没 dispose |
vue-echarts 自动处理,原生要手动 |
| 图例文字被截断 | 文字过长 | legend.formatter 截断或换行 |
| 暗色模式切换图表不变 | theme 没响应式 |
theme 写成 computed |
12. 小决策表
| 需求 | 方案 |
|---|---|
| 快速出图 | vue-echarts |
| 极简场景 | Chart.js |
| 完全自定义视觉 | D3 |
| 大数据量 | sampling: 'lttb' + 关闭动画 |
| 响应式尺寸 | autoresize |
| 暗色模式 | vue-echarts 的 theme prop 绑 computed |
| 业务图表多 | 封装 BaseChart + 适配器函数 |
13. in4vue 能做什么图
一个学习笔记站可以做的数据可视化:
- 笔记阅读趋势:折线图,最近 30 天每篇笔记的访问量
- 分类占比:饼图,每个分类下的笔记数
- AI 调用日志:柱状图,近一周每天的 AI 问答次数
- 学习进度:仪表盘 / 环形进度,"已完成 N/M 篇"
MVP 阶段先不做图表页,但留好 BaseChart 组件,后续加一个"学习数据"页很快。
小练习
- 装 echarts + vue-echarts,按需引入 Bar / Line / Pie
- 做一个
BaseChart组件,支持 loading / empty / 暗色联动 - 做一个柱状图展示学习路线各阶段"已完成 / 剩余"
- 点饼图任意扇区跳转到对应分类的笔记列表
- 切换暗色模式观察图表是否跟着变
延伸阅读
- Apache ECharts 官方文档
- Apache ECharts 示例库(抄示例是最快学法)
- vue-echarts
- ECharts 主题编辑器
- Chart.js(轻量替代)
- D3.js(底层图形库)