@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
@@ -2,8 +2,8 @@
2
2
  * useChartDownload - 图表下载 Hook
3
3
  * 支持下载图表为图片(PNG/JPEG/SVG/PDF)或原始数据(CSV/JSON)
4
4
  */
5
- import { useRef, useCallback } from 'react';
6
- import type { ChartInstance } from './index';
5
+ import { useCallback, useRef } from 'react';
6
+ import type { ChartInstance } from './types';
7
7
  import {
8
8
  generateFilename,
9
9
  downloadBlob,
@@ -114,7 +114,7 @@ export function useChartDownload(
114
114
  try {
115
115
  beforeExport(chart);
116
116
  } catch (e) {
117
- console.warn('[useChartDownload] beforeExport error:', e);
117
+ console.error('[useChartDownload] beforeExport error:', e);
118
118
  }
119
119
  }
120
120
  }, [beforeExport]);
@@ -128,7 +128,7 @@ export function useChartDownload(
128
128
  try {
129
129
  afterExport(result);
130
130
  } catch (e) {
131
- console.warn('[useChartDownload] afterExport error:', e);
131
+ console.error('[useChartDownload] afterExport error:', e);
132
132
  }
133
133
  }
134
134
  },
@@ -164,10 +164,9 @@ export function useChartDownload(
164
164
  });
165
165
  }
166
166
 
167
- console.warn('[useChartDownload] getDataURL not supported');
168
167
  return undefined;
169
168
  } catch (e) {
170
- console.warn('[useChartDownload] Failed to get image data URL:', e);
169
+ console.error('[useChartDownload] getImageDataUrl error:', e);
171
170
  return undefined;
172
171
  }
173
172
  },
@@ -183,7 +182,6 @@ export function useChartDownload(
183
182
 
184
183
  const chart = chartRef.current;
185
184
  if (!chart) {
186
- console.warn('[useChartDownload] No chart instance available');
187
185
  return;
188
186
  }
189
187
 
@@ -219,10 +217,10 @@ export function useChartDownload(
219
217
  downloadDataUrl(dataUrl, `${name}.${fmt}`);
220
218
  executeAfterExport(dataUrl);
221
219
  } else {
222
- console.warn('[useChartDownload] Failed to generate image data URL');
220
+ console.warn('[useChartDownload] No data URL available for format:', fmt);
223
221
  }
224
222
  } catch (e) {
225
- console.warn('[useChartDownload] Failed to download image:', e);
223
+ console.error('[useChartDownload] downloadImage error:', e);
226
224
  }
227
225
  },
228
226
  [format, pixelRatio, backgroundColor, filename, executeBeforeExport, executeAfterExport]
@@ -238,7 +236,6 @@ export function useChartDownload(
238
236
 
239
237
  const chart = chartRef.current;
240
238
  if (!chart) {
241
- console.warn('[useChartDownload] No chart instance available');
242
239
  return;
243
240
  }
244
241
 
@@ -261,7 +258,6 @@ export function useChartDownload(
261
258
  });
262
259
 
263
260
  if (!dataUrl) {
264
- console.warn('[useChartDownload] Failed to get image data for PDF');
265
261
  return;
266
262
  }
267
263
 
@@ -273,7 +269,7 @@ export function useChartDownload(
273
269
  executeAfterExport(pdfDataUrl);
274
270
  }
275
271
  } catch (e) {
276
- console.warn('[useChartDownload] Failed to download PDF:', e);
272
+ console.error('[useChartDownload] downloadPDF error:', e);
277
273
  }
278
274
  },
279
275
  [filename, pixelRatio, backgroundColor, executeBeforeExport, executeAfterExport]
@@ -289,7 +285,7 @@ export function useChartDownload(
289
285
  try {
290
286
  return chart.getOption?.() || null;
291
287
  } catch (e) {
292
- console.warn('[useChartDownload] Failed to get chart data:', e);
288
+ console.error('[useChartDownload] getChartData error:', e);
293
289
  return null;
294
290
  }
295
291
  }, []);
