@agions/taroviz 1.1.1 → 1.2.1
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/README.md +324 -53
- package/dist/cjs/index.js +1 -0
- package/dist/esm/index.js +82979 -0
- package/package.json +160 -30
- package/src/__tests__/integration.test.tsx +168 -0
- package/src/adapters/__tests__/index.test.ts +91 -0
- package/src/adapters/h5/__tests__/index.test.ts +156 -0
- package/src/adapters/h5/index.ts +301 -0
- package/src/adapters/harmony/index.ts +274 -0
- package/src/adapters/index.ts +166 -0
- package/src/adapters/swan/index.ts +274 -0
- package/src/adapters/tt/index.ts +274 -0
- package/src/adapters/types.ts +162 -0
- package/src/adapters/weapp/index.ts +237 -0
- package/src/charts/bar/__tests__/index.test.tsx +113 -0
- package/src/charts/bar/index.tsx +18 -0
- package/src/charts/common/BaseChartWrapper.tsx +136 -0
- package/src/charts/funnel/index.tsx +18 -0
- package/src/charts/gauge/index.tsx +18 -0
- package/src/charts/heatmap/index.tsx +18 -0
- package/src/charts/index.ts +21 -0
- package/src/charts/line/__tests__/index.test.tsx +107 -0
- package/src/charts/line/index.tsx +18 -0
- package/src/charts/pie/__tests__/index.test.tsx +112 -0
- package/src/charts/pie/index.tsx +19 -0
- package/src/charts/radar/index.tsx +18 -0
- package/src/charts/scatter/index.tsx +18 -0
- package/src/charts/types.ts +619 -0
- package/src/charts/utils.ts +56 -0
- package/src/core/__tests__/platform.test.ts +48 -0
- package/src/core/animation/AnimationManager.ts +391 -0
- package/src/core/animation/index.ts +20 -0
- package/src/core/animation/types.ts +248 -0
- package/src/core/components/BaseChart.tsx +1313 -0
- package/src/core/components/ErrorBoundary.tsx +458 -0
- package/src/core/echarts.ts +58 -0
- package/src/core/index.ts +22 -0
- package/src/core/types/chart.ts +66 -0
- package/src/core/types/common.ts +224 -0
- package/src/core/types/index.ts +281 -0
- package/src/core/types/platform.ts +325 -0
- package/src/core/utils/__tests__/common.test.ts +52 -0
- package/src/core/utils/__tests__/environment.test.ts +94 -0
- package/src/core/utils/__tests__/i18n.test.ts +247 -0
- package/src/core/utils/__tests__/index.test.ts +219 -0
- package/src/core/utils/__tests__/uuid.test.ts +78 -0
- package/src/core/utils/chartInstances.ts +69 -0
- package/src/core/utils/codeGenerator/CodeGenerator.ts +655 -0
- package/src/core/utils/codeGenerator/index.ts +13 -0
- package/src/core/utils/codeGenerator/types.ts +198 -0
- package/src/core/utils/common.ts +58 -0
- package/src/core/utils/configGenerator/ConfigGenerator.ts +583 -0
- package/src/core/utils/configGenerator/index.ts +13 -0
- package/src/core/utils/configGenerator/types.ts +445 -0
- package/src/core/utils/debug/DebugPanel.tsx +637 -0
- package/src/core/utils/debug/debugger.ts +322 -0
- package/src/core/utils/debug/index.ts +21 -0
- package/src/core/utils/debug/types.ts +142 -0
- package/src/core/utils/i18n.ts +452 -0
- package/src/core/utils/index.ts +162 -0
- package/src/core/utils/performance/PerformanceAnalyzer.ts +586 -0
- package/src/core/utils/performance/index.ts +13 -0
- package/src/core/utils/performance/types.ts +180 -0
- package/src/core/utils/uuid.ts +30 -0
- package/src/editor/ThemeEditor.tsx +449 -0
- package/src/editor/index.ts +10 -0
- package/src/hooks/__tests__/index.test.tsx +333 -0
- package/src/hooks/index.ts +594 -0
- package/src/index.ts +75 -0
- package/src/main.tsx +247 -0
- package/src/react-dom.d.ts +7 -0
- package/src/themes/__tests__/index.test.ts +91 -0
- package/src/themes/index.ts +860 -0
- package/dist/389.index.esm.js +0 -1
- package/dist/389.index.js +0 -1
- package/dist/633.index.esm.js +0 -1
- package/dist/633.index.js +0 -1
- package/dist/967.index.esm.js +0 -1
- package/dist/967.index.js +0 -1
- package/dist/index.esm.js +0 -1
- package/dist/index.js +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TaroViz 适配器类型定义
|
|
3
|
+
*/
|
|
4
|
+
import { CSSProperties } from 'react';
|
|
5
|
+
|
|
6
|
+
import { PlatformType, Adapter as CoreAdapter } from '../core';
|
|
7
|
+
|
|
8
|
+
export type Adapter = CoreAdapter;
|
|
9
|
+
export { PlatformType };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 基础适配器选项
|
|
13
|
+
*/
|
|
14
|
+
export interface AdapterOptions {
|
|
15
|
+
/**
|
|
16
|
+
* 画布ID
|
|
17
|
+
*/
|
|
18
|
+
canvasId?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 宽度
|
|
22
|
+
*/
|
|
23
|
+
width?: number | string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 高度
|
|
27
|
+
*/
|
|
28
|
+
height?: number | string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 主题
|
|
32
|
+
*/
|
|
33
|
+
theme?: string | object;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 初始化回调
|
|
37
|
+
*/
|
|
38
|
+
onInit?: (instance: any) => void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 图表选项
|
|
42
|
+
*/
|
|
43
|
+
option?: any;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 样式
|
|
47
|
+
*/
|
|
48
|
+
style?: CSSProperties;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 是否自动调整大小
|
|
52
|
+
*/
|
|
53
|
+
autoResize?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 设备像素比
|
|
57
|
+
*/
|
|
58
|
+
devicePixelRatio?: number;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 渲染器类型
|
|
62
|
+
*/
|
|
63
|
+
renderer?: 'canvas' | 'svg';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* CSS类名
|
|
67
|
+
*/
|
|
68
|
+
className?: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 容器引用
|
|
72
|
+
*/
|
|
73
|
+
containerRef?: any;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 额外的平台特定选项
|
|
77
|
+
*/
|
|
78
|
+
[key: string]: any;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* H5适配器选项
|
|
83
|
+
*/
|
|
84
|
+
export interface H5AdapterOptions extends AdapterOptions {
|
|
85
|
+
/**
|
|
86
|
+
* 容器引用
|
|
87
|
+
*/
|
|
88
|
+
containerRef?: any;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 微信小程序适配器选项
|
|
93
|
+
*/
|
|
94
|
+
export interface WeappAdapterOptions extends AdapterOptions {
|
|
95
|
+
/**
|
|
96
|
+
* 微信小程序组件实例
|
|
97
|
+
*/
|
|
98
|
+
component?: any;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 支付宝小程序适配器选项
|
|
103
|
+
*/
|
|
104
|
+
export interface AlipayAdapterOptions extends AdapterOptions {
|
|
105
|
+
/**
|
|
106
|
+
* 支付宝小程序特有属性
|
|
107
|
+
*/
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 百度小程序适配器选项
|
|
112
|
+
*/
|
|
113
|
+
export interface SwanAdapterOptions extends AdapterOptions {
|
|
114
|
+
/**
|
|
115
|
+
* 百度小程序特有属性
|
|
116
|
+
*/
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 鸿蒙OS适配器选项
|
|
121
|
+
*/
|
|
122
|
+
export interface HarmonyAdapterOptions extends AdapterOptions {
|
|
123
|
+
/**
|
|
124
|
+
* HarmonyOS特有属性
|
|
125
|
+
*/
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 钉钉小程序适配器选项
|
|
130
|
+
*/
|
|
131
|
+
export interface DDAdapterOptions extends AdapterOptions {
|
|
132
|
+
/**
|
|
133
|
+
* 钉钉小程序特有属性
|
|
134
|
+
*/
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 企业微信小程序适配器选项
|
|
139
|
+
*/
|
|
140
|
+
export interface QywxAdapterOptions extends AdapterOptions {
|
|
141
|
+
/**
|
|
142
|
+
* 企业微信小程序特有属性
|
|
143
|
+
*/
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 飞书小程序适配器选项
|
|
148
|
+
*/
|
|
149
|
+
export interface LarkAdapterOptions extends AdapterOptions {
|
|
150
|
+
/**
|
|
151
|
+
* 飞书小程序特有属性
|
|
152
|
+
*/
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 小程序通用适配器选项
|
|
157
|
+
*/
|
|
158
|
+
export interface MiniAppAdapterOptions extends AdapterOptions {
|
|
159
|
+
/**
|
|
160
|
+
* 小程序特有属性
|
|
161
|
+
*/
|
|
162
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TaroViz 微信小程序适配器
|
|
3
|
+
* 基于微信小程序canvas组件实现图表渲染
|
|
4
|
+
*/
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Adapter, WeappAdapterOptions } from '../types';
|
|
8
|
+
|
|
9
|
+
// 扩展 WeappAdapterOptions 类型
|
|
10
|
+
interface ExtendedWeappAdapterOptions extends WeappAdapterOptions {
|
|
11
|
+
component?: any;
|
|
12
|
+
canvasId?: string;
|
|
13
|
+
width?: number | string;
|
|
14
|
+
height?: number | string;
|
|
15
|
+
theme?: string | object;
|
|
16
|
+
option?: any;
|
|
17
|
+
onInit?: (instance: any) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 微信小程序环境下的图表适配器
|
|
22
|
+
*/
|
|
23
|
+
class WeappAdapter implements Adapter {
|
|
24
|
+
/**
|
|
25
|
+
* 配置项
|
|
26
|
+
*/
|
|
27
|
+
private config: ExtendedWeappAdapterOptions;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 图表实例
|
|
31
|
+
*/
|
|
32
|
+
private chartInstance: any | null = null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 组件实例
|
|
36
|
+
*/
|
|
37
|
+
private component: any | null = null;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 构造函数
|
|
41
|
+
* @param config 适配器配置
|
|
42
|
+
*/
|
|
43
|
+
constructor(config: ExtendedWeappAdapterOptions) {
|
|
44
|
+
this.config = config;
|
|
45
|
+
this.component = config.component;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 创建微信小程序适配器实例
|
|
50
|
+
* @param options 适配器选项
|
|
51
|
+
* @returns 适配器实例
|
|
52
|
+
*/
|
|
53
|
+
static create(options: ExtendedWeappAdapterOptions): WeappAdapter {
|
|
54
|
+
return new WeappAdapter(options);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 获取图表实例
|
|
59
|
+
*/
|
|
60
|
+
getInstance(): any {
|
|
61
|
+
return this.chartInstance;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 初始化图表
|
|
66
|
+
*/
|
|
67
|
+
init(): any {
|
|
68
|
+
const { canvasId, width, height, theme, option } = this.config;
|
|
69
|
+
|
|
70
|
+
if (!this.component) {
|
|
71
|
+
console.error('[TaroViz] WeappAdapter: component is required');
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!canvasId) {
|
|
76
|
+
console.error('[TaroViz] WeappAdapter: canvasId is required');
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 创建图表实例
|
|
81
|
+
const chart = this.component.createChart({
|
|
82
|
+
id: canvasId,
|
|
83
|
+
width: width,
|
|
84
|
+
height: height,
|
|
85
|
+
theme: theme,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// 设置图表选项
|
|
89
|
+
if (option) {
|
|
90
|
+
chart.setOption(option);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 存储图表实例
|
|
94
|
+
this.chartInstance = chart;
|
|
95
|
+
|
|
96
|
+
// 初始化回调
|
|
97
|
+
if (this.config.onInit) {
|
|
98
|
+
this.config.onInit(chart);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return chart;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 设置图表选项
|
|
106
|
+
*/
|
|
107
|
+
setOption(option: any, opts?: any): void {
|
|
108
|
+
if (this.chartInstance) {
|
|
109
|
+
this.chartInstance.setOption(option, opts);
|
|
110
|
+
} else {
|
|
111
|
+
this.config.option = option;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 设置主题
|
|
117
|
+
*/
|
|
118
|
+
setTheme(theme: string | object): void {
|
|
119
|
+
this.config.theme = theme;
|
|
120
|
+
if (this.chartInstance) {
|
|
121
|
+
this.chartInstance.setTheme?.(theme);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 获取图表宽度
|
|
127
|
+
*/
|
|
128
|
+
getWidth(): number {
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 获取图表高度
|
|
134
|
+
*/
|
|
135
|
+
getHeight(): number {
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 获取DOM元素
|
|
141
|
+
*/
|
|
142
|
+
getDom(): HTMLElement | null {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 转换为DataURL
|
|
148
|
+
*/
|
|
149
|
+
convertToDataURL(opts?: any): string | undefined {
|
|
150
|
+
return this.chartInstance?.getDataURL(opts);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 清空图表
|
|
155
|
+
*/
|
|
156
|
+
clear(): void {
|
|
157
|
+
if (this.chartInstance) {
|
|
158
|
+
this.chartInstance.clear();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 绑定事件
|
|
164
|
+
*/
|
|
165
|
+
on(event: string, handler: (params: any) => void): void {
|
|
166
|
+
if (this.chartInstance) {
|
|
167
|
+
this.chartInstance.on(event, handler);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 解绑事件
|
|
173
|
+
*/
|
|
174
|
+
off(event: string, handler?: (params: any) => void): void {
|
|
175
|
+
if (this.chartInstance) {
|
|
176
|
+
this.chartInstance.off(event, handler);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 显示加载动画
|
|
182
|
+
*/
|
|
183
|
+
showLoading(opts?: object): void {
|
|
184
|
+
if (this.chartInstance) {
|
|
185
|
+
this.chartInstance.showLoading(opts);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 隐藏加载动画
|
|
191
|
+
*/
|
|
192
|
+
hideLoading(): void {
|
|
193
|
+
if (this.chartInstance) {
|
|
194
|
+
this.chartInstance.hideLoading();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 销毁图表
|
|
200
|
+
*/
|
|
201
|
+
dispose(): void {
|
|
202
|
+
if (this.chartInstance) {
|
|
203
|
+
this.chartInstance.dispose();
|
|
204
|
+
this.chartInstance = null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 处理图表大小变化
|
|
210
|
+
*/
|
|
211
|
+
resize(opts?: any): void {
|
|
212
|
+
if (this.chartInstance) {
|
|
213
|
+
this.chartInstance.resize(opts);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 设置组件实例
|
|
219
|
+
*/
|
|
220
|
+
setComponent(component: any): void {
|
|
221
|
+
this.component = component;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 渲染图表组件
|
|
226
|
+
*/
|
|
227
|
+
render(): JSX.Element {
|
|
228
|
+
const { canvasId = 'ec-canvas', width = '100%', height = '300px', style = {} } = this.config;
|
|
229
|
+
// 注意:这里需要根据实际使用的Taro版本和组件库来调整
|
|
230
|
+
return React.createElement('view', {
|
|
231
|
+
id: canvasId,
|
|
232
|
+
style: { width, height, ...style },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default WeappAdapter;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import BarChart from '../index';
|
|
5
|
+
|
|
6
|
+
// Mock ECharts and adapters
|
|
7
|
+
jest.mock('echarts/core', () => ({
|
|
8
|
+
use: jest.fn(),
|
|
9
|
+
init: jest.fn(() => ({
|
|
10
|
+
setOption: jest.fn(),
|
|
11
|
+
showLoading: jest.fn(),
|
|
12
|
+
hideLoading: jest.fn(),
|
|
13
|
+
on: jest.fn(),
|
|
14
|
+
off: jest.fn(),
|
|
15
|
+
dispose: jest.fn(),
|
|
16
|
+
resize: jest.fn(),
|
|
17
|
+
})),
|
|
18
|
+
getInstanceByDom: jest.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Mock specific ECharts chart imports
|
|
22
|
+
jest.mock('echarts/charts', () => ({
|
|
23
|
+
BarChart: jest.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
jest.mock('echarts/components', () => ({
|
|
27
|
+
GridComponent: jest.fn(),
|
|
28
|
+
TooltipComponent: jest.fn(),
|
|
29
|
+
TitleComponent: jest.fn(),
|
|
30
|
+
LegendComponent: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
jest.mock('../../common/BaseChartWrapper', () => ({
|
|
34
|
+
__esModule: true,
|
|
35
|
+
default: (props: any) => (
|
|
36
|
+
<div
|
|
37
|
+
data-testid="base-chart-wrapper"
|
|
38
|
+
className={`taroviz-${props.chartType} ${props.className || ''}`}
|
|
39
|
+
style={{ width: props.width, height: props.height }}
|
|
40
|
+
>
|
|
41
|
+
<div data-testid="chart-option">{JSON.stringify(props.option)}</div>
|
|
42
|
+
</div>
|
|
43
|
+
),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
describe('BarChart Component', () => {
|
|
47
|
+
const mockOption = {
|
|
48
|
+
xAxis: {
|
|
49
|
+
type: 'category' as const,
|
|
50
|
+
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
|
51
|
+
},
|
|
52
|
+
yAxis: {
|
|
53
|
+
type: 'value' as const,
|
|
54
|
+
},
|
|
55
|
+
series: [
|
|
56
|
+
{
|
|
57
|
+
data: [150, 230, 224, 218, 135, 147, 260],
|
|
58
|
+
type: 'bar' as const,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
it('should render correctly with default props', () => {
|
|
64
|
+
const { getByTestId } = render(<BarChart option={mockOption} />);
|
|
65
|
+
|
|
66
|
+
const chartWrapper = getByTestId('base-chart-wrapper');
|
|
67
|
+
expect(chartWrapper).toBeInTheDocument();
|
|
68
|
+
expect(chartWrapper).toHaveClass('taroviz-bar-chart');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should pass the correct option to BaseChartWrapper', () => {
|
|
72
|
+
const { getByTestId } = render(<BarChart option={mockOption} />);
|
|
73
|
+
|
|
74
|
+
const chartOption = getByTestId('chart-option');
|
|
75
|
+
expect(JSON.parse(chartOption.textContent || '')).toEqual(mockOption);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should render with custom width and height', () => {
|
|
79
|
+
const customWidth = '600px';
|
|
80
|
+
const customHeight = '450px';
|
|
81
|
+
|
|
82
|
+
const { getByTestId } = render(
|
|
83
|
+
<BarChart option={mockOption} width={customWidth} height={customHeight} />
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const chartWrapper = getByTestId('base-chart-wrapper');
|
|
87
|
+
expect(chartWrapper).toHaveStyle(`width: ${customWidth}`);
|
|
88
|
+
expect(chartWrapper).toHaveStyle(`height: ${customHeight}`);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should render with custom className', () => {
|
|
92
|
+
const customClass = 'custom-bar-chart';
|
|
93
|
+
|
|
94
|
+
const { getByTestId } = render(<BarChart option={mockOption} className={customClass} />);
|
|
95
|
+
|
|
96
|
+
const chartWrapper = getByTestId('base-chart-wrapper');
|
|
97
|
+
expect(chartWrapper).toHaveClass(customClass);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should render with loading state', () => {
|
|
101
|
+
const { getByTestId } = render(<BarChart option={mockOption} loading={true} />);
|
|
102
|
+
|
|
103
|
+
const chartWrapper = getByTestId('base-chart-wrapper');
|
|
104
|
+
expect(chartWrapper).toBeInTheDocument();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should render with svg renderer', () => {
|
|
108
|
+
const { getByTestId } = render(<BarChart option={mockOption} renderer="svg" />);
|
|
109
|
+
|
|
110
|
+
const chartWrapper = getByTestId('base-chart-wrapper');
|
|
111
|
+
expect(chartWrapper).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 柱状图组件
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import BaseChartWrapper from '../common/BaseChartWrapper';
|
|
7
|
+
import { BarChartProps } from '../types';
|
|
8
|
+
|
|
9
|
+
import '@/core/echarts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 柱状图组件
|
|
13
|
+
*/
|
|
14
|
+
const BarChart: React.FC<BarChartProps> = (props) => (
|
|
15
|
+
<BaseChartWrapper {...props} chartType="bar-chart" />
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default BarChart;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基础图表包装组件
|
|
3
|
+
* 提供统一的图表初始化、渲染和生命周期管理
|
|
4
|
+
*/
|
|
5
|
+
import * as echarts from 'echarts/core';
|
|
6
|
+
import React, { useEffect, useRef, useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { getAdapter } from '../../adapters';
|
|
9
|
+
import { uuid } from '../../core/utils';
|
|
10
|
+
import { BaseChartProps } from '../types';
|
|
11
|
+
import { processAdapterConfig, safeRenderAdapter } from '../utils';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 基础图表包装组件
|
|
15
|
+
* 提供统一的图表初始化、渲染和生命周期管理
|
|
16
|
+
*/
|
|
17
|
+
const BaseChartWrapper: React.FC<BaseChartProps & { chartType: string }> = ({
|
|
18
|
+
option,
|
|
19
|
+
width = '100%',
|
|
20
|
+
height = '300px',
|
|
21
|
+
theme,
|
|
22
|
+
style = {},
|
|
23
|
+
className = '',
|
|
24
|
+
autoResize = true,
|
|
25
|
+
loading = false,
|
|
26
|
+
loadingOption,
|
|
27
|
+
onChartInit,
|
|
28
|
+
onChartReady,
|
|
29
|
+
renderer = 'canvas',
|
|
30
|
+
onEvents = {},
|
|
31
|
+
chartType = 'chart',
|
|
32
|
+
}) => {
|
|
33
|
+
const chartId = useRef<string>(`${chartType}-${uuid()}`);
|
|
34
|
+
const chartInstance = useRef<echarts.ECharts | null>(null);
|
|
35
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
36
|
+
|
|
37
|
+
// 使用 useMemo 缓存适配器配置,并处理类型问题
|
|
38
|
+
const adapterConfig = useMemo(() => {
|
|
39
|
+
return processAdapterConfig({
|
|
40
|
+
canvasId: chartId.current,
|
|
41
|
+
containerRef,
|
|
42
|
+
width,
|
|
43
|
+
height,
|
|
44
|
+
theme,
|
|
45
|
+
autoResize,
|
|
46
|
+
renderer,
|
|
47
|
+
option,
|
|
48
|
+
});
|
|
49
|
+
}, [width, height, theme, autoResize, renderer, option, chartType]);
|
|
50
|
+
|
|
51
|
+
// 处理图表初始化
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const initConfig = processAdapterConfig({
|
|
54
|
+
...adapterConfig,
|
|
55
|
+
onInit: (instance: echarts.ECharts) => {
|
|
56
|
+
chartInstance.current = instance;
|
|
57
|
+
|
|
58
|
+
// 绑定事件
|
|
59
|
+
if (onEvents) {
|
|
60
|
+
Object.keys(onEvents).forEach((eventName) => {
|
|
61
|
+
instance.on(eventName, onEvents[eventName]);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 初始化回调
|
|
66
|
+
if (onChartInit) {
|
|
67
|
+
onChartInit(instance);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 准备好回调
|
|
71
|
+
if (onChartReady) {
|
|
72
|
+
onChartReady(instance);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 获取适配器并初始化
|
|
78
|
+
const adapter = getAdapter(initConfig);
|
|
79
|
+
adapter.init();
|
|
80
|
+
|
|
81
|
+
// 组件卸载时清理
|
|
82
|
+
return () => {
|
|
83
|
+
if (chartInstance.current) {
|
|
84
|
+
// 解绑事件
|
|
85
|
+
if (onEvents) {
|
|
86
|
+
Object.keys(onEvents).forEach((eventName) => {
|
|
87
|
+
chartInstance.current?.off(eventName);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
chartInstance.current.dispose();
|
|
91
|
+
chartInstance.current = null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}, [adapterConfig, onChartInit, onChartReady, onEvents]);
|
|
95
|
+
|
|
96
|
+
// 更新配置
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (chartInstance.current) {
|
|
99
|
+
chartInstance.current.setOption(option, true);
|
|
100
|
+
}
|
|
101
|
+
}, [option]);
|
|
102
|
+
|
|
103
|
+
// 控制加载状态
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (chartInstance.current) {
|
|
106
|
+
if (loading) {
|
|
107
|
+
chartInstance.current.showLoading(loadingOption);
|
|
108
|
+
} else {
|
|
109
|
+
chartInstance.current.hideLoading();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}, [loading, loadingOption]);
|
|
113
|
+
|
|
114
|
+
// 自定义样式
|
|
115
|
+
const mergedStyle = {
|
|
116
|
+
width: typeof width === 'number' ? `${width}px` : width,
|
|
117
|
+
height: typeof height === 'number' ? `${height}px` : height,
|
|
118
|
+
...style,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// 使用带有处理的适配器配置创建适配器实例
|
|
122
|
+
const adapter = getAdapter(adapterConfig);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div
|
|
126
|
+
className={`taroviz-${chartType} ${className}`}
|
|
127
|
+
style={mergedStyle}
|
|
128
|
+
ref={containerRef as React.RefObject<HTMLDivElement>}
|
|
129
|
+
>
|
|
130
|
+
{/* 安全地渲染适配器 */}
|
|
131
|
+
{safeRenderAdapter(adapter)}
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export default BaseChartWrapper;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 漏斗图组件
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import BaseChartWrapper from '../common/BaseChartWrapper';
|
|
7
|
+
import { FunnelChartProps } from '../types';
|
|
8
|
+
|
|
9
|
+
import '@/core/echarts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 漏斗图组件
|
|
13
|
+
*/
|
|
14
|
+
const FunnelChart: React.FC<FunnelChartProps> = (props) => (
|
|
15
|
+
<BaseChartWrapper {...props} chartType="funnel-chart" />
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default FunnelChart;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 仪表盘组件
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import BaseChartWrapper from '../common/BaseChartWrapper';
|
|
7
|
+
import { GaugeChartProps } from '../types';
|
|
8
|
+
|
|
9
|
+
import '@/core/echarts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 仪表盘组件
|
|
13
|
+
*/
|
|
14
|
+
const GaugeChart: React.FC<GaugeChartProps> = (props) => (
|
|
15
|
+
<BaseChartWrapper {...props} chartType="gauge-chart" />
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default GaugeChart;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 热力图组件
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
import BaseChartWrapper from '../common/BaseChartWrapper';
|
|
7
|
+
import { HeatmapChartProps } from '../types';
|
|
8
|
+
|
|
9
|
+
import '@/core/echarts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 热力图组件
|
|
13
|
+
*/
|
|
14
|
+
const HeatmapChart: React.FC<HeatmapChartProps> = (props) => (
|
|
15
|
+
<BaseChartWrapper {...props} chartType="heatmap-chart" />
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default HeatmapChart;
|