@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.
Files changed (59) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/index.js +1 -1
  3. package/dist/esm/index.js +1827 -1547
  4. package/package.json +1 -1
  5. package/src/__tests__/integration.test.tsx +12 -10
  6. package/src/adapters/BaseAdapter.ts +116 -0
  7. package/src/adapters/__tests__/index.test.ts +10 -10
  8. package/src/adapters/h5/index.ts +1 -3
  9. package/src/adapters/index.ts +65 -65
  10. package/src/adapters/swan/index.ts +26 -223
  11. package/src/adapters/tt/index.ts +28 -225
  12. package/src/adapters/types.ts +36 -0
  13. package/src/adapters/weapp/index.ts +29 -189
  14. package/src/charts/bar/index.tsx +5 -9
  15. package/src/charts/candlestick/__tests__/index.test.tsx +37 -0
  16. package/src/charts/candlestick/index.tsx +13 -0
  17. package/src/charts/common/BaseChartWrapper.tsx +49 -46
  18. package/src/charts/funnel/index.tsx +5 -9
  19. package/src/charts/gauge/index.tsx +5 -9
  20. package/src/charts/graph/__tests__/index.test.tsx +41 -0
  21. package/src/charts/graph/index.tsx +13 -0
  22. package/src/charts/heatmap/index.tsx +5 -9
  23. package/src/charts/index.ts +6 -1
  24. package/src/charts/line/index.tsx +4 -7
  25. package/src/charts/pie/index.tsx +5 -10
  26. package/src/charts/radar/index.tsx +5 -9
  27. package/src/charts/scatter/index.tsx +5 -9
  28. package/src/charts/types.ts +48 -4
  29. package/src/charts/wordcloud/__tests__/index.test.tsx +36 -0
  30. package/src/charts/wordcloud/index.tsx +13 -0
  31. package/src/core/animation/AnimationManager.ts +15 -0
  32. package/src/core/components/Annotation.tsx +26 -21
  33. package/src/core/components/BaseChart.tsx +280 -1105
  34. package/src/core/components/ErrorBoundary.tsx +4 -1
  35. package/src/core/components/LazyChart.tsx +42 -55
  36. package/src/core/components/hooks/index.ts +20 -0
  37. package/src/core/components/hooks/useChartEvents.ts +143 -0
  38. package/src/core/components/hooks/useChartInit.ts +80 -0
  39. package/src/core/components/hooks/usePerformance.ts +186 -0
  40. package/src/core/components/hooks/useVirtualScroll.ts +156 -0
  41. package/src/core/echarts.ts +1 -1
  42. package/src/core/themes/ThemeManager.ts +31 -15
  43. package/src/core/types/index.ts +2 -2
  44. package/src/core/utils/chartInstances.ts +18 -2
  45. package/src/core/utils/chartUtils.ts +46 -0
  46. package/src/core/utils/codeGenerator/CodeGenerator.ts +19 -5
  47. package/src/core/utils/common.ts +14 -1
  48. package/src/core/utils/export/ExportUtils.ts +13 -22
  49. package/src/core/utils/performance/PerformanceAnalyzer.ts +32 -5
  50. package/src/core/utils/uuid.ts +9 -5
  51. package/src/editor/ThemeEditor.tsx +1 -6
  52. package/src/hooks/__tests__/index.test.tsx +14 -11
  53. package/src/hooks/__tests__/useDataTransform.test.ts +159 -0
  54. package/src/hooks/index.ts +76 -23
  55. package/src/hooks/useDataTransform.ts +503 -0
  56. package/src/index.ts +15 -2
  57. package/src/main.tsx +4 -4
  58. package/src/themes/__tests__/index.test.ts +2 -2
  59. 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
- setComponent: jest.fn(),
18
- setOption: jest.fn(),
19
- resize: jest.fn(),
20
- on: jest.fn(),
21
- off: jest.fn(),
22
- showLoading: jest.fn(),
23
- hideLoading: jest.fn(),
24
- dispose: jest.fn(),
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
- expect(result.current).toBe('dark');
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
+ });
@@ -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
- try {
98
- const adapter = getAdapter(configRef.current || {});
99
- const chartInstance = adapter as unknown as ChartInstance;
100
- setInstance(chartInstance);
101
- setInitialized(true);
102
- } catch (error) {
103
- console.error('Failed to initialize chart:', error);
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 = getTheme(theme);
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
- const [refreshIndex, setRefreshIndex] = useState(0);
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 >= 0) {
433
+ while (retries > 0 && !currentAbort.cancelled) {
421
434
  try {
422
435
  const result = await fetchFn();
423
- setData(result);
424
- setLoading(false);
436
+ if (!currentAbort.cancelled) {
437
+ setData(result);
438
+ setLoading(false);
439
+ }
425
440
  return;
426
441
  } catch (e) {
427
442
  retries--;
428
- if (retries < 0) {
429
- setError(e as Error);
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 () => clearInterval(timer);
486
+ return () => {
487
+ clearInterval(timer);
488
+ abortRef.current.cancelled = true;
489
+ };
446
490
  }
447
- }, [interval, autoStart, fetchData, refreshIndex]);
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.2.0';
623
+ export const version = '1.4.0';
577
624
 
578
- const hooks = {
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;