@agions/taroviz 1.3.0 → 1.5.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 +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/esm/index.js +1827 -1547
- 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/h5/index.ts +1 -3
- package/src/adapters/index.ts +65 -65
- 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/candlestick/__tests__/index.test.tsx +37 -0
- package/src/charts/candlestick/index.tsx +13 -0
- package/src/charts/common/BaseChartWrapper.tsx +49 -46
- 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 +6 -1
- package/src/charts/line/index.tsx +4 -7
- 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 +18 -2
- package/src/core/utils/chartUtils.ts +46 -0
- package/src/core/utils/codeGenerator/CodeGenerator.ts +19 -5
- 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 +9 -5
- 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 +76 -23
- package/src/hooks/useDataTransform.ts +503 -0
- package/src/index.ts +15 -2
- package/src/main.tsx +4 -4
- package/src/themes/__tests__/index.test.ts +2 -2
- package/src/themes/index.ts +13 -0
|
@@ -13,16 +13,18 @@ import {
|
|
|
13
13
|
|
|
14
14
|
// Mock dependencies
|
|
15
15
|
jest.mock('../../adapters', () => ({
|
|
16
|
-
getAdapter: jest.fn(() =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
getAdapter: jest.fn(() =>
|
|
17
|
+
Promise.resolve({
|
|
18
|
+
setComponent: jest.fn(),
|
|
19
|
+
setOption: jest.fn(),
|
|
20
|
+
resize: jest.fn(),
|
|
21
|
+
on: jest.fn(),
|
|
22
|
+
off: jest.fn(),
|
|
23
|
+
showLoading: jest.fn(),
|
|
24
|
+
hideLoading: jest.fn(),
|
|
25
|
+
dispose: jest.fn(),
|
|
26
|
+
})
|
|
27
|
+
),
|
|
26
28
|
}));
|
|
27
29
|
|
|
28
30
|
describe('React Hooks', () => {
|
|
@@ -244,7 +246,8 @@ describe('React Hooks', () => {
|
|
|
244
246
|
}
|
|
245
247
|
);
|
|
246
248
|
|
|
247
|
-
|
|
249
|
+
// getThemeByName('default') returns the defaultTheme object
|
|
250
|
+
expect(result.current).toEqual(expect.objectContaining({ backgroundColor: '#ffffff', textColor: '#333333' }));
|
|
248
251
|
});
|
|
249
252
|
|
|
250
253
|
it('should return original theme when darkMode is false', () => {
|
|
@@ -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,29 +415,65 @@ 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
|
+
|
|
419
|
+
// 用于取消进行中的请求
|
|
420
|
+
const abortRef = useRef<{ cancelled: boolean }>({ cancelled: false });
|
|
414
421
|
|
|
415
422
|
const fetchData = useCallback(async () => {
|
|
423
|
+
// 取消之前的请求
|
|
424
|
+
abortRef.current.cancelled = true;
|
|
425
|
+
// 创建新的取消标记
|
|
426
|
+
abortRef.current = { cancelled: false };
|
|
427
|
+
const currentAbort = abortRef.current;
|
|
428
|
+
|
|
416
429
|
let retries = retryCount;
|
|
417
430
|
setLoading(true);
|
|
418
431
|
setError(null);
|
|
419
432
|
|
|
420
|
-
while (retries
|
|
433
|
+
while (retries > 0 && !currentAbort.cancelled) {
|
|
421
434
|
try {
|
|
422
435
|
const result = await fetchFn();
|
|
423
|
-
|
|
424
|
-
|
|
436
|
+
if (!currentAbort.cancelled) {
|
|
437
|
+
setData(result);
|
|
438
|
+
setLoading(false);
|
|
439
|
+
}
|
|
425
440
|
return;
|
|
426
441
|
} catch (e) {
|
|
427
442
|
retries--;
|
|
428
|
-
if (retries
|
|
429
|
-
|
|
443
|
+
if (retries <= 0 || currentAbort.cancelled) {
|
|
444
|
+
if (!currentAbort.cancelled) {
|
|
445
|
+
setError(e as Error);
|
|
446
|
+
}
|
|
430
447
|
setLoading(false);
|
|
448
|
+
return;
|
|
431
449
|
} else {
|
|
432
450
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
433
451
|
}
|
|
434
452
|
}
|
|
435
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
|
+
}
|
|
436
477
|
}, [fetchFn, retryCount, retryDelay]);
|
|
437
478
|
|
|
438
479
|
useEffect(() => {
|
|
@@ -442,12 +483,18 @@ export function useDataPolling<T>(
|
|
|
442
483
|
|
|
443
484
|
if (interval > 0) {
|
|
444
485
|
const timer = setInterval(fetchData, interval);
|
|
445
|
-
return () =>
|
|
486
|
+
return () => {
|
|
487
|
+
clearInterval(timer);
|
|
488
|
+
abortRef.current.cancelled = true;
|
|
489
|
+
};
|
|
446
490
|
}
|
|
447
|
-
|
|
491
|
+
|
|
492
|
+
return () => {
|
|
493
|
+
abortRef.current.cancelled = true;
|
|
494
|
+
};
|
|
495
|
+
}, [interval, autoStart, fetchData]);
|
|
448
496
|
|
|
449
497
|
const refresh = useCallback(() => {
|
|
450
|
-
setRefreshIndex((prev) => prev + 1);
|
|
451
498
|
fetchData();
|
|
452
499
|
}, [fetchData]);
|
|
453
500
|
|
|
@@ -573,9 +620,17 @@ export function useChartTools(instance: ChartInstance | null) {
|
|
|
573
620
|
// 导出
|
|
574
621
|
// ============================================================================
|
|
575
622
|
|
|
576
|
-
export const version = '1.
|
|
623
|
+
export const version = '1.4.0';
|
|
577
624
|
|
|
578
|
-
|
|
625
|
+
// 新增数据转换 hooks
|
|
626
|
+
export {
|
|
627
|
+
useDataTransform,
|
|
628
|
+
useTableTransform,
|
|
629
|
+
useTimeSeriesTransform,
|
|
630
|
+
useTransform,
|
|
631
|
+
} from './useDataTransform';
|
|
632
|
+
|
|
633
|
+
export default {
|
|
579
634
|
useChart,
|
|
580
635
|
useOption,
|
|
581
636
|
useResize,
|
|
@@ -590,5 +645,3 @@ const hooks = {
|
|
|
590
645
|
useExport,
|
|
591
646
|
useChartTools,
|
|
592
647
|
};
|
|
593
|
-
|
|
594
|
-
export default hooks;
|