Skip to content

ECharts 图表封装

相关方法

说明

ECharts 图表封装提供了一套完整的 Vue3 Hooks,用于简化 ECharts 图表的使用。

支持按需引入组件、事件监听、智能 resize、数据获取等功能,提供更灵活的使用方式。

概述

提供了四个主要函数:

  • useEcharts - 基础图表 Hook(增强版,支持按需引入)
  • useChartWithData - 带数据获取的高级 Hook
  • useSimpleChart - 简化版 Hook,适用于静态配置
  • registerEchartsComponents - 全局组件注册辅助函数

功能特性

按需引入组件 - 避免打包未使用的图表类型,大幅减少体积
完整的事件系统 - 支持 on/off/once 事件监听
智能 Resize - 支持 ResizeObserver 监听容器大小变化
丰富的 API - 提供 clear、dispatchAction、坐标转换等方法
TypeScript 类型支持 - 完整的类型定义和提示
自动加载状态管理 - 内置 loading 状态
错误处理机制 - 统一的错误处理
更好的内存管理 - 使用 shallowRef,自动清理资源


基础用法

useEcharts - 基础 Hook

适用于完全自定义控制的场景,支持按需引入组件。

按需引入(推荐)

typescript
import { useEcharts } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts'; 
import { GridComponent, TooltipComponent } from 'echarts/components'; 
import { CanvasRenderer } from 'echarts/renderers'; 
import { onMounted } from 'vue';

const chart = useEcharts({
  components: [LineChart, GridComponent, TooltipComponent, CanvasRenderer], 
  autoResize: true, // 自动响应大小变化
  useResizeObserver: true, // 使用 ResizeObserver 监听容器(优先)
  resizeDelay: 300, // 防抖延迟
  theme: 'dark', // 主题
});

onMounted(() => {
  chart.setOption({
    title: { text: '折线图示例' },
    xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
    yAxis: { type: 'value' },
    series: [{ data: [120, 200, 150], type: 'line' }],
  });

  // 监听点击事件
  chart.on('click', (params) => {
    console.log('点击了:', params.name);
  });
});

// 在模板中
return { chartRef: chart.chartRef };

配置选项:

参数类型默认值说明
componentsArray[]需要注册的 ECharts 组件(图表、组件、渲染器)
autoResizebooleantrue是否自动响应大小变化
useResizeObserverbooleantrue使用 ResizeObserver 监听容器(优先于 window)
resizeDelaynumber300resize 防抖延迟(毫秒)
themestring | object-ECharts 主题

基础方法:

方法说明
initChart()初始化图表实例
setOption(option, opts)设置图表配置(完全替换)
updateOption(option)更新图表配置(合并模式)
resize(opts)手动调整图表大小
clear()清空图表
showLoading(config)显示加载动画
hideLoading()隐藏加载动画
dispose()销毁图表实例
getInstance()获取 ECharts 实例

事件方法:

方法说明
on(eventName, handler, context)监听事件
off(eventName, handler)移除事件监听
once(eventName, handler, context)监听事件(仅一次)

扩展方法:

方法说明
dispatchAction(payload)触发图表行为
getDom()获取图表 DOM 容器
getWidth()获取图表宽度
getHeight()获取图表高度
convertToPixel(finder, value)逻辑坐标转换为像素坐标
convertFromPixel(finder, value)像素坐标转换为逻辑坐标

useSimpleChart - 简化版 Hook

适用于静态配置或简单场景,无需数据获取。

