From 19f62a40048bd242a79756d977ab42e8971a8280 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 19 Jul 2023 14:21:22 +0800 Subject: [PATCH] refactor: chart card performance optimization Signed-off-by: neil --- package.json | 2 + src/common/components/BaseCard.tsx | 34 +++++++++-- src/common/components/EChartX/index.tsx | 24 +++++--- src/common/components/Freeze.tsx | 15 +++++ src/common/components/LoadInView.tsx | 25 ++++++-- src/common/debug.ts | 59 ++++++++++++++++++ src/common/hooks/useInViewportDebounce.tsx | 10 +++ .../CodeMergeRatio.tsx | 61 ++++++++++++------- .../Container/ChartDataContainer.tsx | 41 +++++++------ .../Container/ChartOptionContainer.tsx | 11 +++- .../analyze/options/ChartOptionAdapter.tsx | 36 +++++++++++ .../options/builder/getCompareStyleBuilder.ts | 13 ++++ .../options/builder/getLineChartBuilder.ts | 54 ++++++++++++++++ .../builder/getReferenceLineBuilder.ts | 46 ++++++++++++++ src/modules/analyze/options/index.ts | 8 +++ src/modules/analyze/options/useCardManual.tsx | 28 +++++++++ .../analyze/options/useOptionBuilderFns.ts | 20 ++++++ yarn.lock | 12 ++++ 18 files changed, 438 insertions(+), 61 deletions(-) create mode 100644 src/common/components/Freeze.tsx create mode 100644 src/common/debug.ts create mode 100644 src/common/hooks/useInViewportDebounce.tsx create mode 100644 src/modules/analyze/options/ChartOptionAdapter.tsx create mode 100644 src/modules/analyze/options/builder/getCompareStyleBuilder.ts create mode 100644 src/modules/analyze/options/builder/getLineChartBuilder.ts create mode 100644 src/modules/analyze/options/builder/getReferenceLineBuilder.ts create mode 100644 src/modules/analyze/options/index.ts create mode 100644 src/modules/analyze/options/useCardManual.tsx create mode 100644 src/modules/analyze/options/useOptionBuilderFns.ts diff --git a/package.json b/package.json index 144f963e..128a8382 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "color": "^4.2.3", "daisyui": "^2.31.0", "date-fns": "^2.29.2", + "debug": "^4.3.4", "echarts": "^5.4.1", "file-saver": "^2.0.5", "framer-motion": "^7.2.1", @@ -91,6 +92,7 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@types/color": "^3.0.3", + "@types/debug": "^4.1.8", "@types/fs-extra": "^11.0.1", "@types/gtag.js": "^0.0.11", "@types/http-proxy": "^1.17.9", diff --git a/src/common/components/BaseCard.tsx b/src/common/components/BaseCard.tsx index edc9fac9..e1728aa5 100644 --- a/src/common/components/BaseCard.tsx +++ b/src/common/components/BaseCard.tsx @@ -6,6 +6,9 @@ import classnames from 'classnames'; import { BiFullscreen, BiExitFullscreen } from 'react-icons/bi'; import { useHotkeys } from 'react-hotkeys-hook'; import DocPopper from '@common/components/DocPopper'; +import { DebugLogger } from '@common/debug'; + +const logger = new DebugLogger('BaseCard'); const Loading: React.FC<{ className: string }> = ({ className }) => (
@@ -26,6 +29,7 @@ const Loading: React.FC<{ className: string }> = ({ className }) => ( ); interface BaseCardProps { + _tracing?: string; id?: string; loading?: boolean; className?: string; @@ -43,16 +47,22 @@ interface BaseCardProps { setFullScreen: React.Dispatch> ) => ReactNode) | ReactNode; - children: + bodyClass?: string; + bodyRender?: + | ((containerRef: RefObject, fullScreen: boolean) => ReactNode) + | ReactNode; + /** + * @deprecate use bodyRender + */ + children?: | ((containerRef: RefObject, fullScreen: boolean) => ReactNode) | ReactNode; - bodyClass?: string; } const BaseCard: React.FC = ({ + _tracing, id, className = '', - children, loading = false, title = '', description = '', @@ -63,12 +73,18 @@ const BaseCard: React.FC = ({ notes = '', headRight = null, bodyClass = 'h-[350px]', + bodyRender, + children, }) => { const cardRef = useRef(null); const titleRef = useRef(null); const [fullScreen, setFullScreen] = useState(false); const { t } = useTranslation(); + if (_tracing) { + logger.debug(_tracing, { title, description, id }); + } + const cls = classnames( className, 'base-card', @@ -110,14 +126,22 @@ const BaseCard: React.FC = ({ threshold={threshold} detail={detail} notes={notes} - > + />
{typeof headRight === 'function' ? headRight(cardRef, fullScreen, setFullScreen) : headRight}
- + + {typeof bodyRender === 'function' + ? bodyRender(cardRef, fullScreen) + : bodyRender} + {typeof children === 'function' ? children(cardRef, fullScreen) : children} diff --git a/src/common/components/EChartX/index.tsx b/src/common/components/EChartX/index.tsx index e78d33bb..d6e88ee0 100644 --- a/src/common/components/EChartX/index.tsx +++ b/src/common/components/EChartX/index.tsx @@ -1,36 +1,40 @@ import React, { useRef, useEffect, useCallback } from 'react'; -import { useDeepCompareEffect, useInViewport } from 'ahooks'; +import { useDeepCompareEffect } from 'ahooks'; import { connect, init, getInstanceByDom } from 'echarts'; import type { CSSProperties } from 'react'; import type { EChartsOption, ECharts, SetOptionOpts } from 'echarts'; import { useResizeDetector } from 'react-resize-detector'; +import useInViewportDebounce from '@common/hooks/useInViewportDebounce'; +import { DebugLogger } from '@common/debug'; + +const logger = new DebugLogger('EChartX'); connect('group-time'); export interface ReactEChartsProps { - _flag?: string; option: EChartsOption; style?: CSSProperties; settings?: SetOptionOpts; loading?: boolean; theme?: 'light' | 'dark'; containerRef?: React.RefObject; + _tracing?: string; } const EChartX: React.FC = ({ - _flag, option, style, settings = { notMerge: true }, loading, theme, containerRef, + _tracing, }) => { - const [inViewport] = useInViewport(containerRef); + const inView = useInViewportDebounce(containerRef); const chartRef = useRef(null); - if (_flag === 'debug') { - console.log('debug echartx', { inViewport, loading, option }); + if (_tracing) { + logger.debug(_tracing, { inView, loading, option }); } useEffect(() => { @@ -46,24 +50,24 @@ const EChartX: React.FC = ({ useDeepCompareEffect(() => { // Update chart - if (inViewport && chartRef.current !== null) { + if (inView && chartRef.current !== null) { const chart = getInstanceByDom(chartRef.current)!; chart.setOption(option, settings); } - }, [option, settings, inViewport]); + }, [option, settings, inView]); useEffect(() => { // Update chart if (chartRef.current !== null) { const chart = getInstanceByDom(chartRef.current)!; - if (inViewport) { + if (inView) { loading === true ? chart?.showLoading() : chart?.hideLoading(); chart!.group = 'group-time'; } else { chart!.group = ''; } } - }, [loading, inViewport]); + }, [loading, inView]); // ----------------container resize------------------------------ const onResize = useCallback((width?: number, height?: number) => { diff --git a/src/common/components/Freeze.tsx b/src/common/components/Freeze.tsx new file mode 100644 index 00000000..af1727b0 --- /dev/null +++ b/src/common/components/Freeze.tsx @@ -0,0 +1,15 @@ +import React, { memo, PropsWithChildren } from 'react'; + +type Props = PropsWithChildren & { freeze: boolean }; + +const Freeze = memo( + ({ children, freeze }: Props) => <>{children}, + (prevProps, nextProps) => { + if (nextProps.freeze) return true; + return Object.is(prevProps, nextProps); + } +); + +Freeze.displayName = 'Freeze'; + +export default Freeze; diff --git a/src/common/components/LoadInView.tsx b/src/common/components/LoadInView.tsx index f43838c4..4f6f3b46 100644 --- a/src/common/components/LoadInView.tsx +++ b/src/common/components/LoadInView.tsx @@ -1,15 +1,30 @@ -import React, { PropsWithChildren, RefObject, useEffect, useRef } from 'react'; -import { useInViewport } from 'ahooks'; +import React, { + memo, + PropsWithChildren, + RefObject, + useEffect, + useRef, +} from 'react'; +import Freeze from '@common/components/Freeze'; +import useInViewportDebounce from '@common/hooks/useInViewportDebounce'; import classnames from 'classnames'; +import { DebugLogger } from '@common/debug'; + +const logger = new DebugLogger('LoadInView'); const LoadInView: React.FC< PropsWithChildren<{ className?: string; containerRef: RefObject; + _tracing?: string; }> -> = ({ className, children, containerRef }) => { +> = ({ className, children, containerRef, _tracing }) => { const showed = useRef(false); - const [inView] = useInViewport(containerRef); + const inView = useInViewportDebounce(containerRef); + + if (_tracing) { + logger.debug(_tracing, { inView, showed: showed.current }); + } useEffect(() => { if (inView) showed.current = true; @@ -19,7 +34,7 @@ const LoadInView: React.FC< return (
- {isRender ? <>{children} : null} + {isRender ? {children} : null}
); }; diff --git a/src/common/debug.ts b/src/common/debug.ts new file mode 100644 index 00000000..a3686a2e --- /dev/null +++ b/src/common/debug.ts @@ -0,0 +1,59 @@ +import debug from 'debug'; + +type LogLevel = 'debug' | 'info' | 'warn' | 'error'; + +const SESSION_KEY = 'oss-compass:debug'; + +if (typeof window !== 'undefined') { + // enable debug logs if the URL search string contains `debug` + // e.g. http://localhost:3000/?debug + if (window.location.search.includes('debug')) { + sessionStorage.setItem(SESSION_KEY, 'true'); + } + if (sessionStorage.getItem(SESSION_KEY) === 'true') { + // enable all debug logs by default + debug.enable('*'); + console.warn('Debug logs enabled'); + } + if (process.env.NODE_ENV === 'development') { + debug.enable('*'); + console.warn('Debug logs enabled'); + } +} + +export class DebugLogger { + private _debug: debug.Debugger; + + constructor(namespace: string) { + this._debug = debug(namespace); + } + + set enabled(enabled: boolean) { + this._debug.enabled = enabled; + } + + get enabled() { + return this._debug.enabled; + } + + debug(message: string, ...args: any[]) { + this.log('debug', message, ...args); + } + + info(message: string, ...args: any[]) { + this.log('info', message, ...args); + } + + warn(message: string, ...args: any[]) { + this.log('warn', message, ...args); + } + + error(message: string, ...args: any[]) { + this.log('error', message, ...args); + } + + log(level: LogLevel, message: string, ...args: any[]) { + this._debug.log = console[level].bind(console); + this._debug(`[${level.toUpperCase()}] ${message}`, ...args); + } +} diff --git a/src/common/hooks/useInViewportDebounce.tsx b/src/common/hooks/useInViewportDebounce.tsx new file mode 100644 index 00000000..c2202447 --- /dev/null +++ b/src/common/hooks/useInViewportDebounce.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import type { RefObject } from 'react'; +import { useInViewport, useDebounce } from 'ahooks'; + +const useInViewportDebounce = (ref: RefObject | undefined) => { + const [inView] = useInViewport(ref); + return useDebounce(inView, { wait: 200 }); +}; + +export default useInViewportDebounce; diff --git a/src/modules/analyze/DataView/CollaborationDevelopmentIndex/CodeMergeRatio.tsx b/src/modules/analyze/DataView/CollaborationDevelopmentIndex/CodeMergeRatio.tsx index 6f9a3394..b88fe633 100644 --- a/src/modules/analyze/DataView/CollaborationDevelopmentIndex/CodeMergeRatio.tsx +++ b/src/modules/analyze/DataView/CollaborationDevelopmentIndex/CodeMergeRatio.tsx @@ -2,13 +2,19 @@ import React, { useMemo, useState } from 'react'; import { CollaborationDevelopment } from '@modules/analyze/components/SideBar/config'; import BaseCard from '@common/components/BaseCard'; import ChartDataContainer from '@modules/analyze/components/Container/ChartDataContainer'; -import ChartOptionContainer from '@modules/analyze/components/Container/ChartOptionContainer'; import { useTranslation } from 'next-i18next'; import Tab from '@common/components/Tab'; import EChartX from '@common/components/EChartX'; -import { GenChartOptions, TransOpt } from '@modules/analyze/type'; -import useGetRatioLineOption from '@modules/analyze/hooks/useGetRatioLineOption'; +import { TransOpt } from '@modules/analyze/type'; import CardDropDownMenu from '@modules/analyze/components/CardDropDownMenu'; +import { + ChartOptionAdapter, + useCardManual, + useOptionBuilderFns, + getLineChartBuilder, + getCompareStyleBuilder, + getReferenceLineBuilder, +} from '@modules/analyze/options'; const CodeMergeRatio = () => { const { t } = useTranslation(); @@ -43,18 +49,31 @@ const CodeMergeRatio = () => { type TabValue = keyof typeof chartTabs; const [tab, setTab] = useState('1'); const tansOpts: TransOpt = chartTabs[tab]; + const { - getOptions, showMedian, setShowMedian, showAvg, setShowAvg, yAxisScale, setYAxisScale, - } = useGetRatioLineOption({ tab }); + } = useCardManual(); + + const geOptionFn = useOptionBuilderFns([ + getLineChartBuilder({ isRatio: tab === '1', yAxisScale }), + getReferenceLineBuilder({ + showMedian, + showAvg, + medianMame: t('analyze:median'), + avgName: t('analyze:average'), + isRatio: tab === '1', + }), + getCompareStyleBuilder({ indicators: false }), + ]); return ( { /> )} bodyClass={'h-[400px]'} - > - {(ref, fullScreen) => { + bodyRender={(ref, fullScreen) => { return ( <>
@@ -101,30 +119,29 @@ const CodeMergeRatio = () => { onChange={(v) => setTab(v as TabValue)} />
- + {({ loading, result }) => { return ( - - {({ option }) => { - return ( - - ); - }} - + optionFn={geOptionFn} + render={({ option }) => ( + + )} + /> ); }} ); }} -
+ /> ); }; diff --git a/src/modules/analyze/components/Container/ChartDataContainer.tsx b/src/modules/analyze/components/Container/ChartDataContainer.tsx index 04e9d0ef..343d9ec2 100644 --- a/src/modules/analyze/components/Container/ChartDataContainer.tsx +++ b/src/modules/analyze/components/Container/ChartDataContainer.tsx @@ -1,4 +1,5 @@ import React, { ReactNode } from 'react'; +import { Trans } from 'react-i18next'; import { useTranslation } from 'next-i18next'; import transMetricToAxis from '@common/transform/transMetricToAxis'; import transSummaryToAxis from '@common/transform/transSummaryToAxis'; @@ -8,7 +9,9 @@ import { chatUserSettingState } from '@modules/analyze/store'; import { useSnapshot } from 'valtio'; import { TransOpt, DataContainerResult, YResult } from '@modules/analyze/type'; import { isNull, isUndefined } from 'lodash'; -import { Trans } from 'react-i18next'; +import { DebugLogger } from '@common/debug'; + +const logger = new DebugLogger('ChartDataContainer'); const isEmptyData = (result: YResult[]) => { return result.every((r) => { @@ -50,6 +53,7 @@ const Empty = () => { }; const ChartDataContainer: React.FC<{ + _tracing?: string; tansOpts: TransOpt; children: | ((args: { @@ -58,11 +62,10 @@ const ChartDataContainer: React.FC<{ result: DataContainerResult; }) => ReactNode) | ReactNode; -}> = ({ children, tansOpts }) => { +}> = ({ children, tansOpts, _tracing }) => { const data = useMetricQueryData(); const loading = data?.loading; const snap = useSnapshot(chatUserSettingState); - const { xAxis, yResults } = transMetricToAxis( data?.items, tansOpts, @@ -73,30 +76,32 @@ const ChartDataContainer: React.FC<{ xAxis, tansOpts.summaryKey ); - const compareLabels = yResults.map((i) => i.label); const isCompare = yResults.length > 1; - const isEmpty = isEmptyData(yResults); + + if (_tracing) { + logger.debug(_tracing, { loading, data }); + } + if (isEmpty && !loading) { return ; } - const result = { - compareLabels, - isCompare, - xAxis, - yResults, - summaryMean, - summaryMedian, + const childProps = { + loading, + isEmpty, + result: { + compareLabels, + isCompare, + xAxis, + yResults, + summaryMean, + summaryMedian, + }, }; - return ( - <> - {typeof children === 'function' - ? children({ loading, isEmpty, result }) - : children} - + <>{typeof children === 'function' ? children(childProps) : children} ); }; diff --git a/src/modules/analyze/components/Container/ChartOptionContainer.tsx b/src/modules/analyze/components/Container/ChartOptionContainer.tsx index fb3106d8..cda1ac1f 100644 --- a/src/modules/analyze/components/Container/ChartOptionContainer.tsx +++ b/src/modules/analyze/components/Container/ChartOptionContainer.tsx @@ -4,6 +4,9 @@ import { formatISO } from '@common/utils'; import { useSnapshot } from 'valtio'; import { ChartThemeState, chartThemeState } from '@modules/analyze/store'; import { DataContainerResult } from '@modules/analyze/type'; +import { DebugLogger } from '@common/debug'; + +const logger = new DebugLogger('ChartOptionContainer'); const ChartOptionContainer = (props: { data: DataContainerResult; @@ -13,8 +16,14 @@ const ChartOptionContainer = (props: { ) => EChartsOption; indicators?: string; children: ((args: { option: EChartsOption }) => ReactNode) | ReactNode; + _tracing?: string; }) => { - const { optionCallback, indicators, children, data } = props; + const { optionCallback, indicators, children, data, _tracing } = props; + + if (_tracing) { + logger.debug(_tracing); + } + const theme = useSnapshot(chartThemeState); const { isCompare, diff --git a/src/modules/analyze/options/ChartOptionAdapter.tsx b/src/modules/analyze/options/ChartOptionAdapter.tsx new file mode 100644 index 00000000..eabbf48d --- /dev/null +++ b/src/modules/analyze/options/ChartOptionAdapter.tsx @@ -0,0 +1,36 @@ +import React, { ReactNode } from 'react'; +import { EChartsOption } from 'echarts'; +import { useSnapshot } from 'valtio'; +import { ChartThemeState, chartThemeState } from '@modules/analyze/store'; +import { DataContainerResult } from '@modules/analyze/type'; +import { DebugLogger } from '@common/debug'; + +const logger = new DebugLogger('ChartOptionAdapter'); + +export const ChartOptionAdapter = ({ + _tracing, + data, + optionFn, + render, +}: { + data: DataContainerResult; + optionFn: ( + data: DataContainerResult, + theme: ChartThemeState + ) => EChartsOption; + render: ((args: { option: EChartsOption }) => ReactNode) | ReactNode; + _tracing?: string; +}) => { + const theme = useSnapshot(chartThemeState) as ChartThemeState; + const echartsOpts = optionFn(data, theme); + + if (_tracing) { + logger.debug(_tracing, echartsOpts); + } + + return ( + <> + {typeof render === 'function' ? render({ option: echartsOpts }) : render} + + ); +}; diff --git a/src/modules/analyze/options/builder/getCompareStyleBuilder.ts b/src/modules/analyze/options/builder/getCompareStyleBuilder.ts new file mode 100644 index 00000000..a455a408 --- /dev/null +++ b/src/modules/analyze/options/builder/getCompareStyleBuilder.ts @@ -0,0 +1,13 @@ +import type { getBuilderOptionFn } from '@modules/analyze/options/useOptionBuilderFns'; + +export const getCompareStyleBuilder: getBuilderOptionFn<{ + indicators?: boolean; +}> = + ({ indicators = false }) => + (pre, data) => { + if (!data.isCompare) { + pre.grid = { ...pre.grid, top: indicators ? 50 : 10 }; + pre.legend = { show: false }; + } + return pre; + }; diff --git a/src/modules/analyze/options/builder/getLineChartBuilder.ts b/src/modules/analyze/options/builder/getLineChartBuilder.ts new file mode 100644 index 00000000..7acc70a5 --- /dev/null +++ b/src/modules/analyze/options/builder/getLineChartBuilder.ts @@ -0,0 +1,54 @@ +import type { getBuilderOptionFn } from '@modules/analyze/options/useOptionBuilderFns'; +import { EChartsOption } from 'echarts'; +import { + getColorWithLabel, + getLineOption, + legendFormat, + line, + getTooltipsFormatter, +} from '@common/options'; +import { + percentageValueFormat, + percentageUnitFormat, +} from '@common/utils/format'; +import { formatISO } from '@common/utils'; + +export const getLineChartBuilder: getBuilderOptionFn<{ + isRatio: boolean; + yAxisScale: boolean; +}> = + ({ isRatio = false, yAxisScale = true }) => + (pre, data, theme) => { + const { yResults, xAxis, compareLabels } = data; + const series = yResults.map(({ label, level, data }) => { + const color = getColorWithLabel(theme, label); + return line({ + name: label, + data: isRatio ? data.map((v) => percentageValueFormat(v)) : data, + color, + }); + }); + + const yAxis: EChartsOption['yAxis'] = isRatio + ? { + type: 'value', + axisLabel: { formatter: '{value}%' }, + scale: yAxisScale, + } + : { type: 'value', scale: yAxisScale }; + + const opts = getLineOption({ + xAxisData: xAxis.map((i) => formatISO(i)), + series, + yAxis, + legend: legendFormat(compareLabels), + tooltip: { + formatter: getTooltipsFormatter({ + compareLabels, + valueFormat: isRatio ? percentageUnitFormat : undefined, + }), + }, + }); + + return { ...pre, ...opts, series }; + }; diff --git a/src/modules/analyze/options/builder/getReferenceLineBuilder.ts b/src/modules/analyze/options/builder/getReferenceLineBuilder.ts new file mode 100644 index 00000000..2b455004 --- /dev/null +++ b/src/modules/analyze/options/builder/getReferenceLineBuilder.ts @@ -0,0 +1,46 @@ +import { summaryLine } from '@common/options'; +import { LineSeriesOption } from 'echarts'; +import { checkFormatPercentageValue } from '@common/utils/format'; +import type { getBuilderOptionFn } from '@modules/analyze/options'; + +export const getReferenceLineBuilder: getBuilderOptionFn<{ + showMedian?: boolean; + medianMame: string; + showAvg?: boolean; + avgName: string; + isRatio: boolean; +}> = + ({ + showMedian = false, + medianMame, + showAvg = false, + avgName, + isRatio = false, + }) => + (pre, data) => { + const { summaryMean, summaryMedian } = data; + + let series = pre.series as LineSeriesOption[]; + if (showMedian) { + series.push( + summaryLine({ + id: 'median', + name: medianMame, + data: checkFormatPercentageValue(isRatio, summaryMedian), + color: '#5B8FF9', + }) + ); + } + + if (showAvg) { + series.push( + summaryLine({ + id: 'average', + name: avgName, + data: checkFormatPercentageValue(isRatio, summaryMean), + color: '#F95B5B', + }) + ); + } + return { ...pre, series }; + }; diff --git a/src/modules/analyze/options/index.ts b/src/modules/analyze/options/index.ts new file mode 100644 index 00000000..982dc093 --- /dev/null +++ b/src/modules/analyze/options/index.ts @@ -0,0 +1,8 @@ +export * from './useCardManual'; +export * from './useOptionBuilderFns'; + +export * from './ChartOptionAdapter'; + +export * from './builder/getLineChartBuilder'; +export * from './builder/getCompareStyleBuilder'; +export * from './builder/getReferenceLineBuilder'; diff --git a/src/modules/analyze/options/useCardManual.tsx b/src/modules/analyze/options/useCardManual.tsx new file mode 100644 index 00000000..341c37fe --- /dev/null +++ b/src/modules/analyze/options/useCardManual.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useState } from 'react'; + +type Props = { + defaultShowAvg?: boolean; + defaultShowMedian?: boolean; + defaultYAxisScale?: boolean; +}; + +export const useCardManual = (props: Props = {}) => { + const { + defaultShowMedian = false, + defaultShowAvg = false, + defaultYAxisScale = true, + } = props; + + const [showAvg, setShowAvg] = useState(defaultShowAvg); + const [showMedian, setShowMedian] = useState(defaultShowMedian); + const [yAxisScale, setYAxisScale] = useState(defaultYAxisScale); + return { + showAvg, + setShowAvg, + showMedian, + setShowMedian, + yAxisScale, + setYAxisScale, + }; +}; diff --git a/src/modules/analyze/options/useOptionBuilderFns.ts b/src/modules/analyze/options/useOptionBuilderFns.ts new file mode 100644 index 00000000..8382a06d --- /dev/null +++ b/src/modules/analyze/options/useOptionBuilderFns.ts @@ -0,0 +1,20 @@ +import React, { useReducer, useCallback } from 'react'; +import { EChartsOption } from 'echarts'; +import { ChartThemeState } from '@modules/analyze/store'; +import { DataContainerResult } from '../type'; + +export type getBuilderOptionFn = (v: T) => BuilderFn; + +type BuilderFn = ( + pre: EChartsOption, + data: DataContainerResult, + theme: ChartThemeState +) => EChartsOption; + +export const useOptionBuilderFns = (fns: BuilderFn[]) => { + return (data: DataContainerResult, theme: ChartThemeState) => { + return fns.reduce((pre, current) => { + return current(pre, data, theme); + }, {}); + }; +}; diff --git a/yarn.lock b/yarn.lock index 31b87f33..216caebb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5165,6 +5165,13 @@ dependencies: "@types/node" "*" +"@types/debug@^4.1.8": + version "4.1.8" + resolved "https://registry.npmmirror.com/@types/debug/-/debug-4.1.8.tgz#cef723a5d0a90990313faec2d1e22aee5eecb317" + integrity sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ== + dependencies: + "@types/ms" "*" + "@types/detect-port@^1.3.0": version "1.3.2" resolved "https://registry.npmmirror.com/@types/detect-port/-/detect-port-1.3.2.tgz#8c06a975e472803b931ee73740aeebd0a2eb27ae" @@ -5392,6 +5399,11 @@ resolved "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== +"@types/ms@*": + version "0.7.31" + resolved "https://registry.npmmirror.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + "@types/node-fetch@^2.5.7": version "2.6.3" resolved "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.3.tgz#175d977f5e24d93ad0f57602693c435c57ad7e80"