@agions/taroviz 1.11.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +245 -0
- package/README.md +104 -302
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/vendors.js +1 -0
- package/dist/cjs/vendors~echarts.js +1 -0
- package/dist/esm/index.js +1 -58151
- package/dist/esm/vendors.js +1 -0
- package/dist/esm/vendors~echarts.js +1 -0
- package/package.json +19 -25
- package/src/adapters/MiniAppAdapter.ts +136 -0
- package/src/adapters/__tests__/index.test.ts +1 -1
- package/src/adapters/h5/__tests__/index.test.ts +4 -2
- package/src/adapters/h5/index.ts +63 -64
- package/src/adapters/harmony/index.ts +23 -245
- package/src/adapters/index.ts +49 -45
- package/src/adapters/swan/index.ts +6 -69
- package/src/adapters/tt/index.ts +7 -70
- package/src/adapters/types.ts +25 -58
- package/src/adapters/weapp/index.ts +6 -69
- package/src/charts/__tests__/testUtils.tsx +87 -0
- package/src/charts/boxplot/__tests__/index.test.tsx +49 -103
- package/src/charts/boxplot/index.tsx +2 -1
- package/src/charts/boxplot/types.ts +17 -16
- package/src/charts/common/BaseChartWrapper.tsx +90 -82
- package/src/charts/common/__mocks__/BaseChartWrapper.tsx +17 -0
- package/src/charts/createChartComponent.tsx +36 -0
- package/src/charts/createOptionChartComponent.tsx +32 -0
- package/src/charts/funnel/__tests__/index.test.tsx +99 -0
- package/src/charts/funnel/index.tsx +60 -10
- package/src/charts/funnel/types.ts +6 -0
- package/src/charts/graph/__tests__/index.test.tsx +102 -33
- package/src/charts/graph/index.tsx +66 -9
- package/src/charts/graph/types.ts +6 -0
- package/src/charts/heatmap/__tests__/index.test.tsx +139 -0
- package/src/charts/heatmap/index.tsx +103 -10
- package/src/charts/heatmap/types.ts +6 -0
- package/src/charts/index.ts +74 -26
- package/src/charts/liquid/__tests__/index.test.tsx +52 -0
- package/src/charts/liquid/index.tsx +239 -182
- package/src/charts/liquid/types.ts +11 -11
- package/src/charts/parallel/__tests__/index.test.tsx +40 -67
- package/src/charts/parallel/index.tsx +2 -1
- package/src/charts/parallel/types.ts +19 -18
- package/src/charts/radar/__tests__/index.test.tsx +210 -0
- package/src/charts/radar/index.tsx +143 -10
- package/src/charts/radar/types.ts +13 -0
- package/src/charts/sankey/__tests__/index.test.tsx +124 -0
- package/src/charts/sankey/index.tsx +62 -10
- package/src/charts/sankey/types.ts +6 -0
- package/src/charts/tree/__tests__/index.test.tsx +71 -0
- package/src/charts/tree/index.tsx +5 -2
- package/src/charts/tree/types.ts +9 -9
- package/src/charts/types.ts +208 -106
- package/src/charts/utils.ts +9 -7
- package/src/charts/wordcloud/__tests__/index.test.tsx +98 -31
- package/src/charts/wordcloud/index.tsx +75 -9
- package/src/charts/wordcloud/types.ts +6 -0
- package/src/components/DataFilter/index.tsx +32 -10
- package/src/core/animation/types.ts +6 -6
- package/src/core/components/Annotation.tsx +6 -7
- package/src/core/components/BaseChart.tsx +110 -168
- package/src/core/components/ErrorBoundary.tsx +17 -4
- package/src/core/components/LazyChart.tsx +54 -55
- package/src/core/components/hooks/index.ts +6 -2
- package/src/core/components/hooks/useChartInit.ts +6 -3
- package/src/core/components/hooks/usePerformance.ts +8 -2
- package/src/core/components/hooks/useVirtualScroll.ts +2 -1
- package/src/core/index.ts +1 -1
- package/src/core/themes/ThemeManager.ts +1 -1
- package/src/core/types/common.ts +2 -1
- package/src/core/types/index.ts +0 -12
- package/src/core/types/platform.ts +3 -5
- package/src/core/utils/__tests__/deepClone.test.ts +317 -0
- package/src/core/utils/__tests__/index.test.ts +2 -1
- package/src/core/utils/chartInstances.ts +13 -0
- package/src/core/utils/common.ts +20 -29
- package/src/core/utils/deepClone.ts +114 -0
- package/src/core/utils/download.ts +128 -0
- package/src/core/utils/drillDown.ts +34 -353
- package/src/core/utils/drillDownHelpers.ts +426 -0
- package/src/core/utils/events.ts +12 -0
- package/src/core/utils/export/ExportUtils.ts +36 -67
- package/src/core/utils/format.ts +44 -0
- package/src/core/utils/index.ts +21 -154
- package/src/core/utils/merge.ts +25 -0
- package/src/core/utils/performance/PerformanceAnalyzer.ts +38 -21
- package/src/core/utils/performance/hooks.ts +7 -0
- package/src/core/utils/performance/index.ts +2 -0
- package/src/{hooks → core/utils/performance}/useAnimation.ts +45 -41
- package/src/core/utils/performance/useDataZoom.ts +324 -0
- package/src/{hooks → core/utils/performance}/usePerformance.ts +49 -41
- package/src/core/utils/performance/usePerformanceHooks.ts +278 -0
- package/src/core/utils/performanceUtils.ts +310 -0
- package/src/core/utils/runtime.ts +190 -0
- package/src/core/utils/setOptionUtils.ts +59 -0
- package/src/core/version.ts +14 -0
- package/src/editor/EnhancedThemeEditor.tsx +362 -540
- package/src/editor/ThemeEditor.tsx +55 -321
- package/src/editor/components/ThemeBasicSettings.tsx +113 -0
- package/src/editor/components/ThemeColorEditor.tsx +105 -0
- package/src/editor/components/ThemeSelector.tsx +70 -0
- package/src/editor/hooks/useThemeEditorState.ts +201 -0
- package/src/editor/index.ts +10 -2
- package/src/hooks/__tests__/index.test.tsx +3 -1
- package/src/hooks/chartConnectHelpers.ts +341 -0
- package/src/hooks/index.ts +55 -660
- package/src/hooks/types.ts +189 -0
- package/src/hooks/useChartAutoResize.ts +73 -0
- package/src/hooks/useChartConnect.ts +92 -238
- package/src/hooks/useChartDownload.ts +25 -27
- package/src/hooks/useChartHistory.ts +34 -49
- package/src/hooks/useChartInit.ts +59 -0
- package/src/hooks/useChartOptions.ts +259 -0
- package/src/hooks/useChartPerformance.ts +109 -0
- package/src/hooks/useChartSelection.ts +52 -49
- package/src/hooks/useChartTheme.ts +51 -0
- package/src/hooks/useDataTransform.ts +19 -4
- package/src/hooks/utils/chartDownloadUtils.ts +40 -53
- package/src/hooks/utils/dataTransformUtils.ts +22 -0
- package/src/index.ts +48 -34
- package/src/main.tsx +4 -9
- package/src/react-dom.d.ts +3 -3
- package/src/themes/index.ts +30 -855
- package/src/themes/palettes/blue-green.ts +13 -0
- package/src/themes/palettes/chalk.ts +13 -0
- package/src/themes/palettes/cyber.ts +44 -0
- package/src/themes/palettes/dark.ts +52 -0
- package/src/themes/palettes/default.ts +52 -0
- package/src/themes/palettes/elegant.ts +34 -0
- package/src/themes/palettes/forest.ts +13 -0
- package/src/themes/palettes/glass.ts +49 -0
- package/src/themes/palettes/golden.ts +13 -0
- package/src/themes/palettes/neon.ts +43 -0
- package/src/themes/palettes/ocean.ts +39 -0
- package/src/themes/palettes/pastel.ts +37 -0
- package/src/themes/palettes/purple-passion.ts +13 -0
- package/src/themes/palettes/retro.ts +33 -0
- package/src/themes/palettes/sunset.ts +40 -0
- package/src/themes/palettes/walden.ts +13 -0
- package/src/themes/registry.ts +184 -0
- package/src/themes/types.ts +213 -0
- package/src/charts/bar/__tests__/index.test.tsx +0 -113
- package/src/charts/bar/index.tsx +0 -14
- package/src/charts/candlestick/__tests__/index.test.tsx +0 -40
- package/src/charts/candlestick/index.tsx +0 -13
- package/src/charts/gauge/index.tsx +0 -14
- package/src/charts/line/__tests__/index.test.tsx +0 -107
- package/src/charts/line/index.tsx +0 -15
- package/src/charts/pie/__tests__/index.test.tsx +0 -112
- package/src/charts/pie/index.tsx +0 -14
- package/src/charts/scatter/index.tsx +0 -14
- package/src/charts/sunburst/index.tsx +0 -18
- package/src/charts/treemap/index.tsx +0 -18
- package/src/core/utils/codeGenerator/CodeGenerator.ts +0 -669
- package/src/core/utils/codeGenerator/index.ts +0 -13
- package/src/core/utils/codeGenerator/types.ts +0 -198
- package/src/core/utils/configGenerator/ConfigGenerator.ts +0 -583
- package/src/core/utils/configGenerator/index.ts +0 -13
- package/src/core/utils/configGenerator/types.ts +0 -445
- package/src/core/utils/debug/DebugPanel.tsx +0 -637
- package/src/core/utils/debug/debugger.ts +0 -322
- package/src/core/utils/debug/index.ts +0 -21
- package/src/core/utils/debug/types.ts +0 -142
- package/src/hooks/useDataZoom.ts +0 -323
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* useChartDownload - 图表下载 Hook
|
|
3
3
|
* 支持下载图表为图片(PNG/JPEG/SVG/PDF)或原始数据(CSV/JSON)
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import type { ChartInstance } from './
|
|
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.
|
|
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.
|
|
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.
|
|
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]
|
|
220
|
+
console.warn('[useChartDownload] No data URL available for format:', fmt);
|
|
223
221
|
}
|
|
224
222
|
} catch (e) {
|
|
225
|
-
console.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
339
|
+
const blob = csvToBlob(csv);
|
|
346
340
|
downloadBlob(blob, `${name}.csv`);
|
|
347
341
|
executeAfterExport(blob);
|
|
348
342
|
}
|
|
349
343
|
} catch (e) {
|
|
350
|
-
console.
|
|
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
|
|
381
|
+
const blob = jsonToBlob(json);
|
|
390
382
|
downloadBlob(blob, `${name}.json`);
|
|
391
383
|
executeAfterExport(blob);
|
|
392
384
|
}
|
|
393
385
|
} catch (e) {
|
|
394
|
-
console.
|
|
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({
|
|
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({
|
|
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 './
|
|
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 (!
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
168
|
+
undoHolderRef.current.fn?.();
|
|
185
169
|
} else if (e.key === 'y' || (e.key === 'z' && e.shiftKey)) {
|
|
186
170
|
e.preventDefault();
|
|
187
|
-
|
|
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]);
|
|
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(
|
|
245
|
-
|
|
246
|
-
|
|
228
|
+
const push = useCallback(
|
|
229
|
+
(option: EChartsOption) => {
|
|
230
|
+
const stack = historyStack.current;
|
|
231
|
+
const idx = currentIndex;
|
|
247
232
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
}
|