Skip to content

图表

数据可视化图表库,支持丰富的图表类型和交互效果。

Vue ECharts

基本信息

特点

  • ✅ 支持 Vue 3 Composition API
  • ✅ 支持按需引入,减小打包体积
  • ✅ 提供 TypeScript 类型支持
  • ✅ 支持响应式数据更新
  • ✅ 丰富的图表类型(折线图、柱状图、饼图、散点图等)
  • ✅ 强大的交互能力和动画效果
  • ✅ 支持主题定制
  • ✅ 移动端友好,支持触摸操作

安装

bash
pnpm add vue-echarts echarts
bash
bun add vue-echarts echarts
bash
npm install vue-echarts echarts
bash
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     // 提示框
])

相关资源