import { computed, effectScope, nextTick, onScopeDispose, ref, watch } from 'vue'; import { useElementSize } from '@vueuse/core'; import * as echarts from 'echarts/core'; import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts'; import type { BarSeriesOption, GaugeSeriesOption, LineSeriesOption, PictorialBarSeriesOption, PieSeriesOption, RadarSeriesOption, ScatterSeriesOption } 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 { useThemeStore } from '@/store/modules/theme'; export type ECOption = echarts.ComposeOption< | BarSeriesOption | LineSeriesOption | PieSeriesOption | ScatterSeriesOption | 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, LabelLayout, UniversalTransition, CanvasRenderer ]); interface ChartHooks { onRender?: (chart: echarts.ECharts) => void | Promise; onUpdated?: (chart: echarts.ECharts) => void | Promise; onDestroy?: (chart: echarts.ECharts) => void | Promise; } /** * use echarts * * @param optionsFactory echarts options factory function * @param darkMode dark mode */ export function useEcharts(optionsFactory: () => T, hooks: ChartHooks = {}) { const scope = effectScope(); const themeStore = useThemeStore(); const darkMode = computed(() => themeStore.darkMode); const domRef = ref(null); const initialSize = { width: 0, height: 0 }; const { width, height } = useElementSize(domRef, initialSize); 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: themeStore.themeColor, 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(); if (chart) { await onUpdated?.(chart); } } scope.run(() => { watch([width, height], ([newWidth, newHeight]) => { renderChartBySize(newWidth, newHeight); }); watch(darkMode, () => { changeTheme(); }); }); onScopeDispose(() => { destroy(); scope.stop(); }); return { domRef, updateOptions, setOptions }; }