图表
数据可视化图表库,支持丰富的图表类型和交互效果。
Vue ECharts
基本信息
- 简介: Apache ECharts 的 Vue 3 组件封装,提供强大的数据可视化能力
- 官网: https://echarts.apache.org
- GitHub: https://github.com/ecomfe/vue-echarts
- npm: https://www.npmjs.com/package/vue-echarts
特点
- ✅ 支持 Vue 3 Composition API
- ✅ 支持按需引入,减小打包体积
- ✅ 提供 TypeScript 类型支持
- ✅ 支持响应式数据更新
- ✅ 丰富的图表类型(折线图、柱状图、饼图、散点图等)
- ✅ 强大的交互能力和动画效果
- ✅ 支持主题定制
- ✅ 移动端友好,支持触摸操作
安装
bash
pnpm add vue-echarts echartsbash
bun add vue-echarts echartsbash
npm install vue-echarts echartsbash
yarn add vue-echarts echarts基本用法
vue
<template>
<div v-if="isReady" class="w-full" :style="{ height: chartHeight }">
<v-chart :option="option" :autoresize="true" class="w-full h-full" />
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { BarChart, LineChart, PieChart } from 'echarts/charts'
import {
GridComponent,
TooltipComponent,
TitleComponent,
LegendComponent
} from 'echarts/components'
import type { EChartsOption } from 'echarts'
// 注册必要的组件
use([
CanvasRenderer,
BarChart,
LineChart,
PieChart,
GridComponent,
TooltipComponent,
TitleComponent,
LegendComponent
])
// 使用 Vue 3.5+ 解构语法接收 props
const { option, height = 400 }: {
option: EChartsOption
height?: number | string
} = defineProps<{
option: EChartsOption
height?: number | string
}>()
// 处理高度:数字自动添加 px,字符串直接使用
const chartHeight = computed(() => {
return typeof height === 'number' ? `${height}px` : height
})
// 延迟渲染,确保容器尺寸已确定
const isReady = ref(false)
onMounted(() => {
setTimeout(() => {
isReady.value = true
}, 100)
})
</script>示例
柱状图
vue
<template>
<Chart :option="barOption" :height="400" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Chart from './Chart.vue'
import type { EChartsOption } from 'echarts'
const barOption = ref<EChartsOption>({
title: {
text: '月度销售数据'
},
grid: {
left: '50',
right: '30',
bottom: '30',
top: '60'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '销售额',
type: 'bar',
data: [120, 200, 150, 80, 70, 110],
itemStyle: {
color: '#5470c6'
}
}
]
})
</script>折线图
vue
<template>
<Chart :option="lineOption" :height="400" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Chart from './Chart.vue'
import type { EChartsOption } from 'echarts'
const lineOption = ref<EChartsOption>({
title: {
text: '温度变化趋势'
},
grid: {
left: '50',
right: '30',
bottom: '30',
top: '60'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value} °C'
}
},
series: [
{
name: '温度',
type: 'line',
data: [11, 11, 15, 13, 12, 13, 10],
smooth: true,
itemStyle: {
color: '#91cc75'
}
}
]
})
</script>饼图
vue
<template>
<Chart :option="pieOption" :height="400" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Chart from './Chart.vue'
import type { EChartsOption } from 'echarts'
const pieOption = ref<EChartsOption>({
title: {
text: '产品销售占比',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '销售额',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: '产品A' },
{ value: 735, name: '产品B' },
{ value: 580, name: '产品C' },
{ value: 484, name: '产品D' },
{ value: 300, name: '产品E' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
})
</script>进阶用法
响应式数据更新
vue
<template>
<div>
<button @click="updateData">更新数据</button>
<div class="chart-container">
<v-chart :option="option" :autoresize="true" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VChart from 'vue-echarts'
import type { EChartsOption } from 'echarts'
const option = ref<EChartsOption>({
xAxis: {
type: 'category',
data: ['A', 'B', 'C', 'D', 'E']
},
yAxis: {
type: 'value'
},
series: [
{
type: 'bar',
data: [10, 20, 30, 40, 50]
}
]
})
const updateData = () => {
// 直接修改 option,图表会自动更新
option.value = {
...option.value,
series: [
{
type: 'bar',
data: [
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100
]
}
]
}
}
</script>
<style scoped>
.chart-container {
width: 100%;
height: 400px;
margin-top: 20px;
}
</style>使用主题
vue
<template>
<div class="chart-container">
<v-chart :option="option" :theme="theme" :autoresize="true" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VChart from 'vue-echarts'
import type { EChartsOption } from 'echarts'
// 使用内置主题:'light' 或 'dark'
const theme = ref('dark')
const option = ref<EChartsOption>({
title: {
text: '暗色主题示例'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
type: 'line',
data: [150, 230, 224, 218, 135, 147, 260]
}
]
})
</script>
<style scoped>
.chart-container {
width: 100%;
height: 400px;
}
</style>图表事件处理
vue
<template>
<div>
<p v-if="clickedData">点击了: {{ clickedData }}</p>
<div class="chart-container">
<v-chart
ref="chartRef"
:option="option"
:autoresize="true"
@click="handleClick"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import VChart from 'vue-echarts'
import type { EChartsOption } from 'echarts'
const chartRef = ref<InstanceType<typeof VChart>>()
const clickedData = ref('')
const option = ref<EChartsOption>({
xAxis: {
type: 'category',
data: ['A', 'B', 'C', 'D', 'E']
},
yAxis: {
type: 'value'
},
series: [
{
type: 'bar',
data: [10, 20, 30, 40, 50]
}
]
})
const handleClick = (params: any) => {
clickedData.value = `${params.name}: ${params.value}`
}
onMounted(() => {
// 也可以通过 chartRef 访问 ECharts 实例
const chart = chartRef.value?.chart
if (chart) {
chart.on('mouseover', (params: any) => {
console.log('鼠标悬停:', params)
})
}
})
</script>
<style scoped>
.chart-container {
width: 100%;
height: 400px;
}
</style>动态加载数据
vue
<template>
<div>
<button @click="loadData" :disabled="loading">
{{ loading ? '加载中...' : '加载数据' }}
</button>
<div class="chart-container">
<v-chart
:option="option"
:autoresize="true"
:loading="loading"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VChart from 'vue-echarts'
import type { EChartsOption } from 'echarts'
const loading = ref(false)
const option = ref<EChartsOption>({
title: {
text: '异步数据加载'
},
xAxis: {
type: 'category',
data: []
},
yAxis: {
type: 'value'
},
series: [
{
type: 'line',
data: []
}
]
})
const loadData = async () => {
loading.value = true
// 模拟 API 请求
await new Promise(resolve => setTimeout(resolve, 1500))
// 更新数据
option.value = {
...option.value,
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
series: [
{
type: 'line',
data: [120, 200, 150, 80, 70, 110]
}
]
}
loading.value = false
}
</script>
<style scoped>
.chart-container {
width: 100%;
height: 400px;
margin-top: 20px;
}
</style>完整示例
点击查看完整的仪表盘示例
vue
<template>
<div class="dashboard">
<div class="controls">
<button @click="refreshData">刷新数据</button>
<select v-model="selectedTheme" @change="changeTheme">
<option value="light">浅色主题</option>
<option value="dark">深色主题</option>
</select>
</div>
<div class="charts-grid">
<!-- 销售趋势 -->
<div class="chart-card">
<h3>销售趋势</h3>
<v-chart
:option="salesOption"
:theme="selectedTheme"
:autoresize="true"
class="chart"
/>
</div>
<!-- 产品占比 -->
<div class="chart-card">
<h3>产品占比</h3>
<v-chart
:option="pieOption"
:theme="selectedTheme"
:autoresize="true"
class="chart"
/>
</div>
<!-- 地区分布 -->
<div class="chart-card">
<h3>地区分布</h3>
<v-chart
:option="barOption"
:theme="selectedTheme"
:autoresize="true"
class="chart"
/>
</div>
<!-- 实时监控 -->
<div class="chart-card">
<h3>实时监控</h3>
<v-chart
:option="gaugeOption"
:theme="selectedTheme"
:autoresize="true"
class="chart"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import {
LineChart,
PieChart,
BarChart,
GaugeChart
} from 'echarts/charts'
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent
} from 'echarts/components'
import type { EChartsOption } from 'echarts'
use([
CanvasRenderer,
LineChart,
PieChart,
BarChart,
GaugeChart,
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent
])
const selectedTheme = ref<'light' | 'dark'>('light')
// 销售趋势数据
const salesOption = ref<EChartsOption>({
tooltip: {
trigger: 'axis'
},
legend: {
data: ['销售额', '利润']
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '销售额',
type: 'line',
data: [120, 200, 150, 80, 70, 110],
smooth: true
},
{
name: '利润',
type: 'line',
data: [60, 100, 75, 40, 35, 55],
smooth: true
}
]
})
// 产品占比数据
const pieOption = ref<EChartsOption>({
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: '产品A' },
{ value: 735, name: '产品B' },
{ value: 580, name: '产品C' },
{ value: 484, name: '产品D' }
]
}
]
})
// 地区分布数据
const barOption = ref<EChartsOption>({
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['北京', '上海', '广州', '深圳', '杭州']
},
yAxis: {
type: 'value'
},
series: [
{
type: 'bar',
data: [320, 302, 301, 334, 390],
itemStyle: {
color: '#5470c6'
}
}
]
})
// 仪表盘数据
const gaugeOption = ref<EChartsOption>({
series: [
{
type: 'gauge',
progress: {
show: true
},
detail: {
valueAnimation: true,
formatter: '{value}%'
},
data: [
{
value: 70,
name: 'CPU使用率'
}
]
}
]
})
// 刷新数据
const refreshData = () => {
// 更新销售数据
salesOption.value = {
...salesOption.value,
series: [
{
name: '销售额',
type: 'line',
data: Array.from({ length: 6 }, () => Math.floor(Math.random() * 200)),
smooth: true
},
{
name: '利润',
type: 'line',
data: Array.from({ length: 6 }, () => Math.floor(Math.random() * 100)),
smooth: true
}
]
}
// 更新仪表盘
gaugeOption.value = {
...gaugeOption.value,
series: [
{
type: 'gauge',
progress: {
show: true
},
detail: {
valueAnimation: true,
formatter: '{value}%'
},
data: [
{
value: Math.floor(Math.random() * 100),
name: 'CPU使用率'
}
]
}
]
}
}
// 切换主题
const changeTheme = () => {
console.log('切换主题:', selectedTheme.value)
}
// 定时更新仪表盘
let timer: number | null = null
onMounted(() => {
timer = window.setInterval(() => {
gaugeOption.value = {
...gaugeOption.value,
series: [
{
type: 'gauge',
progress: {
show: true
},
detail: {
valueAnimation: true,
formatter: '{value}%'
},
data: [
{
value: Math.floor(Math.random() * 100),
name: 'CPU使用率'
}
]
}
]
}
}, 3000)
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<style scoped>
.dashboard {
padding: 20px;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.controls button,
.controls select {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
.charts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 20px;
}
.chart-card {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
background: #fff;
}
.chart-card h3 {
margin: 0 0 16px 0;
font-size: 18px;
font-weight: 600;
}
.chart {
width: 100%;
height: 300px;
}
</style>最佳实践
1. 按需引入
只引入需要的图表类型和组件,减小打包体积:
typescript
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { BarChart } from 'echarts/charts'
import { GridComponent, TooltipComponent } from 'echarts/components'
use([CanvasRenderer, BarChart, GridComponent, TooltipComponent])2. 响应式设计
使用 :autoresize="true" 让图表自动适应容器大小:
vue
<v-chart :option="option" :autoresize="true" />3. 性能优化
对于大数据量场景,使用数据采样和渐进式渲染:
typescript
const option: EChartsOption = {
series: [
{
type: 'line',
data: largeDataset,
sampling: 'average', // 数据采样
progressive: 1000, // 渐进式渲染
progressiveThreshold: 3000
}
]
}4. TypeScript 支持
使用类型定义获得更好的开发体验:
typescript
import type { EChartsOption } from 'echarts'
import type { ComposeOption } from 'echarts/core'
import type { BarSeriesOption } from 'echarts/charts'
import type { GridComponentOption } from 'echarts/components'
type ECOption = ComposeOption<BarSeriesOption | GridComponentOption>
const option = ref<ECOption>({
// ...
})5. 主题定制
创建自定义主题文件:
typescript
// theme/custom.ts
export const customTheme = {
color: [
'#5470c6',
'#91cc75',
'#fac858',
'#ee6666',
'#73c0de'
],
backgroundColor: '#f4f4f4',
textStyle: {
color: '#333'
}
// ... 更多配置
}
// 使用
import { registerTheme } from 'echarts/core'
import { customTheme } from './theme/custom'
registerTheme('custom', customTheme)vue
<v-chart :option="option" theme="custom" />常见问题
1. 图表不显示
确保容器有明确的高度:
css
.chart-container {
width: 100%;
height: 400px; /* 必须设置高度 */
}2. 图表不响应容器大小变化
使用 :autoresize="true" 属性:
vue
<v-chart :option="option" :autoresize="true" />3. 按需引入后图表不显示
确保引入了所有必要的组件:
typescript
use([
CanvasRenderer, // 渲染器
LineChart, // 图表类型
GridComponent, // 坐标系
TooltipComponent // 提示框
])