@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
@@ -84,11 +84,22 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
84
84
  fontFamily: 'var(--tv-font-family, sans-serif)',
85
85
  }}
86
86
  >
87
- <div style={{ fontSize: '48px', marginBottom: '16px' }} aria-hidden="true">⚠️</div>
88
- <h3 style={{ margin: '0 0 12px', color: 'var(--tv-error-color, #ff4d4f)', fontWeight: 700 }}>
87
+ <div style={{ fontSize: '48px', marginBottom: '16px' }} aria-hidden="true">
88
+ ⚠️
89
+ </div>
90
+ <h3
91
+ style={{ margin: '0 0 12px', color: 'var(--tv-error-color, #ff4d4f)', fontWeight: 700 }}
92
+ >
89
93
  图表渲染失败
90
94
  </h3>
91
- <p style={{ margin: '0 0 16px', color: 'var(--tv-text-color-secondary, #666)', textAlign: 'center', maxWidth: '320px' }}>
95
+ <p
96
+ style={{
97
+ margin: '0 0 16px',
98
+ color: 'var(--tv-text-color-secondary, #666)',
99
+ textAlign: 'center',
100
+ maxWidth: '320px',
101
+ }}
102
+ >
92
103
  图表在渲染过程中遇到错误,请检查数据配置是否正确
93
104
  </p>
94
105
  {showDetails && (
@@ -104,7 +115,9 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
104
115
  maxHeight: '150px',
105
116
  }}
106
117
  >
107
- <summary style={{ cursor: 'pointer', marginBottom: '8px', fontWeight: 600 }}>错误详情</summary>
118
+ <summary style={{ cursor: 'pointer', marginBottom: '8px', fontWeight: 600 }}>
119
+ 错误详情
120
+ </summary>
108
121
  <pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
109
122
  {error.message}
110
123
  {'\n\n'}
@@ -5,32 +5,53 @@
5
5
  import React, { Suspense, lazy, ComponentType } from 'react';
6
6
  import type { BaseChartProps } from '../../charts/types';
7
7
 
8
- // 懒加载各个图表组件
9
- const LazyLineChart = lazy(() => import('../../charts/line'));
10
- const LazyBarChart = lazy(() => import('../../charts/bar'));
11
- const LazyPieChart = lazy(() => import('../../charts/pie'));
12
- const LazyScatterChart = lazy(() => import('../../charts/scatter'));
13
- const LazyRadarChart = lazy(() => import('../../charts/radar'));
14
- const LazyHeatmapChart = lazy(() => import('../../charts/heatmap'));
15
- const LazyGaugeChart = lazy(() => import('../../charts/gauge'));
16
- const LazyFunnelChart = lazy(() => import('../../charts/funnel'));
17
- const LazyTreeMapChart = lazy(() => import('../../charts/treemap'));
18
- const LazySunburstChart = lazy(() => import('../../charts/sunburst'));
19
- const LazySankeyChart = lazy(() => import('../../charts/sankey'));
8
+ /**
9
+ * 图表类型到懒加载组件的映射
10
+ * 统一定义,避免重复
11
+ */
12
+ const LAZY_CHART_REGISTRY: Record<string, ComponentType<BaseChartProps>> = {};
13
+
14
+ /**
15
+ * 创建懒加载组件
16
+ */
17
+ function createLazyComponent(name: string): ComponentType<BaseChartProps> {
18
+ if (!LAZY_CHART_REGISTRY[name]) {
19
+ LAZY_CHART_REGISTRY[name] = lazy(() =>
20
+ import('../../charts').then((m) => ({
21
+ default: m[name as keyof typeof m] as ComponentType<BaseChartProps>,
22
+ }))
23
+ );
24
+ }
25
+ return LAZY_CHART_REGISTRY[name];
26
+ }
20
27
 