@@ -304,7 +300,7 @@ export function useChartDownload(
304
300
  try {
305
301
  return chart.getSvgData?.();
306
302
  } catch (e) {
307
- console.warn('[useChartDownload] Failed to get SVG data:', e);
303
+ console.error('[useChartDownload] getSvgData error:', e);
308
304
  return undefined;
309
305
  }
310
306
  }, []);
@@ -318,7 +314,6 @@ export function useChartDownload(
318
314
 
319
315
  const chart = chartRef.current;
320
316
  if (!chart) {
321
- console.warn('[useChartDownload] No chart instance available');
322
317
  return;
323
318
  }
324
319
 
@@ -328,7 +323,6 @@ export function useChartDownload(
328
323
  try {
329
324
  const option = chart.getOption?.();
330
325
  if (!option) {
331
- console.warn('[useChartDownload] No chart data available');
332
326
  return;
333
327
  }
334
328
 
@@ -342,12 +336,12 @@ export function useChartDownload(
342
336
  const csv = convertToCSV(data, { includeLabels });
343
337
 
344
338
  if (csv) {
345
- const blob = csvToBlob(csv, `${name}.csv`);
339
+ const blob = csvToBlob(csv);
346
340
  downloadBlob(blob, `${name}.csv`);
347
341
  executeAfterExport(blob);
348
342
  }
349
343
  } catch (e) {
350
- console.warn('[useChartDownload] Failed to download CSV:', e);
344
+ console.error('[useChartDownload] downloadCSV error:', e);
351
345
  }
352
346
  },
353
347
  [filename, executeBeforeExport, executeAfterExport]
@@ -362,7 +356,6 @@ export function useChartDownload(
362
356
 
363
357
  const chart = chartRef.current;
364
358
  if (!chart) {
365
- console.warn('[useChartDownload] No chart instance available');
366
359
  return;
367
360
  }
368
361
 
@@ -372,7 +365,6 @@ export function useChartDownload(
372
365
  try {
373
366
  const option = chart.getOption?.();
374
367
  if (!option) {
375
- console.warn('[useChartDownload] No chart data available');
376
368
  return;
377
369
  }
378
370
 
@@ -386,12 +378,12 @@ export function useChartDownload(
386
378
  const json = convertToJSON(data);
387
379
 
388
380
  if (json) {
389
- const blob = jsonToBlob(json, `${name}.json`);
381
+ const blob = jsonToBlob(json);
390
382
  downloadBlob(blob, `${name}.json`);
391
383
  executeAfterExport(blob);
392
384
  }
393
385
  } catch (e) {
394
- console.warn('[useChartDownload] Failed to download JSON:', e);
386
+ console.error('[useChartDownload] downloadJSON error:', e);
395
387
  }
396
388
  },
397
389
  [filename, executeBeforeExport, executeAfterExport]
@@ -414,16 +406,24 @@ export function useChartDownload(
414
406
  switch (fmt) {
415
407
  case 'png':
416
408
  case 'jpeg':
417
- await downloadImage({ format: fmt, filename: exportFilename, pixelRatio: exportRatio, backgroundColor: exportBg });
409
+ await downloadImage({
410
+ format: fmt,
411
+ filename: exportFilename,
412
+ pixelRatio: exportRatio,
413
+ backgroundColor: exportBg,
414
+ });
418
415
  break;
419
416
  case 'svg':
420
417
  await downloadImage({ format: 'svg', filename: exportFilename });
421
418
  break;
422
419
  case 'pdf':
423
- await downloadPDF({ filename: exportFilename, pixelRatio: exportRatio, backgroundColor: exportBg });
420
+ await downloadPDF({
421
+ filename: exportFilename,
422
+ pixelRatio: exportRatio,
423
+ backgroundColor: exportBg,
424
+ });
424
425
  break;
425
426
  default:
426
- console.warn(`[useChartDownload] Unsupported format: ${fmt}`);
427
427
  }
428
428
  },
429
429
  [format, downloadImage, downloadPDF]
@@ -444,5 +444,3 @@ export function useChartDownload(
444
444
  // ============================================================================
445
445
  // 导出
446
446
  // ============================================================================
447
-
448
- export default useChartDownload;
@@ -9,7 +9,7 @@
9
9
  * - 暴露 canUndo / canRedo 状态
10
10
  */
11
11
  import { useEffect, useRef, useCallback, useState } from 'react';
12
- import type { ChartInstance } from './index';
12
+ import type { ChartInstance } from './types';
13
13
  import type { EChartsOption } from 'echarts';
14
14
 
15
15
  // ============================================================================
@@ -53,11 +53,7 @@ export interface UseChartHistoryReturn {
53
53
  // ============================================================================
54
54
 
55
55
  /** 深度省略指定键后比较两个配置是否相等 */
56
- function omitAndCompare(
57
- a: unknown,
58
- b: unknown,
59
- ignoreKeys: Set<string>
60
- ): boolean {
56
+ function omitAndCompare(a: unknown, b: unknown, ignoreKeys: Set<string>): boolean {
61
57
  if (a === b) return true;
62
58
  if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
63
59
  return a === b;
@@ -71,7 +67,7 @@ function omitAndCompare(
71
67
  if (aKeys.length !== bKeys.length) return false;
72
68
 
73
69
  for (const key of aKeys) {
74
- if (!bObj.hasOwnProperty(key)) return false;
70
+ if (!Object.prototype.hasOwnProperty.call(bObj, key)) return false;
75
71
  if (!omitAndCompare(aObj[key], bObj[key], ignoreKeys)) return false;
76
72
  }
77
73
 
@@ -94,12 +90,7 @@ export function useChartHistory(
94
90
  ): UseChartHistoryReturn {
95
91
  const {
96
92
  maxHistorySize = 50,
97
- ignoreKeys = [
98
- 'animation',
99
- 'animationDuration',
100
- 'animationEasing',
101
- 'animationFrame',
102
- ],
93
+ ignoreKeys = ['animation', 'animationDuration', 'animationEasing', 'animationFrame'],
103
94
  enableKeyboard = true,
104
95
  clearOnUnmount = false,
105
96
  } = options;
@@ -120,58 +111,51 @@ export function useChartHistory(
120
111
  // 是否正在执行 undo/redo(避免 push 时重复记录)
121
112
  const isApplyingRef = useRef(false);
122
113
 
123
- // 拦截 chart.setOption,记录历史
114
+ // Undo/Redo function holders — populated after function declarations via a deferred effect
115
+ const undoHolderRef = useRef<{ fn: (() => void) | null }>({ fn: null });
116
+ const redoHolderRef = useRef<{ fn: (() => void) | null }>({ fn: null });
117
+
118
+ // Deferred effect: bind undo/redo to refs once they are in scope
119
+ useEffect(() => {
120
+ undoHolderRef.current.fn = () => undo();
121
+ redoHolderRef.current.fn = () => redo();
122
+ });
123
+
124
124
  useEffect(() => {
125
125
  const chart = chartRef.current;
126
126
  if (!chart) return;
127
127
 
128
128
  const originalSetOption = chart.setOption.bind(chart);
129
129
 
130
- chart.setOption = (
131
- option: EChartsOption,
132
- notMerge?: boolean,
133
- lazyUpdate?: boolean
134
- ) => {
135
- // 如果正在执行 undo/redo,跳过历史记录
130
+ chart.setOption = (option: EChartsOption, notMerge?: boolean, lazyUpdate?: boolean) => {
136
131
  if (isApplyingRef.current) {
137
132
  return originalSetOption(option, notMerge, lazyUpdate);
138
133
  }
139
134
 
140
135
  const stack = historyStack.current;
141
136
  const idx = currentIndex;
142
-
143
- // 如果当前索引不在栈顶,丢弃redo历史(类似 Git 行为)
144
137
  const newStack = idx < stack.length - 1 ? stack.slice(0, idx + 1) : [...stack];
145
138
 
146
- // 检查是否与上一次配置相同(忽略动画字段)
147
139
  const lastOption = newStack[newStack.length - 1];
148
140
  if (lastOption && omitAndCompare(lastOption, option, ignoreKeySet.current)) {
149
- // 配置没变,直接应用
150
141
  return originalSetOption(option, notMerge, lazyUpdate);
151
142
  }
152
143
 
153
- // 入栈
154
144
  newStack.push(option);
155
-
156
- // 裁剪超出 maxHistorySize
157
145
  if (newStack.length > maxHistorySize) {
158
146
  newStack.shift();
159
- } else {
160
- // 更新索引
161
- setCurrentIndex(newStack.length - 1);
162
147
  }
163
-
164
148
  historyStack.current = newStack;
149
+ setCurrentIndex(newStack.length - 1);
165
150
  return originalSetOption(option, notMerge, lazyUpdate);
166
151
  };
167
152
 
168
153
  return () => {
169
- // 恢复原始 setOption
170
154
  chart.setOption = originalSetOption;
171
155
  };
172
156
  }, [chartInstance, currentIndex, maxHistorySize]);
173
157
 
174
- // 键盘快捷键:Ctrl+Z 撤销,Ctrl+Y / Ctrl+Shift+Z 重做
158
+ // Keyboard shortcuts
175
159
  useEffect(() => {
176
160
  if (!enableKeyboard) return;
177
161
 
@@ -181,16 +165,16 @@ export function useChartHistory(
181
165
 
182
166
  if (e.key === 'z' && !e.shiftKey) {
183
167
  e.preventDefault();
184
- undo();
168
+ undoHolderRef.current.fn?.();
185
169
  } else if (e.key === 'y' || (e.key === 'z' && e.shiftKey)) {
186
170
  e.preventDefault();
187
- redo();
171
+ redoHolderRef.current.fn?.();
188
172
  }
189
173
  };
190
174
 
191
175
  window.addEventListener('keydown', handleKeyDown);
192
176
  return () => window.removeEventListener('keydown', handleKeyDown);
193
- }, [enableKeyboard]); // eslint-disable-line react-hooks/exhaustive-deps
177
+ }, [enableKeyboard]);
194
178
 
195
179
  // 组件卸载时清空
196
180
  useEffect(() => {
@@ -213,7 +197,7 @@ export function useChartHistory(
213
197
  chart.setOption(historyStack.current[idx], true, true);
214
198
  isApplyingRef.current = false;
215
199
  setCurrentIndex(idx);
216
- }, [currentIndex]);
200
+ }, [chartRef, currentIndex]);
217
201
 
218
202
  const redo = useCallback(() => {
219
203
  const chart = chartRef.current;
@@ -224,7 +208,7 @@ export function useChartHistory(
224
208
  chart.setOption(historyStack.current[idx], true, true);
225
209
  isApplyingRef.current = false;
226
210
  setCurrentIndex(idx);
227
- }, [currentIndex]);
211
+ }, [chartRef, currentIndex]);
228
212
 
229
213
  const goTo = useCallback(
230
214
  (index: number) => {
@@ -241,16 +225,19 @@ export function useChartHistory(
241
225
  [currentIndex]
242
226
  );
243
227
 
244
- const push = useCallback((option: EChartsOption) => {
245
- const stack = historyStack.current;
246
- const idx = currentIndex;
228
+ const push = useCallback(
229
+ (option: EChartsOption) => {
230
+ const stack = historyStack.current;
231
+ const idx = currentIndex;
247
232
 
248
- const newStack = idx < stack.length - 1 ? stack.slice(0, idx + 1) : [...stack];
249
- newStack.push(option);
250
- if (newStack.length > maxHistorySize) newStack.shift();
251
- historyStack.current = newStack;
252
- setCurrentIndex(newStack.length - 1);
253
- }, [currentIndex, maxHistorySize]);
233
+ const newStack = idx < stack.length - 1 ? stack.slice(0, idx + 1) : [...stack];
234
+ newStack.push(option);
235
+ if (newStack.length > maxHistorySize) newStack.shift();
236
+ historyStack.current = newStack;
237
+ setCurrentIndex(newStack.length - 1);
238
+ },
239
+ [currentIndex, maxHistorySize]
240
+ );
254
241
 
255
242
  const clear = useCallback(() => {
256
243
  historyStack.current = [];
@@ -269,5 +256,3 @@ export function useChartHistory(
269
256
  clear,
270
257
  };
271
258
  }
272
-
273
- export default useChartHistory;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * useChartInit - 图表初始化 Hook
3
+ * 负责初始化 ECharts 图表实例
4
+ */
5
+ import { useState, useEffect, useRef } from 'react';
6
+ import { getAdapter } from '../adapters';
7
+ import type { ChartInstance, ChartConfig } from './types';
8
+
9
+ /**
10
+ * 使用图表 Hook
11
+ * @param chartRef 图表容器的引用
12
+ * @param config 图表配置
13
+ * @returns [图表实例, 设置实例函数, 是否已初始化]
14
+ */
15
+ export function useChart(
16
+ chartRef: React.RefObject<HTMLElement>,
17
+ config?: ChartConfig
18
+ ): [ChartInstance | null, React.Dispatch<React.SetStateAction<ChartInstance | null>>, boolean] {
19
+ const [instance, setInstance] = useState<ChartInstance | null>(null);
20
+ const [initialized, setInitialized] = useState(false);
21
+ const configRef = useRef(config);
22
+ configRef.current = config;
23
+
24
+ useEffect(() => {
25
+ if (!chartRef.current || instance) {
26
+ return;
27
+ }
28
+
29
+ const initAdapter = async () => {
30
+ try {
31
+ const adapter = await getAdapter(configRef.current || {});
32
+ const chartInstance = adapter as unknown as ChartInstance;
33
+ setInstance(chartInstance);
34
+ setInitialized(true);
35
+ } catch (error) {
36
+ console.error('Failed to initialize chart:', error);
37
+ }
38
+ };
39
+
40
+ initAdapter();
41
+
42
+ return () => {
43
+ if (instance) {
44
+ try {
45
+ const inst = instance as ChartInstance;
46
+ if (!inst.isDisposed?.()) {
47
+ inst.dispose();
48
+ }
49
+ } catch (e) {
50
+ console.warn('Failed to dispose chart instance:', e);
51
+ }
52
+ setInstance(null);
53
+ setInitialized(false);
54
+ }
55
+ };
56
+ }, [chartRef]);
57
+
58
+ return [instance, setInstance, initialized];
59
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * useChartOptions - 图表选项与工具相关 Hooks
3
+ * 提供图表选项设置、事件绑定、加载状态、响应式配置、全屏、导出、工具等
4
+ */
5
+ import { useState, useEffect, useMemo, useCallback } from 'react';
6
+ import type { EChartsOption } from 'echarts';
7
+ import type { ChartInstance, EventHandler, LoadingOptions, Breakpoint } from './types';
8
+
9
+ /**
10
+ * 设置图表选项 Hook
11
+ * @param instance 图表实例
12
+ * @param option 图表选项
13
+ * @param options 配置选项
14
+ */
15
+ export function useOption(
16
+ instance: ChartInstance | null,
17
+ option: EChartsOption | null,
18
+ options?: {
19
+ /** 是否不合并 */
20
+ notMerge?: boolean;
21
+ /** 是否延迟更新 */
22
+ lazyUpdate?: boolean;
23
+ /** 是否在数据变化时替换 */
24
+ replaceMerge?: string[];
25
+ /** 依赖数组 */
26
+ deps?: unknown[];
27
+ }
28
+ ) {
29
+ const { notMerge = false, lazyUpdate = false, replaceMerge, deps = [] } = options || {};
30
+
31
+ useEffect(() => {
32
+ if (instance && option) {
33
+ try {
34
+ instance.setOption(option, notMerge, lazyUpdate);
35
+ } catch (e) {
36
+ console.warn('Failed to set chart option:', e);
37
+ }
38
+ }
39
+ }, [instance, option, notMerge, lazyUpdate, replaceMerge, ...deps]);
40
+ }
41
+
42
+ /**
43
+ * 图表事件 Hook
44
+ * @param instance 图表实例
45
+ * @param events 事件对象
46
+ */
47
+ export function useEvents(instance: ChartInstance | null, events: Record<string, EventHandler>) {
48
+ useEffect(() => {
49
+ if (!instance || !events) {
50
+ return;
51
+ }
52
+
53
+ const eventEntries = Object.entries(events);
54
+
55
+ // 绑定事件
56
+ eventEntries.forEach(([eventName, handler]) => {
57
+ try {
58
+ instance.on(eventName, handler);
59
+ } catch (e) {
60
+ console.warn(`Failed to bind event ${eventName}:`, e);
61
+ }
62
+ });
63
+
64
+ // 清理事件
65
+ return () => {
66
+ eventEntries.forEach(([eventName, handler]) => {
67
+ try {
68
+ instance.off(eventName, handler);
69
+ } catch (e) {
70
+ console.warn(`Failed to unbind event ${eventName}:`, e);
71
+ }
72
+ });
73
+ };
74
+ }, [instance, events]);
75
+ }
76
+
77
+ /**
78
+ * 图表加载状态 Hook
79
+ * @param instance 图表实例
80
+ * @param loading 是否加载中
81
+ * @param options 加载选项
82
+ */
83
+ export function useLoading(
84
+ instance: ChartInstance | null,
85
+ loading: boolean,
86
+ options?: LoadingOptions
87
+ ) {
88
+ useEffect(() => {
89
+ if (!instance) {
90
+ return;
91
+ }
92
+
93
+ try {
94
+ if (loading) {
95
+ instance.showLoading(options);
96
+ } else {
97
+ instance.hideLoading();
98
+ }
99
+ } catch (e) {
100
+ console.warn('Failed to set chart loading state:', e);
101
+ }
102
+ }, [instance, loading, options]);
103
+ }
104
+
105
+ /**
106
+ * 使用响应式图表配置
107
+ * @param config 响应式配置
108
+ * @returns 当前断点和配置
109
+ */
110
+ export function useResponsive(config?: {
111
+ /** 断点配置 */
112
+ breakpoints?: Record<Breakpoint, number>;
113
+ /** 默认断点 */
114
+ defaultBreakpoint?: Breakpoint;
115
+ }) {
116
+ const { breakpoints = { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200 }, defaultBreakpoint = 'md' } =
117
+ config || {};
118
+
119
+ const [breakpoint, setBreakpoint] = useState<Breakpoint>(defaultBreakpoint);
120
+ const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
121
+
122
+ useEffect(() => {
123
+ const handleResize = () => {
124
+ const width = window.innerWidth;
125
+ setWindowSize({ width, height: window.innerHeight });
126
+
127
+ // 确定当前断点
128
+ let current: Breakpoint = 'xs';
129
+ if (width >= breakpoints.xl) current = 'xl';
130
+ else if (width >= breakpoints.lg) current = 'lg';
131
+ else if (width >= breakpoints.md) current = 'md';
132
+ else if (width >= breakpoints.sm) current = 'sm';
133
+
134
+ setBreakpoint(current);
135
+ };
136
+
137
+ handleResize();
138
+ window.addEventListener('resize', handleResize);
139
+
140
+ return () => window.removeEventListener('resize', handleResize);
141
+ }, [breakpoints]);
142
+
143
+ return { breakpoint, windowSize };
144
+ }
145
+
146
+ /**
147
+ * 使用图表全屏
148
+ * @param chartRef 图表容器引用
149
+ * @returns [是否全屏, 进入/退出全屏函数]
150
+ */
151
+ export function useFullscreen(chartRef: React.RefObject<HTMLElement>) {
152
+ const [isFullscreen, setIsFullscreen] = useState(false);
153
+
154
+ const toggle = useCallback(() => {
155
+ if (!chartRef.current) return;
156
+
157
+ if (!isFullscreen) {
158
+ if (chartRef.current.requestFullscreen) {
159
+ chartRef.current.requestFullscreen();
160
+ }
161
+ } else {
162
+ if (document.exitFullscreen) {
163
+ document.exitFullscreen();
164
+ }
165
+ }
166
+ }, [chartRef, isFullscreen]);
167
+
168
+ useEffect(() => {
169
+ const handleChange = () => {
170
+ setIsFullscreen(!!document.fullscreenElement);
171
+ };
172
+
173
+ document.addEventListener('fullscreenchange', handleChange);
174
+ return () => document.removeEventListener('fullscreenchange', handleChange);
175
+ }, []);
176
+
177
+ return { isFullscreen, toggle };
178
+ }
179
+
180
+ /**
181
+ * 使用图表导出
182
+ * @param instance 图表实例
183
+ * @returns 导出函数
184
+ */
185
+ export function useExport(instance: ChartInstance | null) {
186
+ const inst = instance as ChartInstance;
187
+ const exportImage = useCallback(
188
+ (options?: { type?: 'png' | 'jpeg'; pixelRatio?: number; backgroundColor?: string }) => {
189
+ if (!inst) return null;
190
+ const { type = 'png', pixelRatio = 2, backgroundColor } = options || {};
191
+ return inst.getDataURL?.({ type, pixelRatio, backgroundColor });
192
+ },
193
+ [inst]
194
+ );
195
+
196
+ const exportSVG = useCallback(() => {
197
+ if (!inst) return null;
198
+ return inst.getSvgData?.();
199
+ }, [inst]);
200
+
201
+ const exportCSV = useCallback(
202
+ (options?: { seriesIndex?: number; dimension?: number }) => {
203
+ if (!inst) return null;
204
+ return inst.getCompressedDataURL?.(options);
205
+ },
206
+ [inst]
207
+ );
208
+
209
+ return { exportImage, exportSVG, exportCSV };
210
+ }
211
+
212
+ /**
213
+ * 使用图表工具
214
+ * @param instance 图表实例
215
+ * @returns 工具函数
216
+ */
217
+ export function useChartTools(instance: ChartInstance | null) {
218
+ const inst = instance as ChartInstance;
219
+ const getInstance = useCallback(() => instance, [instance]);
220
+
221
+ const clear = useCallback(() => {
222
+ inst?.clear?.();
223
+ }, [inst]);
224
+
225
+ const repaint = useCallback(() => {
226
+ inst?.resize?.();
227
+ }, [inst]);
228
+
229
+ const dispatchAction = useCallback(
230
+ (action: { type: string; [key: string]: unknown }) => {
231
+ inst?.dispatchAction?.(action);
232
+ },
233
+ [inst]
234
+ );
235
+
236
+ const showTip = useCallback(
237
+ (seriesIndex?: number, dataIndex?: number) => {
238
+ inst?.dispatchAction?.({ type: 'showTip', seriesIndex, dataIndex });
239
+ },
240
+ [inst]
241
+ );
242
+
243
+ const hideTip = useCallback(() => {
244
+ inst?.dispatchAction?.({ type: 'hideTip' });
245
+ }, [inst]);
246
+
247
+ const zoom = useCallback(
248
+ (start?: number, end?: number) => {
249
+ inst?.dispatchAction?.({
250
+ type: 'dataZoom',
251
+ start: start ?? 0,
252
+ end: end ?? 100,
253
+ });
254
+ },
255
+ [inst]
256
+ );
257
+
258
+ return { getInstance, clear, repaint, dispatchAction, showTip, hideTip, zoom };
259
+ }