一个 Vue 钩子 useEcharts,用于在 Vue 组件中集成 ECharts 图表库

这个文件提供了一个方便的 Vue 钩子 useEcharts,使得在 Vue 组件中集成和管理 ECharts 图表变得更加简单和灵活。它支持动态更新图表配置、响应尺寸变化、切换主题等功能。

返回值

  • domRef:图表容器的引用。
  • updateOptions:更新图表配置项的方法。
  • setOptions:设置图表配置项的方法。

依赖说明:

  • @vueuse/core:导入了 useElementSize,用于监听元素尺寸变化。
  • import { effectScope, nextTick, onScopeDispose, ref, watch } from 'vue'
    import * as echarts from 'echarts/core'
    import {
      BarChart,
      GaugeChart,
      LineChart,
      PictorialBarChart,
      PieChart,
      RadarChart,
      ScatterChart,
      TreemapChart
    } from 'echarts/charts'
    import type {
      BarSeriesOption,
      GaugeSeriesOption,
      LineSeriesOption,
      PictorialBarSeriesOption,
      PieSeriesOption,
      RadarSeriesOption,
      ScatterSeriesOption,
      TreemapSeriesOption
    } from 'echarts/charts'
    import {
      DatasetComponent,
      GridComponent,
      LegendComponent,
      TitleComponent,
      ToolboxComponent,
      TooltipComponent,
      TransformComponent
    } from 'echarts/components'
    import type {
      DatasetComponentOption,
      GridComponentOption,
      LegendComponentOption,
      TitleComponentOption,
      ToolboxComponentOption,
      TooltipComponentOption
    } from 'echarts/components'
    import { LabelLayout, UniversalTransition } from 'echarts/features'
    import { CanvasRenderer } from 'echarts/renderers'
    import { useElementSize } from '@vueuse/core'
    
    export type ECOption = echarts.ComposeOption<
      | BarSeriesOption
      | LineSeriesOption
      | PieSeriesOption
      | ScatterSeriesOption
      | TreemapSeriesOption
      | PictorialBarSeriesOption
      | RadarSeriesOption
      | GaugeSeriesOption
      | TitleComponentOption
      | LegendComponentOption
      | TooltipComponentOption
      | GridComponentOption
      | ToolboxComponentOption
      | DatasetComponentOption
    >
    
    echarts.use([
      TitleComponent,
      LegendComponent,
      TooltipComponent,
      GridComponent,
      DatasetComponent,
      TransformComponent,
      ToolboxComponent,
      BarChart,
      LineChart,
      PieChart,
      ScatterChart,
      PictorialBarChart,
      RadarChart,
      GaugeChart,
      TreemapChart,
      LabelLayout,
      UniversalTransition,
      CanvasRenderer
    ])
    
    interface ChartHooks {
      onRender?: (chart: echarts.ECharts) => void | Promise<void>
      onUpdated?: (chart: echarts.ECharts) => void | Promise<void>
      onDestroy?: (chart: echarts.ECharts) => void | Promise<void>
    }
    
    /**
     * use echarts
     *
     * @param optionsFactory echarts options factory function
     * @param darkMode dark mode
     */
    export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: ChartHooks = {}) {
      const scope = effectScope()
    
      const domRef = ref<HTMLElement | null>(null)
      const initialSize = { width: 0, height: 0 }
      const { width, height } = useElementSize(domRef, initialSize)
    
      const darkMode = ref('light')
      let chart: echarts.ECharts | null = null
      const chartOptions: T = optionsFactory()
    
      const {
        onRender = (instance) => {
          const textColor = darkMode.value ? 'rgb(224, 224, 224)' : 'rgb(31, 31, 31)'
          const maskColor = darkMode.value ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.8)'
    
          instance.showLoading({
            color: 'rgb(var(--primary-6))',
            textColor,
            fontSize: 14,
            maskColor
          })
        },
        onUpdated = (instance) => {
          instance.hideLoading()
        },
        onDestroy
      } = hooks
    
      /**
       * whether can render chart
       *
       * when domRef is ready and initialSize is valid
       */
      function canRender() {
        return domRef.value && initialSize.width > 0 && initialSize.height > 0
      }
    
      /** is chart rendered */
      function isRendered() {
        return Boolean(domRef.value && chart)
      }
    
      /**
       * update chart options
       *
       * @param callback callback function
       */
      async function updateOptions(
        callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions
      ) {
        if (!isRendered()) return
    
        const updatedOpts = callback(chartOptions, optionsFactory)
    
        Object.assign(chartOptions, updatedOpts)
    
        if (isRendered()) {
          chart?.clear()
        }
    
        chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' })
    
        await onUpdated?.(chart!)
      }
    
      function setOptions(options: T) {
        chart?.setOption(options)
      }
    
      /** render chart */
      async function render() {
        if (!isRendered()) {
          const chartTheme = darkMode.value ? 'dark' : 'light'
    
          await nextTick()
    
          chart = echarts.init(domRef.value, chartTheme)
    
          chart.setOption({ ...chartOptions, backgroundColor: 'transparent' })
    
          await onRender?.(chart)
        }
      }
    
      /** resize chart */
      function resize() {
        chart?.resize()
      }
    
      /** destroy chart */
      async function destroy() {
        if (!chart) return
    
        await onDestroy?.(chart)
        chart?.dispose()
        chart = null
      }
    
      /** change chart theme */
      async function changeTheme() {
        await destroy()
        await render()
        await onUpdated?.(chart!)
      }
    
      /**
       * render chart by size
       *
       * @param w width
       * @param h height
       */
      async function renderChartBySize(w: number, h: number) {
        initialSize.width = w
        initialSize.height = h
    
        // size is abnormal, destroy chart
        if (!canRender()) {
          await destroy()
    
          return
        }
    
        // resize chart
        if (isRendered()) {
          resize()
        }
    
        // render chart
        await render()
      }
    
      scope.run(() => {
        watch([width, height], ([newWidth, newHeight]) => {
          renderChartBySize(newWidth, newHeight)
        })
    
        watch(darkMode, () => {
          changeTheme()
        })
      })
    
      onScopeDispose(() => {
        destroy()
        scope.stop()
      })
    
      return {
        domRef,
        updateOptions,
        setOptions
      }
    }
    

    使用示例:

  • <template>
      <div ref="lineRef" class="flex-1"></div>
    </template>
    <script setup lang="ts">
    import { useEcharts } from '@/hooks/echarts'
    import { baseChartColor } from '../config';
    import type { ECOption } from '@/hooks/echarts'
    const option: ECOption = {
      tooltip: {
        trigger: 'axis'
      },
      legend: {
        data: ['单一资管计划', '委外投资组合', '公募基金'],
        icon: 'rich',
        show: true,
        itemWidth: 18,
        itemHeight: 8,
        textStyle: {
          color: '#666',
          fontSize: '12px',
        },
        top: 8,
        itemGap: 34
      },
      grid: {
        show: false
      },
      xAxis: {
        data: [2016, 2018, 2020, 2022, 2024],
        type: 'category',
        boundaryGap: false,
        axisTick: {
          show: false
        },
        axisLabel: {
          interval: 0,
          color: '#999',
          fontSize: 12,
          padding: [10, 0, 0, 0]
        }
      },
      yAxis: {
        type: 'value',
        axisLabel: {
          color: '#999',
          fontSize: 12,
          padding: [0, 10, 0, 0]
        },
        splitLine: {
          lineStyle: {
            color: '#ccc',
            type: 'dashed'
          }
        }
      },
      color: baseChartColor,
      series: [
        {
          name: '单一资管计划',
          type: 'line',
          smooth: true,
          symbol: 'none',
          data: [7, 7, 8, 9, 2],
        },
        {
          name: '委外投资组合',
          data: [1, 2, 3, 4, 20],
          type: 'line',
          smooth: true,
          symbol: 'none',
        },
        {
          name: '公募基金',
          data: [2, 7, 8, 4, 10],
          type: 'line',
          smooth: true,
          symbol: 'none'
        }
      ]
    }
    
    const { domRef: lineRef } = useEcharts(() => option, { onRender() { } })
    
    </script>
    <style lang="less" scoped></style>
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值