@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
|
@@ -1,47 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ParallelChart 组件测试
|
|
3
3
|
*/
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { render, screen } from '@testing-library/react';
|
|
6
6
|
import '@testing-library/jest-dom';
|
|
7
7
|
import ParallelChart from '../index';
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
jest.mock('
|
|
11
|
-
|
|
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="parallel-chart"
|
|
38
|
-
className={`taroviz-parallel ${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
|
-
}));
|
|
9
|
+
// 自动使用 src/charts/common/__mocks__/BaseChartWrapper.tsx
|
|
10
|
+
jest.mock('../../common/BaseChartWrapper');
|
|
11
|
+
jest.mock('echarts/charts', () => ({ ParallelChart: jest.fn() }));
|
|
45
12
|
|
|
46
13
|
describe('ParallelChart', () => {
|
|
47
14
|
const basicOption = {
|
|
@@ -57,26 +24,28 @@ describe('ParallelChart', () => {
|
|
|
57
24
|
type: 'value' as const,
|
|
58
25
|
name: '指标',
|
|
59
26
|
},
|
|
60
|
-
series: [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
[
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
27
|
+
series: [
|
|
28
|
+
{
|
|
29
|
+
type: 'parallel' as const,
|
|
30
|
+
lineStyle: { width: 2, opacity: 0.5 },
|
|
31
|
+
data: [
|
|
32
|
+
[1, 55, 9, 56, 0.46, 2, 35],
|
|
33
|
+
[2, 25, 11, 21, 0.65, 2, 33],
|
|
34
|
+
[3, 56, 7, 63, 0.92, 3, 45],
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
69
38
|
};
|
|
70
39
|
|
|
71
40
|
describe('Basic Rendering', () => {
|
|
72
41
|
it('should render without crashing', () => {
|
|
73
42
|
render(<ParallelChart option={basicOption} />);
|
|
74
|
-
expect(screen.getByTestId('
|
|
43
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
75
44
|
});
|
|
76
45
|
|
|
77
46
|
it('should render with custom width and height', () => {
|
|
78
47
|
render(<ParallelChart option={basicOption} width={600} height={500} />);
|
|
79
|
-
expect(screen.getByTestId('
|
|
48
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
80
49
|
});
|
|
81
50
|
|
|
82
51
|
it('should have correct display name', () => {
|
|
@@ -92,23 +61,23 @@ describe('ParallelChart', () => {
|
|
|
92
61
|
describe('Props', () => {
|
|
93
62
|
it('should accept className prop', () => {
|
|
94
63
|
render(<ParallelChart option={basicOption} className="test-class" />);
|
|
95
|
-
expect(screen.getByTestId('
|
|
64
|
+
expect(screen.getByTestId('base-chart-wrapper')).toHaveClass('test-class');
|
|
96
65
|
});
|
|
97
66
|
|
|
98
67
|
it('should accept style prop', () => {
|
|
99
68
|
const style = { padding: '10px' };
|
|
100
69
|
render(<ParallelChart option={basicOption} style={style} />);
|
|
101
|
-
expect(screen.getByTestId('
|
|
70
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
102
71
|
});
|
|
103
72
|
|
|
104
73
|
it('should accept loading prop', () => {
|
|
105
74
|
render(<ParallelChart option={basicOption} loading={true} />);
|
|
106
|
-
expect(screen.getByTestId('
|
|
75
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
107
76
|
});
|
|
108
77
|
|
|
109
78
|
it('should accept theme prop', () => {
|
|
110
79
|
render(<ParallelChart option={basicOption} theme="dark" />);
|
|
111
|
-
expect(screen.getByTestId('
|
|
80
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
112
81
|
});
|
|
113
82
|
});
|
|
114
83
|
|
|
@@ -124,20 +93,22 @@ describe('ParallelChart', () => {
|
|
|
124
93
|
},
|
|
125
94
|
};
|
|
126
95
|
render(<ParallelChart option={expandableOption} />);
|
|
127
|
-
expect(screen.getByTestId('
|
|
96
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
128
97
|
});
|
|
129
98
|
|
|
130
99
|
it('should render with custom lineStyle', () => {
|
|
131
100
|
const customLineOption = {
|
|
132
101
|
...basicOption,
|
|
133
|
-
series: [
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
102
|
+
series: [
|
|
103
|
+
{
|
|
104
|
+
type: 'parallel' as const,
|
|
105
|
+
lineStyle: { width: 3, color: '#1890ff', opacity: 0.8 },
|
|
106
|
+
data: [[1, 55, 9, 56, 0.46, 2, 35]],
|
|
107
|
+
},
|
|
108
|
+
],
|
|
138
109
|
};
|
|
139
110
|
render(<ParallelChart option={customLineOption} />);
|
|
140
|
-
expect(screen.getByTestId('
|
|
111
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
141
112
|
});
|
|
142
113
|
|
|
143
114
|
it('should render with category axis', () => {
|
|
@@ -149,16 +120,18 @@ describe('ParallelChart', () => {
|
|
|
149
120
|
data: ['A', 'B', 'C', 'D', 'E'],
|
|
150
121
|
name: '分类',
|
|
151
122
|
},
|
|
152
|
-
series: [
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
[
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
123
|
+
series: [
|
|
124
|
+
{
|
|
125
|
+
type: 'parallel' as const,
|
|
126
|
+
data: [
|
|
127
|
+
['A', 55, 9, 56, 0.46, 2, 35],
|
|
128
|
+
['B', 25, 11, 21, 0.65, 2, 33],
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
],
|
|
159
132
|
};
|
|
160
133
|
render(<ParallelChart option={categoryOption} />);
|
|
161
|
-
expect(screen.getByTestId('
|
|
134
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
162
135
|
});
|
|
163
136
|
});
|
|
164
137
|
});
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* 用于展示高维数据各维度之间的关系
|
|
4
4
|
*/
|
|
5
5
|
import React, { memo } from 'react';
|
|
6
|
+
import type { EChartsOption } from 'echarts';
|
|
6
7
|
import BaseChartWrapper from '../common/BaseChartWrapper';
|
|
7
8
|
import { ParallelChartProps } from './types';
|
|
8
9
|
|
|
9
10
|
const ParallelChart: React.FC<ParallelChartProps> = memo((props) => (
|
|
10
|
-
<BaseChartWrapper {...props} chartType="parallel" />
|
|
11
|
+
<BaseChartWrapper {...props} option={props.option as EChartsOption} chartType="parallel" />
|
|
11
12
|
));
|
|
12
13
|
|
|
13
14
|
ParallelChart.displayName = 'ParallelChart';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 平行坐标图类型定义
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { EChartsType, ECElementEvent } from 'echarts';
|
|
5
|
+
import type { EChartsOption, EChartsType, ECElementEvent } from 'echarts';
|
|
6
6
|
import type { LoadingOptions } from '../types';
|
|
7
7
|
|
|
8
8
|
export type ParallelChartProps = {
|
|
@@ -11,11 +11,11 @@ export type ParallelChartProps = {
|
|
|
11
11
|
height?: string | number;
|
|
12
12
|
className?: string;
|
|
13
13
|
style?: React.CSSProperties;
|
|
14
|
-
onEvents?: Record<string, (
|
|
14
|
+
onEvents?: Record<string, (_params: ECElementEvent) => void>;
|
|
15
15
|
loading?: boolean;
|
|
16
16
|
loadingOption?: LoadingOptions;
|
|
17
17
|
theme?: string;
|
|
18
|
-
onChartReady?: (
|
|
18
|
+
onChartReady?: (_chart: EChartsType) => void;
|
|
19
19
|
opts?: {
|
|
20
20
|
devicePixelRatio?: number;
|
|
21
21
|
renderer?: 'canvas' | 'svg';
|
|
@@ -23,18 +23,18 @@ export type ParallelChartProps = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export interface ParallelOption {
|
|
26
|
-
title?:
|
|
27
|
-
legend?:
|
|
28
|
-
parallel?:
|
|
26
|
+
title?: unknown;
|
|
27
|
+
legend?: unknown;
|
|
28
|
+
parallel?: unknown;
|
|
29
29
|
parallelAxisDefault?: ParallelAxisItem;
|
|
30
|
-
grid?:
|
|
31
|
-
tooltip?:
|
|
30
|
+
grid?: unknown;
|
|
31
|
+
tooltip?: unknown;
|
|
32
32
|
series: ParallelSeriesItem[];
|
|
33
|
-
dataset?:
|
|
33
|
+
dataset?: unknown;
|
|
34
34
|
color?: string[];
|
|
35
|
-
backgroundColor?:
|
|
36
|
-
textStyle?:
|
|
37
|
-
[key: string]:
|
|
35
|
+
backgroundColor?: unknown;
|
|
36
|
+
textStyle?: unknown;
|
|
37
|
+
[key: string]: unknown;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export interface ParallelAxisSetting {
|
|
@@ -55,10 +55,10 @@ export interface ParallelAxisItem {
|
|
|
55
55
|
type?: 'value' | 'category';
|
|
56
56
|
name?: string;
|
|
57
57
|
nameLocation?: 'start' | 'middle' | 'center' | 'end';
|
|
58
|
-
nameTextStyle?:
|
|
58
|
+
nameTextStyle?: unknown;
|
|
59
59
|
nameGap?: number;
|
|
60
60
|
silent?: boolean;
|
|
61
|
-
data?:
|
|
61
|
+
data?: unknown[];
|
|
62
62
|
dimension?: number;
|
|
63
63
|
parallelIndex?: number;
|
|
64
64
|
}
|
|
@@ -66,11 +66,12 @@ export interface ParallelAxisItem {
|
|
|
66
66
|
export interface ParallelSeriesItem {
|
|
67
67
|
type: 'parallel';
|
|
68
68
|
name?: string;
|
|
69
|
-
data?:
|
|
70
|
-
lineStyle?:
|
|
71
|
-
emphasis?:
|
|
69
|
+
data?: unknown[];
|
|
70
|
+
lineStyle?: unknown;
|
|
71
|
+
emphasis?: unknown;
|
|
72
72
|
smooth?: boolean | number;
|
|
73
73
|
symbol?: string;
|
|
74
74
|
symbolSize?: number;
|
|
75
|
-
itemStyle?:
|
|
75
|
+
itemStyle?: unknown;
|
|
76
|
+
[key: string]: unknown;
|
|
76
77
|
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RadarChart 组件测试
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render, screen } from '@testing-library/react';
|
|
6
|
+
import '@testing-library/jest-dom';
|
|
7
|
+
import RadarChart from '../index';
|
|
8
|
+
|
|
9
|
+
// 使用正确的 mock 方式,参考 parallel 图表的测试
|
|
10
|
+
jest.mock('../../common/BaseChartWrapper');
|
|
11
|
+
jest.mock('echarts/charts', () => ({ RadarChart: jest.fn() }));
|
|
12
|
+
|
|
13
|
+
describe('RadarChart', () => {
|
|
14
|
+
const mockIndicators = [
|
|
15
|
+
{ name: '速度', max: 100 },
|
|
16
|
+
{ name: '力量', max: 100 },
|
|
17
|
+
{ name: '技巧', max: 100 },
|
|
18
|
+
{ name: '耐力', max: 100 },
|
|
19
|
+
{ name: '敏捷', max: 100 },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const mockData = [
|
|
23
|
+
{
|
|
24
|
+
name: '角色 A',
|
|
25
|
+
value: [80, 70, 90, 60, 85],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: '角色 B',
|
|
29
|
+
value: [60, 85, 75, 90, 70],
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
it('应该渲染雷达图组件', () => {
|
|
34
|
+
render(<RadarChart indicators={mockIndicators} data={mockData} width={400} height={400} />);
|
|
35
|
+
|
|
36
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('应该传递正确的 option 到 BaseChart', () => {
|
|
40
|
+
render(<RadarChart indicators={mockIndicators} data={mockData} width={400} height={400} />);
|
|
41
|
+
|
|
42
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
43
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
44
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
45
|
+
|
|
46
|
+
expect(option.radar).toBeDefined();
|
|
47
|
+
expect(option.radar.indicator).toHaveLength(5);
|
|
48
|
+
expect(option.series).toHaveLength(2);
|
|
49
|
+
expect(option.series[0].type).toBe('radar');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('应该支持自定义 startAngle', () => {
|
|
53
|
+
render(
|
|
54
|
+
<RadarChart
|
|
55
|
+
indicators={mockIndicators}
|
|
56
|
+
data={mockData}
|
|
57
|
+
startAngle={0}
|
|
58
|
+
width={400}
|
|
59
|
+
height={400}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
64
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
65
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
66
|
+
|
|
67
|
+
expect(option.radar.startAngle).toBe(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('应该支持 areaStyle 配置', () => {
|
|
71
|
+
render(
|
|
72
|
+
<RadarChart
|
|
73
|
+
indicators={mockIndicators}
|
|
74
|
+
data={mockData}
|
|
75
|
+
areaStyle={{
|
|
76
|
+
color: '#ff0000',
|
|
77
|
+
opacity: 0.5,
|
|
78
|
+
}}
|
|
79
|
+
width={400}
|
|
80
|
+
height={400}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
85
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
86
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
87
|
+
|
|
88
|
+
expect(option.series[0].areaStyle).toBeDefined();
|
|
89
|
+
expect(option.series[0].areaStyle.color).toBe('#ff0000');
|
|
90
|
+
expect(option.series[0].areaStyle.opacity).toBe(0.5);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('应该支持 label 配置', () => {
|
|
94
|
+
render(
|
|
95
|
+
<RadarChart
|
|
96
|
+
indicators={mockIndicators}
|
|
97
|
+
data={mockData}
|
|
98
|
+
label={{
|
|
99
|
+
show: true,
|
|
100
|
+
position: 'inside',
|
|
101
|
+
}}
|
|
102
|
+
width={400}
|
|
103
|
+
height={400}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
108
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
109
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
110
|
+
|
|
111
|
+
expect(option.series[0].label.show).toBe(true);
|
|
112
|
+
expect(option.series[0].label.position).toBe('inside');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('应该支持 optionMerge 自定义配置', () => {
|
|
116
|
+
const customTitle = { title: { text: '自定义标题', left: 'center' } };
|
|
117
|
+
|
|
118
|
+
render(
|
|
119
|
+
<RadarChart
|
|
120
|
+
indicators={mockIndicators}
|
|
121
|
+
data={mockData}
|
|
122
|
+
optionMerge={customTitle}
|
|
123
|
+
width={400}
|
|
124
|
+
height={400}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
129
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
130
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
131
|
+
|
|
132
|
+
expect(option.title).toEqual(customTitle.title);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('当 indicators 为空时应该返回 null', () => {
|
|
136
|
+
const { container } = render(
|
|
137
|
+
<RadarChart indicators={[]} data={mockData} width={400} height={400} />
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(container.firstChild).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('当 data 为空时应该返回 null', () => {
|
|
144
|
+
const { container } = render(
|
|
145
|
+
<RadarChart indicators={mockIndicators} data={[]} width={400} height={400} />
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(container.firstChild).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('应该支持多个数据系列对比', () => {
|
|
152
|
+
const multiData = [
|
|
153
|
+
{ name: '系列 1', value: [80, 70, 90, 60, 85] },
|
|
154
|
+
{ name: '系列 2', value: [60, 85, 75, 90, 70] },
|
|
155
|
+
{ name: '系列 3', value: [90, 60, 80, 75, 80] },
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
render(<RadarChart indicators={mockIndicators} data={multiData} width={400} height={400} />);
|
|
159
|
+
|
|
160
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
161
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
162
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
163
|
+
|
|
164
|
+
expect(option.series).toHaveLength(3);
|
|
165
|
+
expect(option.legend.data).toHaveLength(3);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('应该支持自定义 lineStyle', () => {
|
|
169
|
+
render(
|
|
170
|
+
<RadarChart
|
|
171
|
+
indicators={mockIndicators}
|
|
172
|
+
data={mockData}
|
|
173
|
+
lineStyle={{
|
|
174
|
+
width: 3,
|
|
175
|
+
type: 'dashed',
|
|
176
|
+
color: '#ff0000',
|
|
177
|
+
}}
|
|
178
|
+
width={400}
|
|
179
|
+
height={400}
|
|
180
|
+
/>
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
184
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
185
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
186
|
+
|
|
187
|
+
expect(option.series[0].lineStyle.width).toBe(3);
|
|
188
|
+
expect(option.series[0].lineStyle.type).toBe('dashed');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('应该支持 centerCircle 配置', () => {
|
|
192
|
+
render(
|
|
193
|
+
<RadarChart
|
|
194
|
+
indicators={mockIndicators}
|
|
195
|
+
data={mockData}
|
|
196
|
+
centerCircle={true}
|
|
197
|
+
centerCircleSize={0.3}
|
|
198
|
+
width={400}
|
|
199
|
+
height={400}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
204
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
205
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
206
|
+
|
|
207
|
+
expect(option.radar.centerCircle).toBe(true);
|
|
208
|
+
expect(option.radar.centerCircleSize).toBe(0.3);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -1,14 +1,147 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* TaroViz 雷达图组件
|
|
3
|
+
*
|
|
4
|
+
* 基于 ECharts radar 系列实现多指标对比可视化
|
|
3
5
|
*/
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
)
|
|
12
|
-
|
|
6
|
+
import { createOptionChartComponent } from '@/charts/createOptionChartComponent';
|
|
7
|
+
import type { RadarChartProps } from './types';
|
|
8
|
+
// 类型 RadarIndicator、RadarDataItem 通过下方 export type 导出供外部使用
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 构建雷达图 ECharts option
|
|
12
|
+
*/
|
|
13
|
+
function buildRadarOption(props: RadarChartProps) {
|
|
14
|
+
const {
|
|
15
|
+
indicators,
|
|
16
|
+
data,
|
|
17
|
+
startAngle = 90,
|
|
18
|
+
centerCircle = false,
|
|
19
|
+
centerCircleSize = 0,
|
|
20
|
+
areaStyle,
|
|
21
|
+
lineStyle,
|
|
22
|
+
label,
|
|
23
|
+
smooth = false,
|
|
24
|
+
optionMerge,
|
|
25
|
+
} = props;
|
|
26
|
+
|
|
27
|
+
// 验证数据
|
|
28
|
+
if (!indicators || indicators.length === 0) {
|
|
29
|
+
console.warn('[TaroViz] RadarChart: indicators is required');
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!data || data.length === 0) {
|
|
34
|
+
console.warn('[TaroViz] RadarChart: data is required');
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 验证数据维度匹配
|
|
39
|
+
const firstDataItem = data[0];
|
|
40
|
+
if (firstDataItem.value.length !== indicators.length) {
|
|
41
|
+
console.warn(
|
|
42
|
+
`[TaroViz] RadarChart: data value length (${firstDataItem.value.length}) ` +
|
|
43
|
+
`does not match indicators count (${indicators.length})`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 构建雷达图 series
|
|
48
|
+
const series = data.map((item, index) => ({
|
|
49
|
+
type: 'radar' as const,
|
|
50
|
+
data: [item],
|
|
51
|
+
symbol: 'circle' as const,
|
|
52
|
+
symbolSize: 6,
|
|
53
|
+
lineStyle: {
|
|
54
|
+
width: lineStyle?.width ?? 2,
|
|
55
|
+
type: lineStyle?.type ?? ('solid' as const),
|
|
56
|
+
color: lineStyle?.color,
|
|
57
|
+
},
|
|
58
|
+
areaStyle: areaStyle
|
|
59
|
+
? {
|
|
60
|
+
color: areaStyle.color,
|
|
61
|
+
opacity: areaStyle.opacity ?? 0.3,
|
|
62
|
+
}
|
|
63
|
+
: undefined,
|
|
64
|
+
label: label
|
|
65
|
+
? {
|
|
66
|
+
show: label.show ?? false,
|
|
67
|
+
position: label.position ?? ('outside' as const),
|
|
68
|
+
}
|
|
69
|
+
: undefined,
|
|
70
|
+
itemStyle: {
|
|
71
|
+
color: `rgba(54, 157, 255, ${0.1 + index * 0.1})`,
|
|
72
|
+
borderColor: '#369dff',
|
|
73
|
+
borderWidth: 2,
|
|
74
|
+
},
|
|
75
|
+
emphasis: {
|
|
76
|
+
focus: 'series' as const,
|
|
77
|
+
},
|
|
78
|
+
smooth,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
// 构建 indicator 配置
|
|
82
|
+
const indicatorConfig = indicators.map((ind) => ({
|
|
83
|
+
name: ind.name,
|
|
84
|
+
max: ind.max,
|
|
85
|
+
min: ind.min ?? 0,
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
const option: any = {
|
|
89
|
+
radar: {
|
|
90
|
+
indicator: indicatorConfig,
|
|
91
|
+
startAngle,
|
|
92
|
+
center: ['50%', '50%'],
|
|
93
|
+
radius: centerCircle ? `${70 - centerCircleSize * 30}%` : '70%',
|
|
94
|
+
centerCircle,
|
|
95
|
+
centerCircleSize,
|
|
96
|
+
splitNumber: 5,
|
|
97
|
+
axisName: {
|
|
98
|
+
color: '#666',
|
|
99
|
+
fontSize: 12,
|
|
100
|
+
},
|
|
101
|
+
splitLine: {
|
|
102
|
+
lineStyle: {
|
|
103
|
+
color: ['#eee', '#ddd'],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
splitArea: {
|
|
107
|
+
areaStyle: {
|
|
108
|
+
color: ['#fafafa', '#f5f5f5'],
|
|
109
|
+
opacity: 0.5,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
axisLine: {
|
|
113
|
+
lineStyle: {
|
|
114
|
+
color: '#ccc',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
series,
|
|
119
|
+
legend: {
|
|
120
|
+
data: data.map((d, i) => d.name || `系列 ${i + 1}`),
|
|
121
|
+
top: 30,
|
|
122
|
+
},
|
|
123
|
+
tooltip: {
|
|
124
|
+
trigger: 'item' as const,
|
|
125
|
+
formatter: (params: any) => {
|
|
126
|
+
if (!params || !params.data) return '';
|
|
127
|
+
const seriesName = params.seriesName || '未知系列';
|
|
128
|
+
const dataItem = params.data as { value: number[] };
|
|
129
|
+
const values = dataItem.value.map(
|
|
130
|
+
(v: number, i: number) => `${indicators[i]?.name || 'N/A'}: ${v}`
|
|
131
|
+
);
|
|
132
|
+
return `<b>${seriesName}</b><br/>${values.join('<br/>')}`;
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// 合并自定义配置
|
|
138
|
+
if (optionMerge) {
|
|
139
|
+
Object.assign(option, optionMerge);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return option;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const RadarChart = createOptionChartComponent<RadarChartProps>('RadarChart', buildRadarOption);
|
|
13
146
|
|
|
14
147
|
export default RadarChart;
|