@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +245 -0
  2. package/README.md +31 -46
  3. package/dist/cjs/index.js +1 -1
  4. package/dist/cjs/vendors.js +1 -1
  5. package/dist/cjs/vendors~echarts.js +1 -1
  6. package/dist/esm/index.js +1 -14270
  7. package/dist/esm/vendors.js +1 -16770
  8. package/dist/esm/vendors~echarts.js +1 -59417
  9. package/package.json +10 -15
  10. package/src/adapters/h5/index.ts +38 -38
  11. package/src/adapters/index.ts +32 -34
  12. package/src/adapters/types.ts +23 -55
  13. package/src/charts/boxplot/types.ts +2 -2
  14. package/src/charts/common/BaseChartWrapper.tsx +9 -7
  15. package/src/charts/createChartComponent.tsx +9 -21
  16. package/src/charts/createOptionChartComponent.tsx +32 -0
  17. package/src/charts/funnel/__tests__/index.test.tsx +99 -0
  18. package/src/charts/funnel/index.tsx +64 -0
  19. package/src/charts/funnel/types.ts +6 -0
  20. package/src/charts/graph/__tests__/index.test.tsx +116 -0
  21. package/src/charts/graph/index.tsx +70 -0
  22. package/src/charts/graph/types.ts +6 -0
  23. package/src/charts/heatmap/__tests__/index.test.tsx +139 -0
  24. package/src/charts/heatmap/index.tsx +107 -0
  25. package/src/charts/heatmap/types.ts +6 -0
  26. package/src/charts/index.ts +47 -57
  27. package/src/charts/liquid/__tests__/index.test.tsx +52 -0
  28. package/src/charts/liquid/index.tsx +7 -133
  29. package/src/charts/liquid/types.ts +6 -6
  30. package/src/charts/parallel/types.ts +3 -3
  31. package/src/charts/radar/__tests__/index.test.tsx +210 -0
  32. package/src/charts/radar/index.tsx +147 -0
  33. package/src/charts/radar/types.ts +13 -0
  34. package/src/charts/sankey/__tests__/index.test.tsx +124 -0
  35. package/src/charts/sankey/index.tsx +70 -0
  36. package/src/charts/sankey/types.ts +6 -0
  37. package/src/charts/tree/__tests__/index.test.tsx +71 -0
  38. package/src/charts/tree/index.tsx +1 -1
  39. package/src/charts/tree/types.ts +8 -8
  40. package/src/charts/types.ts +208 -106
  41. package/src/charts/wordcloud/__tests__/index.test.tsx +106 -0
  42. package/src/charts/wordcloud/index.tsx +79 -0
  43. package/src/charts/wordcloud/types.ts +6 -0
  44. package/src/components/DataFilter/index.tsx +7 -6
  45. package/src/core/animation/types.ts +6 -6
  46. package/src/core/components/Annotation.tsx +6 -6
  47. package/src/core/components/BaseChart.tsx +97 -133
  48. package/src/core/components/LazyChart.tsx +3 -8
  49. package/src/core/components/hooks/index.ts +6 -2
  50. package/src/core/components/hooks/usePerformance.ts +8 -2
  51. package/src/core/components/hooks/useVirtualScroll.ts +2 -1
  52. package/src/core/types/common.ts +2 -1
  53. package/src/core/types/platform.ts +1 -0
  54. package/src/core/utils/__tests__/deepClone.test.ts +317 -0
  55. package/src/core/utils/__tests__/index.test.ts +2 -1
  56. package/src/core/utils/chartInstances.ts +13 -0
  57. package/src/core/utils/common.ts +20 -36
  58. package/src/core/utils/deepClone.ts +114 -0
  59. package/src/core/utils/download.ts +22 -28
  60. package/src/core/utils/drillDown.ts +1 -0
  61. package/src/core/utils/events.ts +12 -0
  62. package/src/core/utils/export/ExportUtils.ts +2 -1
  63. package/src/core/utils/format.ts +44 -0
  64. package/src/core/utils/index.ts +18 -159
  65. package/src/core/utils/merge.ts +25 -0
  66. package/src/core/utils/performance/PerformanceAnalyzer.ts +3 -1
  67. package/src/core/utils/performance/hooks.ts +7 -0
  68. package/src/core/utils/performance/index.ts +2 -0
  69. package/src/{hooks → core/utils/performance}/useAnimation.ts +6 -5
  70. package/src/{hooks → core/utils/performance}/useDataZoom.ts +7 -2
  71. package/src/{hooks → core/utils/performance}/usePerformance.ts +39 -39
  72. package/src/{hooks → core/utils/performance}/usePerformanceHooks.ts +39 -39
  73. package/src/core/utils/runtime.ts +190 -0
  74. package/src/editor/components/ThemeSelector.tsx +3 -3
  75. package/src/hooks/chartConnectHelpers.ts +6 -0
  76. package/src/hooks/index.ts +54 -626
  77. package/src/hooks/types.ts +27 -0
  78. package/src/hooks/useChartAutoResize.ts +73 -0
  79. package/src/hooks/useChartConnect.ts +5 -1
  80. package/src/hooks/useChartDownload.ts +1 -1
  81. package/src/hooks/useChartHistory.ts +1 -3
  82. package/src/hooks/useChartInit.ts +59 -0
  83. package/src/hooks/useChartOptions.ts +259 -0
  84. package/src/hooks/useChartPerformance.ts +109 -0
  85. package/src/hooks/useChartSelection.ts +23 -12
  86. package/src/hooks/useChartTheme.ts +51 -0
  87. package/src/hooks/useDataTransform.ts +19 -4
  88. package/src/index.ts +5 -10
  89. package/src/react-dom.d.ts +3 -3
  90. package/src/themes/index.ts +30 -855
  91. package/src/themes/palettes/blue-green.ts +13 -0
  92. package/src/themes/palettes/chalk.ts +13 -0
  93. package/src/themes/palettes/cyber.ts +44 -0
  94. package/src/themes/palettes/dark.ts +52 -0
  95. package/src/themes/palettes/default.ts +52 -0
  96. package/src/themes/palettes/elegant.ts +34 -0
  97. package/src/themes/palettes/forest.ts +13 -0
  98. package/src/themes/palettes/glass.ts +49 -0
  99. package/src/themes/palettes/golden.ts +13 -0
  100. package/src/themes/palettes/neon.ts +43 -0
  101. package/src/themes/palettes/ocean.ts +39 -0
  102. package/src/themes/palettes/pastel.ts +37 -0
  103. package/src/themes/palettes/purple-passion.ts +13 -0
  104. package/src/themes/palettes/retro.ts +33 -0
  105. package/src/themes/palettes/sunset.ts +40 -0
  106. package/src/themes/palettes/walden.ts +13 -0
  107. package/src/themes/registry.ts +184 -0
  108. package/src/themes/types.ts +213 -0
  109. package/src/core/utils/codeGenerator/CodeGenerator.ts +0 -669
  110. package/src/core/utils/codeGenerator/index.ts +0 -13
  111. package/src/core/utils/codeGenerator/types.ts +0 -198
  112. package/src/core/utils/configGenerator/ConfigGenerator.ts +0 -583
  113. package/src/core/utils/configGenerator/index.ts +0 -13
  114. package/src/core/utils/configGenerator/types.ts +0 -449
  115. package/src/core/utils/debug/DebugPanel.tsx +0 -640
  116. package/src/core/utils/debug/debugger.ts +0 -322
  117. package/src/core/utils/debug/index.ts +0 -21
  118. package/src/core/utils/debug/types.ts +0 -142
