2024-01-24 01:48:23 +08:00
|
|
|
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';
|
2024-01-24 01:48:23 +08:00
|
|
|
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>;
|
2024-01-24 01:48:23 +08:00
|
|
|
onUpdated?: (chart: echarts.ECharts) => void | Promise<void>;
|
2024-01-19 02:17:17 +08:00
|
|
|
onDestroy?: (chart: echarts.ECharts) => void | Promise<void>;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* use echarts
|
|
|
|
*
|
2024-01-24 01:48:23 +08:00
|
|
|
* @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();
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
const themeStore = useThemeStore();
|
|
|
|
const darkMode = computed(() => themeStore.darkMode);
|
2024-01-19 02:17:17 +08:00
|
|
|
|
2024-01-24 01:48:23 +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;
|
2024-01-24 01:48:23 +08:00
|
|
|
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;
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
/**
|
|
|
|
* whether can render chart
|
|
|
|
*
|
|
|
|
* when domRef is ready and initialSize is valid
|
|
|
|
*/
|
2024-01-19 02:17:17 +08:00
|
|
|
function canRender() {
|
2024-01-24 01:48:23 +08:00
|
|
|
return domRef.value && initialSize.width > 0 && initialSize.height > 0;
|
2024-01-19 02:17:17 +08:00
|
|
|
}
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
/** is chart rendered */
|
2024-01-19 02:17:17 +08:00
|
|
|
function isRendered() {
|
|
|
|
return Boolean(domRef.value && chart);
|
|
|
|
}
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
/**
|
|
|
|
* 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();
|
|
|
|
}
|
2024-01-24 01:48:23 +08:00
|
|
|
|
|
|
|
chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' });
|
|
|
|
|
2024-01-26 02:10:00 +08:00
|
|
|
await onUpdated?.(chart!);
|
2024-01-19 02:17:17 +08:00
|
|
|
}
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
/** render chart */
|
2024-01-19 02:17:17 +08:00
|
|
|
async function render() {
|
2024-01-24 01:48:23 +08:00
|
|
|
if (!isRendered()) {
|
2024-01-19 02:17:17 +08:00
|
|
|
const chartTheme = darkMode.value ? 'dark' : 'light';
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
chart = echarts.init(domRef.value, chartTheme);
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
/** resize chart */
|
2024-01-19 02:17:17 +08:00
|
|
|
function resize() {
|
|
|
|
chart?.resize();
|
|
|
|
}
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
/** 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;
|
|
|
|
}
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
/** 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
|
|
|
|
*
|
2024-01-24 01:48:23 +08:00
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
|
2024-01-24 01:48:23 +08:00
|
|
|
// resize chart
|
|
|
|
if (isRendered()) {
|
|
|
|
resize();
|
2024-01-19 02:17:17 +08:00
|
|
|
}
|
|
|
|
|
2024-01-24 01:48:23 +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,
|
2024-01-24 01:48:23 +08:00
|
|
|
updateOptions
|
2024-01-19 02:17:17 +08:00
|
|
|
};
|
|
|
|
}
|