typescript
import { useSimpleChart } from '@vue3-simple-bui/platform';
import { PieChart } from 'echarts/charts';
import { LegendComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';

// 静态配置
const chart = useSimpleChart(
  {
    title: { text: '饼图示例' },
    series: [
      {
        type: 'pie',
        data: [
          { value: 335, name: '直接访问' },
          { value: 234, name: '邮件营销' },
          { value: 1548, name: '搜索引擎' },
        ],
      },
    ],
  },
  {
    components: [PieChart, LegendComponent, TooltipComponent, CanvasRenderer], 
  },
);

// 动态配置(函数形式)
const dynamicChart = useSimpleChart(
  () => ({
    series: [{ data: props.chartData, type: 'bar' }],
  }),
  {
    components: [BarChart, GridComponent, CanvasRenderer],
  },
);

return { chartRef: chart.chartRef };

useChartWithData - 高级 Hook

适用于需要异步获取数据的场景,自动处理加载状态和错误。

场景 A:静态数据图表

typescript
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts'; 
import { GridComponent } from 'echarts/components'; 
import { CanvasRenderer } from 'echarts/renderers'; 
import { pieChartOptionFunc } from './config/cloud-chart-options';

const pieChart = useChartWithData({
  components: [LineChart, GridComponent, CanvasRenderer], 
  optionGenerator: pieChartOptionFunc,
  immediate: true, // 默认 true,自动初始化
});

// 在模板中
return {
  pieChartRef: pieChart.chartRef,
};

场景 B:从 API 获取数据

typescript
import { useChartWithData } from '@vue3-simple-bui/platform';
import { BarChart } from 'echarts/charts'; 
import { GridComponent, TooltipComponent } from 'echarts/components'; 
import { CanvasRenderer } from 'echarts/renderers'; 
import { getTaskStatistics } from '@/api/dashboard';

const taskChart = useChartWithData({
  components: [BarChart, GridComponent, TooltipComponent, CanvasRenderer], 

  // 数据获取函数
  fetchData: async () => {
    const res = await getTaskStatistics();
    if (res.code === 200) {
      return res.data;
    }
    throw new Error(res.message || '数据加载失败');
  },
    if (res.code === 200) {
      return res.data;
    }
    throw new Error(res.message || '数据加载失败');
  },

  // 配置生成函数
  optionGenerator: (data) => ({
    title: { text: '任务统计' },
    series: [
      {
        type: 'pie',
        data: data.taskList,
      },
    ],
  }),

  // 错误处理(可选)
  onError: (error) => {
    console.error('图表加载失败:', error);
    message.error(`加载失败: ${error.message}`);
  },

  // 其他选项
  autoResize: true,
  resizeDelay: 300,
});

// 手动刷新
const handleRefresh = () => {
  taskChart.refresh();
};

场景 C:延迟初始化

typescript
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts'; 
import { GridComponent } from 'echarts/components'; 
import { CanvasRenderer } from 'echarts/renderers'; 

const chart = useChartWithData({
  components: [LineChart, GridComponent, CanvasRenderer], 
  immediate: false, // 不自动初始化
  optionGenerator: myOptionFunc,
});

onMounted(() => {
  // 在某个时机手动加载
  setTimeout(() => {
    chart.loadChart();
  }, 1000);
});

场景 D:使用现有的配置函数

typescript
import { useChartWithData } from '@vue3-simple-bui/platform';
import { GaugeChart, PieChart } from 'echarts/charts'; 
import { TitleComponent, LegendComponent } from 'echarts/components'; 
import { CanvasRenderer } from 'echarts/renderers'; 
import * as chartOptions from './config/cloud-chart-options';

// 内存使用率仪表盘
const memoryGauge = useChartWithData({
  components: [GaugeChart, TitleComponent, CanvasRenderer], 
  fetchData: async () => {
    const res = await getMemoryUsage();
    if (res.code === 200) return res.data;
    throw new Error(res.message);
  },
  optionGenerator: chartOptions.memoryGaugeOptionFunc,
});

// CPU 使用率仪表盘
const cpuGauge = useChartWithData({
  components: [GaugeChart, TitleComponent, CanvasRenderer], 
  fetchData: async () => {
    const res = await getCpuUsage();
    if (res.code === 200) return res.data;
    throw new Error(res.message);
  },
  optionGenerator: chartOptions.cpuGaugeOptionFunc,
});

// 磁盘使用饼图
const diskPie = useChartWithData({
  components: [PieChart, LegendComponent, CanvasRenderer], 
  fetchData: async () => {
    const res = await getDiskUsage();
    if (res.code === 200) return res.data;
    throw new Error(res.message);
  },
  optionGenerator: chartOptions.diskPieOptionFunc,
});

进阶用法

