背景
近期在做数据大屏项目,使用到了ECharts图表,一段时间没用又要去翻文档了,好在现在有了通义灵码和Kimi.ai的辅助,一些小的配置项只用问这些AI即可,大部分都能够答对。但是有些配置项在新版本ECharts里做了更改,比如normal
属性,需要注意鉴别。这里顺便再推荐下大屏适配的库:autofit。
对于Vue3,直接挂载Echarts到vue实例上不是个好选择。因为与<script setup>
搭配使用组合式API时没有this
共开发者调用,需要先获取当前实例再调用,如下方式:
const { proxy } = getCurrentInstance()
const options = {}
proxy.$echarts.init(options)
这里我使用了组合式API去封装ECharts,然后在各图标组件中调用。
以下是具体做法:
封装
// hooks/useECharts.js
import * as echarts from 'echarts/core'
// 按需引入所需图表
import {
BarChart,
EffectScatterChart,
LineChart,
LinesChart,
MapChart,
PieChart
} from 'echarts/charts'
// 引入图表所需组件
import {
DatasetComponent,
GeoComponent,
GraphicComponent,
GridComponent,
LegendComponent,
TitleComponent,
TooltipComponent,
TransformComponent,
} from 'echarts/components'
import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
GeoComponent,
LabelLayout,
UniversalTransition,
CanvasRenderer,
MapChart,
BarChart,
LinesChart,
LineChart,
PieChart,
EffectScatterChart,
LegendComponent,
GraphicComponent,
])
export default function () {
const chartElement = shallowRef()
const chartInstance = shallowRef()
// 尺寸变化
function resize() {
chartInstance.value?.resize()
}
// 注册地图
function registerMap(name: string, json: any) {
echarts.registerMap(name, json)
}
// 图表绘制
function render() {
chartInstance.value = echarts.init(chartElement.value)
}
// 图表销毁
function destroy() {
chartInstance.value?.dispose()
chartInstance.value = null
}
onMounted(() => {
render()
window.addEventListener('resize', resize)
})
onBeforeUnmount(() => {
destroy()
window.removeEventListener('resize', resize)
})
return {
registerMap,
chartInstance,
chartElement,
}
}
使用
<script setup>
import useCharts from '@/hooks/useEcharts'
const { chartInstance, chartElement } = useCharts()
const options = {
// ...
}
onMounted(() => {
if (chartInstance.value)
chartInstance.value.setOption(options)
})
</script>
<template>
<div class="view-container">
<div ref="chartElement" class="line-chart" />
</div>
</template>
其他方法
以上是我目前使用ECharts的方法,最近看到个开源的后台项目:nova-admin,里面也封装了ECharts,这里也放下这个项目的封装方法:
import * as echarts from 'echarts/core'
import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts'
// 系列类型的定义后缀都为 SeriesOption
import type {
BarSeriesOption,
LineSeriesOption,
PieSeriesOption,
RadarSeriesOption,
} from 'echarts/charts'
// 组件类型的定义后缀都为 ComponentOption
import type {
DatasetComponentOption,
GridComponentOption,
LegendComponentOption,
TitleComponentOption,
ToolboxComponentOption,
TooltipComponentOption,
} from 'echarts/components'
import {
DatasetComponent, // 数据集组件
GridComponent,
LegendComponent,
TitleComponent,
ToolboxComponent,
TooltipComponent,
TransformComponent, // 内置数据转换器组件 (filter, sort)
} from 'echarts/components'
import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
import { useAppStore } from '@/store'
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = echarts.ComposeOption<
| BarSeriesOption
| PieSeriesOption
| LineSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| LegendComponentOption
| DatasetComponentOption
| ToolboxComponentOption
| RadarSeriesOption
>
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
DatasetComponent,
TransformComponent,
BarChart,
PieChart,
LineChart,
LabelLayout,
UniversalTransition,
CanvasRenderer,
ToolboxComponent,
RadarChart,
])
/**
* Echarts hooks函数
* @param options - 图表配置
* @description 按需引入图表组件,没注册的组件需要先引入
*/
export function useEcharts(options: Ref<ECOption>) {
const appStore = useAppStore()
const domRef = ref<HTMLElement>()
let chart: echarts.ECharts | null = null
const initialSize = { width: 0, height: 0 }
// useElementSize是@vueuse/core库中的,由于项目设置了自动导入,所以上方没有引入
const { width, height } = useElementSize(domRef, initialSize)
function canRender() {
return initialSize.width > 0 && initialSize.height > 0
}
function isRendered() {
return Boolean(domRef.value && chart)
}
async function render() {
const chartTheme = appStore.colorMode ? 'dark' : 'light'
await nextTick()
if (domRef.value) {
chart = echarts.init(domRef.value, chartTheme)
update(options.value)
}
}
function update(updateOptions: ECOption) {
if (isRendered())
chart!.setOption({ ...updateOptions, backgroundColor: 'transparent' })
}
function resize() {
chart?.resize()
}
function destroy() {
chart?.dispose()
chart = null
}
const sizeWatch = watch([width, height], async ([newWidth, newHeight]) => {
initialSize.width = newWidth
initialSize.height = newHeight
if (newWidth === 0 && newHeight === 0) {
// 节点被删除 将chart置为空
chart = null
}
if (!canRender())
return
if (isRendered())
resize()
else await render()
})
const OptionWatch = watch(options, (newValue) => {
update(newValue)
})
onUnmounted(() => {
sizeWatch()
OptionWatch()
destroy()
})
return {
domRef,
}
}
使用起来无需手动调用init了,只需传配置项即可:
<script setup lang="ts">
import { type ECOption, useEcharts } from '@/hooks'
const option = ref<ECOption>({
//...
}) as Ref<ECOption>
const { domRef: lineRef } = useEcharts(option)
</script>
<template>
<div ref="lineRef" />
</template>
这里使用是比我用的方法更加方便的,但是需要注册地图registerMap
的话就要另外加逻辑了,也没有导出EChart实例,有些效果如dispatchAction
手动高亮不好实现。简单的图表展示需求,还是可以考虑这种方法。