@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
@@ -1,162 +1,29 @@
1
- // 事件定义
2
- export const events = {
3
- click: 'click',
4
- mousemove: 'mousemove',
5
- mouseup: 'mouseup',
6
- mousedown: 'mousedown',
7
- mouseover: 'mouseover',
8
- mouseout: 'mouseout',
9
- globalout: 'globalout',
10
- };
1
+ // Re-export barrel file — all public APIs preserved
2
+ // External import paths remain unchanged
11
3
 
12
- // 导入UUID工具函数
13
- import * as i18n from './i18n';
14
- import { uuid, shortId, prefixedId } from './uuid';
15
-
16
- // 导入国际化工具
17
-
18
- /**
19
- * 深度合并对象
20
- * @param target 目标对象
21
- * @param source 源对象
22
- * @returns 合并后的对象
23
- */
24
- export function deepMerge<T extends Record<string, any>>(
25
- target: T,
26
- source: Record<string, any>
27
- ): T {
28
- const result: any = { ...target };
29
-
30
- Object.keys(source).forEach((key) => {
31
- if (source[key] instanceof Object && key in target && target[key] instanceof Object) {
32
- result[key] = deepMerge(target[key], source[key]);
33
- } else {
34
- result[key] = source[key];
35
- }
36
- });
37
-
38
- return result;
39
- }
40
-
41
- /**
42
- * 防抖函数
43
- * @param fn 需要防抖的函数
44
- * @param delay 延迟时间(毫秒)
45
- * @returns 防抖后的函数
46
- */
47
- export function debounce<T extends (...args: any[]) => any>(
48
- fn: T,
49
- delay: number
50
- ): (...args: Parameters<T>) => void {
51
- let timer: ReturnType<typeof setTimeout> | null = null;
52
-
53
- return function (this: any, ...args: Parameters<T>): void {
54
- if (timer) {
55
- clearTimeout(timer);
56
- }
57
-
58
- timer = setTimeout(() => {
59
- fn.apply(this, args);
60
- timer = null;
61
- }, delay);
62
- };
63
- }
64
-
65
- /**
66
- * 节流函数
67
- * @param fn 需要节流的函数
68
- * @param interval 间隔时间(毫秒)
69
- * @returns 节流后的函数
70
- */
71
- export function throttle<T extends (...args: any[]) => any>(
72
- fn: T,
73
- interval: number
74
- ): (...args: Parameters<T>) => void {
75
- let lastTime = 0;
4
+ // 事件常量
5
+ export { events } from './events';
76
6
 
77
- return function (this: any, ...args: Parameters<T>): void {
78
- const now = Date.now();
7
+ // 对象合并
8
+ export { deepMerge } from './merge';
79
9
 
80
- if (now - lastTime >= interval) {
81
- fn.apply(this, args);
82
- lastTime = now;
83
- }
84
- };
85
- }
10
+ // 格式化与颜色工具
11
+ export { formatNumber, getContrastColor } from './format';
86
12
 
87
- /**
88
- * 检测当前环境
89
- * @returns 环境信息
90
- */
91
- export function getEnvironment() {
92
- const isServer = typeof window === 'undefined';
93
- const isClient = !isServer;
13
+ // 检测当前环境(简易版,向后兼容)
14
+ export { getEnvironment } from './runtime';
94
15
 
95
- // 使用类型断言解决wx和my未定义的问题
96
- const isWeapp =
97
- typeof (window as any)['wx'] !== 'undefined' &&
98
- typeof (window as any)['wx'].getSystemInfoSync === 'function';
99
- const isAlipay =
100
- typeof (window as any)['my'] !== 'undefined' &&
101
- typeof (window as any)['my'].getSystemInfoSync === 'function';
102
- const isWeb = isClient && !isWeapp && !isAlipay;
16
+ // 统一运行时检测
17
+ export { detectRuntime, resetRuntimeCache } from './runtime';
18
+ export type { RuntimeInfo, MiniAppType } from './runtime';
103
19
 
104
- return {
105
- isServer,
106
- isClient,
107
- isWeapp,
108
- isAlipay,
109
- isWeb,
110
- };
111
- }
20
+ // UUID工具函数
21
+ export { uuid, shortId, prefixedId } from './uuid';
112
22
 