事件监听

使用 onoffonce 方法监听图表事件。

typescript
import { useEcharts } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';

const chart = useEcharts({
  components: [LineChart, GridComponent, CanvasRenderer],
});

onMounted(() => {
  chart.setOption({
    xAxis: { type: 'category', data: ['A', 'B', 'C'] },
    yAxis: { type: 'value' },
    series: [{ data: [120, 200, 150], type: 'line' }],
  });

  // 监听点击事件
  chart.on('click', (params) => {
    console.log('点击了:', params.name, params.value);

    // 触发高亮
    chart.dispatchAction({
      type: 'highlight',
      seriesIndex: 0,
      dataIndex: params.dataIndex,
    });
  });

  // 监听鼠标移出
  chart.on('mouseout', () => {
    chart.dispatchAction({
      type: 'downplay',
      seriesIndex: 0,
    });
  });

  // 单次监听(渲染完成)
  chart.once('finished', () => {
    console.log('图表渲染完成');
  });
});

// 移除事件监听
const removeListeners = () => {
  chart.off('click'); 
  chart.off('mouseout'); 
};

全局注册组件

适用于多处使用相同组件的场景,可在应用入口一次性注册。

typescript
// main.ts 或 config/echarts.ts
import { registerEchartsComponents } from '@vue3-simple-bui/platform';
import { LineChart, BarChart, PieChart } from 'echarts/charts';
import {
  GridComponent,
  TooltipComponent,
  LegendComponent,
  TitleComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';

// 全局注册常用组件
registerEchartsComponents([
  LineChart,
  BarChart,
  PieChart,
  GridComponent,
  TooltipComponent,
  LegendComponent,
  TitleComponent,
  CanvasRenderer,
]);

// 之后使用时不需要传 components
const chart = useEcharts(); 

响应式配置更新

监听数据变化,自动更新图表。

typescript
import { ref, watch } from 'vue';
import { useEcharts } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';

const chartData = ref([120, 200, 150]);

const chart = useEcharts({
  components: [LineChart, GridComponent, CanvasRenderer],
});

// 监听数据变化
watch(chartData, (newData) => {
  chart.updateOption({
    // 使用 updateOption 合并更新
    series: [{ data: newData }],
  });
});

// 修改数据,图表自动更新
const updateData = () => {
  chartData.value = [150, 230, 180]; 
};

图表联动

多个图表之间的交互联动。

typescript
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent, DataZoomComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';

const chart1 = useChartWithData({
  components: [LineChart, GridComponent, DataZoomComponent, CanvasRenderer],
  optionGenerator: option1Func,
});

const chart2 = useChartWithData({
  components: [LineChart, GridComponent, DataZoomComponent, CanvasRenderer],
  optionGenerator: option2Func,
});

onMounted(() => {
  const instance1 = chart1.getInstance();
  const instance2 = chart2.getInstance();

  // 图表1 的 dataZoom 联动到图表2
  instance1?.on('dataZoom', (params) => {
    instance2?.dispatchAction({
      type: 'dataZoom',
      ...params,
    });
  });
});

完整示例

vue
<template>
  <div class="dashboard">
    <div ref="chart1Ref" class="chart"></div>
    <div ref="chart2Ref" class="chart"></div>
    <a-button @click="refreshCharts">刷新</a-button>
  </div>
</template>

<script setup lang="ts">
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart, BarChart } from 'echarts/charts'; 
import { GridComponent, TooltipComponent } from 'echarts/components'; 
import { CanvasRenderer } from 'echarts/renderers'; 
import { getChartData } from '@/api/dashboard';
import * as chartOptions from './config/cloud-chart-options';
import { message } from 'ant-design-vue';

// 图表1:静态配置
const chart1 = useChartWithData({
  components: [LineChart, GridComponent, TooltipComponent, CanvasRenderer], 
  optionGenerator: chartOptions.pieChartOptionFunc,
});