21
- // 统一的图表类型到懒加载组件映射
22
- const LAZY_CHART_MODULES: Record<string, () => Promise<{ default: ComponentType<BaseChartProps> }>> = {
23
- line: () => import('../../charts/line'),
24
- bar: () => import('../../charts/bar'),
25
- pie: () => import('../../charts/pie'),
26
- scatter: () => import('../../charts/scatter'),
27
- radar: () => import('../../charts/radar'),
28
- heatmap: () => import('../../charts/heatmap'),
29
- gauge: () => import('../../charts/gauge'),
30
- funnel: () => import('../../charts/funnel'),
31
- treemap: () => import('../../charts/treemap'),
32
- sunburst: () => import('../../charts/sunburst'),
33
- sankey: () => import('../../charts/sankey'),
28
+ // 预创建常用图表的懒加载组件
29
+ export const LazyLineChart = createLazyComponent('LineChart');
30
+ export const LazyBarChart = createLazyComponent('BarChart');
31
+ export const LazyPieChart = createLazyComponent('PieChart');
32
+ export const LazyScatterChart = createLazyComponent('ScatterChart');
33
+ export const LazyRadarChart = createLazyComponent('RadarChartCustom');
34
+ export const LazyHeatmapChart = createLazyComponent('HeatmapChart');
35
+ export const LazyFunnelChart = createLazyComponent('FunnelChart');
36
+ export const LazyTreeMapChart = createLazyComponent('TreeMapChart');
37
+ export const LazySunburstChart = createLazyComponent('SunburstChart');
38
+ export const LazySankeyChart = createLazyComponent('SankeyChart');
39
+
40
+ /**
41
+ * 图表类型到懒加载模块的映射
42
+ * 用于预加载功能
43
+ */
44
+ const LAZY_CHART_MODULES: Record<string, () => Promise<{ default: React.ComponentType<any> }>> = {
45
+ line: () => import('../../charts').then((m) => ({ default: m.LineChart })),
46
+ bar: () => import('../../charts').then((m) => ({ default: m.BarChart })),
47
+ pie: () => import('../../charts').then((m) => ({ default: m.PieChart })),
48
+ scatter: () => import('../../charts').then((m) => ({ default: m.ScatterChart })),
49
+ radar: () => import('../../charts').then((m) => ({ default: m.RadarChartCustom })),
50
+ heatmap: () => import('../../charts').then((m) => ({ default: m.HeatmapChart })),
51
+ funnel: () => import('../../charts').then((m) => ({ default: m.FunnelChart })),
52
+ treemap: () => import('../../charts').then((m) => ({ default: m.TreeMapChart })),
53
+ sunburst: () => import('../../charts').then((m) => ({ default: m.SunburstChart })),
54
+ sankey: () => import('../../charts').then((m) => ({ default: m.SankeyChart })),
34
55
  };
35
56
 
36
57
  export const LAZY_CHART_TYPES = Object.keys(LAZY_CHART_MODULES);
@@ -94,7 +115,12 @@ const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中
94
115
  >
95
116
  {text}
96
117
  </span>
97
- <span style={{ color: 'var(--tv-text-color-secondary, #666)', fontSize: 'var(--tv-font-size, 14px)' }}>
118
+ <span
119
+ style={{
120
+ color: 'var(--tv-text-color-secondary, #666)',
121
+ fontSize: 'var(--tv-font-size, 14px)',
122
+ }}
123
+ >
98
124
  {text}
99
125
  </span>
100
126
  </div>
@@ -164,21 +190,8 @@ export function preloadAllCharts(): Promise<void[]> {
164
190
  * 用于动态导入图表
165
191
  */
166
192
  export function createLazyChart(chartType: string): ComponentType<BaseChartProps> | null {
167
- const lazyCharts: Record<string, ComponentType<BaseChartProps>> = {
168
- line: LazyLineChart,
169
- bar: LazyBarChart,
170
- pie: LazyPieChart,
171
- scatter: LazyScatterChart,
172
- radar: LazyRadarChart,
173
- heatmap: LazyHeatmapChart,
174
- gauge: LazyGaugeChart,
175
- funnel: LazyFunnelChart,
176
- treemap: LazyTreeMapChart,
177
- sunburst: LazySunburstChart,
178
- sankey: LazySankeyChart,
179
- };
180
-
181
- return lazyCharts[chartType] || null;
193
+ const chartName = `${chartType.charAt(0).toUpperCase() + chartType.slice(1)}Chart`;
194
+ return LAZY_CHART_REGISTRY[chartName] || null;
182
195
  }
183
196
 
184
197
  /**
@@ -198,17 +211,3 @@ export const LazyChartRegistry = {
198
211
  return preloadAllCharts();
199
212
  },
200
213
  };
201
-
202
- export {
203
- LazyLineChart,
204
- LazyBarChart,
205
- LazyPieChart,
206
- LazyScatterChart,
207
- LazyRadarChart,
208
- LazyHeatmapChart,
209
- LazyGaugeChart,
210
- LazyFunnelChart,
211
- LazyTreeMapChart,
212
- LazySunburstChart,
213
- LazySankeyChart,
214
- };
@@ -16,5 +16,9 @@ export type {
16
16
  export { useVirtualScroll } from './useVirtualScroll';
17
17
  export type { UseVirtualScrollOptions, VirtualScrollState } from './useVirtualScroll';
18
18
 
19
- export { usePerformance } from './usePerformance';
20
- export type { PerformanceData, UsePerformanceOptions } from './usePerformance';
19
+ export { useChartPerformanceMetrics, usePerformance } from './usePerformance';
20
+ export type {
21
+ PerformanceData,
22
+ UseChartPerformanceMetricsOptions,
23
+ UsePerformanceOptions,
24
+ } from './usePerformance';
@@ -36,6 +36,8 @@ export function useChartInit(
36
36
  let adapter: Adapter | null = null;
37
37
 
38
38
  const initChart = async () => {
39
+ if (!mounted) return;
40
+
39
41
  try {
40
42
  adapter = await getAdapter({
41
43
  width: options.width,
@@ -47,14 +49,15 @@ export function useChartInit(
47
49
  direction: options.direction,
48
50
  });
49
51
 
50
- if (!mounted) return;
51
- if (!adapter) return;
52
+ if (!mounted || !adapter) return;
52
53
 
53
54
  adapterRef.current = adapter;
54
55
  isReadyRef.current = true;
55
56
  adapter.init();
56
57
  } catch (error) {
57
- console.error('[TaroViz] Failed to initialize chart:', error);
58
+ if (mounted) {
59
+ console.error('[TaroViz] Failed to initialize chart:', error);
60
+ }
58
61
  }
59
62
  };
60
63
 
@@ -13,12 +13,15 @@ export interface PerformanceData {
13
13
  dataSize: number;
14
14
  }
15
15
 
16
- export interface UsePerformanceOptions {
16
+ export interface UseChartPerformanceMetricsOptions {
17
17
  enabled?: boolean;
18
18
  onPerformance?: (data: PerformanceData) => void;
19
19
  }
20
20
 
21
- export function usePerformance(options: UsePerformanceOptions = {}) {
21
+ /** @deprecated Use `UseChartPerformanceMetricsOptions` instead */
22
+ export type UsePerformanceOptions = UseChartPerformanceMetricsOptions;
23
+
24
+ export function useChartPerformanceMetrics(options: UseChartPerformanceMetricsOptions = {}) {
22
25
  const { enabled = false, onPerformance } = options;
23
26
 
24
27
  const analyzerRef = useRef<PerformanceAnalyzer | null>(null);
@@ -184,3 +187,6 @@ export function usePerformance(options: UsePerformanceOptions = {}) {
184
187
  dispose,
185
188
  };
186
189
  }
190
+
191
+ /** @deprecated Use `useChartPerformanceMetrics` instead */
192
+ export const usePerformance = useChartPerformanceMetrics;
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { useRef, useCallback } from 'react';
6
6
  import type { EChartsOption } from 'echarts';
7
+ import { deepClone } from '../../../core/utils/deepClone';
7
8
 
8
9
  export interface UseVirtualScrollOptions {
9
10
  enabled?: boolean;
@@ -71,7 +72,7 @@ export function useVirtualScroll(options: UseVirtualScrollOptions = {}) {
71
72
  }
72
73
 
73
74
  // 深拷贝避免修改原始数据
74
- const processedOption = JSON.parse(JSON.stringify(originalOption)) as EChartsOption;
75
+ const processedOption = deepClone(originalOption) as EChartsOption;
75
76
 
76
77
  (processedOption.series as unknown[]).forEach((seriesItem: unknown, _index: number) => {
77
78
  const s = seriesItem as { data?: unknown[] };
package/src/core/index.ts CHANGED
@@ -19,4 +19,4 @@ export { default as echarts } from './echarts';
19
19
  * 库信息
20
20
  */
21
21
  export const name = 'taroviz';
22
- export const version = '1.2.0';
22
+ export { VERSION as version, versionInfo } from './version';
@@ -248,7 +248,7 @@ const PRESET_THEMES: Record<PresetThemeName, ThemeConfig> = {
248
248
  '--tv-bg-color-secondary': '#f5f7fa',
249
249
  '--tv-text-color': '#1a1a1a',
250
250
  '--tv-text-color-secondary': '#666666',
251
- '--tv-primary-color': '#277 ace',
251
+ '--tv-primary-color': '#277ace',
252
252
  '--tv-primary-color-hover': '#3a8ee6',
253
253
  '--tv-primary-color-active': '#146bb3',
254
254
  '--tv-success-color': '#2fc25b',
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
1
2
  /**
2
3
  * ECharts 类型定义
3
4
  */
@@ -120,7 +121,7 @@ export type EChartsEventParams =
120
121
  /**
121
122
  * 图表事件监听器
122
123
  */
123
- export type ChartEventListener = Record<string, (params: ChartEventParams) => void>;
124
+ export type _ChartEventListener = Record<string, (_params: ChartEventParams) => void>;
124
125
 
125
126
  /**
126
127
  * 图表渲染器类型
@@ -123,18 +123,6 @@ export interface ThemeType {
123
123
  [key: string]: unknown;
124
124
  }
125
125
 
126
- /**
127
- * 渲染性能优化配置
128
- */
129
- export interface RenderOptimizationConfig {
130
- progressive?: boolean;
131
- progressiveThreshold?: number;
132
- lazyUpdate?: boolean;
133
- animation?: boolean;
134
- hardwareAcceleration?: boolean;
135
- frameRate?: number;
136
- }
137
-
138
126
  /**
139
127
  * 适配器配置选项
140
128
  */
@@ -1,10 +1,8 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
1
2
  /**
2
3
  * 支持的平台类型
3
4
  */
4
- import type {
5
- EChartsType,
6
- EChartsOption,
7
- } from 'echarts';
5
+ import type { EChartsType, EChartsOption } from 'echarts';
8
6
  import type { CSSProperties } from 'react';
9
7
  import type { ChartEventParams } from './common';
10
8
 
@@ -115,7 +113,7 @@ export interface HarmonyAdapterOptions extends AdapterOptions {
115
113
  export interface Adapter {
116
114
  init(options?: object): EChartsType;
117
115
  getInstance(): EChartsType | null;
118
- setOption(option: EChartsOption, opts?: object): void;
116
+ setOption(option: EChartsOption, notMerge?: boolean, lazyUpdate?: boolean): void;
119
117
  getWidth(): number;
120
118
  getHeight(): number;
121
119
  getDom(): HTMLElement | null;
@@ -0,0 +1,317 @@
1
+ import { deepClone, deepMerge } from '../deepClone';
2
+
3
+ describe('deepClone', () => {
4
+ // Primitives
5
+ it('clones primitives', () => {
6
+ expect(deepClone(42)).toBe(42);
7
+ expect(deepClone('hello')).toBe('hello');
8
+ expect(deepClone(true)).toBe(true);
9
+ expect(deepClone(false)).toBe(false);
10
+ expect(deepClone(0)).toBe(0);
11
+ expect(deepClone(-1)).toBe(-1);
12
+ expect(deepClone(3.14)).toBe(3.14);
13
+ });
14
+
15
+ it('handles null and undefined', () => {
16
+ expect(deepClone(null)).toBe(null);
17
+ expect(deepClone(undefined)).toBe(undefined);
18
+ });
19
+
20
+ // Objects
21
+ it('clones plain objects', () => {
22
+ const obj = { a: 1, b: 'two', c: true };
23
+ const clone = deepClone(obj);
24
+ expect(clone).toEqual(obj);
25
+ expect(clone).not.toBe(obj);
26
+ });
27
+
28
+ it('clones nested objects', () => {
29
+ const obj = { a: { b: { c: { d: 42 } } } };
30
+ const clone = deepClone(obj);
31
+ expect(clone).toEqual(obj);
32
+ expect(clone).not.toBe(obj);
33
+ expect(clone.a).not.toBe(obj.a);
34
+ expect(clone.a.b).not.toBe(obj.a.b);
35
+ });
36
+
37
+ // Arrays
38
+ it('clones arrays', () => {
39
+ const arr = [1, 2, 3, [4, 5]];
40
+ const clone = deepClone(arr);
41
+ expect(clone).toEqual(arr);
42
+ expect(clone).not.toBe(arr);
43
+ expect(clone[3]).not.toBe(arr[3]);
44
+ });
45
+
46
+ it('clones arrays of objects', () => {
47
+ const arr = [{ a: 1 }, { b: 2 }];
48
+ const clone = deepClone(arr);
49
+ expect(clone).toEqual(arr);
50
+ expect(clone[0]).not.toBe(arr[0]);
51
+ });
52
+
53
+ // Date
54
+ it('clones Date objects', () => {
55
+ const date = new Date('2024-01-15');
56
+ const clone = deepClone(date);
57
+ expect(clone).toEqual(date);
58
+ expect(clone).not.toBe(date);
59
+ expect(clone.getTime()).toBe(date.getTime());
60
+ });
61
+
62
+ // RegExp
63
+ it('clones RegExp objects', () => {
64
+ const re = /foo/gi;
65
+ const clone = deepClone(re);
66
+ expect(clone.source).toBe('foo');
67
+ expect(clone.flags).toBe('gi');
68
+ expect(clone).not.toBe(re);
69
+ });
70
+
71
+ // Functions
72
+ it('preserves function references', () => {
73
+ const fn = (x: number) => x * 2;
74
+ expect(deepClone(fn)).toBe(fn);
75
+ });
76
+
77
+ it('preserves nested function references', () => {
78
+ const fn = () => 'hello';
79
+ const obj = { formatter: fn, nested: { callback: fn } };
80
+ const clone = deepClone(obj);
81
+ expect(clone.formatter).toBe(fn);
82
+ expect(clone.nested.callback).toBe(fn);
83
+ expect(clone).not.toBe(obj);
84
+ expect(clone.nested).not.toBe(obj.nested);
85
+ });
86
+
87
+ // Map
88
+ it('clones Map objects', () => {
89
+ const map = new Map<string, number>([
90
+ ['a', 1],
91
+ ['b', 2],
92
+ ]);
93
+ const clone = deepClone(map);
94
+ expect(clone).not.toBe(map);
95
+ expect(clone.get('a')).toBe(1);
96
+ expect(clone.get('b')).toBe(2);
97
+ expect(clone.size).toBe(2);
98
+ });
99
+
100
+ it('clones Map with object keys and values', () => {
101
+ const key = { id: 1 };
102
+ const val = { name: 'test' };
103
+ const map = new Map([[key, val]]);
104
+ const clone = deepClone(map);
105
+ expect(clone).not.toBe(map);
106
+ // Keys are cloned, so we can't look up with the original key
107
+ const clonedEntries = Array.from(clone.entries());
108
+ expect(clonedEntries).toHaveLength(1);
109
+ expect(clonedEntries[0][0]).toEqual(key);
110
+ expect(clonedEntries[0][0]).not.toBe(key);
111
+ expect(clonedEntries[0][1]).toEqual(val);
112
+ expect(clonedEntries[0][1]).not.toBe(val);
113
+ });
114
+
115
+ // Set
116
+ it('clones Set objects', () => {
117
+ const set = new Set([1, 2, 3]);
118
+ const clone = deepClone(set);
119
+ expect(clone).not.toBe(set);
120
+ expect(clone.size).toBe(3);
121
+ expect(clone.has(1)).toBe(true);
122
+ expect(clone.has(2)).toBe(true);
123
+ expect(clone.has(3)).toBe(true);
124
+ });
125
+
126
+ it('clones Set with object values', () => {
127
+ const obj = { a: 1 };
128
+ const set = new Set([obj]);
129
+ const clone = deepClone(set);
130
+ const clonedValues = Array.from(clone.values());
131
+ expect(clonedValues[0]).toEqual(obj);
132
+ expect(clonedValues[0]).not.toBe(obj);
133
+ });
134
+
135
+ // Circular references
136
+ it('handles circular references in objects', () => {
137
+ const obj: Record<string, unknown> = { a: 1 };
138
+ obj.self = obj;
139
+ const clone = deepClone(obj);
140
+ expect(clone.a).toBe(1);
141
+ expect(clone.self).toBe(clone);
142
+ expect(clone).not.toBe(obj);
143
+ });
144
+
145
+ it('handles circular references in arrays', () => {
146
+ const arr: unknown[] = [1, 2];
147
+ arr.push(arr);
148
+ const clone = deepClone(arr);
149
+ expect(clone[0]).toBe(1);
150
+ expect(clone[1]).toBe(2);
151
+ expect(clone[2]).toBe(clone);
152
+ expect(clone).not.toBe(arr);
153
+ });
154
+
155
+ it('handles cross-reference between objects', () => {
156
+ const shared = { value: 42 };
157
+ const obj = { a: shared, b: shared };
158
+ const clone = deepClone(obj);
159
+ expect(clone.a).toEqual(shared);
160
+ expect(clone.b).toEqual(shared);
161
+ // Both references should point to the same clone
162
+ expect(clone.a).toBe(clone.b);
163
+ expect(clone.a).not.toBe(shared);
164
+ });
165
+
166
+ // ECharts-like option objects
167
+ it('clones ECharts-like option objects', () => {
168
+ const formatter = (params: { value: number }) => `${params.value}%`;
169
+ const options = {
170
+ title: {
171
+ text: 'Sales',
172
+ subtext: '2024',
173
+ },
174
+ tooltip: {
175
+ trigger: 'axis' as const,
176
+ formatter,
177
+ },
178
+ xAxis: {
179
+ type: 'category' as const,
180
+ data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
181
+ },
182
+ yAxis: {
183
+ type: 'value' as const,
184
+ },
185
+ series: [
186
+ {
187
+ name: 'Sales',
188
+ type: 'line' as const,
189
+ data: [150, 230, 224, 218, 135],
190
+ label: {
191
+ show: true,
192
+ formatter: (params: { value: number }) => `${params.value}`,
193
+ },
194
+ },
195
+ ],
196
+ };
197
+
198
+ const clone = deepClone(options);
199
+
200
+ // Structure equality
201
+ expect(clone).toEqual(options);
202
+ // Deep copy
203
+ expect(clone).not.toBe(options);
204
+ expect(clone.title).not.toBe(options.title);
205
+ expect(clone.series).not.toBe(options.series);
206
+ expect(clone.series[0]).not.toBe(options.series[0]);
207
+ // Function preserved
208
+ expect(clone.tooltip.formatter).toBe(formatter);
209
+ expect(clone.series[0].label.formatter).toBe(options.series[0].label.formatter);
210
+ });
211
+
212
+ // Mixed types
213
+ it('clones complex mixed structures', () => {
214
+ const obj = {
215
+ str: 'hello',
216
+ num: 42,
217
+ bool: true,
218
+ nil: null,
219
+ undef: undefined,
220
+ date: new Date('2024-06-01'),
221
+ regex: /test/gi,
222
+ arr: [1, [2, 3], { a: 4 }],
223
+ fn: () => 'test',
224
+ map: new Map([['key', { nested: true }]]),
225
+ set: new Set([{ x: 1 }, { x: 2 }]),
226
+ nested: {
227
+ deep: {
228
+ deeper: {
229
+ value: 'bottom',
230
+ },
231
+ },
232
+ },
233
+ };
234
+
235
+ const clone = deepClone(obj);
236
+
237
+ expect(clone.str).toBe('hello');
238
+ expect(clone.num).toBe(42);
239
+ expect(clone.bool).toBe(true);
240
+ expect(clone.nil).toBe(null);
241
+ expect(clone.undef).toBe(undefined);
242
+ expect(clone.date).toEqual(obj.date);
243
+ expect(clone.date).not.toBe(obj.date);
244
+ expect(clone.regex.source).toBe('test');
245
+ expect(clone.regex).not.toBe(obj.regex);
246
+ expect(clone.arr).toEqual([1, [2, 3], { a: 4 }]);
247
+ expect(clone.arr).not.toBe(obj.arr);
248
+ expect(clone.fn).toBe(obj.fn);
249
+ expect(clone.map).not.toBe(obj.map);
250
+ expect(clone.set).not.toBe(obj.set);
251
+ expect(clone.nested.deep.deeper.value).toBe('bottom');
252
+ expect(clone.nested).not.toBe(obj.nested);
253
+ });
254
+ });
255
+
256
+ describe('deepMerge', () => {
257
+ it('merges flat objects', () => {
258
+ const target = { a: 1, b: 2 };
259
+ const source = { b: 3, c: 4 };
260
+ const result = deepMerge(target, source);
261
+ expect(result).toEqual({ a: 1, b: 3, c: 4 });
262
+ });
263
+
264
+ it('merges nested objects recursively', () => {
265
+ const target = { a: { x: 1, y: 2 }, b: 1 };
266
+ const source = { a: { y: 3, z: 4 } };
267
+ const result = deepMerge(target, source);
268
+ expect(result).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 1 });
269
+ });
270
+
271
+ it('replaces arrays instead of merging them', () => {
272
+ const target = { arr: [1, 2, 3] };
273
+ const source = { arr: [4, 5] };
274
+ const result = deepMerge(target, source);
275
+ expect(result.arr).toEqual([4, 5]);
276
+ });
277
+
278
+ it('replaces primitives with source values', () => {
279
+ const target = { a: 1, b: 'old' };
280
+ const source = { a: 2, b: 'new' };
281
+ const result = deepMerge(target, source);
282
+ expect(result).toEqual({ a: 2, b: 'new' });
283
+ });
284
+
285
+ it('preserves function references from source', () => {
286
+ const fn = () => 'hello';
287
+ const target = { a: 1 };
288
+ const source = { formatter: fn };
289
+ const result = deepMerge(target, source);
290
+ expect(result.formatter).toBe(fn);
291
+ });
292
+
293
+ it('does not mutate target or source', () => {
294
+ const target = { a: { x: 1 } };
295
+ const source = { a: { y: 2 } };
296
+ deepMerge(target, source);
297
+ expect(target).toEqual({ a: { x: 1 } });
298
+ expect(source).toEqual({ a: { y: 2 } });
299
+ });
300
+
301
+ it('merges ECharts-like option objects', () => {
302
+ const target = {
303
+ title: { text: 'Chart' },
304
+ tooltip: { trigger: 'axis' as const },
305
+ series: [{ type: 'line' as const, data: [1, 2, 3] }],
306
+ };
307
+ const source = {
308
+ title: { subtext: 'Sub' },
309
+ tooltip: { formatter: (p: { value: number }) => `${p.value}%` },
310
+ };
311
+ const result = deepMerge(target, source);
312
+ expect(result.title).toEqual({ text: 'Chart', subtext: 'Sub' });
313
+ expect(result.tooltip.trigger).toBe('axis');
314
+ expect(typeof result.tooltip.formatter).toBe('function');
315
+ expect(result.series).toEqual([{ type: 'line', data: [1, 2, 3] }]);
316
+ });
317
+ });
@@ -92,7 +92,8 @@ describe('Data Processing Utilities', () => {
92
92
 
93
93
  it('should limit execution to once per interval', () => {
94
94
  const mockFn = jest.fn();
95
- const throttledFn = throttle(mockFn, 100);
95
+ // 使用 leading=true, trailing=false 来匹配简单 throttle 行为
96
+ const throttledFn = throttle(mockFn, 100, { leading: true, trailing: false });
96
97
 
97
98
  throttledFn();
98
99
  expect(mockFn).toHaveBeenCalledTimes(1);
@@ -83,3 +83,16 @@ export function resizeAllCharts(): void {
83
83
  }
84
84
  });
85
85
  }
86
+
87
+ /**
88
+ * Dispose and remove stale chart instances (those whose DOM container is gone)
89
+ * Call periodically or from ErrorBoundary componentDidCatch
90
+ */
91
+ export function disposeStaleCharts(): void {
92
+ Object.keys(CHART_INSTANCES).forEach((id) => {
93
+ const instance = CHART_INSTANCES[id];
94
+ if (!instance || instance.isDisposed()) {
95
+ delete CHART_INSTANCES[id];
96
+ }
97
+ });
98
+ }