useECharts.ts 4.1 KB
Newer Older
P
Peter Pan 已提交
1
import {MutableRefObject, useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';
P
Peter Pan 已提交
2
import {maskColor, position, primaryColor, size, textColor} from '~/utils/style';
3

4
import type {ECharts} from 'echarts';
P
Peter Pan 已提交
5 6
import {dataURL2Blob} from '~/utils/image';
import {saveAs} from 'file-saver';
P
Peter Pan 已提交
7
import styled from 'styled-components';
8

9
export type Options = {
P
Peter Pan 已提交
10
    loading?: boolean;
11
    gl?: boolean;
P
Peter Pan 已提交
12
    zoom?: boolean;
P
Peter Pan 已提交
13
    autoFit?: boolean;
14 15 16 17 18 19 20
    onInit?: (echarts: ECharts) => unknown;
    onDispose?: (echarts: ECharts) => unknown;
};

const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElement>(
    options: Options
): {
P
Peter Pan 已提交
21
    ref: MutableRefObject<T | null>;
P
Peter Pan 已提交
22 23
    wrapper: MutableRefObject<W | null>;
    echart: ECharts | null;
P
Peter Pan 已提交
24
    saveAsImage: (filename?: string) => void;
P
Peter Pan 已提交
25
} => {
26 27
    const ref = useRef<T | null>(null);
    const echartInstance = useRef<ECharts | null>(null);
P
Peter Pan 已提交
28
    const [echart, setEchart] = useState<ECharts | null>(null);
29

30 31 32
    const onInit = useRef(options.onInit);
    const onDispose = useRef(options.onDispose);

33 34
    const hideTip = useCallback(() => echartInstance.current?.dispatchAction({type: 'hideTip'}), []);

35
    const createChart = useCallback(() => {
P
Peter Pan 已提交
36
        (async () => {
37
            const {default: echarts} = await import('echarts');
P
Peter Pan 已提交
38 39 40
            if (options.gl) {
                await import('echarts-gl');
            }
41 42 43
            if (!ref.current) {
                return;
            }
P
Peter Pan 已提交
44
            echartInstance.current = echarts.init((ref.current as unknown) as HTMLDivElement);
45

46 47
            ref.current.addEventListener('mouseleave', hideTip);

48 49
            setTimeout(() => {
                if (options.zoom) {
P
Peter Pan 已提交
50 51 52 53 54
                    echartInstance.current?.dispatchAction({
                        type: 'takeGlobalCursor',
                        key: 'dataZoomSelect',
                        dataZoomSelectActive: true
                    });
55 56 57 58 59 60 61
                }

                if (echartInstance.current) {
                    onInit.current?.(echartInstance.current);
                }
            }, 0);

P
Peter Pan 已提交
62
            setEchart(echartInstance.current);
P
Peter Pan 已提交
63
        })();
64
    }, [options.gl, options.zoom, hideTip]);
65 66

    const destroyChart = useCallback(() => {
67 68 69
        if (echartInstance.current) {
            onDispose.current?.(echartInstance.current);
        }
P
Peter Pan 已提交
70
        echartInstance.current?.dispose();
71
        ref.current?.removeEventListener('mouseleave', hideTip);
P
Peter Pan 已提交
72
        setEchart(null);
73
    }, [hideTip]);
74 75

    useEffect(() => {
76 77
        createChart();
        return destroyChart;
78 79 80
    }, [createChart, destroyChart]);

    useEffect(() => {
81 82 83 84 85 86 87 88 89 90
        if (options.loading) {
            echartInstance.current?.showLoading('default', {
                text: '',
                color: primaryColor,
                textColor,
                maskColor,
                zlevel: 0
            });
        } else {
            echartInstance.current?.hideLoading();
91
        }
P
Peter Pan 已提交
92 93 94 95
    }, [options.loading]);

    const wrapper = useRef<W | null>(null);
    useLayoutEffect(() => {
96
        if (options.autoFit) {
P
Peter Pan 已提交
97 98 99 100 101 102 103 104 105 106
            const w = wrapper.current;
            if (w) {
                const observer = new ResizeObserver(() => {
                    echartInstance.current?.resize();
                });
                observer.observe(w);
                return () => observer.unobserve(w);
            }
        }
    }, [options.autoFit]);
107

P
Peter Pan 已提交
108 109 110 111 112 113 114 115 116 117 118
    const saveAsImage = useCallback(
        (filename?: string) => {
            if (echart) {
                const blob = dataURL2Blob(echart.getDataURL({type: 'png', pixelRatio: 2, backgroundColor: '#FFF'}));
                saveAs(blob, `${filename?.replace(/[/\\?%*:|"<>]/g, '_') || 'chart'}.png`);
            }
        },
        [echart]
    );

    return {ref, echart, wrapper, saveAsImage};
119 120 121
};

export default useECharts;
P
Peter Pan 已提交
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140

export const Wrapper = styled.div`
    position: relative;
    display: flex;
    justify-content: center;
    align-items: stretch;

    > .echarts {
        width: 100%;
    }

    > .loading {
        ${size('100%')}
        ${position('absolute', 0, null, null, 0)}
        display: flex;
        justify-content: center;
        align-items: center;
    }
`;