// 图表2:动态数据
const chart2 = useChartWithData({
  components: [BarChart, GridComponent, TooltipComponent, CanvasRenderer], 
  fetchData: async () => {
    const res = await getChartData();
    if (res.code === 200) return res.data;
    throw new Error(res.message);
  },
  optionGenerator: (data) => ({
    xAxis: { data: data.categories },
    series: [{ data: data.values, type: 'bar' }],
  }),
  onError: (err) => {
    message.error(`数据加载失败: ${err.message}`);
  },
});

// 刷新所有图表
const refreshCharts = () => {
  chart1.refresh();
  chart2.refresh();
};

// 暴露给模板
defineExpose({
  chart1Ref: chart1.chartRef,
  chart2Ref: chart2.chartRef,
  refreshCharts,
});
</script>

<style scoped>
.dashboard {
  display: flex;
  gap: 20px;
}

.chart {
  width: 100%;
  height: 400px;
}
</style>

TypeScript 类型定义

typescript
interface UseEchartsOptions {
  autoResize?: boolean; // 是否自动响应大小变化
  useResizeObserver?: boolean; // 使用 ResizeObserver(优先)
  resizeDelay?: number; // resize 防抖延迟(毫秒)
  theme?: string | object; // ECharts 主题
  components?: any[]; // 需要注册的组件
}

interface UseChartWithDataOptions<T = any> extends UseEchartsOptions {
  fetchData?: () => Promise<T>; // 数据获取函数
  optionGenerator: (data: T) => EChartsOption; // 配置生成函数
  onError?: (error: Error) => void; // 错误处理
  immediate?: boolean; // 是否立即加载(默认 true)
}

type EventCallback = (params: any) => void;

常见问题

Q1: 图表不显示?

确保:

  1. 容器元素有明确的宽高
  2. chartRef 正确绑定到 DOM 元素
  3. 已注册必需的组件(图表类型、组件、渲染器)
  4. 检查控制台是否有错误
vue
<template>
  <!-- ✅ 正确:明确宽高 -->
  <div ref="chartRef" style="width: 100%; height: 400px;"></div>
</template>

Q2: 报错 "Component xxx not exists"

需要注册对应的组件。ECharts 5.x 需要按需引入。

typescript
// ❌ 错误:缺少组件
const chart = useEcharts();

// ✅ 正确:注册所需组件
import { LineChart } from 'echarts/charts'; 
import { GridComponent } from 'echarts/components'; 
import { CanvasRenderer } from 'echarts/renderers'; 

const chart = useEcharts({
  components: [LineChart, GridComponent, CanvasRenderer], 
});

常见组件对照表:

图表类型需要的组件
折线图LineChart + GridComponent + CanvasRenderer
柱状图BarChart + GridComponent + CanvasRenderer
饼图PieChart + LegendComponent + CanvasRenderer
仪表盘GaugeChart + CanvasRenderer

Q3: 如何手动刷新图表?

typescript
// useChartWithData 提供 refresh 方法
const chart = useChartWithData({ ... });
chart.refresh(); // 重新获取数据并渲染

// useEcharts 可以重新设置配置
const chart = useEcharts({ ... });
chart.setOption(newOption); // 重新设置配置

Q4: 如何访问原生 ECharts 实例?

typescript
const chart = useEcharts({ ... });
const instance = chart.getInstance(); 

instance?.on('click', (params) => {
  console.log(params);
});

Q5: 图表容器大小变化时不响应?

确保 autoResize: trueuseResizeObserver: true(默认都开启)。

typescript
const chart = useEcharts({
  autoResize: true, // 默认 true
  useResizeObserver: true, // 优先使用 ResizeObserver
  resizeDelay: 300, // 防抖延迟
});

ResizeObserver vs Window Resize:

  • useResizeObserver: true - 监听容器大小变化(推荐)
  • useResizeObserver: false - 降级到监听 window.resize

Q6: 如何优化打包体积?

使用按需引入,只注册需要的组件。

typescript
// ❌ 不推荐:全局注册所有组件(~200KB)
registerEchartsComponents([
  LineChart,
  BarChart,
  PieChart,
  GaugeChart, // 所有图表类型
  GridComponent,
  TooltipComponent,
  LegendComponent,
  TitleComponent, // 所有组件
  CanvasRenderer,
]);

