TypeScript, Vue

2023.01.11

封裝並按需引入 ECharts,圖表的寬高自動使用父層的寬高

最近在重構公司專案,想要把 Apache ECharts 套件的處理,寫得更好一些,不然每次隔一段時間回去看 code,都覺得超失憶 😂…

這次試著按需引入 ECharts 的元件,我只有用到 Line Chart 以及 Bar Chart;然後再把 Echarts 封裝起來,畢竟有很多 options 設定是一樣的,可以整在一起。

最後還有一個,我覺得很難處理的部分,就是要特別去指定 ECharts 圖表的寬高,單位必須是 px,不能為 %

  • 使用版本:Apache ECharts v5.4
  • 使用框架:Vue3 + TypeScript

如何看 ECharts 引入了哪些元件

我們可以從 ECharts 提供的範例與完整語法,來看到他用了哪些元件

▼ 假設我選了一個「折線圖堆疊」,點進去之後,切換到「完整代碼」的分頁,把「按需引入」打開,就可以看到完整的 code 了。

TypeScript 的按需引入

另外如果你也是用 TypeScript,ECharts 有提供完整的 TypeScript 按需引入範例

import * as echarts from 'echarts/core';
import {
  BarChart,
  // 系列類型的定義後綴都為 SeriesOption
  BarSeriesOption,
  LineChart,
  LineSeriesOption
} from 'echarts/charts';
import {
  TitleComponent,
  // 元件類型的定義後綴都為 ComponentOption
  TitleComponentOption,
  TooltipComponent,
  TooltipComponentOption,
  GridComponent,
  GridComponentOption,
  // 資料集元件
  DatasetComponent,
  DatasetComponentOption,
  // 內建資料轉換器元件 (filter, sort)
  TransformComponent
} from 'echarts/components';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';

// 通過 ComposeOption 來組合出一個只有必須元件和圖表的 Option 類型
type ECOption = echarts.ComposeOption<
  | BarSeriesOption
  | LineSeriesOption
  | TitleComponentOption
  | TooltipComponentOption
  | GridComponentOption
  | DatasetComponentOption
>;

// 註冊必須的元件
echarts.use([
  TitleComponent,
  TooltipComponent,
  GridComponent,
  DatasetComponent,
  TransformComponent,
  BarChart,
  LineChart,
  LabelLayout,
  UniversalTransition,
  CanvasRenderer
]);

const option: ECOption = {
  // ...
};

封裝 ECharts

我封裝的函式如下:

  • setOption():設定 ECharts 的選項 (非共用)
  • resize():調整螢幕寬度時,圖表要自適應調整
  • dispose():離開時要銷毀 ECharts 元件

▼ 以下是完整的用 TypeScript 做的,按需引入並封裝 ECharts 函式

import * as echarts from 'echarts/core'
import { ComposeOption } from 'echarts/core'
import { UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'

import {
  TitleComponent,
  TitleComponentOption,
  ToolboxComponent,
  ToolboxComponentOption,
  TooltipComponent,
  TooltipComponentOption,
  GridComponent,
  GridComponentOption,
  LegendComponent,
  LegendComponentOption,
  DatasetComponent,
  DatasetComponentOption,
  TransformComponent
} from 'echarts/components'

import {
  LineChart,
  LineSeriesOption,
  BarChart,
  BarSeriesOption
} from 'echarts/charts'

type ECOption = ComposeOption<
  | BarSeriesOption
  | LineSeriesOption
  | TitleComponentOption
  | ToolboxComponentOption
  | TooltipComponentOption
  | GridComponentOption
  | LegendComponentOption
  | DatasetComponentOption
>

echarts.use([
  TitleComponent,
  ToolboxComponent,
  TooltipComponent,
  GridComponent,
  LegendComponent,
  DatasetComponent,
  TransformComponent,
  LineChart,
  BarChart,
  CanvasRenderer,
  UniversalTransition
])

const useChart = (element: HTMLDivElement) => {
  const myChart = echarts.init(element)
  const setOption = (optionData: any) => {
    const option: ECOption = {
      tooltip: {},
      xAxis: {
        data: ['襯衫', '羊毛衫', '雪紡衫', '褲子', '高跟鞋', '襪子']
      },
      yAxis: {},
      ...optionData
    }
    return myChart.setOption(option)
  }

  const resize = () => myChart.resize()
  return { setOption, resize }
}

export default useChart

在 Vue 中使用 ECharts

▼ 以下是完整的語法,包含使用父層的寬高當做圖表寬高,以及寫入獨立的 option 設定

<template>
  <div class="w-full h-full" ref="containerRef">
    <div ref="chartRef"></div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue'
import useChart from '@/plugins/EChart'

const parentHeight = ref(0)
const containerRef = ref<HTMLDivElement>()
const chartRef = ref<HTMLDivElement>()
const computedParentHeight = computed({
  get () { return parentHeight.value },
  set (val: number) {
    parentHeight.value = val;
    (chartRef.value as any).style.height = `${val}px`
  }
})

let chartInstance: ReturnType<typeof useChart>

onMounted(() => {
  nextTick(() => {
    setTimeout(() => {
      parentHeight.value = (containerRef.value as HTMLDivElement).clientHeight
      computedParentHeight.value = parentHeight.value
      chartInstance = useChart(chartRef.value as HTMLDivElement)
      const { setOption, resize } = chartInstance
      setOption({
        series: [
          {
            name: '銷量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
          }
        ]
      })
      window.addEventListener('resize', () => {
        if (!chartInstance) return
        chartInstance.resize()
      })
    }, 1000)
  })
})

onUnmounted(() => {
  const { resize } = chartInstance
  window.removeEventListener('resize', () => { resize() })
})
</script>

基本上,都是使用 nextTick() 等 dom 渲染完。

但我的 dom 是用另外一個套件產生的,所以才在裡面加了 setTimeout(),多一點等待時間,讓 dom 完全渲染完畢。

歡迎給我點鼓勵,讓我知道你來過 :)


更多 Vue 相關文章

guest

0 則留言
Inline Feedbacks
View all comments
Copyright (C) MUKI space* / Reborn Theme All Rights Reserved.