ruoyi-plus-soybean/src/hooks/common/echarts.ts

231 lines
5.1 KiB
TypeScript
Raw Normal View History

import { computed, effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
2024-01-19 02:17:17 +08:00
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 { useElementSize } from '@vueuse/core';
import { useThemeStore } from '@/store/modules/theme';
2024-01-19 02:17:17 +08:00
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<void>;
onUpdated?: (chart: echarts.ECharts) => void | Promise<void>;
2024-01-19 02:17:17 +08:00
onDestroy?: (chart: echarts.ECharts) => void | Promise<void>;
}
/**
* use echarts
*
* @param optionsFactory echarts options factory function
2024-01-19 02:17:17 +08:00
* @param darkMode dark mode
*/
2024-01-26 02:10:00 +08:00
export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: ChartHooks = {}) {
2024-01-19 02:17:17 +08:00
const scope = effectScope();
const themeStore = useThemeStore();
const darkMode = computed(() => themeStore.darkMode);
2024-01-19 02:17:17 +08:00
const domRef = ref<HTMLElement | null>(null);
2024-01-19 02:17:17 +08:00
const initialSize = { width: 0, height: 0 };
const { width, height } = useElementSize(domRef, initialSize);
let chart: echarts.ECharts | null = null;
const chartOptions: T = optionsFactory();
2024-01-19 02:17:17 +08:00
2024-01-26 02:10:00 +08:00
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
*/
2024-01-19 02:17:17 +08:00
function canRender() {
return domRef.value && initialSize.width > 0 && initialSize.height > 0;
2024-01-19 02:17:17 +08:00
}
/** is chart rendered */
2024-01-19 02:17:17 +08:00
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);
2024-01-19 02:17:17 +08:00
if (isRendered()) {
chart?.clear();
}
chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' });
2024-01-26 02:10:00 +08:00
await onUpdated?.(chart!);
2024-01-19 02:17:17 +08:00
}
/** render chart */
2024-01-19 02:17:17 +08:00
async function render() {
if (!isRendered()) {
2024-01-19 02:17:17 +08:00
const chartTheme = darkMode.value ? 'dark' : 'light';
await nextTick();
chart = echarts.init(domRef.value, chartTheme);
chart.setOption({ ...chartOptions, backgroundColor: 'transparent' });
2024-01-19 02:17:17 +08:00
2024-01-26 02:10:00 +08:00
await onRender?.(chart);
2024-01-19 02:17:17 +08:00
}
}
/** resize chart */
2024-01-19 02:17:17 +08:00
function resize() {
chart?.resize();
}
/** destroy chart */
2024-01-19 02:17:17 +08:00
async function destroy() {
if (!chart) return;
2024-01-26 02:10:00 +08:00
await onDestroy?.(chart);
2024-01-19 02:17:17 +08:00
chart?.dispose();
chart = null;
}
/** change chart theme */
2024-01-19 02:17:17 +08:00
async function changeTheme() {
await destroy();
await render();
2024-01-26 02:10:00 +08:00
await onUpdated?.(chart!);
2024-01-19 02:17:17 +08:00
}
/**
* render chart by size
*
* @param w width
* @param h height
2024-01-19 02:17:17 +08:00
*/
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();
2024-01-19 02:17:17 +08:00
}
// render chart
await render();
2024-01-19 02:17:17 +08:00
}
scope.run(() => {
watch([width, height], ([newWidth, newHeight]) => {
renderChartBySize(newWidth, newHeight);
});
watch(darkMode, () => {
changeTheme();
});
});
onScopeDispose(() => {
destroy();
scope.stop();
});
return {
domRef,
updateOptions
2024-01-19 02:17:17 +08:00
};
}