@agions/taroviz 1.3.1 → 1.6.0
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/README.md +4 -4
- package/dist/cjs/index.js +1 -1
- package/dist/esm/index.js +3814 -2724
- package/package.json +1 -1
- package/src/__tests__/integration.test.tsx +12 -10
- package/src/adapters/BaseAdapter.ts +116 -0
- package/src/adapters/__tests__/index.test.ts +10 -10
- package/src/adapters/index.ts +63 -74
- package/src/adapters/swan/index.ts +26 -223
- package/src/adapters/tt/index.ts +28 -225
- package/src/adapters/types.ts +36 -0
- package/src/adapters/weapp/index.ts +29 -189
- package/src/charts/bar/index.tsx +5 -9
- package/src/charts/boxplot/__tests__/index.test.tsx +130 -0
- package/src/charts/boxplot/index.tsx +18 -0
- package/src/charts/boxplot/types.ts +46 -0
- package/src/charts/candlestick/__tests__/index.test.tsx +37 -0
- package/src/charts/candlestick/index.tsx +13 -0
- package/src/charts/common/BaseChartWrapper.tsx +47 -38
- package/src/charts/funnel/index.tsx +5 -9
- package/src/charts/gauge/index.tsx +5 -9
- package/src/charts/graph/__tests__/index.test.tsx +41 -0
- package/src/charts/graph/index.tsx +13 -0
- package/src/charts/heatmap/index.tsx +5 -9
- package/src/charts/index.ts +10 -1
- package/src/charts/line/index.tsx +4 -7
- package/src/charts/parallel/__tests__/index.test.tsx +164 -0
- package/src/charts/parallel/index.tsx +18 -0
- package/src/charts/parallel/types.ts +73 -0
- package/src/charts/pie/index.tsx +5 -10
- package/src/charts/radar/index.tsx +5 -9
- package/src/charts/scatter/index.tsx +5 -9
- package/src/charts/types.ts +48 -4
- package/src/charts/wordcloud/__tests__/index.test.tsx +36 -0
- package/src/charts/wordcloud/index.tsx +13 -0
- package/src/core/animation/AnimationManager.ts +15 -0
- package/src/core/components/Annotation.tsx +26 -21
- package/src/core/components/BaseChart.tsx +280 -1105
- package/src/core/components/ErrorBoundary.tsx +4 -1
- package/src/core/components/LazyChart.tsx +42 -55
- package/src/core/components/hooks/index.ts +20 -0
- package/src/core/components/hooks/useChartEvents.ts +143 -0
- package/src/core/components/hooks/useChartInit.ts +80 -0
- package/src/core/components/hooks/usePerformance.ts +186 -0
- package/src/core/components/hooks/useVirtualScroll.ts +156 -0
- package/src/core/echarts.ts +1 -1
- package/src/core/themes/ThemeManager.ts +31 -15
- package/src/core/types/index.ts +2 -2
- package/src/core/utils/chartInstances.ts +10 -3
- package/src/core/utils/chartUtils.ts +46 -0
- package/src/core/utils/common.ts +14 -1
- package/src/core/utils/export/ExportUtils.ts +13 -22
- package/src/core/utils/performance/PerformanceAnalyzer.ts +32 -5
- package/src/core/utils/uuid.ts +1 -1
- package/src/editor/EnhancedThemeEditor.tsx +624 -0
- package/src/editor/ThemeEditor.tsx +1 -6
- package/src/hooks/__tests__/index.test.tsx +14 -11
- package/src/hooks/__tests__/useDataTransform.test.ts +159 -0
- package/src/hooks/index.ts +54 -19
- package/src/hooks/useDataTransform.ts +503 -0
- package/src/index.ts +27 -9
- package/src/main.tsx +4 -4
- package/src/themes/__tests__/index.test.ts +2 -2
- package/src/themes/index.ts +13 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @version v1.5.0
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { renderHook } from '@testing-library/react';
|
|
6
|
+
import { useDataTransform, useTableTransform, useTimeSeriesTransform } from '../useDataTransform';
|
|
7
|
+
|
|
8
|
+
describe('useDataTransform', () => {
|
|
9
|
+
describe('useDataTransform - line/bar chart', () => {
|
|
10
|
+
it('transforms simple line chart data', () => {
|
|
11
|
+
const { result } = renderHook(() =>
|
|
12
|
+
useDataTransform({
|
|
13
|
+
data: {
|
|
14
|
+
categories: ['周一', '周二', '周三'],
|
|
15
|
+
series: [
|
|
16
|
+
{ name: '销量', value: 120 },
|
|
17
|
+
{ name: '销量', value: 200 },
|
|
18
|
+
{ name: '销量', value: 150 },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
chartType: 'line',
|
|
22
|
+
mapping: { xField: 'name', yField: 'value' },
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(result.current.xAxis).toBeTruthy();
|
|
27
|
+
expect(result.current.series).toBeTruthy();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('transforms bar chart data with series', () => {
|
|
31
|
+
const { result } = renderHook(() =>
|
|
32
|
+
useDataTransform({
|
|
33
|
+
data: {
|
|
34
|
+
categories: ['周一', '周二', '周三'],
|
|
35
|
+
series: [
|
|
36
|
+
{ name: 'A', value: 100 },
|
|
37
|
+
{ name: 'A', value: 200 },
|
|
38
|
+
{ name: 'A', value: 150 },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
chartType: 'bar',
|
|
42
|
+
mapping: { xField: 'name', yField: 'value', seriesField: 'name' },
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// All data points have name='A', so they should be grouped into 1 series
|
|
47
|
+
expect(result.current.series).toHaveLength(1);
|
|
48
|
+
const series = result.current.series as unknown as Array<{ data: unknown[] }>;
|
|
49
|
+
expect(series[0].data).toHaveLength(3);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('useDataTransform - pie chart', () => {
|
|
54
|
+
it('transforms pie chart data', () => {
|
|
55
|
+
const { result } = renderHook(() =>
|
|
56
|
+
useDataTransform({
|
|
57
|
+
data: {
|
|
58
|
+
series: [
|
|
59
|
+
{ name: '直接访问', value: 335 },
|
|
60
|
+
{ name: '邮件营销', value: 310 },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
chartType: 'pie',
|
|
64
|
+
mapping: { nameField: 'name', valueField: 'value' },
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(result.current.series).toBeTruthy();
|
|
69
|
+
const series = result.current.series as unknown as Array<{ data: unknown[] }>;
|
|
70
|
+
expect(series[0].data).toHaveLength(2);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('useDataTransform - scatter chart', () => {
|
|
75
|
+
it('transforms scatter chart data', () => {
|
|
76
|
+
const { result } = renderHook(() =>
|
|
77
|
+
useDataTransform({
|
|
78
|
+
data: {
|
|
79
|
+
series: [
|
|
80
|
+
{ x: 10, y: 20, size: 5 },
|
|
81
|
+
{ x: 30, y: 40, size: 10 },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
chartType: 'scatter',
|
|
85
|
+
mapping: { xField: 'x', yField: 'y', sizeField: 'size' },
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(result.current.series).toBeTruthy();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('useTableTransform', () => {
|
|
94
|
+
it('transforms table data to bar chart config', () => {
|
|
95
|
+
const { result } = renderHook(() =>
|
|
96
|
+
useTableTransform({
|
|
97
|
+
data: [
|
|
98
|
+
{ month: '一月', sales: 1200, profit: 300 },
|
|
99
|
+
{ month: '二月', sales: 2000, profit: 500 },
|
|
100
|
+
],
|
|
101
|
+
columns: [
|
|
102
|
+
{ field: 'month', label: '月份' },
|
|
103
|
+
{ field: 'sales', label: '销量' },
|
|
104
|
+
],
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(result.current.xAxis).toBeTruthy();
|
|
109
|
+
expect(result.current.series).toBeTruthy();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('handles empty data', () => {
|
|
113
|
+
const { result } = renderHook(() =>
|
|
114
|
+
useTableTransform({
|
|
115
|
+
data: [],
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(result.current).toEqual({});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('useTimeSeriesTransform', () => {
|
|
124
|
+
it('transforms time series data', () => {
|
|
125
|
+
const { result } = renderHook(() =>
|
|
126
|
+
useTimeSeriesTransform({
|
|
127
|
+
data: [
|
|
128
|
+
{ date: '2024-01-01', value: 100 },
|
|
129
|
+
{ date: '2024-01-02', value: 200 },
|
|
130
|
+
{ date: '2024-01-03', value: 150 },
|
|
131
|
+
],
|
|
132
|
+
dateField: 'date',
|
|
133
|
+
valueField: 'value',
|
|
134
|
+
period: 'day',
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(result.current.xAxis).toBeTruthy();
|
|
139
|
+
expect(result.current.series).toBeTruthy();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('handles grouped time series', () => {
|
|
143
|
+
const { result } = renderHook(() =>
|
|
144
|
+
useTimeSeriesTransform({
|
|
145
|
+
data: [
|
|
146
|
+
{ date: '2024-01-01', value: 100, category: 'A' },
|
|
147
|
+
{ date: '2024-01-01', value: 150, category: 'B' },
|
|
148
|
+
],
|
|
149
|
+
dateField: 'date',
|
|
150
|
+
valueField: 'value',
|
|
151
|
+
groupField: 'category',
|
|
152
|
+
period: 'day',
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(result.current.series).toHaveLength(2);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
package/src/hooks/index.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
6
6
|
import { getAdapter } from '../adapters';
|
|
7
|
+
import { getThemeByName } from '../themes';
|
|
7
8
|
import type { EChartsOption } from 'echarts';
|
|
8
9
|
|
|
9
10
|
// ============================================================================
|
|
@@ -94,14 +95,18 @@ export function useChart(
|
|
|
94
95
|
return;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
const initAdapter = async () => {
|
|
99
|
+
try {
|
|
100
|
+
const adapter = await getAdapter(configRef.current || {});
|
|
101
|
+
const chartInstance = adapter as unknown as ChartInstance;
|
|
102
|
+
setInstance(chartInstance);
|
|
103
|
+
setInitialized(true);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('Failed to initialize chart:', error);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
initAdapter();
|
|
105
110
|
|
|
106
111
|
return () => {
|
|
107
112
|
if (instance) {
|
|
@@ -294,9 +299,9 @@ export function useLoading(
|
|
|
294
299
|
export function useChartTheme(theme: string | Record<string, unknown>, darkMode = false) {
|
|
295
300
|
return useMemo(() => {
|
|
296
301
|
if (typeof theme === 'string') {
|
|
297
|
-
//
|
|
302
|
+
// 如果是字符串,尝试获取内置主题配置
|
|
298
303
|
try {
|
|
299
|
-
const builtinTheme =
|
|
304
|
+
const builtinTheme = getThemeByName(theme);
|
|
300
305
|
return builtinTheme || (darkMode ? 'dark' : theme);
|
|
301
306
|
} catch {
|
|
302
307
|
return darkMode ? 'dark' : theme;
|
|
@@ -410,7 +415,7 @@ export function useDataPolling<T>(
|
|
|
410
415
|
const [data, setData] = useState<T | null>(null);
|
|
411
416
|
const [loading, setLoading] = useState(autoStart);
|
|
412
417
|
const [error, setError] = useState<Error | null>(null);
|
|
413
|
-
|
|
418
|
+
|
|
414
419
|
// 用于取消进行中的请求
|
|
415
420
|
const abortRef = useRef<{ cancelled: boolean }>({ cancelled: false });
|
|
416
421
|
|
|
@@ -420,12 +425,12 @@ export function useDataPolling<T>(
|
|
|
420
425
|
// 创建新的取消标记
|
|
421
426
|
abortRef.current = { cancelled: false };
|
|
422
427
|
const currentAbort = abortRef.current;
|
|
423
|
-
|
|
428
|
+
|
|
424
429
|
let retries = retryCount;
|
|
425
430
|
setLoading(true);
|
|
426
431
|
setError(null);
|
|
427
432
|
|
|
428
|
-
while (retries
|
|
433
|
+
while (retries > 0 && !currentAbort.cancelled) {
|
|
429
434
|
try {
|
|
430
435
|
const result = await fetchFn();
|
|
431
436
|
if (!currentAbort.cancelled) {
|
|
@@ -435,16 +440,40 @@ export function useDataPolling<T>(
|
|
|
435
440
|
return;
|
|
436
441
|
} catch (e) {
|
|
437
442
|
retries--;
|
|
438
|
-
if (retries
|
|
443
|
+
if (retries <= 0 || currentAbort.cancelled) {
|
|
439
444
|
if (!currentAbort.cancelled) {
|
|
440
445
|
setError(e as Error);
|
|
441
446
|
}
|
|
442
447
|
setLoading(false);
|
|
448
|
+
return;
|
|
443
449
|
} else {
|
|
444
450
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
445
451
|
}
|
|
446
452
|
}
|
|
447
453
|
}
|
|
454
|
+
|
|
455
|
+
// No retries configured — if fetch fails immediately, that's an error
|
|
456
|
+
if (retryCount <= 0 && !currentAbort.cancelled) {
|
|
457
|
+
try {
|
|
458
|
+
const result = await fetchFn();
|
|
459
|
+
if (!currentAbort.cancelled) {
|
|
460
|
+
setData(result);
|
|
461
|
+
setLoading(false);
|
|
462
|
+
}
|
|
463
|
+
} catch (e) {
|
|
464
|
+
if (!currentAbort.cancelled) {
|
|
465
|
+
setError(e as Error);
|
|
466
|
+
setLoading(false);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// All retries exhausted without success (caught in loop already handled above)
|
|
473
|
+
if (!currentAbort.cancelled) {
|
|
474
|
+
setError(new Error('All retries failed'));
|
|
475
|
+
setLoading(false);
|
|
476
|
+
}
|
|
448
477
|
}, [fetchFn, retryCount, retryDelay]);
|
|
449
478
|
|
|
450
479
|
useEffect(() => {
|
|
@@ -459,7 +488,7 @@ export function useDataPolling<T>(
|
|
|
459
488
|
abortRef.current.cancelled = true;
|
|
460
489
|
};
|
|
461
490
|
}
|
|
462
|
-
|
|
491
|
+
|
|
463
492
|
return () => {
|
|
464
493
|
abortRef.current.cancelled = true;
|
|
465
494
|
};
|
|
@@ -591,9 +620,17 @@ export function useChartTools(instance: ChartInstance | null) {
|
|
|
591
620
|
// 导出
|
|
592
621
|
// ============================================================================
|
|
593
622
|
|
|
594
|
-
export const version = '1.
|
|
623
|
+
export const version = '1.4.0';
|
|
624
|
+
|
|
625
|
+
// 新增数据转换 hooks
|
|
626
|
+
export {
|
|
627
|
+
useDataTransform,
|
|
628
|
+
useTableTransform,
|
|
629
|
+
useTimeSeriesTransform,
|
|
630
|
+
useTransform,
|
|
631
|
+
} from './useDataTransform';
|
|
595
632
|
|
|
596
|
-
|
|
633
|
+
export default {
|
|
597
634
|
useChart,
|
|
598
635
|
useOption,
|
|
599
636
|
useResize,
|
|
@@ -608,5 +645,3 @@ const hooks = {
|
|
|
608
645
|
useExport,
|
|
609
646
|
useChartTools,
|
|
610
647
|
};
|
|
611
|
-
|
|
612
|
-
export default hooks;
|