113
- /**
114
- * 格式化数值
115
- * @param value 要格式化的数值
116
- * @param digits 小数位数
117
- * @param options 配置选项
118
- * @returns 格式化后的字符串
119
- */
120
- export function formatNumber(
121
- value: number,
122
- digits: number = 2,
123
- options: {
124
- useGrouping?: boolean;
125
- locale?: string;
126
- } = {}
127
- ): string {
128
- const { useGrouping = true, locale = 'zh-CN' } = options;
129
-
130
- return new Intl.NumberFormat(locale, {
131
- minimumFractionDigits: digits,
132
- maximumFractionDigits: digits,
133
- useGrouping,
134
- }).format(value);
135
- }
136
-
137
- /**
138
- * 获取颜色的对比色
139
- * @param color 十六进制颜色值
140
- * @returns 对比色
141
- */
142
- export function getContrastColor(color: string): string {
143
- // 移除#前缀
144
- const hex = color.replace('#', '');
145
-
146
- // 转换为RGB
147
- const r = parseInt(hex.substring(0, 2), 16);
148
- const g = parseInt(hex.substring(2, 4), 16);
149
- const b = parseInt(hex.substring(4, 6), 16);
150
-
151
- // 计算亮度
152
- const brightness = (r * 299 + g * 587 + b * 114) / 1000;
153
-
154
- // 根据亮度返回黑色或白色
155
- return brightness > 128 ? '#000000' : '#FFFFFF';
156
- }
157
-
158
- // 导出UUID工具函数
159
- export { uuid, shortId, prefixedId };
160
-
161
- // 导出国际化工具
23
+ // 国际化工具
24
+ import * as i18n from './i18n';
162
25
  export { i18n };
26
+
27
+ // 性能优化工具(含 debounce, throttle, DebounceManager 等)
28
+ export * from './performanceUtils';
29
+ export { DebounceManager } from './performanceUtils';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 深度合并对象
3
+ * @param target 目标对象
4
+ * @param source 源对象
5
+ * @returns 合并后的对象
6
+ */
7
+ export function deepMerge<T extends Record<string, unknown>>(
8
+ target: T,
9
+ source: Partial<Record<string, unknown>>
10
+ ): T {
11
+ const result: Record<string, unknown> = { ...target };
12
+
13
+ Object.keys(source).forEach((key) => {
14
+ if (source[key] instanceof Object && key in target && target[key] instanceof Object) {
15
+ result[key] = deepMerge(
16
+ target[key] as Record<string, unknown>,
17
+ source[key] as Record<string, unknown>
18
+ );
19
+ } else {
20
+ result[key] = source[key];
21
+ }
22
+ });
23
+
24
+ return result as T;
25
+ }
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
1
2
  /**
2
3
  * TaroViz 性能分析工具核心实现
3
4
  */
@@ -12,6 +13,15 @@ import {
12
13
  PerformanceEventData,
13
14
  PerformanceReportConfig,
14
15
  } from './types';
16
+ const DEFAULT_10 = 10;
17
+ const DEFAULT_15 = 15;
18
+ const DEFAULT_20 = 20;
19
+ const DEFAULT_25 = 25;
20
+ const DEFAULT_30 = 30;
21
+ const DEFAULT_50 = 50;
22
+ const DEFAULT_100 = 100;
23
+ const DEFAULT_1000 = 1000;
24
+ const DEFAULT_1024 = 1024;
15
25
 
16
26
  /**
17
27
  * 性能分析器类
@@ -122,14 +132,25 @@ export class PerformanceAnalyzer {
122
132
  private emit(eventType: PerformanceEventType.MONITORING_START): void;
123
133
  private emit(eventType: PerformanceEventType.MONITORING_END): void;
124
134
  private emit(eventType: PerformanceEventType.METRIC_UPDATE, data: PerformanceMetric): void;
125
- private emit(eventType: PerformanceEventType.ANALYSIS_COMPLETE, data: PerformanceAnalysisResult): void;
126
- private emit(eventType: PerformanceEventType, data?: PerformanceMetric | PerformanceAnalysisResult): void {
135
+ private emit(
136
+ eventType: PerformanceEventType.ANALYSIS_COMPLETE,
137
+ data: PerformanceAnalysisResult
138
+ ): void;
139
+ private emit(
140
+ eventType: PerformanceEventType,
141
+ data?: PerformanceMetric | PerformanceAnalysisResult
142
+ ): void {
127
143
  const handlers = this.eventHandlers.get(eventType);
128
- const eventData: PerformanceEventData = data === undefined
129
- ? { type: eventType as PerformanceEventType.MONITORING_START | PerformanceEventType.MONITORING_END }
130
- : eventType === PerformanceEventType.METRIC_UPDATE
131
- ? { type: eventType, data: data as PerformanceMetric }
132
- : { type: eventType, data: data as PerformanceAnalysisResult };
144
+ const eventData: PerformanceEventData =
145
+ data === undefined
146
+ ? {
147
+ type: eventType as
148
+ | PerformanceEventType.MONITORING_START
149
+ | PerformanceEventType.MONITORING_END,
150
+ }
151
+ : eventType === PerformanceEventType.METRIC_UPDATE
152
+ ? { type: eventType, data: data as PerformanceMetric }
153
+ : { type: eventType, data: data as PerformanceAnalysisResult };
133
154
  handlers?.forEach((handler) => {
134
155
  try {
135
156
  handler(eventData);
@@ -149,7 +170,7 @@ export class PerformanceAnalyzer {
149
170
 
150
171
  this.isMonitoring = true;
151
172
  this.startTime = Date.now();
152
- this.lastFrameTime = performance.now();
173
+ this.lastFrameTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
153
174
 
154
175
  // 启动采样定时器
155
176
  if (this.config.sampleInterval && this.config.realTime) {
@@ -211,6 +232,7 @@ export class PerformanceAnalyzer {
211
232
  * 开始帧率监控
212
233
  */
