@agions/taroviz 1.5.0 → 1.7.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.
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ import React from 'react';
5
+ import { render, screen } from '@testing-library/react';
6
+ import '@testing-library/jest-dom';
7
+ import ParallelChart 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="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
+ }));
45
+
46
+ describe('ParallelChart', () => {
47
+ const basicOption = {
48
+ title: { text: '平行坐标图测试' },
49
+ parallel: {
50
+ left: '5%',
51
+ right: '10%',
52
+ bottom: '10%',
53
+ top: '20%',
54
+ height: '50%',
55
+ },
56
+ parallelAxisDefault: {
57
+ type: 'value' as const,
58
+ name: '指标',
59
+ },
60
+ series: [{
61
+ type: 'parallel' as const,
62
+ lineStyle: { width: 2, opacity: 0.5 },
63
+ data: [
64
+ [1, 55, 9, 56, 0.46, 2, 35],
65
+ [2, 25, 11, 21, 0.65, 2, 33],
66
+ [3, 56, 7, 63, 0.92, 3, 45],
67
+ ],
68
+ }],
69
+ };
70
+
71
+ describe('Basic Rendering', () => {
72
+ it('should render without crashing', () => {
73
+ render(<ParallelChart option={basicOption} />);
74
+ expect(screen.getByTestId('parallel-chart')).toBeInTheDocument();
75
+ });
76
+
77
+ it('should render with custom width and height', () => {
78
+ render(<ParallelChart option={basicOption} width={600} height={500} />);
79
+ expect(screen.getByTestId('parallel-chart')).toBeInTheDocument();
80
+ });
81
+
82
+ it('should have correct display name', () => {
83
+ expect(ParallelChart.displayName).toBe('ParallelChart');
84
+ });
85
+
86
+ it('should pass option to wrapper', () => {
87
+ render(<ParallelChart option={basicOption} />);
88
+ expect(screen.getByTestId('chart-option')).toHaveTextContent('平行坐标图测试');
89
+ });
90
+ });
91
+
92
+ describe('Props', () => {
93
+ it('should accept className prop', () => {
94
+ render(<ParallelChart option={basicOption} className="test-class" />);
95
+ expect(screen.getByTestId('parallel-chart')).toHaveClass('test-class');
96
+ });
97
+
98
+ it('should accept style prop', () => {
99
+ const style = { padding: '10px' };
100
+ render(<ParallelChart option={basicOption} style={style} />);
101
+ expect(screen.getByTestId('parallel-chart')).toBeInTheDocument();
102
+ });
103
+
104
+ it('should accept loading prop', () => {
105
+ render(<ParallelChart option={basicOption} loading={true} />);
106
+ expect(screen.getByTestId('parallel-chart')).toBeInTheDocument();
107
+ });
108
+
109
+ it('should accept theme prop', () => {
110
+ render(<ParallelChart option={basicOption} theme="dark" />);
111
+ expect(screen.getByTestId('parallel-chart')).toBeInTheDocument();
112
+ });
113
+ });
114
+
115
+ describe('Chart Options', () => {
116
+ it('should render with expandable axis', () => {
117
+ const expandableOption = {
118
+ ...basicOption,
119
+ parallel: {
120
+ ...basicOption.parallel,
121
+ axisExpandable: true,
122
+ axisExpandCenter: 3,
123
+ axisExpandCount: 3,
124
+ },
125
+ };
126
+ render(<ParallelChart option={expandableOption} />);
127
+ expect(screen.getByTestId('parallel-chart')).toBeInTheDocument();
128
+ });
129
+
130
+ it('should render with custom lineStyle', () => {
131
+ const customLineOption = {
132
+ ...basicOption,
133
+ series: [{
134
+ type: 'parallel' as const,
135
+ lineStyle: { width: 3, color: '#1890ff', opacity: 0.8 },
136
+ data: [[1, 55, 9, 56, 0.46, 2, 35]],
137
+ }],
138
+ };
139
+ render(<ParallelChart option={customLineOption} />);
140
+ expect(screen.getByTestId('parallel-chart')).toBeInTheDocument();
141
+ });
142
+
143
+ it('should render with category axis', () => {
144
+ const categoryOption = {
145
+ title: { text: '分类轴测试' },
146
+ parallel: { left: '5%', right: '10%', bottom: '10%', top: '20%' },
147
+ parallelAxisDefault: {
148
+ type: 'category' as const,
149
+ data: ['A', 'B', 'C', 'D', 'E'],
150
+ name: '分类',
151
+ },
152
+ series: [{
153
+ type: 'parallel' as const,
154
+ data: [
155
+ ['A', 55, 9, 56, 0.46, 2, 35],
156
+ ['B', 25, 11, 21, 0.65, 2, 33],
157
+ ],
158
+ }],
159
+ };
160
+ render(<ParallelChart option={categoryOption} />);
161
+ expect(screen.getByTestId('parallel-chart')).toBeInTheDocument();
162
+ });
163
+ });
164
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 平行坐标图组件
3
+ * 用于展示高维数据各维度之间的关系
4
+ */
5
+ import React, { memo } from 'react';
6
+ import BaseChartWrapper from '../common/BaseChartWrapper';
7
+ import { ParallelChartProps } from './types';
8
+
9
+ const ParallelChart: React.FC<ParallelChartProps> = memo((props) => (
10
+ <BaseChartWrapper {...props} chartType="parallel" />
11
+ ));
12
+
13
+ ParallelChart.displayName = 'ParallelChart';
14
+
15
+ export default ParallelChart;
16
+
17
+ // 导出类型
18
+ export type { ParallelChartProps, ParallelOption, ParallelAxisSetting } from './types';
@@ -0,0 +1,73 @@
1
+ /**
2
+ * 平行坐标图类型定义
3
+ */
4
+
5
+ export type ParallelChartProps = {
6
+ option?: ParallelOption;
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
+ };
20
+ };
21
+
22
+ export interface ParallelOption {
23
+ title?: any;
24
+ legend?: any;
25
+ parallel?: ParallelAxisSetting;
26
+ parallelAxisDefault?: ParallelAxisItem;
27
+ grid?: any;
28
+ tooltip?: any;
29
+ series: ParallelSeriesItem[];
30
+ dataset?: any;
31
+ color?: string[];
32
+ backgroundColor?: any;
33
+ textStyle?: any;
34
+ [key: string]: any;
35
+ }
36
+
37
+ export interface ParallelAxisSetting {
38
+ left?: number | string;
39
+ right?: number | string;
40
+ top?: number | string;
41
+ bottom?: number | string;
42
+ width?: number | string;
43
+ height?: number | string;
44
+ axisExpandable?: boolean;
45
+ axisExpandCenter?: number;
46
+ axisExpandCount?: number;
47
+ axisExpandWidth?: number;
48
+ z?: number;
49
+ }
50
+
51
+ export interface ParallelAxisItem {
52
+ type?: 'value' | 'category';
53
+ name?: string;
54
+ nameLocation?: 'start' | 'middle' | 'center' | 'end';
55
+ nameTextStyle?: any;
56
+ nameGap?: number;
57
+ silent?: boolean;
58
+ data?: any[];
59
+ dimension?: number;
60
+ parallelIndex?: number;
61
+ }
62
+
63
+ export interface ParallelSeriesItem {
64
+ type: 'parallel';
65
+ name?: string;
66
+ data?: any[];
67
+ lineStyle?: any;
68
+ emphasis?: any;
69
+ smooth?: boolean | number;
70
+ symbol?: string;
71
+ symbolSize?: number;
72
+ itemStyle?: any;
73
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * 树图组件
3
+ * ECharts 内置 tree 类型
4
+ */
5
+ import React, { memo, useMemo } from 'react';
6
+ import BaseChartWrapper from '../common/BaseChartWrapper';
7
+ import { TreeChartProps, TreeSeries } from './types';
8
+
9
+ /**
10
+ * 树图组件
11
+ */
12
+ const TreeChart: React.FC<TreeChartProps> = memo((props) => {
13
+ const {
14
+ option,
15
+ treeData,
16
+ layout = 'orthogonal',
17
+ orient = 'horizontal',
18
+ nodeGap,
19
+ nodeWidth,
20
+ initialTreeDepth,
21
+ symbol,
22
+ symbolSize,
23
+ showLine = true,
24
+ lineCurveness,
25
+ label,
26
+ labelPosition,
27
+ labelAlign,
28
+ lineStyle,
29
+ itemStyle,
30
+ ...restProps
31
+ } = props;
32
+
33
+ // 构建树图配置
34
+ const mergedOption = useMemo(() => {
35
+ const baseOption = option || {};
36
+
37
+ // 如果用户提供了 series,直接返回
38
+ if (baseOption.series && baseOption.series.length > 0) {
39
+ return baseOption;
40
+ }
41
+
42
+ // 构建树图 series
43
+ const seriesConfig: TreeSeries = {
44
+ type: 'tree',
45
+ layout,
46
+ orient,
47
+ data: treeData,
48
+ showLine,
49
+ };
50
+
51
+ // 添加可选参数
52
+ if (nodeGap !== undefined) {
53
+ seriesConfig.nodeGap = nodeGap;
54
+ }
55
+ if (nodeWidth !== undefined) {
56
+ seriesConfig.nodeWidth = nodeWidth;
57
+ }
58
+ if (initialTreeDepth !== undefined) {
59
+ seriesConfig.initialTreeDepth = initialTreeDepth;
60
+ }
61
+ if (symbol !== undefined) {
62
+ seriesConfig.symbol = symbol;
63
+ }
64
+ if (symbolSize !== undefined) {
65
+ seriesConfig.symbolSize = symbolSize;
66
+ }
67
+ if (lineCurveness !== undefined) {
68
+ seriesConfig.lineCurveness = lineCurveness;
69
+ }
70
+ if (label !== undefined) {
71
+ seriesConfig.label = label;
72
+ }
73
+ if (labelPosition !== undefined) {
74
+ seriesConfig.labelPosition = labelPosition;
75
+ }
76
+ if (labelAlign !== undefined) {
77
+ seriesConfig.labelAlign = labelAlign;
78
+ }
79
+ if (lineStyle !== undefined) {
80
+ seriesConfig.lineStyle = lineStyle;
81
+ }
82
+ if (itemStyle !== undefined) {
83
+ seriesConfig.itemStyle = itemStyle;
84
+ }
85
+
86
+ return {
87
+ ...baseOption,
88
+ series: [seriesConfig],
89
+ };
90
+ }, [
91
+ option,
92
+ treeData,
93
+ layout,
94
+ orient,
95
+ nodeGap,
96
+ nodeWidth,
97
+ initialTreeDepth,
98
+ symbol,
99
+ symbolSize,
100
+ showLine,
101
+ lineCurveness,
102
+ label,
103
+ labelPosition,
104
+ labelAlign,
105
+ lineStyle,
106
+ itemStyle,
107
+ ]);
108
+
109
+ return <BaseChartWrapper {...restProps} option={mergedOption as any} chartType="tree" />;
110
+ });
111
+
112
+ TreeChart.displayName = 'TreeChart';
113
+
114
+ export default TreeChart;
115
+
116
+ // 导出类型
117
+ export type { TreeChartProps, TreeOption, TreeNode, TreeSeries } from './types';
@@ -0,0 +1,174 @@
1
+ /**
2
+ * 树图类型定义
3
+ * ECharts 内置 tree 类型
4
+ */
5
+ import type { EChartsOption } from 'echarts';
6
+
7
+ // ============================================================================
8
+ // 树图数据节点
9
+ // ============================================================================
10
+
11
+ /**
12
+ * 树图数据节点
13
+ */
14
+ export interface TreeNode {
15
+ /** 节点名称 */
16
+ name: string;
17
+ /** 节点值 */
18
+ value?: number;
19
+ /** 是否展开 */
20
+ collapsed?: boolean;
21
+ /** 图标 */
22
+ icon?: string;
23
+ /** 图形 */
24
+ symbol?: string;
25
+ /** 图形大小 */
26
+ symbolSize?: number | [number, number];
27
+ /** 标签配置 */
28
+ label?: Record<string, unknown>;
29
+ /** 元素样式 */
30
+ itemStyle?: Record<string, unknown>;
31
+ /** emphasis状态 */
32
+ emphasis?: Record<string, unknown>;
33
+ /** 线条样式 */
34
+ lineStyle?: Record<string, unknown>;
35
+ /** 子节点 */
36
+ children?: TreeNode[];
37
+ /** 其他自定义属性 */
38
+ [key: string]: any;
39
+ }
40
+
41
+ // ============================================================================
42
+ // 树图系列配置
43
+ // ============================================================================
44
+
45
+ /** 树图系列配置 */
46
+ export interface TreeSeries {
47
+ /** 系列类型 */
48
+ type?: 'tree';
49
+ /** 系列名称 */
50
+ name?: string;
51
+ /** 数据数组 (树的根节点) */
52
+ data?: TreeNode[];
53
+ /** 树的布局: 'orthogonal' | 'radial' */
54
+ layout?: string;
55
+ /** 树的方向: 'horizontal' | 'vertical' */
56
+ orient?: string;
57
+ /** 节点之间的间距 */
58
+ nodeGap?: number;
59
+ /** 节点的宽度 */
60
+ nodeWidth?: number;
61
+ /** 展开层级 */
62
+ initialTreeDepth?: number;
63
+ /** 标签配置 */
64
+ label?: Record<string, unknown>;
65
+ /** 标签位置 */
66
+ labelPosition?: 'left' | 'right' | 'top' | 'bottom';
67
+ /** 标签对齐 */
68
+ labelAlign?: 'left' | 'right' | 'center';
69
+ /** 标签格式化 */
70
+ labelFormatter?: string | ((value: number, data: TreeNode) => string);
71
+ /** 线条样式 */
72
+ lineStyle?: Record<string, unknown>;
73
+ /** 是否显示连接线 */
74
+ showLine?: boolean;
75
+ /** 连接线的弯曲度 */
76
+ lineCurveness?: number;
77
+ /** 树节点图形 */
78
+ symbol?: string;
79
+ /** 树节点大小 */
80
+ symbolSize?: number | [number, number];
81
+ /** 图形内标签 */
82
+ innerLabel?: Record<string, unknown>;
83
+ /** 元素样式 */
84
+ itemStyle?: Record<string, unknown>;
85
+ /** emphasis状态 */
86
+ emphasis?: Record<string, unknown>;
87
+ /** 聚焦状态 */
88
+ focus?: Record<string, unknown>;
89
+ /** leaves配置 */
90
+ leaves?: Record<string, unknown>;
91
+ /** 动画配置 */
92
+ animation?: boolean;
93
+ /** 动画时长 */
94
+ animationDuration?: number;
95
+ /** 动画缓动函数 */
96
+ animationEasing?: string;
97
+ /** 动画延迟 */
98
+ animationDelay?: number | ((idx: number) => number);
99
+ /** 动画更新时长 */
100
+ animationDurationUpdate?: number;
101
+ /** 动画更新缓动函数 */
102
+ animationEasingUpdate?: string;
103
+ }
104
+
105
+ /** 树图选项配置 */
106
+ export interface TreeOption extends Omit<EChartsOption, 'series'> {
107
+ series?: TreeSeries[];
108
+ }
109
+
110
+ // ============================================================================
111
+ // 组件 Props
112
+ // ============================================================================
113
+
114
+ /**
115
+ * 树图组件属性
116
+ */
117
+ export interface TreeChartProps {
118
+ /** 图表配置项 (EChartsOption) */
119
+ option?: TreeOption;
120
+ /** 宽度 */
121
+ width?: string | number;
122
+ /** 高度 */
123
+ height?: string | number;
124
+ /** 树形数据 */
125
+ treeData?: TreeNode[];
126
+ /** 树的布局: 'orthogonal' (直角树) | 'radial' (辐射树) */
127
+ layout?: 'orthogonal' | 'radial';
128
+ /** 树的方向: 'horizontal' (水平) | 'vertical' (垂直) */
129
+ orient?: 'horizontal' | 'vertical';
130
+ /** 节点之间的间距 */
131
+ nodeGap?: number;
132
+ /** 节点的宽度 */
133
+ nodeWidth?: number;
134
+ /** 展开层级 */
135
+ initialTreeDepth?: number;
136
+ /** 节点图形 */
137
+ symbol?: string;
138
+ /** 节点大小 */
139
+ symbolSize?: number | [number, number];
140
+ /** 是否显示连接线 */
141
+ showLine?: boolean;
142
+ /** 连接线的弯曲度 */
143
+ lineCurveness?: number;
144
+ /** 标签配置 */
145
+ label?: Record<string, unknown>;
146
+ /** 标签位置 */
147
+ labelPosition?: 'left' | 'right' | 'top' | 'bottom';
148
+ /** 标签对齐 */
149
+ labelAlign?: 'left' | 'right' | 'center';
150
+ /** 线条样式 */
151
+ lineStyle?: Record<string, unknown>;
152
+ /** 元素样式 */
153
+ itemStyle?: Record<string, unknown>;
154
+ /** 主题 */
155
+ theme?: string | Record<string, unknown>;
156
+ /** 样式 */
157
+ style?: React.CSSProperties;
158
+ /** 类名 */
159
+ className?: string;
160
+ /** 是否自动调整大小 */
161
+ autoResize?: boolean;
162
+ /** 渲染器类型 */
163
+ renderer?: 'canvas' | 'svg';
164
+ /** 加载状态 */
165
+ loading?: boolean;
166
+ /** 加载配置 */
167
+ loadingOption?: Record<string, unknown>;
168
+ /** 图表初始化回调 */
169
+ onChartInit?: (chart: any) => void;
170
+ /** 图表就绪回调 */
171
+ onChartReady?: (chart: any) => void;
172
+ /** 事件回调 */
173
+ onEvents?: Record<string, (params: any) => void>;
174
+ }