@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.
Files changed (164) hide show
  1. package/CHANGELOG.md +245 -0
  2. package/README.md +104 -302
  3. package/dist/cjs/index.js +1 -1
  4. package/dist/cjs/vendors.js +1 -0
  5. package/dist/cjs/vendors~echarts.js +1 -0
  6. package/dist/esm/index.js +1 -58151
  7. package/dist/esm/vendors.js +1 -0
  8. package/dist/esm/vendors~echarts.js +1 -0
  9. package/package.json +19 -25
  10. package/src/adapters/MiniAppAdapter.ts +136 -0
  11. package/src/adapters/__tests__/index.test.ts +1 -1
  12. package/src/adapters/h5/__tests__/index.test.ts +4 -2
  13. package/src/adapters/h5/index.ts +63 -64
  14. package/src/adapters/harmony/index.ts +23 -245
  15. package/src/adapters/index.ts +49 -45
  16. package/src/adapters/swan/index.ts +6 -69
  17. package/src/adapters/tt/index.ts +7 -70
  18. package/src/adapters/types.ts +25 -58
  19. package/src/adapters/weapp/index.ts +6 -69
  20. package/src/charts/__tests__/testUtils.tsx +87 -0
  21. package/src/charts/boxplot/__tests__/index.test.tsx +49 -103
  22. package/src/charts/boxplot/index.tsx +2 -1
  23. package/src/charts/boxplot/types.ts +17 -16
  24. package/src/charts/common/BaseChartWrapper.tsx +90 -82
  25. package/src/charts/common/__mocks__/BaseChartWrapper.tsx +17 -0
  26. package/src/charts/createChartComponent.tsx +36 -0
  27. package/src/charts/createOptionChartComponent.tsx +32 -0
  28. package/src/charts/funnel/__tests__/index.test.tsx +99 -0
  29. package/src/charts/funnel/index.tsx +60 -10
  30. package/src/charts/funnel/types.ts +6 -0
  31. package/src/charts/graph/__tests__/index.test.tsx +102 -33
  32. package/src/charts/graph/index.tsx +66 -9
  33. package/src/charts/graph/types.ts +6 -0
  34. package/src/charts/heatmap/__tests__/index.test.tsx +139 -0
  35. package/src/charts/heatmap/index.tsx +103 -10
  36. package/src/charts/heatmap/types.ts +6 -0
  37. package/src/charts/index.ts +74 -26
  38. package/src/charts/liquid/__tests__/index.test.tsx +52 -0
  39. package/src/charts/liquid/index.tsx +239 -182
  40. package/src/charts/liquid/types.ts +11 -11
  41. package/src/charts/parallel/__tests__/index.test.tsx +40 -67
  42. package/src/charts/parallel/index.tsx +2 -1
  43. package/src/charts/parallel/types.ts +19 -18
  44. package/src/charts/radar/__tests__/index.test.tsx +210 -0
  45. package/src/charts/radar/index.tsx +143 -10
  46. package/src/charts/radar/types.ts +13 -0
  47. package/src/charts/sankey/__tests__/index.test.tsx +124 -0
  48. package/src/charts/sankey/index.tsx +62 -10
  49. package/src/charts/sankey/types.ts +6 -0
  50. package/src/charts/tree/__tests__/index.test.tsx +71 -0
  51. package/src/charts/tree/index.tsx +5 -2
  52. package/src/charts/tree/types.ts +9 -9
  53. package/src/charts/types.ts +208 -106
  54. package/src/charts/utils.ts +9 -7
  55. package/src/charts/wordcloud/__tests__/index.test.tsx +98 -31
  56. package/src/charts/wordcloud/index.tsx +75 -9
  57. package/src/charts/wordcloud/types.ts +6 -0
  58. package/src/components/DataFilter/index.tsx +32 -10
  59. package/src/core/animation/types.ts +6 -6
  60. package/src/core/components/Annotation.tsx +6 -7
  61. package/src/core/components/BaseChart.tsx +110 -168
  62. package/src/core/components/ErrorBoundary.tsx +17 -4
  63. package/src/core/components/LazyChart.tsx +54 -55
  64. package/src/core/components/hooks/index.ts +6 -2
  65. package/src/core/components/hooks/useChartInit.ts +6 -3
  66. package/src/core/components/hooks/usePerformance.ts +8 -2
  67. package/src/core/components/hooks/useVirtualScroll.ts +2 -1
  68. package/src/core/index.ts +1 -1
  69. package/src/core/themes/ThemeManager.ts +1 -1
  70. package/src/core/types/common.ts +2 -1
  71. package/src/core/types/index.ts +0 -12
  72. package/src/core/types/platform.ts +3 -5
  73. package/src/core/utils/__tests__/deepClone.test.ts +317 -0
  74. package/src/core/utils/__tests__/index.test.ts +2 -1
  75. package/src/core/utils/chartInstances.ts +13 -0
  76. package/src/core/utils/common.ts +20 -29
  77. package/src/core/utils/deepClone.ts +114 -0
  78. package/src/core/utils/download.ts +128 -0
  79. package/src/core/utils/drillDown.ts +34 -353
  80. package/src/core/utils/drillDownHelpers.ts +426 -0
  81. package/src/core/utils/events.ts +12 -0
  82. package/src/core/utils/export/ExportUtils.ts +36 -67
  83. package/src/core/utils/format.ts +44 -0
  84. package/src/core/utils/index.ts +21 -154
  85. package/src/core/utils/merge.ts +25 -0
  86. package/src/core/utils/performance/PerformanceAnalyzer.ts +38 -21
  87. package/src/core/utils/performance/hooks.ts +7 -0
  88. package/src/core/utils/performance/index.ts +2 -0
  89. package/src/{hooks → core/utils/performance}/useAnimation.ts +45 -41
  90. package/src/core/utils/performance/useDataZoom.ts +324 -0
  91. package/src/{hooks → core/utils/performance}/usePerformance.ts +49 -41
  92. package/src/core/utils/performance/usePerformanceHooks.ts +278 -0
  93. package/src/core/utils/performanceUtils.ts +310 -0
  94. package/src/core/utils/runtime.ts +190 -0
  95. package/src/core/utils/setOptionUtils.ts +59 -0
  96. package/src/core/version.ts +14 -0
  97. package/src/editor/EnhancedThemeEditor.tsx +362 -540
  98. package/src/editor/ThemeEditor.tsx +55 -321
  99. package/src/editor/components/ThemeBasicSettings.tsx +113 -0
  100. package/src/editor/components/ThemeColorEditor.tsx +105 -0
  101. package/src/editor/components/ThemeSelector.tsx +70 -0
  102. package/src/editor/hooks/useThemeEditorState.ts +201 -0
  103. package/src/editor/index.ts +10 -2
  104. package/src/hooks/__tests__/index.test.tsx +3 -1
  105. package/src/hooks/chartConnectHelpers.ts +341 -0
  106. package/src/hooks/index.ts +55 -660
  107. package/src/hooks/types.ts +189 -0
  108. package/src/hooks/useChartAutoResize.ts +73 -0
  109. package/src/hooks/useChartConnect.ts +92 -238
  110. package/src/hooks/useChartDownload.ts +25 -27
  111. package/src/hooks/useChartHistory.ts +34 -49
  112. package/src/hooks/useChartInit.ts +59 -0
  113. package/src/hooks/useChartOptions.ts +259 -0
  114. package/src/hooks/useChartPerformance.ts +109 -0
  115. package/src/hooks/useChartSelection.ts +52 -49
  116. package/src/hooks/useChartTheme.ts +51 -0
  117. package/src/hooks/useDataTransform.ts +19 -4
  118. package/src/hooks/utils/chartDownloadUtils.ts +40 -53
  119. package/src/hooks/utils/dataTransformUtils.ts +22 -0
  120. package/src/index.ts +48 -34
  121. package/src/main.tsx +4 -9
  122. package/src/react-dom.d.ts +3 -3
  123. package/src/themes/index.ts +30 -855
  124. package/src/themes/palettes/blue-green.ts +13 -0
  125. package/src/themes/palettes/chalk.ts +13 -0
  126. package/src/themes/palettes/cyber.ts +44 -0
  127. package/src/themes/palettes/dark.ts +52 -0
  128. package/src/themes/palettes/default.ts +52 -0
  129. package/src/themes/palettes/elegant.ts +34 -0
  130. package/src/themes/palettes/forest.ts +13 -0
  131. package/src/themes/palettes/glass.ts +49 -0
  132. package/src/themes/palettes/golden.ts +13 -0
  133. package/src/themes/palettes/neon.ts +43 -0
  134. package/src/themes/palettes/ocean.ts +39 -0
  135. package/src/themes/palettes/pastel.ts +37 -0
  136. package/src/themes/palettes/purple-passion.ts +13 -0
  137. package/src/themes/palettes/retro.ts +33 -0
  138. package/src/themes/palettes/sunset.ts +40 -0
  139. package/src/themes/palettes/walden.ts +13 -0
  140. package/src/themes/registry.ts +184 -0
  141. package/src/themes/types.ts +213 -0
  142. package/src/charts/bar/__tests__/index.test.tsx +0 -113
  143. package/src/charts/bar/index.tsx +0 -14
  144. package/src/charts/candlestick/__tests__/index.test.tsx +0 -40
  145. package/src/charts/candlestick/index.tsx +0 -13
  146. package/src/charts/gauge/index.tsx +0 -14
  147. package/src/charts/line/__tests__/index.test.tsx +0 -107
  148. package/src/charts/line/index.tsx +0 -15
  149. package/src/charts/pie/__tests__/index.test.tsx +0 -112
  150. package/src/charts/pie/index.tsx +0 -14
  151. package/src/charts/scatter/index.tsx +0 -14
  152. package/src/charts/sunburst/index.tsx +0 -18
  153. package/src/charts/treemap/index.tsx +0 -18
  154. package/src/core/utils/codeGenerator/CodeGenerator.ts +0 -669
  155. package/src/core/utils/codeGenerator/index.ts +0 -13
  156. package/src/core/utils/codeGenerator/types.ts +0 -198
  157. package/src/core/utils/configGenerator/ConfigGenerator.ts +0 -583
  158. package/src/core/utils/configGenerator/index.ts +0 -13
  159. package/src/core/utils/configGenerator/types.ts +0 -445
  160. package/src/core/utils/debug/DebugPanel.tsx +0 -637
  161. package/src/core/utils/debug/debugger.ts +0 -322
  162. package/src/core/utils/debug/index.ts +0 -21
  163. package/src/core/utils/debug/types.ts +0 -142
  164. 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
+ }