@agions/taroviz 1.11.5 → 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 +31 -46
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/vendors.js +1 -1
- package/dist/cjs/vendors~echarts.js +1 -1
- package/dist/esm/index.js +1 -14270
- package/dist/esm/vendors.js +1 -16770
- package/dist/esm/vendors~echarts.js +1 -59417
- package/package.json +10 -15
- package/src/adapters/h5/index.ts +38 -38
- package/src/adapters/index.ts +32 -34
- package/src/adapters/types.ts +23 -55
- package/src/charts/boxplot/types.ts +2 -2
- package/src/charts/common/BaseChartWrapper.tsx +9 -7
- package/src/charts/createChartComponent.tsx +9 -21
- package/src/charts/createOptionChartComponent.tsx +32 -0
- package/src/charts/funnel/__tests__/index.test.tsx +99 -0
- package/src/charts/funnel/index.tsx +64 -0
- package/src/charts/funnel/types.ts +6 -0
- package/src/charts/graph/__tests__/index.test.tsx +116 -0
- package/src/charts/graph/index.tsx +70 -0
- package/src/charts/graph/types.ts +6 -0
- package/src/charts/heatmap/__tests__/index.test.tsx +139 -0
- package/src/charts/heatmap/index.tsx +107 -0
- package/src/charts/heatmap/types.ts +6 -0
- package/src/charts/index.ts +47 -57
- package/src/charts/liquid/__tests__/index.test.tsx +52 -0
- package/src/charts/liquid/index.tsx +7 -133
- package/src/charts/liquid/types.ts +6 -6
- package/src/charts/parallel/types.ts +3 -3
- package/src/charts/radar/__tests__/index.test.tsx +210 -0
- package/src/charts/radar/index.tsx +147 -0
- package/src/charts/radar/types.ts +13 -0
- package/src/charts/sankey/__tests__/index.test.tsx +124 -0
- package/src/charts/sankey/index.tsx +70 -0
- package/src/charts/sankey/types.ts +6 -0
- package/src/charts/tree/__tests__/index.test.tsx +71 -0
- package/src/charts/tree/index.tsx +1 -1
- package/src/charts/tree/types.ts +8 -8
- package/src/charts/types.ts +208 -106
- package/src/charts/wordcloud/__tests__/index.test.tsx +106 -0
- package/src/charts/wordcloud/index.tsx +79 -0
- package/src/charts/wordcloud/types.ts +6 -0
- package/src/components/DataFilter/index.tsx +7 -6
- package/src/core/animation/types.ts +6 -6
- package/src/core/components/Annotation.tsx +6 -6
- package/src/core/components/BaseChart.tsx +97 -133
- package/src/core/components/LazyChart.tsx +3 -8
- package/src/core/components/hooks/index.ts +6 -2
- package/src/core/components/hooks/usePerformance.ts +8 -2
- package/src/core/components/hooks/useVirtualScroll.ts +2 -1
- package/src/core/types/common.ts +2 -1
- package/src/core/types/platform.ts +1 -0
- 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 -36
- package/src/core/utils/deepClone.ts +114 -0
- package/src/core/utils/download.ts +22 -28
- package/src/core/utils/drillDown.ts +1 -0
- package/src/core/utils/events.ts +12 -0
- package/src/core/utils/export/ExportUtils.ts +2 -1
- package/src/core/utils/format.ts +44 -0
- package/src/core/utils/index.ts +18 -159
- package/src/core/utils/merge.ts +25 -0
- package/src/core/utils/performance/PerformanceAnalyzer.ts +3 -1
- 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 +6 -5
- package/src/{hooks → core/utils/performance}/useDataZoom.ts +7 -2
- package/src/{hooks → core/utils/performance}/usePerformance.ts +39 -39
- package/src/{hooks → core/utils/performance}/usePerformanceHooks.ts +39 -39
- package/src/core/utils/runtime.ts +190 -0
- package/src/editor/components/ThemeSelector.tsx +3 -3
- package/src/hooks/chartConnectHelpers.ts +6 -0
- package/src/hooks/index.ts +54 -626
- package/src/hooks/types.ts +27 -0
- package/src/hooks/useChartAutoResize.ts +73 -0
- package/src/hooks/useChartConnect.ts +5 -1
- package/src/hooks/useChartDownload.ts +1 -1
- package/src/hooks/useChartHistory.ts +1 -3
- 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 +23 -12
- package/src/hooks/useChartTheme.ts +51 -0
- package/src/hooks/useDataTransform.ts +19 -4
- package/src/index.ts +5 -10
- 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/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 -449
- package/src/core/utils/debug/DebugPanel.tsx +0 -640
- 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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TaroViz 雷达图组件
|
|
3
|
+
*
|
|
4
|
+
* 基于 ECharts radar 系列实现多指标对比可视化
|
|
5
|
+
*/
|
|
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);
|
|
146
|
+
|
|
147
|
+
export default RadarChart;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SankeyChart 组件测试
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render, screen } from '@testing-library/react';
|
|
6
|
+
import '@testing-library/jest-dom';
|
|
7
|
+
import SankeyChart from '../index';
|
|
8
|
+
|
|
9
|
+
// 使用正确的 mock 方式,参考 parallel 图表的测试
|
|
10
|
+
jest.mock('../../common/BaseChartWrapper');
|
|
11
|
+
jest.mock('echarts/charts', () => ({ SankeyChart: jest.fn() }));
|
|
12
|
+
|
|
13
|
+
describe('SankeyChart', () => {
|
|
14
|
+
const mockNodes = [
|
|
15
|
+
{ name: '节点 A' },
|
|
16
|
+
{ name: '节点 B' },
|
|
17
|
+
{ name: '节点 C' },
|
|
18
|
+
{ name: '节点 D' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const mockLinks = [
|
|
22
|
+
{ source: '节点 A', target: '节点 B', value: 10 },
|
|
23
|
+
{ source: '节点 A', target: '节点 C', value: 20 },
|
|
24
|
+
{ source: '节点 B', target: '节点 D', value: 15 },
|
|
25
|
+
{ source: '节点 C', target: '节点 D', value: 25 },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
it('应该渲染桑基图组件', () => {
|
|
29
|
+
render(<SankeyChart nodes={mockNodes} links={mockLinks} width={800} height={400} />);
|
|
30
|
+
|
|
31
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('应该传递正确的 option 到 BaseChart', () => {
|
|
35
|
+
render(<SankeyChart nodes={mockNodes} links={mockLinks} width={800} height={400} />);
|
|
36
|
+
|
|
37
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
38
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
39
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
40
|
+
|
|
41
|
+
expect(option.series).toBeDefined();
|
|
42
|
+
expect(option.series.type).toBe('sankey');
|
|
43
|
+
expect(option.series.data).toEqual(mockNodes);
|
|
44
|
+
expect(option.series.links).toEqual(mockLinks);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('当 nodes 为空时应该返回 null', () => {
|
|
48
|
+
const { container } = render(
|
|
49
|
+
<SankeyChart nodes={[]} links={mockLinks} width={800} height={400} />
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(container.firstChild).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('当 links 为空时应该返回 null', () => {
|
|
56
|
+
const { container } = render(
|
|
57
|
+
<SankeyChart nodes={mockNodes} links={[]} width={800} height={400} />
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
expect(container.firstChild).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('应该支持自定义 orient', () => {
|
|
64
|
+
render(
|
|
65
|
+
<SankeyChart nodes={mockNodes} links={mockLinks} orient="vertical" width={800} height={400} />
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
69
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
70
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
71
|
+
|
|
72
|
+
expect(option.series.orient).toBe('vertical');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('应该支持自定义 nodeAlign', () => {
|
|
76
|
+
render(
|
|
77
|
+
<SankeyChart
|
|
78
|
+
nodes={mockNodes}
|
|
79
|
+
links={mockLinks}
|
|
80
|
+
nodeAlign="center"
|
|
81
|
+
width={800}
|
|
82
|
+
height={400}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
87
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
88
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
89
|
+
|
|
90
|
+
expect(option.series.nodeAlign).toBe('center');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('应该支持自定义 nodeGap', () => {
|
|
94
|
+
render(
|
|
95
|
+
<SankeyChart nodes={mockNodes} links={mockLinks} nodeGap={15} width={800} height={400} />
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
99
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
100
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
101
|
+
|
|
102
|
+
expect(option.series.nodeGap).toBe(15);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('应该支持 optionMerge 自定义配置', () => {
|
|
106
|
+
const customTitle = { title: { text: '桑基图标题', left: 'center' } };
|
|
107
|
+
|
|
108
|
+
render(
|
|
109
|
+
<SankeyChart
|
|
110
|
+
nodes={mockNodes}
|
|
111
|
+
links={mockLinks}
|
|
112
|
+
optionMerge={customTitle}
|
|
113
|
+
width={800}
|
|
114
|
+
height={400}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const baseChartWrapper = screen.getByTestId('base-chart-wrapper');
|
|
119
|
+
const optionElement = baseChartWrapper.querySelector('[data-testid="chart-option"]');
|
|
120
|
+
const option = JSON.parse(optionElement?.textContent || '{}');
|
|
121
|
+
|
|
122
|
+
expect(option.title).toEqual(customTitle.title);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TaroViz 桑基图组件
|
|
3
|
+
*
|
|
4
|
+
* 基于 ECharts sankey 系列实现桑基图可视化
|
|
5
|
+
*/
|
|
6
|
+
import { createOptionChartComponent } from '@/charts/createOptionChartComponent';
|
|
7
|
+
import type { SankeyChartProps } from './types';
|
|
8
|
+
// 类型 SankeyNode、SankeyLink 通过下方 export type 导出供外部使用
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 构建桑基图 ECharts option
|
|
12
|
+
*/
|
|
13
|
+
function buildSankeyOption(props: SankeyChartProps) {
|
|
14
|
+
const { nodes, links, nodeAlign, nodeGap, nodeWidth, orient, optionMerge } = props;
|
|
15
|
+
|
|
16
|
+
// 验证数据
|
|
17
|
+
if (!nodes || nodes.length === 0) {
|
|
18
|
+
console.warn('[TaroViz] SankeyChart: nodes is required');
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!links || links.length === 0) {
|
|
23
|
+
console.warn('[TaroViz] SankeyChart: links is required');
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 构建桑基图 series
|
|
28
|
+
const series = {
|
|
29
|
+
type: 'sankey' as const,
|
|
30
|
+
layout: 'none',
|
|
31
|
+
data: nodes,
|
|
32
|
+
links: links,
|
|
33
|
+
orient: orient || 'horizontal',
|
|
34
|
+
nodeAlign: nodeAlign || 'left',
|
|
35
|
+
nodeGap: nodeGap || 8,
|
|
36
|
+
nodeWidth: nodeWidth || 20,
|
|
37
|
+
label: {
|
|
38
|
+
show: true,
|
|
39
|
+
position: 'right',
|
|
40
|
+
},
|
|
41
|
+
emphasis: {
|
|
42
|
+
focus: 'adjacency',
|
|
43
|
+
lineStyle: {
|
|
44
|
+
width: 4,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const option: any = {
|
|
50
|
+
tooltip: {
|
|
51
|
+
trigger: 'item',
|
|
52
|
+
formatter: (params: any) => {
|
|
53
|
+
if (!params || !params.data) return '';
|
|
54
|
+
return `<b>${params.data.name}</b>`;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
series,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// 合并自定义配置
|
|
61
|
+
if (optionMerge) {
|
|
62
|
+
Object.assign(option, optionMerge);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return option;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const SankeyChart = createOptionChartComponent<SankeyChartProps>('SankeyChart', buildSankeyOption);
|
|
69
|
+
|
|
70
|
+
export default SankeyChart;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TreeChart 测试
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render, screen } from '@testing-library/react';
|
|
6
|
+
import '@testing-library/jest-dom';
|
|
7
|
+
import TreeChart from '../index';
|
|
8
|
+
|
|
9
|
+
// Mock BaseChartWrapper
|
|
10
|
+
jest.mock('../../common/BaseChartWrapper');
|
|
11
|
+
|
|
12
|
+
describe('TreeChart', () => {
|
|
13
|
+
const mockData = {
|
|
14
|
+
name: 'root',
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
name: 'child1',
|
|
18
|
+
children: [{ name: 'grandchild1' }, { name: 'grandchild2' }],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'child2',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const mockProps = {
|
|
27
|
+
data: [mockData],
|
|
28
|
+
width: 600,
|
|
29
|
+
height: 400,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
it('should render without crashing', () => {
|
|
33
|
+
render(<TreeChart {...mockProps} />);
|
|
34
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should render with correct orientation', () => {
|
|
38
|
+
render(<TreeChart {...mockProps} orient="horizontal" />);
|
|
39
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle different layout types', () => {
|
|
43
|
+
render(<TreeChart {...mockProps} layout="orthogonal" />);
|
|
44
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should handle node gap', () => {
|
|
48
|
+
render(<TreeChart {...mockProps} nodeGap={20} />);
|
|
49
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle initial tree depth', () => {
|
|
53
|
+
render(<TreeChart {...mockProps} initialTreeDepth={2} />);
|
|
54
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should handle label position', () => {
|
|
58
|
+
render(<TreeChart {...mockProps} labelPosition="left" />);
|
|
59
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle line curveness', () => {
|
|
63
|
+
render(<TreeChart {...mockProps} lineCurveness={0.5} />);
|
|
64
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle symbol size', () => {
|
|
68
|
+
render(<TreeChart {...mockProps} symbolSize={10} />);
|
|
69
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
});
|
package/src/charts/tree/types.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 树图类型定义
|
|
3
3
|
* ECharts 内置 tree 类型
|
|
4
4
|
*/
|
|
5
|
-
import type {
|
|
5
|
+
import type { EChartsType, ECElementEvent, EChartsOption } from 'echarts';
|
|
6
6
|
|
|
7
7
|
// ============================================================================
|
|
8
8
|
// 树图数据节点
|
|
@@ -15,7 +15,7 @@ export interface TreeNode {
|
|
|
15
15
|
/** 节点名称 */
|
|
16
16
|
name: string;
|
|
17
17
|
/** 节点值 */
|
|
18
|
-
|
|
18
|
+
_value?: number;
|
|
19
19
|
/** 是否展开 */
|
|
20
20
|
collapsed?: boolean;
|
|
21
21
|
/** 图标 */
|
|
@@ -49,7 +49,7 @@ export interface TreeSeries {
|
|
|
49
49
|
/** 系列名称 */
|
|
50
50
|
name?: string;
|
|
51
51
|
/** 数据数组 (树的根节点) */
|
|
52
|
-
|
|
52
|
+
_data?: TreeNode[];
|
|
53
53
|
/** 树的布局: 'orthogonal' | 'radial' */
|
|
54
54
|
layout?: string;
|
|
55
55
|
/** 树的方向: 'horizontal' | 'vertical' */
|
|
@@ -67,7 +67,7 @@ export interface TreeSeries {
|
|
|
67
67
|
/** 标签对齐 */
|
|
68
68
|
labelAlign?: 'left' | 'right' | 'center';
|
|
69
69
|
/** 标签格式化 */
|
|
70
|
-
labelFormatter?: string | ((
|
|
70
|
+
labelFormatter?: string | ((_value: number, _data: TreeNode) => string);
|
|
71
71
|
/** 线条样式 */
|
|
72
72
|
lineStyle?: Record<string, unknown>;
|
|
73
73
|
/** 是否显示连接线 */
|
|
@@ -95,7 +95,7 @@ export interface TreeSeries {
|
|
|
95
95
|
/** 动画缓动函数 */
|
|
96
96
|
animationEasing?: string;
|
|
97
97
|
/** 动画延迟 */
|
|
98
|
-
animationDelay?: number | ((
|
|
98
|
+
animationDelay?: number | ((_idx: number) => number);
|
|
99
99
|
/** 动画更新时长 */
|
|
100
100
|
animationDurationUpdate?: number;
|
|
101
101
|
/** 动画更新缓动函数 */
|
|
@@ -166,9 +166,9 @@ export interface TreeChartProps {
|
|
|
166
166
|
/** 加载配置 */
|
|
167
167
|
loadingOption?: Record<string, unknown>;
|
|
168
168
|
/** 图表初始化回调 */
|
|
169
|
-
onChartInit?: (
|
|
169
|
+
onChartInit?: (_chart: EChartsType) => void;
|
|
170
170
|
/** 图表就绪回调 */
|
|
171
|
-
onChartReady?: (
|
|
171
|
+
onChartReady?: (_chart: EChartsType) => void;
|
|
172
172
|
/** 事件回调 */
|
|
173
|
-
onEvents?: Record<string, (
|
|
173
|
+
onEvents?: Record<string, (_params: ECElementEvent) => void>;
|
|
174
174
|
}
|