数据可视化:ECharts 集成

1. 为什么选 ECharts

中后台项目 90% 的图表需求用 ECharts 都能搞定:

什么时候不用 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 帮你做了

推荐 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>

关键点


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" />

好处


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. 常见坑点

现象 原因 解法
图表不显示 容器没高度 父元素显式设 heighth-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 能做什么图

一个学习笔记站可以做的数据可视化:

MVP 阶段先不做图表页,但留好 BaseChart 组件,后续加一个"学习数据"页很快。


小练习

  1. 装 echarts + vue-echarts,按需引入 Bar / Line / Pie
  2. 做一个 BaseChart 组件,支持 loading / empty / 暗色联动
  3. 做一个柱状图展示学习路线各阶段"已完成 / 剩余"
  4. 点饼图任意扇区跳转到对应分类的笔记列表
  5. 切换暗色模式观察图表是否跟着变

延伸阅读