@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.
Files changed (64) hide show
  1. package/README.md +4 -4
  2. package/dist/cjs/index.js +1 -1
  3. package/dist/esm/index.js +3814 -2724
  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/index.ts +63 -74
  9. package/src/adapters/swan/index.ts +26 -223
  10. package/src/adapters/tt/index.ts +28 -225
  11. package/src/adapters/types.ts +36 -0
  12. package/src/adapters/weapp/index.ts +29 -189
  13. package/src/charts/bar/index.tsx +5 -9
  14. package/src/charts/boxplot/__tests__/index.test.tsx +130 -0
  15. package/src/charts/boxplot/index.tsx +18 -0
  16. package/src/charts/boxplot/types.ts +46 -0
  17. package/src/charts/candlestick/__tests__/index.test.tsx +37 -0
  18. package/src/charts/candlestick/index.tsx +13 -0
  19. package/src/charts/common/BaseChartWrapper.tsx +47 -38
  20. package/src/charts/funnel/index.tsx +5 -9
  21. package/src/charts/gauge/index.tsx +5 -9
  22. package/src/charts/graph/__tests__/index.test.tsx +41 -0
  23. package/src/charts/graph/index.tsx +13 -0
  24. package/src/charts/heatmap/index.tsx +5 -9
  25. package/src/charts/index.ts +10 -1
  26. package/src/charts/line/index.tsx +4 -7
  27. package/src/charts/parallel/__tests__/index.test.tsx +164 -0
  28. package/src/charts/parallel/index.tsx +18 -0
  29. package/src/charts/parallel/types.ts +73 -0
  30. package/src/charts/pie/index.tsx +5 -10
  31. package/src/charts/radar/index.tsx +5 -9
  32. package/src/charts/scatter/index.tsx +5 -9
  33. package/src/charts/types.ts +48 -4
  34. package/src/charts/wordcloud/__tests__/index.test.tsx +36 -0
  35. package/src/charts/wordcloud/index.tsx +13 -0
  36. package/src/core/animation/AnimationManager.ts +15 -0
  37. package/src/core/components/Annotation.tsx +26 -21
  38. package/src/core/components/BaseChart.tsx +280 -1105
  39. package/src/core/components/ErrorBoundary.tsx +4 -1
  40. package/src/core/components/LazyChart.tsx +42 -55
  41. package/src/core/components/hooks/index.ts +20 -0
  42. package/src/core/components/hooks/useChartEvents.ts +143 -0
  43. package/src/core/components/hooks/useChartInit.ts +80 -0
  44. package/src/core/components/hooks/usePerformance.ts +186 -0
  45. package/src/core/components/hooks/useVirtualScroll.ts +156 -0
  46. package/src/core/echarts.ts +1 -1
  47. package/src/core/themes/ThemeManager.ts +31 -15
  48. package/src/core/types/index.ts +2 -2
  49. package/src/core/utils/chartInstances.ts +10 -3
  50. package/src/core/utils/chartUtils.ts +46 -0
  51. package/src/core/utils/common.ts +14 -1
  52. package/src/core/utils/export/ExportUtils.ts +13 -22
  53. package/src/core/utils/performance/PerformanceAnalyzer.ts +32 -5
  54. package/src/core/utils/uuid.ts +1 -1
  55. package/src/editor/EnhancedThemeEditor.tsx +624 -0
  56. package/src/editor/ThemeEditor.tsx +1 -6
  57. package/src/hooks/__tests__/index.test.tsx +14 -11
  58. package/src/hooks/__tests__/useDataTransform.test.ts +159 -0
  59. package/src/hooks/index.ts +54 -19
  60. package/src/hooks/useDataTransform.ts +503 -0
  61. package/src/index.ts +27 -9
  62. package/src/main.tsx +4 -4
  63. package/src/themes/__tests__/index.test.ts +2 -2
  64. package/src/themes/index.ts +13 -0
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ import React from 'react';
5
+ import { render } from '@testing-library/react';
6
+ import '@testing-library/jest-dom';
7
+ import BoxplotChart from '../index';
8
+
9
+ // Mock ECharts and adapters
10
+ jest.mock('echarts/core', () => ({
11
+ use: jest.fn(),
12
+ init: jest.fn(() => ({
13
+ setOption: jest.fn(),
14
+ showLoading: jest.fn(),
15
+ hideLoading: jest.fn(),
16
+ on: jest.fn(),
17
+ off: jest.fn(),
18
+ dispose: jest.fn(),
19
+ resize: jest.fn(),
20
+ })),
21
+ getInstanceByDom: jest.fn(),
22
+ }));
23
+
24
+ // Mock ECharts components
25
+ jest.mock('echarts/components', () => ({
26
+ GridComponent: jest.fn(),
27
+ TooltipComponent: jest.fn(),
28
+ TitleComponent: jest.fn(),
29
+ LegendComponent: jest.fn(),
30
+ }));
31
+
32
+ // Mock BaseChartWrapper
33
+ jest.mock('../../common/BaseChartWrapper', () => ({
34
+ __esModule: true,
35
+ default: (props: any) => (
36
+ <div
37
+ data-testid="boxplot-chart"
38
+ className={`taroviz-boxplot ${props.className || ''}`}
39
+ style={{ width: props.width || '100%', height: props.height || 300, ...props.style }}
40
+ >
41
+ <div data-testid="chart-option">{JSON.stringify(props.option)}</div>
42
+ </div>
43
+ ),
44
+ }));
45
+
46
+ describe('BoxplotChart', () => {
47
+ const basicOption = {
48
+ title: { text: '箱线图测试' },
49
+ xAxis: { type: 'category' as const, data: ['A', 'B', 'C'] },
50
+ yAxis: { type: 'value' as const },
51
+ series: [{
52
+ type: 'boxplot' as const,
53
+ data: [
54
+ [850, 940, 980, 1050, 1130],
55
+ [920, 1000, 1050, 1150, 1200],
56
+ [780, 850, 920, 1050, 1150],
57
+ ],
58
+ }],
59
+ };
60
+
61
+ describe('Basic Rendering', () => {
62
+ it('should render without crashing', () => {
63
+ const { getByTestId } = render(<BoxplotChart option={basicOption} />);
64
+ expect(getByTestId('boxplot-chart')).toBeInTheDocument();
65
+ });
66
+
67
+ it('should render with custom width and height', () => {
68
+ const { getByTestId } = render(<BoxplotChart option={basicOption} width={600} height={500} />);
69
+ expect(getByTestId('boxplot-chart')).toBeInTheDocument();
70
+ });
71
+
72
+ it('should have correct display name', () => {
73
+ expect(BoxplotChart.displayName).toBe('BoxplotChart');
74
+ });
75
+
76
+ it('should pass option to wrapper', () => {
77
+ const { getByTestId } = render(<BoxplotChart option={basicOption} />);
78
+ expect(getByTestId('chart-option')).toHaveTextContent('箱线图测试');
79
+ });
80
+ });
81
+
82
+ describe('Props', () => {
83
+ it('should accept className prop', () => {
84
+ const { getByTestId } = render(<BoxplotChart option={basicOption} className="test-class" />);
85
+ expect(getByTestId('boxplot-chart')).toHaveClass('test-class');
86
+ });
87
+
88
+ it('should accept style prop', () => {
89
+ const { getByTestId } = render(<BoxplotChart option={basicOption} style={{ padding: '10px' }} />);
90
+ expect(getByTestId('boxplot-chart')).toBeInTheDocument();
91
+ });
92
+
93
+ it('should accept loading prop', () => {
94
+ const { getByTestId } = render(<BoxplotChart option={basicOption} loading={true} />);
95
+ expect(getByTestId('boxplot-chart')).toBeInTheDocument();
96
+ });
97
+
98
+ it('should accept theme prop', () => {
99
+ const { getByTestId } = render(<BoxplotChart option={basicOption} theme="dark" />);
100
+ expect(getByTestId('boxplot-chart')).toBeInTheDocument();
101
+ });
102
+ });
103
+
104
+ describe('Chart Options', () => {
105
+ it('should render with multiple data series', () => {
106
+ const multiOption = {
107
+ ...basicOption,
108
+ series: [
109
+ { type: 'boxplot' as const, name: '2024', data: [[850, 940, 980, 1050, 1130]] },
110
+ { type: 'boxplot' as const, name: '2025', data: [[920, 1000, 1050, 1150, 1200]] },
111
+ ],
112
+ };
113
+ const { getByTestId } = render(<BoxplotChart option={multiOption} />);
114
+ expect(getByTestId('boxplot-chart')).toBeInTheDocument();
115
+ });
116
+
117
+ it('should render with custom itemStyle', () => {
118
+ const optionWithStyle = {
119
+ ...basicOption,
120
+ series: [{
121
+ type: 'boxplot' as const,
122
+ data: [[850, 940, 980, 1050, 1130]],
123
+ itemStyle: { color: '#1890ff', borderColor: '#000' },
124
+ }],
125
+ };
126
+ const { getByTestId } = render(<BoxplotChart option={optionWithStyle} />);
127
+ expect(getByTestId('boxplot-chart')).toBeInTheDocument();
128
+ });
129
+ });
130
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 箱线图组件
3
+ * 用于展示数据的分布情况,包括最小值、Q1、中位数、Q3、最大值
4
+ */
5
+ import React, { memo } from 'react';
6
+ import BaseChartWrapper from '../common/BaseChartWrapper';
7
+ import { BoxplotChartProps } from './types';
8
+
9
+ const BoxplotChart: React.FC<BoxplotChartProps> = memo((props) => (
10
+ <BaseChartWrapper {...props} chartType="boxplot" />
11
+ ));
12
+
13
+ BoxplotChart.displayName = 'BoxplotChart';
14
+
15
+ export default BoxplotChart;
16
+
17
+ // 导出类型
18
+ export type { BoxplotChartProps, BoxplotOption, BoxplotSeriesItem } from './types';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 箱线图类型定义
3
+ */
4
+
5
+ export type BoxplotChartProps = {
6
+ option?: BoxplotOption;
7
+ width?: string | number;
8
+ height?: string | number;
9
+ className?: string;
10
+ style?: React.CSSProperties;
11
+ onEvents?: Record<string, (params: any) => void>;
12
+ loading?: boolean;
13
+ loadingOption?: any;
14
+ theme?: string;
15
+ onChartReady?: (chart: any) => void;
16
+ opts?: {
17
+ devicePixelRatio?: number;
18
+ renderer?: 'canvas' | 'svg';
19
+ useDirtyRect?: boolean;
20
+ };
21
+ };
22
+
23
+ export interface BoxplotOption {
24
+ title?: any;
25
+ legend?: any;
26
+ grid?: any;
27
+ xAxis?: any;
28
+ yAxis?: any;
29
+ tooltip?: any;
30
+ series: BoxplotSeriesItem[];
31
+ dataset?: any;
32
+ color?: string[];
33
+ backgroundColor?: any;
34
+ textStyle?: any;
35
+ [key: string]: any;
36
+ }
37
+
38
+ export interface BoxplotSeriesItem {
39
+ type: 'boxplot';
40
+ name?: string;
41
+ data?: any[];
42
+ itemStyle?: any;
43
+ emphasis?: any;
44
+ dimensions?: string[];
45
+ encode?: any;
46
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @version v1.5.0
3
+ */
4
+
5
+ import React from 'react';
6
+ import { render } from '@testing-library/react';
7
+ import CandlestickChart from '../index';
8
+
9
+ describe('CandlestickChart', () => {
10
+ it('renders without crashing', () => {
11
+ const { container } = render(<CandlestickChart />);
12
+ expect(container).toBeTruthy();
13
+ });
14
+
15
+ it('renders with custom width and height', () => {
16
+ const { container } = render(<CandlestickChart width={500} height={400} />);
17
+ expect(container.firstChild).toHaveStyle({ width: '500px', height: '400px' });
18
+ });
19
+
20
+ it('renders with stock data', () => {
21
+ const option = {
22
+ xAxis: { data: ['2024-01', '2024-02', '2024-03'] },
23
+ series: [
24
+ {
25
+ type: 'candlestick' as const,
26
+ data: [
27
+ [20, 30, 15, 35], // [open, close, lowest, highest]
28
+ [25, 35, 20, 40],
29
+ [30, 25, 20, 35],
30
+ ],
31
+ },
32
+ ],
33
+ };
34
+ const { container } = render(<CandlestickChart option={option} />);
35
+ expect(container).toBeTruthy();
36
+ });
37
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * CandlestickChart组件
3
+ */
4
+ import React, { memo } from 'react';
5
+ import BaseChartWrapper from '../common/BaseChartWrapper';
6
+ import { CandlestickChartProps } from '../types';
7
+
8
+ const CandlestickChart: React.FC<CandlestickChartProps> = memo((props) => (
9
+ <BaseChartWrapper {...props} chartType="candlestick-chart" />
10
+ ));
11
+ CandlestickChart.displayName = 'CandlestickChart';
12
+
13
+ export default CandlestickChart;
@@ -2,7 +2,6 @@
2
2
  * 基础图表包装组件
3
3
  * 提供统一的图表初始化、渲染和生命周期管理
4
4
  */
5
- import * as echarts from 'echarts/core';
6
5
  import React, { useEffect, useRef, useMemo } from 'react';
7
6
 
8
7
  import { getAdapter } from '../../adapters';
@@ -31,7 +30,7 @@ const BaseChartWrapper: React.FC<BaseChartProps & { chartType: string }> = ({
31
30
  chartType = 'chart',
32
31
  }) => {
33
32
  const chartId = useRef<string>(`${chartType}-${uuid()}`);
34
- const chartInstance = useRef<echarts.ECharts | null>(null);
33
+ const chartInstance = useRef<any>(null);
35
34
  const containerRef = useRef<HTMLDivElement>(null);
36
35
 
37
36
  // 使用 useMemo 缓存适配器配置,并处理类型问题
@@ -46,56 +45,66 @@ const BaseChartWrapper: React.FC<BaseChartProps & { chartType: string }> = ({
46
45
  renderer,
47
46
  option,
48
47
  });
49
- }, [width, height, theme, autoResize, renderer, option, chartType]);
48
+ }, [width, height, theme, autoResize, renderer, option]);
50
49
 
51
50
  // 处理图表初始化
52
51
  useEffect(() => {
53
- const initConfig = processAdapterConfig({
54
- ...adapterConfig,
55
- onInit: (instance: echarts.ECharts) => {
56
- chartInstance.current = instance;
57
-
58
- // 绑定事件
59
- if (onEvents) {
60
- Object.keys(onEvents).forEach((eventName) => {
61
- instance.on(eventName, onEvents[eventName]);
62
- });
63
- }
52
+ const initChart = async () => {
53
+ const initConfig = processAdapterConfig({
54
+ ...adapterConfig,
55
+ onInit: (instance: any) => {
56
+ chartInstance.current = instance;
64
57
 
65
- // 初始化回调
66
- if (onChartInit) {
67
- onChartInit(instance);
68
- }
58
+ // 绑定事件
59
+ if (onEvents) {
60
+ Object.keys(onEvents).forEach((eventName) => {
61
+ instance.on(eventName, (onEvents as any)[eventName]);
62
+ });
63
+ }
64
+
65
+ // 初始化回调
66
+ if (onChartInit) {
67
+ onChartInit(instance);
68
+ }
69
+
70
+ // 准备好回调
71
+ if (onChartReady) {
72
+ onChartReady(instance);
73
+ }
74
+ },
75
+ });
76
+
77
+ // 获取适配器并初始化(异步动态导入)
78
+ const adapter = await getAdapter(initConfig);
79
+ adapter.init();
69
80
 
70
- // 准备好回调
71
- if (onChartReady) {
72
- onChartReady(instance);
81
+ // 返回清理函数
82
+ return () => {
83
+ if (chartInstance.current) {
84
+ // 解绑事件
85
+ if (onEvents) {
86
+ Object.keys(onEvents).forEach((eventName) => {
87
+ chartInstance.current?.off(eventName);
88
+ });
89
+ }
90
+ chartInstance.current.dispose();
91
+ chartInstance.current = null;
73
92
  }
74
- },
75
- });
93
+ };
94
+ };
76
95
 
77
- // 获取适配器并初始化
78
- const adapter = getAdapter(initConfig);
79
- adapter.init();
96
+ // 执行异步初始化并获取清理函数
97
+ const cleanupPromise = initChart();
80
98
 
81
- // 组件卸载时清理
99
+ // 返回清理函数
82
100
  return () => {
83
- if (chartInstance.current) {
84
- // 解绑事件
85
- if (onEvents) {
86
- Object.keys(onEvents).forEach((eventName) => {
87
- chartInstance.current?.off(eventName);
88
- });
89
- }
90
- chartInstance.current.dispose();
91
- chartInstance.current = null;
92
- }
101
+ cleanupPromise.then((cleanup) => cleanup?.());
93
102
  };
94
103
  }, [adapterConfig, onChartInit, onChartReady, onEvents]);
95
104
 
96
105
  // 更新配置
97
106
  useEffect(() => {
98
- if (chartInstance.current) {
107
+ if (chartInstance.current && option) {
99
108
  chartInstance.current.setOption(option, true);
100
109
  }
101
110
  }, [option]);
@@ -1,18 +1,14 @@
1
1
  /**
2
- * 漏斗图组件
2
+ * FunnelChart组件
3
3
  */
4
- import React from 'react';
5
-
4
+ import React, { memo } from 'react';
6
5
  import BaseChartWrapper from '../common/BaseChartWrapper';
7
6
  import { FunnelChartProps } from '../types';
8
-
9
7
  import '@/core/echarts';
10
8
 
11
- /**
12
- * 漏斗图组件
13
- */
14
- const FunnelChart: React.FC<FunnelChartProps> = (props) => (
9
+ const FunnelChart: React.FC<FunnelChartProps> = memo((props) => (
15
10
  <BaseChartWrapper {...props} chartType="funnel-chart" />
16
- );
11
+ ));
12
+ FunnelChart.displayName = 'FunnelChart';
17
13
 
18
14
  export default FunnelChart;
@@ -1,18 +1,14 @@
1
1
  /**
2
- * 仪表盘组件
2
+ * GaugeChart组件
3
3
  */
4
- import React from 'react';
5
-
4
+ import React, { memo } from 'react';
6
5
  import BaseChartWrapper from '../common/BaseChartWrapper';
7
6
  import { GaugeChartProps } from '../types';
8
-
9
7
  import '@/core/echarts';
10
8
 
11
- /**
12
- * 仪表盘组件
13
- */
14
- const GaugeChart: React.FC<GaugeChartProps> = (props) => (
9
+ const GaugeChart: React.FC<GaugeChartProps> = memo((props) => (
15
10
  <BaseChartWrapper {...props} chartType="gauge-chart" />
16
- );
11
+ ));
12
+ GaugeChart.displayName = 'GaugeChart';
17
13
 
18
14
  export default GaugeChart;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @version v1.5.0
3
+ */
4
+
5
+ import React from 'react';
6
+ import { render } from '@testing-library/react';
7
+ import GraphChart from '../index';
8
+
9
+ describe('GraphChart', () => {
10
+ it('renders without crashing', () => {
11
+ const { container } = render(<GraphChart />);
12
+ expect(container).toBeTruthy();
13
+ });
14
+
15
+ it('renders with custom className', () => {
16
+ const { container } = render(<GraphChart className="test-graph" />);
17
+ expect(container.firstChild).toHaveClass('test-graph');
18
+ });
19
+
20
+ it('renders with custom width and height', () => {
21
+ const { container } = render(<GraphChart width={500} height={400} />);
22
+ expect(container.firstChild).toHaveStyle({ width: '500px', height: '400px' });
23
+ });
24
+
25
+ it('renders with basic option', () => {
26
+ const option = {
27
+ series: [
28
+ {
29
+ type: 'graph' as const,
30
+ nodes: [
31
+ { id: '1', name: 'Node 1' },
32
+ { id: '2', name: 'Node 2' },
33
+ ],
34
+ links: [{ source: '1', target: '2' }],
35
+ },
36
+ ],
37
+ };
38
+ const { container } = render(<GraphChart option={option} />);
39
+ expect(container).toBeTruthy();
40
+ });
41
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * GraphChart组件
3
+ */
4
+ import React, { memo } from 'react';
5
+ import BaseChartWrapper from '../common/BaseChartWrapper';
6
+ import { GraphChartProps } from '../types';
7
+
8
+ const GraphChart: React.FC<GraphChartProps> = memo((props) => (
9
+ <BaseChartWrapper {...props} chartType="graph-chart" />
10
+ ));
11
+ GraphChart.displayName = 'GraphChart';
12
+
13
+ export default GraphChart;
@@ -1,18 +1,14 @@
1
1
  /**
2
- * 热力图组件
2
+ * HeatmapChart组件
3
3
  */
4
- import React from 'react';
5
-
4
+ import React, { memo } from 'react';
6
5
  import BaseChartWrapper from '../common/BaseChartWrapper';
7
6
  import { HeatmapChartProps } from '../types';
8
-
9
7
  import '@/core/echarts';
10
8
 
11
- /**
12
- * 热力图组件
13
- */
14
- const HeatmapChart: React.FC<HeatmapChartProps> = (props) => (
9
+ const HeatmapChart: React.FC<HeatmapChartProps> = memo((props) => (
15
10
  <BaseChartWrapper {...props} chartType="heatmap-chart" />
16
- );
11
+ ));
12
+ HeatmapChart.displayName = 'HeatmapChart';
17
13
 
18
14
  export default HeatmapChart;
@@ -17,10 +17,19 @@ export { default as TreeMapChart } from './treemap';
17
17
  export { default as SunburstChart } from './sunburst';
18
18
  export { default as SankeyChart } from './sankey';
19
19
 
20
+ // 导出新增图表组件
21
+ export { default as GraphChart } from './graph';
22
+ export { default as CandlestickChart } from './candlestick';
23
+ export { default as WordCloudChart } from './wordcloud';
24
+
25
+ // 导出 v1.6.0 新增图表组件
26
+ export { default as BoxplotChart } from './boxplot';
27
+ export { default as ParallelChart } from './parallel';
28
+
20
29
  // 导出类型定义
21
30
  export * from './types';
22
31
 
23
32
  /**
24
33
  * 版本信息
25
34
  */
26
- export const version = '1.2.1';
35
+ export const version = '1.6.0';
@@ -1,18 +1,15 @@
1
1
  /**
2
2
  * 折线图组件
3
3
  */
4
- import React from 'react';
5
-
4
+ import React, { memo } from 'react';
6
5
  import BaseChartWrapper from '../common/BaseChartWrapper';
7
6
  import { LineChartProps } from '../types';
8
7
 
9
8
  import '@/core/echarts';
10
9
 
11
- /**
12
- * 折线图组件
13
- */
14
- const LineChart: React.FC<LineChartProps> = (props) => (
10
+ const LineChart: React.FC<LineChartProps> = memo((props) => (
15
11
  <BaseChartWrapper {...props} chartType="line-chart" />
16
- );
12
+ ));
13
+ LineChart.displayName = 'LineChart';
17
14
 
18
15
  export default LineChart;