@@ -3,28 +3,15 @@
3
3
  * 使用工厂函数消除重复代码
4
4
  */
5
5
 
6
- import { createChartComponent, createChartComponentWithOptionCast } from './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
- SankeyChartProps,
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
- /** 需要 option 类型转换的图表 */
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
- export { default as LiquidChart } from './liquid';
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
- LiquidChartProps,
89
- LiquidOption,
90
- LiquidShape,
91
- LiquidSeries,
92
- LiquidSeriesDataItem,
93
- } from './liquid/types';
94
- export type { TreeChartProps, TreeOption, TreeNode, TreeSeries } from './tree/types';
95
-
96
- /** 版本信息 */
97
- export { VERSION as version } from '../core/version';
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, useEffect, useRef, useMemo } from 'react';
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
- <div
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
- value: number;
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 | ((idx: number) => 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?: (value: number) => string;
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?: (chart: EChartsType) => void;
125
+ onChartInit?: (_chart: EChartsType) => void;
126
126
  /** 图表就绪回调 */
127
- onChartReady?: (chart: EChartsType) => void;
127
+ onChartReady?: (_chart: EChartsType) => void;
128
128
  /** 事件回调 */
129
- onEvents?: Record<string, (params: ECElementEvent) => void>;
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, (params: ECElementEvent) => void>;
14
+ onEvents?: Record<string, (_params: ECElementEvent) => void>;
15
15
  loading?: boolean;
16
16
  loadingOption?: LoadingOptions;
17
17
  theme?: string;
18
- onChartReady?: (chart: EChartsType) => void;
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
+ });