@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
package/src/charts/index.ts
CHANGED
|
@@ -3,28 +3,15 @@
|
|
|
3
3
|
* 使用工厂函数消除重复代码
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createChartComponent
|
|
6
|
+
import { createChartComponent } from './createChartComponent';
|
|
7
7
|
import type {
|
|
8
|
-
BaseChartProps,
|
|
9
8
|
LineChartProps,
|
|
10
9
|
BarChartProps,
|
|
11
10
|
PieChartProps,
|
|
12
11
|
ScatterChartProps,
|
|
13
|
-
RadarChartProps,
|
|
14
|
-
FunnelChartProps,
|
|
15
|
-
GaugeChartProps,
|
|
16
|
-
HeatmapChartProps,
|
|
17
|
-
SunburstChartProps,
|
|
18
12
|
TreeMapChartProps,
|
|
19
|
-
|
|
20
|
-
GraphChartProps,
|
|
21
|
-
WordCloudChartProps,
|
|
22
|
-
CandlestickChartProps,
|
|
13
|
+
SunburstChartProps,
|
|
23
14
|
} from './types';
|
|
24
|
-
import type { BoxplotChartProps } from './boxplot/types';
|
|
25
|
-
import type { ParallelChartProps } from './parallel/types';
|
|
26
|
-
import type { LiquidChartProps } from './liquid/types';
|
|
27
|
-
import type { TreeChartProps } from './tree/types';
|
|
28
15
|
|
|
29
16
|
// ===== 标准图表(用工厂函数创建)=====
|
|
30
17
|
|
|
@@ -36,13 +23,6 @@ export const ScatterChart = createChartComponent<ScatterChartProps>(
|
|
|
36
23
|
'ScatterChart',
|
|
37
24
|
'scatter-chart'
|
|
38
25
|
);
|
|
39
|
-
export const RadarChart = createChartComponent<RadarChartProps>('RadarChart', 'radar-chart');
|
|
40
|
-
export const HeatmapChart = createChartComponent<HeatmapChartProps>(
|
|
41
|
-
'HeatmapChart',
|
|
42
|
-
'heatmap-chart'
|
|
43
|
-
);
|
|
44
|
-
export const GaugeChart = createChartComponent<GaugeChartProps>('GaugeChart', 'gauge-chart');
|
|
45
|
-
export const FunnelChart = createChartComponent<FunnelChartProps>('FunnelChart', 'funnel-chart');
|
|
46
26
|
|
|
47
27
|
/** 扩展图表 */
|
|
48
28
|
export const TreeMapChart = createChartComponent<TreeMapChartProps>(
|
|
@@ -53,45 +33,55 @@ export const SunburstChart = createChartComponent<SunburstChartProps>(
|
|
|
53
33
|
'SunburstChart',
|
|
54
34
|
'sunburst-chart'
|
|
55
35
|
);
|
|
56
|
-
export const SankeyChart = createChartComponent<SankeyChartProps>('SankeyChart', 'sankey-chart');
|
|
57
|
-
export const GraphChart = createChartComponent<GraphChartProps>('GraphChart', 'graph-chart');
|
|
58
|
-
export const WordCloudChart = createChartComponent<WordCloudChartProps>(
|
|
59
|
-
'WordCloudChart',
|
|
60
|
-
'wordcloud-chart'
|
|
61
|
-
);
|
|
62
|
-
export const CandlestickChart = createChartComponent<CandlestickChartProps>(
|
|
63
|
-
'CandlestickChart',
|
|
64
|
-
'candlestick-chart'
|
|
65
|
-
);
|
|
66
36
|
|
|
67
|
-
|
|
68
|
-
export const BoxplotChart = createChartComponentWithOptionCast<BoxplotChartProps>(
|
|
69
|
-
'BoxplotChart',
|
|
70
|
-
'boxplot'
|
|
71
|
-
);
|
|
72
|
-
export const ParallelChart = createChartComponentWithOptionCast<ParallelChartProps>(
|
|
73
|
-
'ParallelChart',
|
|
74
|
-
'parallel'
|
|
75
|
-
);
|
|
37
|
+
// ===== 特殊图表(自定义实现)=====
|
|
76
38
|
|
|
77
|
-
|
|
39
|
+
/** 热力图 - 使用自定义实现 */
|
|
40
|
+
export { default as HeatmapChart } from './heatmap';
|
|
78
41
|
|
|
79
|
-
|
|
42
|
+
/** 漏斗图 - 使用自定义实现 */
|
|
43
|
+
export { default as FunnelChart } from './funnel';
|
|
44
|
+
|
|
45
|
+
/** 平行坐标图 - 使用自定义实现 */
|
|
46
|
+
export { default as ParallelChart } from './parallel';
|
|
47
|
+
|
|
48
|
+
/** 箱线图 - 使用自定义实现 */
|
|
49
|
+
export { default as BoxplotChart } from './boxplot';
|
|
50
|
+
|
|
51
|
+
/** 树图 - 使用自定义实现 */
|
|
80
52
|
export { default as TreeChart } from './tree';
|
|
81
53
|
|
|
82
|
-
|
|
54
|
+
/** 雷达图 - 使用自定义实现 */
|
|
55
|
+
export { default as RadarChartCustom } from './radar';
|
|
56
|
+
|
|
57
|
+
/** 关系图 - 使用自定义实现 */
|
|
58
|
+
export { default as GraphChart } from './graph';
|
|
59
|
+
|
|
60
|
+
/** 桑基图 - 使用自定义实现 */
|
|
61
|
+
export { default as SankeyChart } from './sankey';
|
|
62
|
+
|
|
63
|
+
/** 词云图 - 使用自定义实现 */
|
|
64
|
+
export { default as WordCloudChart } from './wordcloud';
|
|
65
|
+
|
|
66
|
+
// ===== 类型导出 =====
|
|
83
67
|
|
|
84
|
-
export * from './types';
|
|
85
|
-
export type { BoxplotChartProps, BoxplotOption, BoxplotSeriesItem } from './boxplot/types';
|
|
86
|
-
export type { ParallelChartProps, ParallelOption, ParallelAxisSetting } from './parallel/types';
|
|
87
68
|
export type {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
69
|
+
BaseChartProps,
|
|
70
|
+
LineChartProps,
|
|
71
|
+
BarChartProps,
|
|
72
|
+
PieChartProps,
|
|
73
|
+
ScatterChartProps,
|
|
74
|
+
RadarChartProps,
|
|
75
|
+
FunnelChartProps,
|
|
76
|
+
HeatmapChartProps,
|
|
77
|
+
SunburstChartProps,
|
|
78
|
+
TreeMapChartProps,
|
|
79
|
+
SankeyChartProps,
|
|
80
|
+
GraphChartProps,
|
|
81
|
+
WordCloudChartProps,
|
|
82
|
+
BoxplotChartProps,
|
|
83
|
+
TreeChartProps,
|
|
84
|
+
} from './types';
|
|
85
|
+
|
|
86
|
+
// 特殊图表类型(从独立目录导出)
|
|
87
|
+
export type { ParallelChartProps } from './parallel/types';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiquidChart 测试
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render, screen } from '@testing-library/react';
|
|
6
|
+
import LiquidChart from '../index';
|
|
7
|
+
|
|
8
|
+
// Mock BaseChartWrapper
|
|
9
|
+
jest.mock('../../common/BaseChartWrapper');
|
|
10
|
+
|
|
11
|
+
describe('LiquidChart', () => {
|
|
12
|
+
const mockProps = {
|
|
13
|
+
width: 200,
|
|
14
|
+
height: 200,
|
|
15
|
+
waveData: [0.6],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
it('should render without crashing', () => {
|
|
19
|
+
render(<LiquidChart {...mockProps} />);
|
|
20
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should render with different wave data', () => {
|
|
24
|
+
render(<LiquidChart {...mockProps} waveData={[0.3, 0.5, 0.7]} />);
|
|
25
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should handle shape prop', () => {
|
|
29
|
+
render(<LiquidChart {...mockProps} shape="rect" />);
|
|
30
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should handle amplitude prop', () => {
|
|
34
|
+
render(<LiquidChart {...mockProps} amplitude={50} />);
|
|
35
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should handle color prop', () => {
|
|
39
|
+
render(<LiquidChart {...mockProps} color={['#ff0000', '#00ff00']} />);
|
|
40
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should handle backgroundColor prop', () => {
|
|
44
|
+
render(<LiquidChart {...mockProps} backgroundColor="#f0f0f0" />);
|
|
45
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle showLabel prop', () => {
|
|
49
|
+
render(<LiquidChart {...mockProps} showLabel={true} />);
|
|
50
|
+
expect(screen.getByTestId('base-chart-wrapper')).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -4,18 +4,11 @@
|
|
|
4
4
|
* 使用 ECharts 5.x 自定义系列实现水球图功能,
|
|
5
5
|
* 不依赖有 zrender 兼容性问题的 echarts-liquidfill。
|
|
6
6
|
*/
|
|
7
|
-
import React, { memo,
|
|
8
|
-
import type {
|
|
9
|
-
EChartsType,
|
|
10
|
-
ECElementEvent,
|
|
11
|
-
EChartsOption,
|
|
12
|
-
CustomSeriesRenderItem,
|
|
13
|
-
CustomSeriesRenderItemReturn,
|
|
14
|
-
} from 'echarts';
|
|
15
|
-
import { getAdapter } from '../../adapters';
|
|
16
|
-
import { uuid } from '../../core/utils';
|
|
17
|
-
import { processAdapterConfig } from '../utils';
|
|
7
|
+
import React, { memo, useMemo } from 'react';
|
|
8
|
+
import type { EChartsOption, CustomSeriesRenderItem, CustomSeriesRenderItemReturn } from 'echarts';
|
|
18
9
|
import { LiquidChartProps } from './types';
|
|
10
|
+
import type { BaseChartProps } from '../types';
|
|
11
|
+
import BaseChartWrapper from '../common/BaseChartWrapper';
|
|
19
12
|
|
|
20
13
|
/** 包装容器的最大半径(px),用于归一化半径 */
|
|
21
14
|
const MAX_RADIUS = 100;
|
|
@@ -216,18 +209,6 @@ function buildLiquidOption(props: {
|
|
|
216
209
|
const LiquidChart: React.FC<LiquidChartProps> = memo((props) => {
|
|
217
210
|
const {
|
|
218
211
|
option,
|
|
219
|
-
width = '100%',
|
|
220
|
-
height = '300px',
|
|
221
|
-
theme,
|
|
222
|
-
style = {},
|
|
223
|
-
className = '',
|
|
224
|
-
autoResize = true,
|
|
225
|
-
loading = false,
|
|
226
|
-
loadingOption,
|
|
227
|
-
onChartInit,
|
|
228
|
-
onChartReady,
|
|
229
|
-
renderer = 'canvas',
|
|
230
|
-
onEvents = {},
|
|
231
212
|
waveData = [0.6],
|
|
232
213
|
shape,
|
|
233
214
|
amplitude,
|
|
@@ -238,13 +219,9 @@ const LiquidChart: React.FC<LiquidChartProps> = memo((props) => {
|
|
|
238
219
|
color,
|
|
239
220
|
showLabel = true,
|
|
240
221
|
labelFormatter,
|
|
222
|
+
...rest
|
|
241
223
|
} = props;
|
|
242
224
|
|
|
243
|
-
const chartId = useRef<string>(`liquid-${uuid()}`);
|
|
244
|
-
const chartInstance = useRef<EChartsType | null>(null);
|
|
245
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
246
|
-
|
|
247
|
-
// 构建 ECharts 配置
|
|
248
225
|
const liquidOption = useMemo((): EChartsOption => {
|
|
249
226
|
const baseOption = (option || {}) as EChartsOption;
|
|
250
227
|
|
|
@@ -286,113 +263,10 @@ const LiquidChart: React.FC<LiquidChartProps> = memo((props) => {
|
|
|
286
263
|
labelFormatter,
|
|
287
264
|
]);
|
|
288
265
|
|
|
289
|
-
|
|
290
|
-
useEffect(() => {
|
|
291
|
-
let mounted = true;
|
|
292
|
-
|
|
293
|
-
const initChart = async (): Promise<(() => void) | undefined> => {
|
|
294
|
-
if (!mounted || !containerRef.current) return undefined;
|
|
295
|
-
|
|
296
|
-
const initConfig = processAdapterConfig({
|
|
297
|
-
canvasId: chartId.current,
|
|
298
|
-
containerRef,
|
|
299
|
-
width,
|
|
300
|
-
height,
|
|
301
|
-
theme,
|
|
302
|
-
autoResize,
|
|
303
|
-
renderer,
|
|
304
|
-
option: liquidOption,
|
|
305
|
-
onInit: (instance: EChartsType) => {
|
|
306
|
-
chartInstance.current = instance;
|
|
307
|
-
|
|
308
|
-
// 绑定事件
|
|
309
|
-
if (onEvents) {
|
|
310
|
-
Object.entries(onEvents).forEach(([eventName, handler]) => {
|
|
311
|
-
(
|
|
312
|
-
instance as unknown as { on: (e: string, h: (ev: ECElementEvent) => void) => void }
|
|
313
|
-
).on(eventName, handler);
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (onChartInit) {
|
|
318
|
-
onChartInit(instance);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (onChartReady) {
|
|
322
|
-
onChartReady(instance);
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
const adapter = await getAdapter(initConfig);
|
|
328
|
-
adapter.init();
|
|
329
|
-
|
|
330
|
-
return () => {
|
|
331
|
-
const instance = chartInstance.current;
|
|
332
|
-
if (instance) {
|
|
333
|
-
if (onEvents) {
|
|
334
|
-
Object.keys(onEvents).forEach((eventName) => {
|
|
335
|
-
instance.off(eventName);
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
instance.dispose();
|
|
339
|
-
chartInstance.current = null;
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
let cleanupFn: (() => void) | undefined;
|
|
345
|
-
initChart().then((cleanup) => {
|
|
346
|
-
cleanupFn = cleanup;
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
return () => {
|
|
350
|
-
mounted = false;
|
|
351
|
-
cleanupFn?.();
|
|
352
|
-
};
|
|
353
|
-
}, [
|
|
354
|
-
liquidOption,
|
|
355
|
-
width,
|
|
356
|
-
height,
|
|
357
|
-
theme,
|
|
358
|
-
autoResize,
|
|
359
|
-
renderer,
|
|
360
|
-
onChartInit,
|
|
361
|
-
onChartReady,
|
|
362
|
-
onEvents,
|
|
363
|
-
]);
|
|
364
|
-
|
|
365
|
-
// 更新配置
|
|
366
|
-
useEffect(() => {
|
|
367
|
-
if (chartInstance.current && liquidOption) {
|
|
368
|
-
chartInstance.current.setOption(liquidOption, true);
|
|
369
|
-
}
|
|
370
|
-
}, [liquidOption]);
|
|
371
|
-
|
|
372
|
-
// 控制加载状态
|
|
373
|
-
useEffect(() => {
|
|
374
|
-
if (chartInstance.current) {
|
|
375
|
-
if (loading) {
|
|
376
|
-
chartInstance.current.showLoading(loadingOption);
|
|
377
|
-
} else {
|
|
378
|
-
chartInstance.current.hideLoading();
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}, [loading, loadingOption]);
|
|
382
|
-
|
|
383
|
-
// 自定义样式
|
|
384
|
-
const mergedStyle = {
|
|
385
|
-
width: typeof width === 'number' ? `${width}px` : width,
|
|
386
|
-
height: typeof height === 'number' ? `${height}px` : height,
|
|
387
|
-
...style,
|
|
388
|
-
};
|
|
266
|
+
if (!liquidOption) return null;
|
|
389
267
|
|
|
390
268
|
return (
|
|
391
|
-
<
|
|
392
|
-
className={`taroviz-liquid ${className}`}
|
|
393
|
-
style={mergedStyle}
|
|
394
|
-
ref={containerRef as React.RefObject<HTMLDivElement>}
|
|
395
|
-
/>
|
|
269
|
+
<BaseChartWrapper {...(rest as BaseChartProps)} option={liquidOption} chartType="liquid" />
|
|
396
270
|
);
|
|
397
271
|
});
|
|
398
272
|
|
|
@@ -12,7 +12,7 @@ import type { EChartsOption, EChartsType, ECElementEvent } from 'echarts';
|
|
|
12
12
|
/** 水球图系列数据项 */
|
|
13
13
|
export interface LiquidSeriesDataItem {
|
|
14
14
|
/** 数据值,范围 [0, 1] */
|
|
15
|
-
|
|
15
|
+
_value: number;
|
|
16
16
|
/** 数据项名称 */
|
|
17
17
|
name?: string;
|
|
18
18
|
/** 图形样式 */
|
|
@@ -53,7 +53,7 @@ export interface LiquidSeries {
|
|
|
53
53
|
/** 动画缓动函数 */
|
|
54
54
|
animationEasing?: string;
|
|
55
55
|
/** 动画延迟 */
|
|
56
|
-
animationDelay?: number | ((
|
|
56
|
+
animationDelay?: number | ((_idx: number) => number);
|
|
57
57
|
/** 颜色数组 */
|
|
58
58
|
color?: string[];
|
|
59
59
|
/** 背景色 */
|
|
@@ -106,7 +106,7 @@ export interface LiquidChartProps {
|
|
|
106
106
|
/** 是否显示标签 */
|
|
107
107
|
showLabel?: boolean;
|
|
108
108
|
/** 标签格式化 */
|
|
109
|
-
labelFormatter?: (
|
|
109
|
+
labelFormatter?: (_value: number) => string;
|
|
110
110
|
/** 主题 */
|
|
111
111
|
theme?: string | Record<string, unknown>;
|
|
112
112
|
/** 样式 */
|
|
@@ -122,9 +122,9 @@ export interface LiquidChartProps {
|
|
|
122
122
|
/** 加载配置 */
|
|
123
123
|
loadingOption?: Record<string, unknown>;
|
|
124
124
|
/** 图表初始化回调 */
|
|
125
|
-
onChartInit?: (
|
|
125
|
+
onChartInit?: (_chart: EChartsType) => void;
|
|
126
126
|
/** 图表就绪回调 */
|
|
127
|
-
onChartReady?: (
|
|
127
|
+
onChartReady?: (_chart: EChartsType) => void;
|
|
128
128
|
/** 事件回调 */
|
|
129
|
-
onEvents?: Record<string, (
|
|
129
|
+
onEvents?: Record<string, (_params: ECElementEvent) => void>;
|
|
130
130
|
}
|
|
@@ -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';
|
|
@@ -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
|
+
});
|