// ✅ 推荐:按需注册(~50KB)
const chart = useEcharts({
  components: [LineChart, GridComponent, CanvasRenderer], // 仅注册需要的
});

体积对比:

方式大小节省
全局注册所有组件~200KB-
按需注册(折线图)~50KB75%
按需注册(折线图+柱状图)~65KB67%

Q7: 如何实现图表的响应式更新?

typescript
import { ref, watch } from 'vue';

const chartData = ref([120, 200, 150]);

const chart = useEcharts({
  components: [LineChart, GridComponent, CanvasRenderer],
});

// 方式1:使用 watch 监听
watch(chartData, (newData) => {
  chart.updateOption({ series: [{ data: newData }] }); // 合并更新
});

// 方式2:使用 computed
const chartOption = computed(() => ({
  series: [{ data: chartData.value, type: 'line' }],
}));

watch(chartOption, (newOption) => {
  chart.setOption(newOption); // 完全替换
});

最佳实践

1. 优先使用按需引入

typescript
// ✅ 推荐:减少打包体积
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';

const chart = useEcharts({
  components: [LineChart, GridComponent, CanvasRenderer],
});

// ❌ 不推荐:引入全部
import * as echarts from 'echarts';

2. 封装业务图表 Hook

将常用的图表封装成独立的 Hook,提高复用性。

typescript
// hooks/useBusinessChart.ts
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { fetchSalesData } from '@/api/sales';

export const useSalesChart = () => {
  return useChartWithData({
    components: [LineChart, GridComponent, TooltipComponent, CanvasRenderer],
    fetchData: fetchSalesData,
    optionGenerator: (data) => ({
      title: { text: '销售数据' },
      xAxis: { type: 'category', data: data.dates },
      yAxis: { type: 'value' },
      series: [{ data: data.values, type: 'line' }],
    }),
  });
};

3. 全局注册常用组件

对于多处使用的组件,可在应用入口统一注册。

typescript
// config/echarts.ts
import { registerEchartsComponents } from '@vue3-simple-bui/platform';
import { LineChart, BarChart, PieChart } from 'echarts/charts';
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';

export const setupEcharts = () => {
  registerEchartsComponents([
    LineChart,
    BarChart,
    PieChart,
    GridComponent,
    TooltipComponent,
    LegendComponent,
    CanvasRenderer,
  ]);
};

// main.ts
import { setupEcharts } from './config/echarts';
setupEcharts();

4. 使用 TypeScript 类型

充分利用 TypeScript 的类型提示。

typescript
import type { EChartsOption } from 'echarts/types/dist/shared';

const option: EChartsOption = {
  // 完整的类型提示
  title: { text: '标题' },
  xAxis: { type: 'category', data: ['A', 'B', 'C'] },
  yAxis: { type: 'value' },
  series: [{ data: [1, 2, 3], type: 'line' }],
};

5. 统一错误处理

typescript
const chart = useChartWithData({
  fetchData: async () => {
    const res = await fetchData();
    if (res.code !== 200) {
      throw new Error(res.message); 
    }
    return res.data;
  },
  optionGenerator: generateOption,
  onError: (error) => {
    message.error(`加载失败: ${error.message}`);
    console.error('图表错误:', error);
  },
});

6. 性能优化

typescript
const chart = useEcharts({
  components: [LineChart, GridComponent, CanvasRenderer],
  resizeDelay: 300, // 调整防抖延迟
  useResizeObserver: true, // 使用 ResizeObserver
});

// 使用 shallowRef 存储大量数据
import { shallowRef } from 'vue';
const bigData = shallowRef(largeArray);

7. 组件清理

虽然 Hook 会自动清理,但手动清理事件监听是好习惯。

typescript
import { onBeforeUnmount } from 'vue';

const chart = useEcharts({
  components: [LineChart, GridComponent, CanvasRenderer],
});

const handleClick = (params: any) => {
  console.log(params);
};

onMounted(() => {
  chart.on('click', handleClick);
});

onBeforeUnmount(() => {
  chart.off('click', handleClick); // 手动清理
});

相关链接

基于 MIT 许可发布