213
234
  private startFrameRateMonitoring(): void {
235
+ if (typeof requestAnimationFrame === 'undefined' || typeof performance === 'undefined') return;
214
236
  const updateFrameRate = () => {
215
237
  if (!this.isMonitoring) {
216
238
  return;
@@ -264,9 +286,11 @@ export class PerformanceAnalyzer {
264
286
  // 采样内存使用(如果支持)
265
287
  if (
266
288
  this.config.metrics?.includes('memoryUsage') &&
267
- typeof (performance as any).memory !== 'undefined'
289
+ typeof (performance as Performance & { memory?: { usedJSHeapSize: number } }).memory !==
290
+ 'undefined'
268
291
  ) {
269
- const memoryUsage = ((performance as any).memory.usedJSHeapSize / (1024 * 1024)).toFixed(2);
292
+ const memory = (performance as Performance & { memory?: { usedJSHeapSize: number } }).memory;
293
+ const memoryUsage = ((memory?.usedJSHeapSize ?? 0) / (1024 * 1024)).toFixed(2);
270
294
  this.recordMetric('memoryUsage', parseFloat(memoryUsage), 'MB');
271
295
  }
272
296
  }
@@ -389,22 +413,22 @@ export class PerformanceAnalyzer {
389
413
  });
390
414
 
391
415
  // 计算性能评分和建议
392
- if (averages.renderTime > 100) {
416
+ if (averages.renderTime !== undefined && averages.renderTime > 100) {
393
417
  score -= 20;
394
418
  suggestions.push('渲染时间过长,建议优化数据处理或减少图表复杂度');
395
419
  }
396
420
 
397
- if (averages.updateTime > 50) {
421
+ if (averages.updateTime !== undefined && averages.updateTime > 50) {
398
422
  score -= 15;
399
423
  suggestions.push('更新时间过长,建议优化数据更新逻辑');
400
424
  }
401
425
 
402
- if (averages.frameRate < 30) {
426
+ if (averages.frameRate !== undefined && averages.frameRate < 30) {
403
427
  score -= 25;
404
428
  suggestions.push('帧率过低,建议减少动画效果或优化渲染逻辑');
405
429
  }
406
430
 
407
- if (averages.dataSize > 100) {
431
+ if (averages.dataSize !== undefined && averages.dataSize > 100) {
408
432
  score -= 10;
409
433
  suggestions.push('数据量过大,建议进行数据压缩或分页处理');
410
434
  }
@@ -611,13 +635,6 @@ export class PerformanceAnalyzer {
611
635
  this.config = { ...this.config, ...config };
612
636
  }
613
637
 
614
- /**
615
- * 获取所有指标数据
616
- */
617
- public getAllMetrics(): Map<PerformanceMetricType, PerformanceMetric[]> {
618
- return new Map(this.metrics);
619
- }
620
-
621
638
  /**
622
639
  * 获取特定类型的指标数据
623
640
  */
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 性能优化 Hooks
3
+ * 提供动画、缩放等性能相关的 hooks
4
+ */
5
+
6
+ export { useAnimation } from './useAnimation';
7
+ export { useDataZoom } from './useDataZoom';
@@ -11,3 +11,5 @@ export { PerformanceAnalyzer } from './PerformanceAnalyzer';
11
11
  // 导出默认实例
12
12
  import { PerformanceAnalyzer } from './PerformanceAnalyzer';
13
13
  export const performanceAnalyzer = PerformanceAnalyzer.getInstance();
14
+
15
+ export * from './hooks';
@@ -1,9 +1,10 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
1
2
  /**
2
3
  * useAnimation - 图表动画控制 Hook
3
4
  * 提供图表动画的播放、暂停、控制等功能
4
5
  */
5
- import { useState, useCallback, useRef, useEffect } from 'react';
6
- import type { ChartInstance } from './index';
6
+ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
7
+ import type { ChartInstance } from '../../../hooks/types';
7
8
 
8
9
  /**
9
10
  * 动画状态
@@ -47,11 +48,11 @@ export interface UseAnimationReturn {
47
48
  /** 停止动画并重置 */
48
49
  stop: () => void;
49
50
  /** 跳转到指定帧 */
50
- seekTo: (frame: number) => void;
51
+ seekTo: (_frame: number) => void;
51
52
  /** 跳转到指定进度 */
52
- seekToProgress: (progress: number) => void;
53
+ seekToProgress: (_progress: number) => void;
53
54
  /** 设置播放速度 */
54
- setPlaybackSpeed: (speed: number) => void;
55
+ setPlaybackSpeed: (_speed: number) => void;
55
56
  /** 播放速度 */
56
57
  playbackSpeed: number;
57
58
  }
@@ -81,35 +82,45 @@ export function useAnimation(
81
82
  const [playbackSpeed, setPlaybackSpeedState] = useState(1);
82
83
 
83
84
  // Refs
85
+ const chartRef = useRef<ChartInstance | null>(null);
86
+ chartRef.current = chartInstance;
87
+
88
+ // Animation state ref — tracks RAF id, start time, current frame, loop count, paused flag
84
89
  const animationRef = useRef<{
90
+ animationId: number | null;
85
91
  startTime: number;
86
92
  currentFrame: number;
87
93
  loopCounter: number;
88
- animationId: number | null;
89
94
  isPaused: boolean;
90
95
  }>({
96
+ animationId: null,
91
97
  startTime: 0,
92
98
  currentFrame: 0,
93
99
  loopCounter: 0,
94
- animationId: null,
95
100
  isPaused: false,
96
101
  });
97
102
 
98
- const chartRef = useRef<ChartInstance | null>(null);
99
- chartRef.current = chartInstance;
103
+ // 缓动函数 - 使用 useMemo 缓存,避免每次重新创建
104
+ const easingFunctions = useMemo<Record<string, (t: number) => number>>(
105
+ () => ({
106
+ cubicOut: (t) => 1 - Math.pow(1 - t, 3),
107
+ cubicIn: (t) => t * t * t,
108
+ cubicInOut: (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2),
109
+ linear: (t) => t,
110
+ sinusoidalIn: (t) => 1 - Math.cos((t * Math.PI) / 2),
111
+ sinusoidalOut: (t) => Math.sin((t * Math.PI) / 2),
112
+ }),
113
+ []
114
+ );
100
115
 
101
116
  // 计算总帧数(假设 60fps)
102
- const totalFrames = Math.ceil((duration / 1000) * 60);
103
-
104
- // 缓动函数
105
- const easingFunctions: Record<string, (t: number) => number> = {
106
- cubicOut: (t) => 1 - Math.pow(1 - t, 3),
107
- cubicIn: (t) => t * t * t,
108
- cubicInOut: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
109
- linear: (t) => t,
110
- sinusoidalIn: (t) => 1 - Math.cos((t * Math.PI) / 2),
111
- sinusoidalOut: (t) => Math.sin((t * Math.PI) / 2),
112
- };
117
+ const totalFrames = useMemo(() => Math.ceil((duration / 1000) * 60), [duration]);
118
+
119
+ // 获取当前缓动函数
120
+ const getEasing = useCallback(
121
+ (easingName: string) => easingFunctions[easingName] ?? easingFunctions.linear,
122
+ [easingFunctions]
123
+ );
113
124
 
114
125
  // 计算当前进度对应的帧
115
126
  const calculateFrame = useCallback(
@@ -128,10 +139,7 @@ export function useAnimation(
128
139
  const animate = useCallback(() => {
129
140
  const anim = animationRef.current;
130
141
  const chart = chartRef.current;
131
-
132
- if (!chart || anim.isPaused || disabled) {
133
- return;
134
- }
142
+ if (!chart || anim.isPaused || disabled) return;
135
143
 
136
144
  const elapsed = performance.now() - anim.startTime;
137
145
  const adjustedDuration = duration / playbackSpeed;
@@ -154,9 +162,7 @@ export function useAnimation(
154
162
 
155
163
  // 更新图表配置以反映动画进度
156
164
  try {
157
- // ECharts 通过 setOption 配合 notMerge: false 更新动画
158
165
  if (chart.setOption && !anim.isPaused) {
159
- // 触发图表重新渲染
160
166
  chart.setOption({}, false, true);
161
167
  }
162
168
  } catch (e) {
@@ -166,13 +172,11 @@ export function useAnimation(
166
172
  // 检查是否完成
167
173
  if (effectiveElapsed >= adjustedDuration) {
168
174
  if (loop && (loopCount === -1 || anim.loopCounter < loopCount)) {
169
- // 继续循环
170
175
  anim.loopCounter++;
171
176
  anim.startTime = performance.now();
172
177
  anim.isPaused = false;
173
178
  setStatus('playing');
174
179
  } else {
175
- // 动画结束
176
180
  setStatus('stopped');
177
181
  anim.animationId = null;
178
182
  return;
@@ -180,22 +184,20 @@ export function useAnimation(
180
184
  }
181
185
 
182
186
  anim.animationId = requestAnimationFrame(animate);
183
- }, [duration, easing, disabled, delay, loop, loopCount, playbackSpeed, easingFunctions, calculateFrame]);
187
+ }, [duration, delay, loop, loopCount, playbackSpeed, disabled, easing, calculateFrame]);
184
188
 
185
189
  // 播放动画
186
- const play = useCallback(() => {
190
+ const playRef = useRef<() => void>(() => {});
191
+ playRef.current = () => {
187
192
  const chart = chartRef.current;
188
193
  if (!chart || disabled) return;
189
194
 
190
195
  const anim = animationRef.current;
191
-
192
196
  if (status === 'paused') {
193
- // 从暂停恢复
194
197
  anim.isPaused = false;
195
198
  anim.startTime = performance.now() - (anim.currentFrame / totalFrames) * duration;
196
199
  setStatus('playing');
197
200
  } else {
198
- // 开始新动画
199
201
  anim.startTime = performance.now();
200
202
  anim.currentFrame = 0;
201
203
  anim.loopCounter = 0;
@@ -206,7 +208,11 @@ export function useAnimation(
206
208
  if (anim.animationId === null) {
207
209
  anim.animationId = requestAnimationFrame(animate);
208
210
  }
209
- }, [status, disabled, totalFrames, duration, animate]);
211
+ };
212
+
213
+ const play = useCallback(() => {
214
+ playRef.current();
215
+ }, []);
210
216
 
211
217
  // 暂停动画
212
218
  const pause = useCallback(() => {
@@ -238,12 +244,12 @@ export function useAnimation(
238
244
  const chart = chartRef.current;
239
245
  if (!chart) return;
240
246
 
241
- const clampedFrame = Math.max(0, Math.min(totalFrames, targetFrame));
247
+ const currentTotalFrames = Math.ceil((duration / 1000) * 60);
248
+ const clampedFrame = Math.max(0, Math.min(currentTotalFrames, targetFrame));
242
249
  anim.currentFrame = clampedFrame;
243
250
  setFrame(clampedFrame);
244
251
 
245
- // 更新图表状态
246
- const progress = clampedFrame / totalFrames;
252
+ const _progress = clampedFrame / currentTotalFrames;
247
253
  try {
248
254
  if (chart.setOption) {
249
255
  chart.setOption({}, false, true);
@@ -252,7 +258,7 @@ export function useAnimation(
252
258
  console.warn('[useAnimation] Failed to seek:', e);
253
259
  }
254
260
  },
255
- [totalFrames]
261
+ [duration]
256
262
  );
257
263
 
258
264
  // 跳转到指定进度
@@ -362,7 +368,7 @@ export function useProgressiveLoading(
362
368
  setCurrentBatch(0);
363
369
 
364
370
  intervalRef.current = window.setInterval(() => {
365
- setCurrentBatch(prev => {
371
+ setCurrentBatch((prev) => {
366
372
  const next = prev + 1;
367
373
  onBatchLoaded?.(next, totalBatches);
368
374
 
@@ -423,5 +429,3 @@ export function useProgressiveLoading(
423
429
  reset,
424
430
  };
425
431
  }
426
-
427
- export default useAnimation;