@agions/taroviz 1.11.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +245 -0
- package/README.md +104 -302
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/vendors.js +1 -0
- package/dist/cjs/vendors~echarts.js +1 -0
- package/dist/esm/index.js +1 -58151
- package/dist/esm/vendors.js +1 -0
- package/dist/esm/vendors~echarts.js +1 -0
- package/package.json +19 -25
- package/src/adapters/MiniAppAdapter.ts +136 -0
- package/src/adapters/__tests__/index.test.ts +1 -1
- package/src/adapters/h5/__tests__/index.test.ts +4 -2
- package/src/adapters/h5/index.ts +63 -64
- package/src/adapters/harmony/index.ts +23 -245
- package/src/adapters/index.ts +49 -45
- package/src/adapters/swan/index.ts +6 -69
- package/src/adapters/tt/index.ts +7 -70
- package/src/adapters/types.ts +25 -58
- package/src/adapters/weapp/index.ts +6 -69
- package/src/charts/__tests__/testUtils.tsx +87 -0
- package/src/charts/boxplot/__tests__/index.test.tsx +49 -103
- package/src/charts/boxplot/index.tsx +2 -1
- package/src/charts/boxplot/types.ts +17 -16
- package/src/charts/common/BaseChartWrapper.tsx +90 -82
- package/src/charts/common/__mocks__/BaseChartWrapper.tsx +17 -0
- package/src/charts/createChartComponent.tsx +36 -0
- package/src/charts/createOptionChartComponent.tsx +32 -0
- package/src/charts/funnel/__tests__/index.test.tsx +99 -0
- package/src/charts/funnel/index.tsx +60 -10
- package/src/charts/funnel/types.ts +6 -0
- package/src/charts/graph/__tests__/index.test.tsx +102 -33
- package/src/charts/graph/index.tsx +66 -9
- package/src/charts/graph/types.ts +6 -0
- package/src/charts/heatmap/__tests__/index.test.tsx +139 -0
- package/src/charts/heatmap/index.tsx +103 -10
- package/src/charts/heatmap/types.ts +6 -0
- package/src/charts/index.ts +74 -26
- package/src/charts/liquid/__tests__/index.test.tsx +52 -0
- package/src/charts/liquid/index.tsx +239 -182
- package/src/charts/liquid/types.ts +11 -11
- package/src/charts/parallel/__tests__/index.test.tsx +40 -67
- package/src/charts/parallel/index.tsx +2 -1
- package/src/charts/parallel/types.ts +19 -18
- package/src/charts/radar/__tests__/index.test.tsx +210 -0
- package/src/charts/radar/index.tsx +143 -10
- package/src/charts/radar/types.ts +13 -0
- package/src/charts/sankey/__tests__/index.test.tsx +124 -0
- package/src/charts/sankey/index.tsx +62 -10
- package/src/charts/sankey/types.ts +6 -0
- package/src/charts/tree/__tests__/index.test.tsx +71 -0
- package/src/charts/tree/index.tsx +5 -2
- package/src/charts/tree/types.ts +9 -9
- package/src/charts/types.ts +208 -106
- package/src/charts/utils.ts +9 -7
- package/src/charts/wordcloud/__tests__/index.test.tsx +98 -31
- package/src/charts/wordcloud/index.tsx +75 -9
- package/src/charts/wordcloud/types.ts +6 -0
- package/src/components/DataFilter/index.tsx +32 -10
- package/src/core/animation/types.ts +6 -6
- package/src/core/components/Annotation.tsx +6 -7
- package/src/core/components/BaseChart.tsx +110 -168
- package/src/core/components/ErrorBoundary.tsx +17 -4
- package/src/core/components/LazyChart.tsx +54 -55
- package/src/core/components/hooks/index.ts +6 -2
- package/src/core/components/hooks/useChartInit.ts +6 -3
- package/src/core/components/hooks/usePerformance.ts +8 -2
- package/src/core/components/hooks/useVirtualScroll.ts +2 -1
- package/src/core/index.ts +1 -1
- package/src/core/themes/ThemeManager.ts +1 -1
- package/src/core/types/common.ts +2 -1
- package/src/core/types/index.ts +0 -12
- package/src/core/types/platform.ts +3 -5
- package/src/core/utils/__tests__/deepClone.test.ts +317 -0
- package/src/core/utils/__tests__/index.test.ts +2 -1
- package/src/core/utils/chartInstances.ts +13 -0
- package/src/core/utils/common.ts +20 -29
- package/src/core/utils/deepClone.ts +114 -0
- package/src/core/utils/download.ts +128 -0
- package/src/core/utils/drillDown.ts +34 -353
- package/src/core/utils/drillDownHelpers.ts +426 -0
- package/src/core/utils/events.ts +12 -0
- package/src/core/utils/export/ExportUtils.ts +36 -67
- package/src/core/utils/format.ts +44 -0
- package/src/core/utils/index.ts +21 -154
- package/src/core/utils/merge.ts +25 -0
- package/src/core/utils/performance/PerformanceAnalyzer.ts +38 -21
- package/src/core/utils/performance/hooks.ts +7 -0
- package/src/core/utils/performance/index.ts +2 -0
- package/src/{hooks → core/utils/performance}/useAnimation.ts +45 -41
- package/src/core/utils/performance/useDataZoom.ts +324 -0
- package/src/{hooks → core/utils/performance}/usePerformance.ts +49 -41
- package/src/core/utils/performance/usePerformanceHooks.ts +278 -0
- package/src/core/utils/performanceUtils.ts +310 -0
- package/src/core/utils/runtime.ts +190 -0
- package/src/core/utils/setOptionUtils.ts +59 -0
- package/src/core/version.ts +14 -0
- package/src/editor/EnhancedThemeEditor.tsx +362 -540
- package/src/editor/ThemeEditor.tsx +55 -321
- package/src/editor/components/ThemeBasicSettings.tsx +113 -0
- package/src/editor/components/ThemeColorEditor.tsx +105 -0
- package/src/editor/components/ThemeSelector.tsx +70 -0
- package/src/editor/hooks/useThemeEditorState.ts +201 -0
- package/src/editor/index.ts +10 -2
- package/src/hooks/__tests__/index.test.tsx +3 -1
- package/src/hooks/chartConnectHelpers.ts +341 -0
- package/src/hooks/index.ts +55 -660
- package/src/hooks/types.ts +189 -0
- package/src/hooks/useChartAutoResize.ts +73 -0
- package/src/hooks/useChartConnect.ts +92 -238
- package/src/hooks/useChartDownload.ts +25 -27
- package/src/hooks/useChartHistory.ts +34 -49
- package/src/hooks/useChartInit.ts +59 -0
- package/src/hooks/useChartOptions.ts +259 -0
- package/src/hooks/useChartPerformance.ts +109 -0
- package/src/hooks/useChartSelection.ts +52 -49
- package/src/hooks/useChartTheme.ts +51 -0
- package/src/hooks/useDataTransform.ts +19 -4
- package/src/hooks/utils/chartDownloadUtils.ts +40 -53
- package/src/hooks/utils/dataTransformUtils.ts +22 -0
- package/src/index.ts +48 -34
- package/src/main.tsx +4 -9
- package/src/react-dom.d.ts +3 -3
- package/src/themes/index.ts +30 -855
- package/src/themes/palettes/blue-green.ts +13 -0
- package/src/themes/palettes/chalk.ts +13 -0
- package/src/themes/palettes/cyber.ts +44 -0
- package/src/themes/palettes/dark.ts +52 -0
- package/src/themes/palettes/default.ts +52 -0
- package/src/themes/palettes/elegant.ts +34 -0
- package/src/themes/palettes/forest.ts +13 -0
- package/src/themes/palettes/glass.ts +49 -0
- package/src/themes/palettes/golden.ts +13 -0
- package/src/themes/palettes/neon.ts +43 -0
- package/src/themes/palettes/ocean.ts +39 -0
- package/src/themes/palettes/pastel.ts +37 -0
- package/src/themes/palettes/purple-passion.ts +13 -0
- package/src/themes/palettes/retro.ts +33 -0
- package/src/themes/palettes/sunset.ts +40 -0
- package/src/themes/palettes/walden.ts +13 -0
- package/src/themes/registry.ts +184 -0
- package/src/themes/types.ts +213 -0
- package/src/charts/bar/__tests__/index.test.tsx +0 -113
- package/src/charts/bar/index.tsx +0 -14
- package/src/charts/candlestick/__tests__/index.test.tsx +0 -40
- package/src/charts/candlestick/index.tsx +0 -13
- package/src/charts/gauge/index.tsx +0 -14
- package/src/charts/line/__tests__/index.test.tsx +0 -107
- package/src/charts/line/index.tsx +0 -15
- package/src/charts/pie/__tests__/index.test.tsx +0 -112
- package/src/charts/pie/index.tsx +0 -14
- package/src/charts/scatter/index.tsx +0 -14
- package/src/charts/sunburst/index.tsx +0 -18
- package/src/charts/treemap/index.tsx +0 -18
- package/src/core/utils/codeGenerator/CodeGenerator.ts +0 -669
- package/src/core/utils/codeGenerator/index.ts +0 -13
- package/src/core/utils/codeGenerator/types.ts +0 -198
- package/src/core/utils/configGenerator/ConfigGenerator.ts +0 -583
- package/src/core/utils/configGenerator/index.ts +0 -13
- package/src/core/utils/configGenerator/types.ts +0 -445
- package/src/core/utils/debug/DebugPanel.tsx +0 -637
- package/src/core/utils/debug/debugger.ts +0 -322
- package/src/core/utils/debug/index.ts +0 -21
- package/src/core/utils/debug/types.ts +0 -142
- package/src/hooks/useDataZoom.ts +0 -323
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 性能优化 Hooks
|
|
3
|
+
* 提供防抖、节流等性能优化 Hook
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useRef, useEffect, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 防抖 Hook
|
|
10
|
+
* @param callback 要防抖的回调
|
|
11
|
+
* @param delay 延迟时间 (ms)
|
|
12
|
+
* @returns 防抖后的回调(包含 cancel 方法)
|
|
13
|
+
*/
|
|
14
|
+
export function useDebounce<T extends (..._args: unknown[]) => unknown>(
|
|
15
|
+
callback: T,
|
|
16
|
+
delay: number
|
|
17
|
+
): {
|
|
18
|
+
(..._args: Parameters<T>): void;
|
|
19
|
+
cancel: () => void;
|
|
20
|
+
flush: () => void;
|
|
21
|
+
} {
|
|
22
|
+
const callbackRef = useRef(callback);
|
|
23
|
+
const _timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
24
|
+
|
|
25
|
+
// 更新 callback 引用
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
callbackRef.current = callback;
|
|
28
|
+
}, [callback]);
|
|
29
|
+
|
|
30
|
+
// 清理定时器
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
return () => {
|
|
33
|
+
if (_timeoutRef.current) {
|
|
34
|
+
clearTimeout(_timeoutRef.current);
|
|
35
|
+
_timeoutRef.current = null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const debouncedCallback = useCallback(
|
|
41
|
+
(..._args: Parameters<T>) => {
|
|
42
|
+
if (_timeoutRef.current) {
|
|
43
|
+
clearTimeout(_timeoutRef.current);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_timeoutRef.current = setTimeout(() => {
|
|
47
|
+
callbackRef.current(..._args);
|
|
48
|
+
_timeoutRef.current = null;
|
|
49
|
+
}, delay);
|
|
50
|
+
},
|
|
51
|
+
[delay]
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// 添加 cancel 方法
|
|
55
|
+
const cancel = useCallback(() => {
|
|
56
|
+
if (_timeoutRef.current) {
|
|
57
|
+
clearTimeout(_timeoutRef.current);
|
|
58
|
+
_timeoutRef.current = null;
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
// 添加 flush 方法
|
|
63
|
+
const flush = useCallback((..._args: Parameters<T>) => {
|
|
64
|
+
if (_timeoutRef.current) {
|
|
65
|
+
clearTimeout(_timeoutRef.current);
|
|
66
|
+
_timeoutRef.current = null;
|
|
67
|
+
}
|
|
68
|
+
callbackRef.current(..._args);
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
// 将 cancel 和 flush 附加到回调函数上
|
|
72
|
+
Object.assign(debouncedCallback, { cancel, flush });
|
|
73
|
+
|
|
74
|
+
return debouncedCallback as never;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 节流 Hook
|
|
79
|
+
* @param callback 要节流的回调
|
|
80
|
+
* @param limit 最小时间间隔 (ms)
|
|
81
|
+
* @param options 节流选项
|
|
82
|
+
* @returns 节流后的回调
|
|
83
|
+
*/
|
|
84
|
+
export function useThrottle<T extends (..._args: unknown[]) => unknown>(
|
|
85
|
+
callback: T,
|
|
86
|
+
limit: number,
|
|
87
|
+
options?: {
|
|
88
|
+
leading?: boolean;
|
|
89
|
+
trailing?: boolean;
|
|
90
|
+
}
|
|
91
|
+
): (..._args: Parameters<T>) => void {
|
|
92
|
+
const callbackRef = useRef(callback);
|
|
93
|
+
const lastCallTimeRef = useRef<number>(0);
|
|
94
|
+
const _timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
95
|
+
const lastArgsRef = useRef<Parameters<T> | null>(null);
|
|
96
|
+
const lastThisRef = useRef<unknown>(null);
|
|
97
|
+
|
|
98
|
+
const { leading = true, trailing = true } = options ?? {};
|
|
99
|
+
|
|
100
|
+
// 更新 callback 引用
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
callbackRef.current = callback;
|
|
103
|
+
}, [callback]);
|
|
104
|
+
|
|
105
|
+
// 清理
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
return () => {
|
|
108
|
+
if (_timeoutRef.current) {
|
|
109
|
+
clearTimeout(_timeoutRef.current);
|
|
110
|
+
_timeoutRef.current = null;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
const throttledCallback = useCallback(
|
|
116
|
+
(..._args: Parameters<T>) => {
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
const elapsed = now - lastCallTimeRef.current;
|
|
119
|
+
|
|
120
|
+
// 保存调用上下文和参数
|
|
121
|
+
lastArgsRef.current = _args;
|
|
122
|
+
lastThisRef.current = _args.length > 0 ? _args[0] : null;
|
|
123
|
+
|
|
124
|
+
if (elapsed >= limit) {
|
|
125
|
+
// 超过限制,立即执行
|
|
126
|
+
if (_timeoutRef.current) {
|
|
127
|
+
clearTimeout(_timeoutRef.current);
|
|
128
|
+
_timeoutRef.current = null;
|
|
129
|
+
}
|
|
130
|
+
lastCallTimeRef.current = now;
|
|
131
|
+
callbackRef.current(..._args);
|
|
132
|
+
lastArgsRef.current = null;
|
|
133
|
+
lastThisRef.current = null;
|
|
134
|
+
} else if (trailing && !_timeoutRef.current) {
|
|
135
|
+
// 在节流窗口结束时执行最后一次调用
|
|
136
|
+
_timeoutRef.current = setTimeout(() => {
|
|
137
|
+
_timeoutRef.current = null;
|
|
138
|
+
lastCallTimeRef.current = Date.now();
|
|
139
|
+
if (lastArgsRef.current && lastThisRef.current !== null) {
|
|
140
|
+
callbackRef.current(...lastArgsRef.current);
|
|
141
|
+
}
|
|
142
|
+
lastArgsRef.current = null;
|
|
143
|
+
lastThisRef.current = null;
|
|
144
|
+
}, limit - elapsed);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[limit, leading, trailing]
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return throttledCallback;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 请求动画帧 Hook
|
|
155
|
+
* 用于优化动画性能
|
|
156
|
+
*/
|
|
157
|
+
export function useAnimationFrame(callback: (_time: number) => void, enabled = true): void {
|
|
158
|
+
const callbackRef = useRef(callback);
|
|
159
|
+
const animationFrameRef = useRef<number | null>(null);
|
|
160
|
+
|
|
161
|
+
// 更新 callback 引用
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
callbackRef.current = callback;
|
|
164
|
+
}, [callback]);
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (!enabled) {
|
|
168
|
+
if (animationFrameRef.current !== null) {
|
|
169
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
170
|
+
animationFrameRef.current = null;
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const loop = (_time: number) => {
|
|
176
|
+
callbackRef.current(_time);
|
|
177
|
+
animationFrameRef.current = requestAnimationFrame(loop);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
animationFrameRef.current = requestAnimationFrame(loop);
|
|
181
|
+
|
|
182
|
+
return () => {
|
|
183
|
+
if (animationFrameRef.current !== null) {
|
|
184
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}, [enabled]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 窗口大小防抖 Hook
|
|
192
|
+
* 用于优化窗口 resize 事件处理
|
|
193
|
+
*/
|
|
194
|
+
export function useWindowSizeDebounce(delay = 150) {
|
|
195
|
+
const [size, setSize] = useState({
|
|
196
|
+
width: typeof window !== 'undefined' ? window.innerWidth : 0,
|
|
197
|
+
height: typeof window !== 'undefined' ? window.innerHeight : 0,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const handleResize = useDebounce(() => {
|
|
201
|
+
if (typeof window !== 'undefined') {
|
|
202
|
+
setSize({
|
|
203
|
+
width: window.innerWidth,
|
|
204
|
+
height: window.innerHeight,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}, delay);
|
|
208
|
+
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
window.addEventListener('resize', handleResize);
|
|
211
|
+
return () => {
|
|
212
|
+
window.removeEventListener('resize', handleResize);
|
|
213
|
+
handleResize.cancel();
|
|
214
|
+
};
|
|
215
|
+
}, [handleResize]);
|
|
216
|
+
|
|
217
|
+
return size;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 滚动位置防抖 Hook
|
|
222
|
+
* 用于优化滚动事件处理
|
|
223
|
+
*/
|
|
224
|
+
export function useScrollPositionDebounce(delay = 150, targetElement?: HTMLElement | Window) {
|
|
225
|
+
const [scrollPosition, setScrollPosition] = useState({
|
|
226
|
+
x: 0,
|
|
227
|
+
y: 0,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const handleScroll = useDebounce(() => {
|
|
231
|
+
const element = targetElement || window;
|
|
232
|
+
if (element === window) {
|
|
233
|
+
setScrollPosition({
|
|
234
|
+
x: window.scrollX ?? (window as unknown as { pageXOffset: number }).pageXOffset,
|
|
235
|
+
y: window.scrollY ?? (window as unknown as { pageYOffset: number }).pageYOffset,
|
|
236
|
+
});
|
|
237
|
+
} else if (element) {
|
|
238
|
+
setScrollPosition({
|
|
239
|
+
x: (element as HTMLElement).scrollLeft,
|
|
240
|
+
y: (element as HTMLElement).scrollTop,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}, delay);
|
|
244
|
+
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
const element = targetElement || window;
|
|
247
|
+
element.addEventListener('scroll', handleScroll, { passive: true });
|
|
248
|
+
return () => {
|
|
249
|
+
element.removeEventListener('scroll', handleScroll);
|
|
250
|
+
handleScroll.cancel();
|
|
251
|
+
};
|
|
252
|
+
}, [handleScroll, targetElement]);
|
|
253
|
+
|
|
254
|
+
return scrollPosition;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 鼠标位置节流 Hook
|
|
259
|
+
* 用于优化鼠标移动事件处理
|
|
260
|
+
*/
|
|
261
|
+
export function useMousePositionThrottle(limit = 100) {
|
|
262
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
263
|
+
|
|
264
|
+
const handleMouseMove = useThrottle(
|
|
265
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
266
|
+
(e: any) => {
|
|
267
|
+
setPosition({ x: e.clientX, y: e.clientY });
|
|
268
|
+
},
|
|
269
|
+
limit
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
274
|
+
return () => window.removeEventListener('mousemove', handleMouseMove);
|
|
275
|
+
}, [handleMouseMove]);
|
|
276
|
+
|
|
277
|
+
return position;
|
|
278
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 性能优化工具
|
|
3
|
+
* 提供防抖、节流等性能优化函数
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ECharts 性能配置接口
|
|
8
|
+
*/
|
|
9
|
+
export interface EChartsPerformanceConfig {
|
|
10
|
+
/** 渐进式渲染阈值 */
|
|
11
|
+
progressive?: number;
|
|
12
|
+
/** 大系列优化 */
|
|
13
|
+
large?: boolean;
|
|
14
|
+
/** 渲染模式 */
|
|
15
|
+
renderMode?: 'auto' | 'canvas' | 'svg';
|
|
16
|
+
/** 增量渲染 */
|
|
17
|
+
incremental?: boolean;
|
|
18
|
+
/** 是否使用 WebGL 渲染器(大数据量推荐) */
|
|
19
|
+
useWebGL?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* ECharts 渲染优化配置
|
|
24
|
+
* 用于提升大数据量图表的渲染性能
|
|
25
|
+
*/
|
|
26
|
+
export const ECHARTS_PERFORMANCE_CONFIG = {
|
|
27
|
+
// 开启渐进式渲染
|
|
28
|
+
progressive: 1000,
|
|
29
|
+
// 开启大系列优化
|
|
30
|
+
large: true,
|
|
31
|
+
// 开启渲染优化
|
|
32
|
+
renderMode: 'auto',
|
|
33
|
+
// 开启增量渲染
|
|
34
|
+
incremental: false,
|
|
35
|
+
} as const satisfies EChartsPerformanceConfig;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 防抖函数
|
|
39
|
+
* @param fn 要防抖的函数
|
|
40
|
+
* @param delay 延迟时间 (ms)
|
|
41
|
+
* @returns 防抖后的函数(包含 cancel 方法)
|
|
42
|
+
*/
|
|
43
|
+
export function debounce<T extends (...args: unknown[]) => unknown>(
|
|
44
|
+
fn: T,
|
|
45
|
+
delay: number
|
|
46
|
+
): {
|
|
47
|
+
(...args: Parameters<T>): void;
|
|
48
|
+
cancel: () => void;
|
|
49
|
+
flush: () => void;
|
|
50
|
+
} {
|
|
51
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
52
|
+
|
|
53
|
+
const debounced = function (this: unknown, ...args: Parameters<T>) {
|
|
54
|
+
if (timeoutId !== null) {
|
|
55
|
+
clearTimeout(timeoutId);
|
|
56
|
+
}
|
|
57
|
+
timeoutId = setTimeout(() => {
|
|
58
|
+
fn.apply(this, args);
|
|
59
|
+
timeoutId = null;
|
|
60
|
+
}, delay);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** 取消待执行的函数调用 */
|
|
64
|
+
debounced.cancel = function () {
|
|
65
|
+
if (timeoutId !== null) {
|
|
66
|
+
clearTimeout(timeoutId);
|
|
67
|
+
timeoutId = null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** 立即执行待执行的函数调用 */
|
|
72
|
+
debounced.flush = function (this: unknown, ...args: Parameters<T>) {
|
|
73
|
+
if (timeoutId !== null) {
|
|
74
|
+
clearTimeout(timeoutId);
|
|
75
|
+
timeoutId = null;
|
|
76
|
+
}
|
|
77
|
+
fn.apply(this, args);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return debounced;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 节流函数
|
|
85
|
+
* @param fn 要节流的函数
|
|
86
|
+
* @param limit 最小时间间隔 (ms)
|
|
87
|
+
* @param options 节流选项
|
|
88
|
+
* @returns 节流后的函数
|
|
89
|
+
*/
|
|
90
|
+
export function throttle<T extends (...args: unknown[]) => unknown>(
|
|
91
|
+
fn: T,
|
|
92
|
+
limit: number,
|
|
93
|
+
options?: {
|
|
94
|
+
/** 是否在节流开始时执行(leading) */
|
|
95
|
+
leading?: boolean;
|
|
96
|
+
/** 是否在节流结束时执行(trailing) */
|
|
97
|
+
trailing?: boolean;
|
|
98
|
+
}
|
|
99
|
+
): (...args: Parameters<T>) => void {
|
|
100
|
+
const { leading = true, trailing = true } = options ?? {};
|
|
101
|
+
|
|
102
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
103
|
+
let lastCallTime = 0;
|
|
104
|
+
let lastArgs: Parameters<T> | null = null;
|
|
105
|
+
let lastThis: unknown = null;
|
|
106
|
+
|
|
107
|
+
const shouldInvokeLeading = leading && !lastCallTime;
|
|
108
|
+
const shouldInvokeTrailing = trailing;
|
|
109
|
+
|
|
110
|
+
const invokeFunc = (time: number) => {
|
|
111
|
+
const args = lastArgs;
|
|
112
|
+
const thisArg = lastThis;
|
|
113
|
+
|
|
114
|
+
lastArgs = lastThis = null;
|
|
115
|
+
lastCallTime = time;
|
|
116
|
+
return args ? fn.apply(thisArg, args) : undefined;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const remaining = limit - (Date.now() - lastCallTime);
|
|
120
|
+
|
|
121
|
+
const wrapped = function (this: unknown, ...args: Parameters<T>) {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
const elapsed = now - lastCallTime;
|
|
124
|
+
|
|
125
|
+
// 保存调用上下文和参数
|
|
126
|
+
lastArgs = args;
|
|
127
|
+
lastThis = this;
|
|
128
|
+
|
|
129
|
+
// 判断是否应该执行
|
|
130
|
+
if (elapsed >= limit) {
|
|
131
|
+
if (timeoutId !== null) {
|
|
132
|
+
clearTimeout(timeoutId);
|
|
133
|
+
timeoutId = null;
|
|
134
|
+
}
|
|
135
|
+
lastCallTime = now;
|
|
136
|
+
return fn.apply(this, args);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 处理 trailing edge
|
|
140
|
+
if (!timeoutId && shouldInvokeTrailing) {
|
|
141
|
+
timeoutId = setTimeout(() => {
|
|
142
|
+
timeoutId = null;
|
|
143
|
+
if (lastArgs) {
|
|
144
|
+
const result = fn.apply(lastThis, lastArgs);
|
|
145
|
+
lastArgs = lastThis = null;
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
}, remaining);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return undefined;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return wrapped;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 获取 ECharts 性能优化配置
|
|
159
|
+
* @param dataCount 数据量
|
|
160
|
+
* @param seriesCount 系列数量(可选)
|
|
161
|
+
* @returns ECharts 配置片段
|
|
162
|
+
*/
|
|
163
|
+
export function getPerformanceConfig(
|
|
164
|
+
dataCount: number,
|
|
165
|
+
seriesCount: number = 1
|
|
166
|
+
): EChartsPerformanceConfig {
|
|
167
|
+
const config: EChartsPerformanceConfig = {};
|
|
168
|
+
|
|
169
|
+
if (dataCount > 10000) {
|
|
170
|
+
// 超大数量:使用 WebGL 渲染器 + 渐进式渲染
|
|
171
|
+
config.useWebGL = true;
|
|
172
|
+
config.progressive = Math.max(1000, Math.floor(dataCount / 20));
|
|
173
|
+
config.large = true;
|
|
174
|
+
} else if (dataCount > 1000) {
|
|
175
|
+
// 大数据量:开启渐进式渲染
|
|
176
|
+
config.progressive = Math.max(400, Math.floor(dataCount / 10));
|
|
177
|
+
config.large = true;
|
|
178
|
+
} else if (dataCount > 500) {
|
|
179
|
+
// 中等数据量:开启部分优化
|
|
180
|
+
config.progressive = 400;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 多系列优化
|
|
184
|
+
if (seriesCount > 5) {
|
|
185
|
+
config.renderMode = 'canvas'; // Canvas 渲染多系列更快
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return config;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 图表类型枚举
|
|
193
|
+
*/
|
|
194
|
+
export type ChartType =
|
|
195
|
+
| 'line'
|
|
196
|
+
| 'bar'
|
|
197
|
+
| 'pie'
|
|
198
|
+
| 'scatter'
|
|
199
|
+
| 'map'
|
|
200
|
+
| 'heatmap'
|
|
201
|
+
| 'graph'
|
|
202
|
+
| 'treemap'
|
|
203
|
+
| 'sunburst'
|
|
204
|
+
| 'custom';
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 计算图表渲染预估时间
|
|
208
|
+
* @param dataCount 数据量
|
|
209
|
+
* @param seriesCount 系列数量
|
|
210
|
+
* @param chartType 图表类型(可选)
|
|
211
|
+
* @returns 预估渲染时间 (ms)
|
|
212
|
+
*/
|
|
213
|
+
export function estimateRenderTime(
|
|
214
|
+
dataCount: number,
|
|
215
|
+
seriesCount: number,
|
|
216
|
+
chartType: ChartType = 'line'
|
|
217
|
+
): number {
|
|
218
|
+
// 基础渲染时间
|
|
219
|
+
const baseTime = 50;
|
|
220
|
+
|
|
221
|
+
// 数据量系数(不同图表类型渲染复杂度不同)
|
|
222
|
+
const chartComplexity: Record<ChartType, number> = {
|
|
223
|
+
line: 0.01,
|
|
224
|
+
bar: 0.015,
|
|
225
|
+
pie: 0.008,
|
|
226
|
+
scatter: 0.02,
|
|
227
|
+
map: 0.05,
|
|
228
|
+
heatmap: 0.03,
|
|
229
|
+
graph: 0.04,
|
|
230
|
+
treemap: 0.025,
|
|
231
|
+
sunburst: 0.02,
|
|
232
|
+
custom: 0.03,
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const dataTime = dataCount * chartComplexity[chartType];
|
|
236
|
+
const seriesTime = seriesCount * 20;
|
|
237
|
+
|
|
238
|
+
// 添加一些随机波动(模拟实际场景)
|
|
239
|
+
const variance = Math.random() * 0.1 - 0.05; // ±5%
|
|
240
|
+
|
|
241
|
+
return Math.round((baseTime + dataTime + seriesTime) * (1 + variance));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 批量更新防抖函数
|
|
246
|
+
* 用于同时管理多个防抖函数
|
|
247
|
+
*/
|
|
248
|
+
export class DebounceManager {
|
|
249
|
+
private debounces = new Map<string, { cancel: () => void; flush: () => void }>();
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 注册防抖函数
|
|
253
|
+
*/
|
|
254
|
+
register<T extends (...args: unknown[]) => unknown>(
|
|
255
|
+
key: string,
|
|
256
|
+
fn: T,
|
|
257
|
+
delay: number
|
|
258
|
+
): ReturnType<typeof debounce<T>> {
|
|
259
|
+
const debounced = debounce(fn, delay);
|
|
260
|
+
this.debounces.set(key, {
|
|
261
|
+
cancel: debounced.cancel,
|
|
262
|
+
flush: debounced.flush,
|
|
263
|
+
});
|
|
264
|
+
return debounced;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 取消指定防抖函数
|
|
269
|
+
*/
|
|
270
|
+
cancel(key: string): void {
|
|
271
|
+
const debounced = this.debounces.get(key);
|
|
272
|
+
if (debounced) {
|
|
273
|
+
debounced.cancel();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 取消所有防抖函数
|
|
279
|
+
*/
|
|
280
|
+
cancelAll(): void {
|
|
281
|
+
for (const debounced of this.debounces.values()) {
|
|
282
|
+
debounced.cancel();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* 刷新指定防抖函数
|
|
288
|
+
*/
|
|
289
|
+
flush<T extends (...args: unknown[]) => unknown>(
|
|
290
|
+
key: string,
|
|
291
|
+
...args: Parameters<T>
|
|
292
|
+
): ReturnType<T> {
|
|
293
|
+
const debounced = this.debounces.get(key);
|
|
294
|
+
if (debounced) {
|
|
295
|
+
// 从注册时保存的引用调用 flush
|
|
296
|
+
// 注意:这里需要原始引用,所以返回 undefined
|
|
297
|
+
debounced.flush();
|
|
298
|
+
return undefined as never;
|
|
299
|
+
}
|
|
300
|
+
return undefined as never;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 清理管理器
|
|
305
|
+
*/
|
|
306
|
+
destroy(): void {
|
|
307
|
+
this.cancelAll();
|
|
308
|
+
this.debounces.clear();
|
|
309
|
+
}
|
|
310
|
+
}
|