@agions/taroviz 1.3.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/cjs/index.js +1 -1
- package/dist/esm/index.js +3814 -2724
- package/package.json +1 -1
- package/src/__tests__/integration.test.tsx +12 -10
- package/src/adapters/BaseAdapter.ts +116 -0
- package/src/adapters/__tests__/index.test.ts +10 -10
- package/src/adapters/index.ts +63 -74
- package/src/adapters/swan/index.ts +26 -223
- package/src/adapters/tt/index.ts +28 -225
- package/src/adapters/types.ts +36 -0
- package/src/adapters/weapp/index.ts +29 -189
- package/src/charts/bar/index.tsx +5 -9
- package/src/charts/boxplot/__tests__/index.test.tsx +130 -0
- package/src/charts/boxplot/index.tsx +18 -0
- package/src/charts/boxplot/types.ts +46 -0
- package/src/charts/candlestick/__tests__/index.test.tsx +37 -0
- package/src/charts/candlestick/index.tsx +13 -0
- package/src/charts/common/BaseChartWrapper.tsx +47 -38
- package/src/charts/funnel/index.tsx +5 -9
- package/src/charts/gauge/index.tsx +5 -9
- package/src/charts/graph/__tests__/index.test.tsx +41 -0
- package/src/charts/graph/index.tsx +13 -0
- package/src/charts/heatmap/index.tsx +5 -9
- package/src/charts/index.ts +10 -1
- package/src/charts/line/index.tsx +4 -7
- package/src/charts/parallel/__tests__/index.test.tsx +164 -0
- package/src/charts/parallel/index.tsx +18 -0
- package/src/charts/parallel/types.ts +73 -0
- package/src/charts/pie/index.tsx +5 -10
- package/src/charts/radar/index.tsx +5 -9
- package/src/charts/scatter/index.tsx +5 -9
- package/src/charts/types.ts +48 -4
- package/src/charts/wordcloud/__tests__/index.test.tsx +36 -0
- package/src/charts/wordcloud/index.tsx +13 -0
- package/src/core/animation/AnimationManager.ts +15 -0
- package/src/core/components/Annotation.tsx +26 -21
- package/src/core/components/BaseChart.tsx +280 -1105
- package/src/core/components/ErrorBoundary.tsx +4 -1
- package/src/core/components/LazyChart.tsx +42 -55
- package/src/core/components/hooks/index.ts +20 -0
- package/src/core/components/hooks/useChartEvents.ts +143 -0
- package/src/core/components/hooks/useChartInit.ts +80 -0
- package/src/core/components/hooks/usePerformance.ts +186 -0
- package/src/core/components/hooks/useVirtualScroll.ts +156 -0
- package/src/core/echarts.ts +1 -1
- package/src/core/themes/ThemeManager.ts +31 -15
- package/src/core/types/index.ts +2 -2
- package/src/core/utils/chartInstances.ts +10 -3
- package/src/core/utils/chartUtils.ts +46 -0
- package/src/core/utils/common.ts +14 -1
- package/src/core/utils/export/ExportUtils.ts +13 -22
- package/src/core/utils/performance/PerformanceAnalyzer.ts +32 -5
- package/src/core/utils/uuid.ts +1 -1
- package/src/editor/EnhancedThemeEditor.tsx +624 -0
- package/src/editor/ThemeEditor.tsx +1 -6
- package/src/hooks/__tests__/index.test.tsx +14 -11
- package/src/hooks/__tests__/useDataTransform.test.ts +159 -0
- package/src/hooks/index.ts +54 -19
- package/src/hooks/useDataTransform.ts +503 -0
- package/src/index.ts +27 -9
- package/src/main.tsx +4 -4
- package/src/themes/__tests__/index.test.ts +2 -2
- package/src/themes/index.ts +13 -0
|
@@ -5,451 +5,108 @@
|
|
|
5
5
|
* 该组件提供了图表的基础功能,包括初始化、事件处理、主题设置等
|
|
6
6
|
* 所有具体的图表组件(如折线图、柱状图等)都继承自该组件
|
|
7
7
|
*/
|
|
8
|
-
import React, { useEffect, useRef } from 'react';
|
|
8
|
+
import React, { useEffect, useRef, useMemo, useCallback } from 'react';
|
|
9
9
|
|
|
10
|
-
import { getAdapter } from '../../adapters';
|
|
11
10
|
import { generateEChartsAnimationConfig } from '../animation';
|
|
12
11
|
import { EChartsOption, EChartsType, AnimationConfig } from '../types';
|
|
13
12
|
import { registerChart, removeChart, getChart } from '../utils/chartInstances';
|
|
14
13
|
import { DebugPanel, DebugPanelOptions, updateDebugInfo } from '../utils/debug';
|
|
15
14
|
import { PerformanceAnalyzer } from '../utils/performance';
|
|
15
|
+
import { normalizeSize, calculateDataLength, filterDataByKeys } from '../utils/chartUtils';
|
|
16
|
+
import BaseChartWrapper from '../../charts/common/BaseChartWrapper';
|
|
17
|
+
import type { BaseChartProps } from '../../charts/types';
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// 接口定义
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/** 图表事件参数类型 */
|
|
23
24
|
export interface ChartEventParams {
|
|
24
|
-
/**
|
|
25
|
-
* 事件参数的键值对
|
|
26
|
-
*/
|
|
27
25
|
[key: string]: any;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
/**
|
|
31
|
-
* 图表导出选项
|
|
32
|
-
*/
|
|
28
|
+
/** 图表导出选项 */
|
|
33
29
|
export interface ChartExportOptions {
|
|
34
|
-
/**
|
|
35
|
-
* 导出类型
|
|
36
|
-
*/
|
|
37
30
|
type?: 'png' | 'jpeg' | 'svg';
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 导出文件名
|
|
41
|
-
*/
|
|
42
31
|
filename?: string;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 像素比
|
|
46
|
-
*/
|
|
47
32
|
pixelRatio?: number;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 背景色
|
|
51
|
-
*/
|
|
52
33
|
backgroundColor?: string;
|
|
53
34
|
}
|
|
54
35
|
|
|
55
|
-
/**
|
|
56
|
-
* 图表联动配置
|
|
57
|
-
*/
|
|
36
|
+
/** 图表联动配置 */
|
|
58
37
|
export interface ChartLinkageConfig {
|
|
59
|
-
/**
|
|
60
|
-
* 联动的图表ID列表
|
|
61
|
-
*/
|
|
62
38
|
linkedChartIds?: string[];
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 是否启用点击联动
|
|
66
|
-
*/
|
|
67
39
|
enableClickLinkage?: boolean;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 是否启用缩放联动
|
|
71
|
-
*/
|
|
72
40
|
enableZoomLinkage?: boolean;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* 是否启用图例联动
|
|
76
|
-
*/
|
|
77
41
|
enableLegendLinkage?: boolean;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 是否启用数据筛选联动
|
|
81
|
-
*/
|
|
82
42
|
enableFilterLinkage?: boolean;
|
|
83
43
|
}
|
|
84
44
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
* @description 定义了所有图表组件的基础属性
|
|
90
|
-
*/
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// ChartProps - 与原有接口保持完全兼容
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
91
49
|
export interface ChartProps {
|
|
92
|
-
/**
|
|
93
|
-
* 图表ID,用于图表联动和实例管理
|
|
94
|
-
*/
|
|
95
50
|
chartId?: string;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 图表配置项
|
|
99
|
-
*
|
|
100
|
-
* @type {EChartsOption}
|
|
101
|
-
* @description 符合 ECharts 规范的图表配置对象
|
|
102
|
-
*/
|
|
103
51
|
option?: EChartsOption;
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 动画配置
|
|
107
|
-
*
|
|
108
|
-
* @type {AnimationConfig}
|
|
109
|
-
* @description 图表动画配置,支持自定义动画时长、缓动函数等
|
|
110
|
-
*/
|
|
111
52
|
animation?: AnimationConfig;
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 调试配置
|
|
115
|
-
*
|
|
116
|
-
* @type {boolean | DebugPanelOptions}
|
|
117
|
-
* @description 是否启用调试面板或调试面板配置
|
|
118
|
-
*/
|
|
119
53
|
debug?: boolean | DebugPanelOptions;
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 图表宽度
|
|
123
|
-
*
|
|
124
|
-
* @type {number | string}
|
|
125
|
-
* @default '100%'
|
|
126
|
-
* @description 图表的宽度,可以是像素值或百分比
|
|
127
|
-
*/
|
|
128
54
|
width?: number | string;
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 图表高度
|
|
132
|
-
*
|
|
133
|
-
* @type {number | string}
|
|
134
|
-
* @default '300px'
|
|
135
|
-
* @description 图表的高度,可以是像素值或百分比
|
|
136
|
-
*/
|
|
137
55
|
height?: number | string;
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* 图表主题
|
|
141
|
-
*
|
|
142
|
-
* @type {string | object}
|
|
143
|
-
* @description 图表的主题,可以是内置主题名称或自定义主题对象
|
|
144
|
-
*/
|
|
145
56
|
theme?: string | object;
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* 是否自动调整大小
|
|
149
|
-
*
|
|
150
|
-
* @type {boolean}
|
|
151
|
-
* @default true
|
|
152
|
-
* @description 当窗口大小变化时,是否自动调整图表大小
|
|
153
|
-
*/
|
|
154
57
|
autoResize?: boolean;
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 布局方向
|
|
158
|
-
*
|
|
159
|
-
* @type {'ltr' | 'rtl'}
|
|
160
|
-
* @default 'ltr'
|
|
161
|
-
* @description 图表的布局方向,支持从左到右(ltr)和从右到左(rtl)
|
|
162
|
-
*/
|
|
163
58
|
direction?: 'ltr' | 'rtl';
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* 初始化回调
|
|
167
|
-
*
|
|
168
|
-
* @type {(instance: EChartsType) => void}
|
|
169
|
-
* @description 图表初始化完成后的回调函数,返回 ECharts 实例
|
|
170
|
-
*/
|
|
171
59
|
onInit?: (instance: EChartsType) => void;
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 图表点击事件
|
|
175
|
-
*
|
|
176
|
-
* @type {(params: ChartEventParams) => void}
|
|
177
|
-
* @description 图表点击事件的回调函数
|
|
178
|
-
*/
|
|
179
60
|
onClick?: (params: ChartEventParams) => void;
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* 图表数据变化事件
|
|
183
|
-
*
|
|
184
|
-
* @type {(params: ChartEventParams) => void}
|
|
185
|
-
* @description 图表数据缩放事件的回调函数
|
|
186
|
-
*/
|
|
187
61
|
onDataZoom?: (params: ChartEventParams) => void;
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* 样式属性
|
|
191
|
-
*
|
|
192
|
-
* @type {React.CSSProperties}
|
|
193
|
-
* @description 图表容器的 CSS 样式
|
|
194
|
-
*/
|
|
195
62
|
style?: React.CSSProperties;
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* CSS类名
|
|
199
|
-
*
|
|
200
|
-
* @type {string}
|
|
201
|
-
* @description 图表容器的 CSS 类名
|
|
202
|
-
*/
|
|
203
63
|
className?: string;
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* 子元素
|
|
207
|
-
*
|
|
208
|
-
* @type {React.ReactNode}
|
|
209
|
-
* @description 图表容器的子元素
|
|
210
|
-
*/
|
|
211
64
|
children?: React.ReactNode;
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* 是否启用虚拟滚动
|
|
215
|
-
*
|
|
216
|
-
* @type {boolean}
|
|
217
|
-
* @default false
|
|
218
|
-
* @description 当数据量较大时,启用虚拟滚动可以提高性能
|
|
219
|
-
*/
|
|
220
65
|
virtualScroll?: boolean;
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* 虚拟滚动的每页数据量
|
|
224
|
-
*
|
|
225
|
-
* @type {number}
|
|
226
|
-
* @default 100
|
|
227
|
-
* @description 每次渲染的数据量
|
|
228
|
-
*/
|
|
229
66
|
virtualScrollPageSize?: number;
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* 虚拟滚动的预加载数量
|
|
233
|
-
*
|
|
234
|
-
* @type {number}
|
|
235
|
-
* @default 50
|
|
236
|
-
* @description 预加载的数据量,用于平滑滚动
|
|
237
|
-
*/
|
|
238
67
|
virtualScrollPreloadSize?: number;
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* 是否启用性能监控
|
|
242
|
-
*
|
|
243
|
-
* @type {boolean}
|
|
244
|
-
* @default false
|
|
245
|
-
* @description 启用后会收集图表渲染性能数据
|
|
246
|
-
*/
|
|
247
68
|
enablePerformanceMonitoring?: boolean;
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* 性能监控回调函数
|
|
251
|
-
*
|
|
252
|
-
* @type {(performanceData: { renderTime: number; initTime: number; updateTime: number; dataSize: number }) => void}
|
|
253
|
-
* @description 当图表渲染、初始化或更新时调用,返回性能数据
|
|
254
|
-
*/
|
|
255
|
-
onPerformance?: (performanceData: {
|
|
69
|
+
onPerformance?: (data: {
|
|
256
70
|
renderTime: number;
|
|
257
71
|
initTime: number;
|
|
258
72
|
updateTime: number;
|
|
259
73
|
dataSize: number;
|
|
260
74
|
}) => void;
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* 是否启用图表缩放功能
|
|
264
|
-
*
|
|
265
|
-
* @type {boolean}
|
|
266
|
-
* @default false
|
|
267
|
-
* @description 启用后允许用户通过鼠标滚轮或触摸手势缩放图表
|
|
268
|
-
*/
|
|
269
75
|
enableZoom?: boolean;
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* 缩放事件回调函数
|
|
273
|
-
*
|
|
274
|
-
* @type {(zoomData: { start: number; end: number; dataZoomIndex: number }) => void}
|
|
275
|
-
* @description 当图表缩放时调用,返回缩放数据
|
|
276
|
-
*/
|
|
277
|
-
onZoom?: (zoomData: { start: number; end: number; dataZoomIndex: number }) => void;
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* 是否启用数据筛选功能
|
|
281
|
-
*
|
|
282
|
-
* @type {boolean}
|
|
283
|
-
* @default false
|
|
284
|
-
* @description 启用后允许用户筛选图表数据
|
|
285
|
-
*/
|
|
76
|
+
onZoom?: (data: { start: number; end: number; dataZoomIndex: number }) => void;
|
|
286
77
|
enableDataFiltering?: boolean;
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* 当前筛选条件
|
|
290
|
-
*
|
|
291
|
-
* @type {Record<string, any>}
|
|
292
|
-
* @description 用于筛选图表数据的条件
|
|
293
|
-
*/
|
|
294
78
|
filters?: Record<string, any>;
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* 数据筛选回调函数
|
|
298
|
-
*
|
|
299
|
-
* @type {(filteredData: any[], filters: Record<string, any>) => void}
|
|
300
|
-
* @description 当数据筛选时调用,返回筛选后的数据和筛选条件
|
|
301
|
-
*/
|
|
302
79
|
onDataFiltered?: (filteredData: any[], filters: Record<string, any>) => void;
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* 是否启用增强图例交互
|
|
306
|
-
*
|
|
307
|
-
* @type {boolean}
|
|
308
|
-
* @default false
|
|
309
|
-
* @description 启用后允许用户通过图例进行更丰富的交互操作
|
|
310
|
-
*/
|
|
311
80
|
enableLegendInteraction?: boolean;
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* 图例交互模式
|
|
315
|
-
*
|
|
316
|
-
* @type {'single' | 'multiple' | 'all'}
|
|
317
|
-
* @default 'single'
|
|
318
|
-
* @description 图例交互模式:single(单选)、multiple(多选)、all(全选/反选)
|
|
319
|
-
*/
|
|
320
81
|
legendInteractionMode?: 'single' | 'multiple' | 'all';
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* 图例选择回调函数
|
|
324
|
-
*
|
|
325
|
-
* @type {(params: { name: string; selected: Record<string, boolean> }) => void}
|
|
326
|
-
* @description 当图例项被选择时调用
|
|
327
|
-
*/
|
|
328
82
|
onLegendSelect?: (params: { name: string; selected: Record<string, boolean> }) => void;
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* 图例取消选择回调函数
|
|
332
|
-
*
|
|
333
|
-
* @type {(params: { name: string; selected: Record<string, boolean> }) => void}
|
|
334
|
-
* @description 当图例项被取消选择时调用
|
|
335
|
-
*/
|
|
336
83
|
onLegendUnselect?: (params: { name: string; selected: Record<string, boolean> }) => void;
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* 图例全选回调函数
|
|
340
|
-
*
|
|
341
|
-
* @type {(params: { selected: Record<string, boolean> }) => void}
|
|
342
|
-
* @description 当图例全选时调用
|
|
343
|
-
*/
|
|
344
84
|
onLegendSelectAll?: (params: { selected: Record<string, boolean> }) => void;
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* 图例反选回调函数
|
|
348
|
-
*
|
|
349
|
-
* @type {(params: { selected: Record<string, boolean> }) => void}
|
|
350
|
-
* @description 当图例反选时调用
|
|
351
|
-
*/
|
|
352
85
|
onLegendInverseSelect?: (params: { selected: Record<string, boolean> }) => void;
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* 是否启用自定义提示框
|
|
356
|
-
*
|
|
357
|
-
* @type {boolean}
|
|
358
|
-
* @default false
|
|
359
|
-
* @description 启用后允许自定义提示框内容和样式
|
|
360
|
-
*/
|
|
361
86
|
enableCustomTooltip?: boolean;
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* 自定义提示框内容
|
|
365
|
-
*
|
|
366
|
-
* @type {(params: any) => React.ReactNode}
|
|
367
|
-
* @description 自定义提示框内容的渲染函数
|
|
368
|
-
*/
|
|
369
87
|
customTooltipContent?: (params: any) => React.ReactNode;
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* 自定义提示框样式
|
|
373
|
-
*
|
|
374
|
-
* @type {React.CSSProperties}
|
|
375
|
-
* @description 自定义提示框的样式
|
|
376
|
-
*/
|
|
377
88
|
customTooltipStyle?: React.CSSProperties;
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* 提示框显示事件回调函数
|
|
381
|
-
*
|
|
382
|
-
* @type {(params: any) => void}
|
|
383
|
-
* @description 当提示框显示时调用
|
|
384
|
-
*/
|
|
385
89
|
onTooltipShow?: (params: any) => void;
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* 提示框隐藏事件回调函数
|
|
389
|
-
*
|
|
390
|
-
* @type {(params: any) => void}
|
|
391
|
-
* @description 当提示框隐藏时调用
|
|
392
|
-
*/
|
|
393
90
|
onTooltipHide?: (params: any) => void;
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* 图表导出回调函数
|
|
397
|
-
*
|
|
398
|
-
* @type {(dataURL: string, options: ChartExportOptions) => void}
|
|
399
|
-
* @description 当图表导出完成时调用,返回导出的数据URL和选项
|
|
400
|
-
*/
|
|
401
91
|
onExport?: (dataURL: string, options: ChartExportOptions) => void;
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* 图表联动配置
|
|
405
|
-
*/
|
|
406
92
|
linkageConfig?: ChartLinkageConfig;
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* 数据更新回调函数
|
|
410
|
-
*
|
|
411
|
-
* @type {(oldOption: EChartsOption | undefined, newOption: EChartsOption | undefined) => void}
|
|
412
|
-
* @description 当图表数据更新时调用,返回旧的配置和新的配置
|
|
413
|
-
*/
|
|
414
93
|
onDataUpdate?: (
|
|
415
94
|
oldOption: EChartsOption | undefined,
|
|
416
95
|
newOption: EChartsOption | undefined
|
|
417
96
|
) => void;
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* 数据更新监听选项
|
|
421
|
-
*/
|
|
422
|
-
dataUpdateOptions?: {
|
|
423
|
-
/**
|
|
424
|
-
* 是否启用数据更新监听
|
|
425
|
-
*/
|
|
426
|
-
enabled?: boolean;
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* 数据更新监听的深度
|
|
430
|
-
*/
|
|
431
|
-
deepCompare?: boolean;
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* 防抖延迟(毫秒)
|
|
435
|
-
*/
|
|
436
|
-
debounceDelay?: number;
|
|
437
|
-
};
|
|
97
|
+
dataUpdateOptions?: { enabled?: boolean; deepCompare?: boolean; debounceDelay?: number };
|
|
438
98
|
}
|
|
439
99
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
* @returns {JSX.Element} - 返回的 JSX 元素
|
|
445
|
-
*
|
|
446
|
-
* @description 基础图表组件,所有具体图表组件的基类
|
|
447
|
-
* 负责处理图表的初始化、事件绑定、主题设置等核心逻辑
|
|
448
|
-
*/
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// BaseChart 组件
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
449
104
|
const BaseChart: React.FC<ChartProps> = (props) => {
|
|
450
105
|
const {
|
|
451
106
|
chartId,
|
|
452
107
|
option,
|
|
108
|
+
animation,
|
|
109
|
+
debug,
|
|
453
110
|
width = '100%',
|
|
454
111
|
height = '300px',
|
|
455
112
|
theme,
|
|
@@ -460,13 +117,13 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
460
117
|
onDataZoom,
|
|
461
118
|
style,
|
|
462
119
|
className,
|
|
463
|
-
children,
|
|
120
|
+
children: _children,
|
|
464
121
|
virtualScroll = false,
|
|
465
122
|
virtualScrollPageSize = 100,
|
|
466
123
|
virtualScrollPreloadSize = 50,
|
|
467
124
|
enablePerformanceMonitoring = false,
|
|
468
125
|
onPerformance,
|
|
469
|
-
enableZoom = false,
|
|
126
|
+
enableZoom: _enableZoom = false,
|
|
470
127
|
onZoom,
|
|
471
128
|
enableDataFiltering = false,
|
|
472
129
|
filters = {},
|
|
@@ -482,42 +139,14 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
482
139
|
customTooltipStyle,
|
|
483
140
|
onTooltipShow,
|
|
484
141
|
onTooltipHide,
|
|
485
|
-
onExport,
|
|
142
|
+
onExport: _onExport,
|
|
486
143
|
linkageConfig = {},
|
|
487
144
|
onDataUpdate,
|
|
488
145
|
dataUpdateOptions = {},
|
|
489
146
|
} = props;
|
|
490
147
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
*/
|
|
494
|
-
const chartRef = useRef<HTMLDivElement>(null);
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* 图表适配器的引用
|
|
498
|
-
*/
|
|
499
|
-
const adapterRef = useRef<any>(null);
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* 虚拟滚动状态
|
|
503
|
-
*/
|
|
504
|
-
const virtualScrollRef = useRef({
|
|
505
|
-
currentPage: 0,
|
|
506
|
-
totalPages: 1,
|
|
507
|
-
totalDataCount: 0,
|
|
508
|
-
startIndex: 0,
|
|
509
|
-
endIndex: 0,
|
|
510
|
-
isScrolling: false,
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* 性能分析器实例引用
|
|
515
|
-
*/
|
|
516
|
-
const performanceAnalyzerRef = useRef<PerformanceAnalyzer | null>(null);
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* 性能监控状态
|
|
520
|
-
*/
|
|
148
|
+
// Refs
|
|
149
|
+
const chartInstanceRef = useRef<any>(null);
|
|
521
150
|
const performanceRef = useRef({
|
|
522
151
|
initStartTime: 0,
|
|
523
152
|
initEndTime: 0,
|
|
@@ -525,329 +154,80 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
525
154
|
renderEndTime: 0,
|
|
526
155
|
updateStartTime: 0,
|
|
527
156
|
updateEndTime: 0,
|
|
528
|
-
dataSize: 0,
|
|
529
157
|
});
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
158
|
+
const virtualScrollRef = useRef({
|
|
159
|
+
currentPage: 0,
|
|
160
|
+
totalPages: 1,
|
|
161
|
+
totalDataCount: 0,
|
|
162
|
+
isScrolling: false,
|
|
163
|
+
});
|
|
534
164
|
const oldOptionRef = useRef<EChartsOption | undefined>(option);
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* 调试配置引用
|
|
538
|
-
*/
|
|
165
|
+
const adapterRef = useRef<any>(null);
|
|
539
166
|
const debugConfigRef = useRef<DebugPanelOptions | null>(null);
|
|
167
|
+
const performanceAnalyzerRef = useRef<PerformanceAnalyzer | null>(null);
|
|
540
168
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const dataURL = adapterRef.current.convertToDataURL({
|
|
576
|
-
type,
|
|
577
|
-
pixelRatio,
|
|
578
|
-
backgroundColor,
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
// 触发导出回调
|
|
582
|
-
if (onExport && dataURL) {
|
|
583
|
-
onExport(dataURL, options);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
return dataURL;
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* 导出图表并下载
|
|
591
|
-
* @param options 导出选项
|
|
592
|
-
*/
|
|
593
|
-
const exportChart = (options: ChartExportOptions = {}): void => {
|
|
594
|
-
const dataURL = exportChartToDataURL(options);
|
|
595
|
-
if (!dataURL) {
|
|
596
|
-
console.error('Failed to export chart');
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
const { type = 'png', filename = `chart-${Date.now()}` } = options;
|
|
601
|
-
const fullFilename = `${filename}.${type}`;
|
|
602
|
-
|
|
603
|
-
// 创建下载链接
|
|
604
|
-
const link = document.createElement('a');
|
|
605
|
-
link.download = fullFilename;
|
|
606
|
-
link.href = dataURL;
|
|
607
|
-
link.click();
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* 组件实例引用,用于暴露公共方法
|
|
612
|
-
*/
|
|
613
|
-
const chartInstanceRef = useRef({
|
|
614
|
-
exportChartToDataURL,
|
|
615
|
-
exportChart,
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
// 暴露组件实例方法
|
|
619
|
-
React.useImperativeHandle(props as any, () => chartInstanceRef.current);
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* 记录性能数据
|
|
623
|
-
* @param type 性能数据类型
|
|
624
|
-
* @param data 性能数据
|
|
625
|
-
*/
|
|
626
|
-
const recordPerformance = (type: 'init' | 'render' | 'update', _data?: any) => {
|
|
627
|
-
const now = Date.now();
|
|
628
|
-
|
|
629
|
-
switch (type) {
|
|
630
|
-
case 'init':
|
|
631
|
-
if (!performanceRef.current.initStartTime) {
|
|
632
|
-
performanceRef.current.initStartTime = now;
|
|
633
|
-
} else {
|
|
634
|
-
performanceRef.current.initEndTime = now;
|
|
635
|
-
const initTime =
|
|
636
|
-
performanceRef.current.initEndTime - performanceRef.current.initStartTime;
|
|
637
|
-
|
|
638
|
-
// 计算数据大小
|
|
639
|
-
const dataSize = JSON.stringify(option).length;
|
|
640
|
-
|
|
641
|
-
// 使用性能分析器记录数据
|
|
642
|
-
if (performanceAnalyzerRef.current) {
|
|
643
|
-
performanceAnalyzerRef.current.recordInitTime(initTime);
|
|
644
|
-
performanceAnalyzerRef.current.recordDataSize(option);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// 触发性能回调
|
|
648
|
-
if (onPerformance) {
|
|
649
|
-
onPerformance({
|
|
650
|
-
renderTime: 0,
|
|
651
|
-
initTime,
|
|
652
|
-
updateTime: 0,
|
|
653
|
-
dataSize,
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
break;
|
|
658
|
-
|
|
659
|
-
case 'render':
|
|
660
|
-
if (!performanceRef.current.renderStartTime) {
|
|
661
|
-
performanceRef.current.renderStartTime = now;
|
|
662
|
-
} else {
|
|
663
|
-
performanceRef.current.renderEndTime = now;
|
|
664
|
-
const renderTime =
|
|
665
|
-
performanceRef.current.renderEndTime - performanceRef.current.renderStartTime;
|
|
666
|
-
|
|
667
|
-
// 计算数据大小
|
|
668
|
-
const dataSize = JSON.stringify(option).length;
|
|
669
|
-
|
|
670
|
-
// 使用性能分析器记录数据
|
|
671
|
-
if (performanceAnalyzerRef.current) {
|
|
672
|
-
performanceAnalyzerRef.current.recordRenderTime(renderTime);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// 触发性能回调
|
|
676
|
-
if (onPerformance) {
|
|
677
|
-
onPerformance({
|
|
678
|
-
renderTime,
|
|
679
|
-
initTime: performanceRef.current.initEndTime - performanceRef.current.initStartTime,
|
|
680
|
-
updateTime: 0,
|
|
681
|
-
dataSize,
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
break;
|
|
686
|
-
|
|
687
|
-
case 'update':
|
|
688
|
-
if (!performanceRef.current.updateStartTime) {
|
|
689
|
-
performanceRef.current.updateStartTime = now;
|
|
690
|
-
} else {
|
|
691
|
-
performanceRef.current.updateEndTime = now;
|
|
692
|
-
const updateTime =
|
|
693
|
-
performanceRef.current.updateEndTime - performanceRef.current.updateStartTime;
|
|
694
|
-
|
|
695
|
-
// 计算数据大小
|
|
696
|
-
const dataSize = JSON.stringify(option).length;
|
|
697
|
-
|
|
698
|
-
// 使用性能分析器记录数据
|
|
699
|
-
if (performanceAnalyzerRef.current) {
|
|
700
|
-
performanceAnalyzerRef.current.recordUpdateTime(updateTime);
|
|
701
|
-
performanceAnalyzerRef.current.recordDataSize(option);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// 触发性能回调
|
|
705
|
-
if (onPerformance) {
|
|
706
|
-
onPerformance({
|
|
707
|
-
renderTime: 0,
|
|
708
|
-
initTime: 0,
|
|
709
|
-
updateTime,
|
|
710
|
-
dataSize,
|
|
711
|
-
});
|
|
169
|
+
// Debug config
|
|
170
|
+
const debugConfig = useMemo(() => {
|
|
171
|
+
if (!debug) return null;
|
|
172
|
+
return typeof debug === 'boolean'
|
|
173
|
+
? { enabled: debug, autoExpand: false }
|
|
174
|
+
: { enabled: true, ...debug };
|
|
175
|
+
}, [debug]);
|
|
176
|
+
|
|
177
|
+
// Wrapper option that applies virtual scroll + data filtering
|
|
178
|
+
const wrappedOption = useMemo(() => {
|
|
179
|
+
if (!option) return undefined;
|
|
180
|
+
let processed = { ...option };
|
|
181
|
+
|
|
182
|
+
// Apply data filtering
|
|
183
|
+
if (enableDataFiltering && filters && Object.keys(filters).length > 0) {
|
|
184
|
+
processed = JSON.parse(JSON.stringify(processed));
|
|
185
|
+
if (processed.series && Array.isArray(processed.series)) {
|
|
186
|
+
processed.series = processed.series.map((s: any) => {
|
|
187
|
+
if (s.data && Array.isArray(s.data)) {
|
|
188
|
+
const filtered = filterDataByKeys(s.data, filters);
|
|
189
|
+
if (onDataFiltered) onDataFiltered(filtered, filters);
|
|
190
|
+
if (virtualScroll) {
|
|
191
|
+
virtualScrollRef.current.totalDataCount = filtered.length;
|
|
192
|
+
virtualScrollRef.current.totalPages = Math.ceil(
|
|
193
|
+
filtered.length / virtualScrollPageSize
|
|
194
|
+
);
|
|
195
|
+
const start = virtualScrollRef.current.currentPage * virtualScrollPageSize;
|
|
196
|
+
const end = Math.min(
|
|
197
|
+
start + virtualScrollPageSize + virtualScrollPreloadSize,
|
|
198
|
+
filtered.length
|
|
199
|
+
);
|
|
200
|
+
return { ...s, data: filtered.slice(start, end) };
|
|
201
|
+
}
|
|
202
|
+
return { ...s, data: filtered };
|
|
712
203
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
|
|
718
|
-
/**
|
|
719
|
-
* 筛选数据
|
|
720
|
-
* @param data 原始数据
|
|
721
|
-
* @param filters 筛选条件
|
|
722
|
-
* @returns 筛选后的数据
|
|
723
|
-
*/
|
|
724
|
-
const filterData = (data: any[], filters: Record<string, any>): any[] => {
|
|
725
|
-
if (!enableDataFiltering || !filters || Object.keys(filters).length === 0) {
|
|
726
|
-
return data;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
return data.filter((item) => {
|
|
730
|
-
// 遍历所有筛选条件
|
|
731
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
732
|
-
// 检查数据项是否满足筛选条件
|
|
733
|
-
if (item[key] !== value && !item[key]?.includes?.(value)) {
|
|
734
|
-
return false;
|
|
735
|
-
}
|
|
204
|
+
return s;
|
|
205
|
+
});
|
|
736
206
|
}
|
|
737
|
-
return true;
|
|
738
|
-
});
|
|
739
|
-
};
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* 计算数据长度,用于动画性能优化
|
|
743
|
-
*/
|
|
744
|
-
const calculateDataLength = (option: EChartsOption): number => {
|
|
745
|
-
let count = 0;
|
|
746
|
-
|
|
747
|
-
// 计算系列数据长度
|
|
748
|
-
if (option.series) {
|
|
749
|
-
const series = Array.isArray(option.series) ? option.series : [option.series];
|
|
750
|
-
series.forEach((seriesItem: any) => {
|
|
751
|
-
if (seriesItem.data) {
|
|
752
|
-
if (Array.isArray(seriesItem.data)) {
|
|
753
|
-
count += seriesItem.data.length;
|
|
754
|
-
} else if (typeof seriesItem.data === 'object') {
|
|
755
|
-
// 处理对象类型数据
|
|
756
|
-
count += Object.keys(seriesItem.data).length;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
return count;
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
/**
|
|
766
|
-
* 处理大数据集的虚拟滚动和数据筛选
|
|
767
|
-
* @param originalOption 原始图表配置
|
|
768
|
-
* @returns 处理后的图表配置,只包含当前页的数据和筛选后的数据
|
|
769
|
-
*/
|
|
770
|
-
const processVirtualScroll = (originalOption: EChartsOption): EChartsOption => {
|
|
771
|
-
if (!originalOption) {
|
|
772
|
-
return originalOption;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// 深拷贝原始配置,避免修改原始数据
|
|
776
|
-
const processedOption = JSON.parse(JSON.stringify(originalOption)) as EChartsOption;
|
|
777
|
-
|
|
778
|
-
// 处理series数据
|
|
779
|
-
if (processedOption.series && Array.isArray(processedOption.series)) {
|
|
780
|
-
// 使用类型断言解决类型不匹配问题
|
|
781
|
-
(processedOption.series as any) = processedOption.series.map((series: any) => {
|
|
782
|
-
if (series.data && Array.isArray(series.data)) {
|
|
783
|
-
const data = series.data;
|
|
784
|
-
|
|
785
|
-
// 应用数据筛选
|
|
786
|
-
const filteredData = filterData(data, filters);
|
|
787
|
-
|
|
788
|
-
// 触发数据筛选回调
|
|
789
|
-
if (onDataFiltered) {
|
|
790
|
-
onDataFiltered(filteredData, filters);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// 应用虚拟滚动
|
|
794
|
-
if (virtualScroll) {
|
|
795
|
-
virtualScrollRef.current.totalDataCount = filteredData.length;
|
|
796
|
-
virtualScrollRef.current.totalPages = Math.ceil(
|
|
797
|
-
filteredData.length / virtualScrollPageSize
|
|
798
|
-
);
|
|
799
|
-
|
|
800
|
-
// 计算当前页的起始和结束索引
|
|
801
|
-
const startIndex = virtualScrollRef.current.currentPage * virtualScrollPageSize;
|
|
802
|
-
const endIndex = Math.min(
|
|
803
|
-
startIndex + virtualScrollPageSize + virtualScrollPreloadSize,
|
|
804
|
-
filteredData.length
|
|
805
|
-
);
|
|
806
|
-
|
|
807
|
-
virtualScrollRef.current.startIndex = startIndex;
|
|
808
|
-
virtualScrollRef.current.endIndex = endIndex;
|
|
809
|
-
|
|
810
|
-
// 返回只包含当前页数据的series
|
|
811
|
-
return {
|
|
812
|
-
...series,
|
|
813
|
-
data: filteredData.slice(startIndex, endIndex),
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// 只应用数据筛选,不应用虚拟滚动
|
|
818
|
-
return {
|
|
819
|
-
...series,
|
|
820
|
-
data: filteredData,
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
return series;
|
|
824
|
-
});
|
|
825
207
|
}
|
|
826
208
|
|
|
827
|
-
//
|
|
828
|
-
const dataLength = calculateDataLength(
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
209
|
+
// Apply animation config
|
|
210
|
+
const dataLength = calculateDataLength(processed);
|
|
211
|
+
const animConfig = generateEChartsAnimationConfig(animation, dataLength);
|
|
212
|
+
return { ...processed, ...animConfig } as EChartsOption;
|
|
213
|
+
}, [
|
|
214
|
+
option,
|
|
215
|
+
animation,
|
|
216
|
+
enableDataFiltering,
|
|
217
|
+
filters,
|
|
218
|
+
virtualScroll,
|
|
219
|
+
virtualScrollPageSize,
|
|
220
|
+
virtualScrollPreloadSize,
|
|
221
|
+
onDataFiltered,
|
|
222
|
+
]);
|
|
837
223
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
*/
|
|
844
|
-
useEffect(() => {
|
|
845
|
-
if (chartRef.current) {
|
|
846
|
-
// 处理调试配置
|
|
847
|
-
const debugConfig = processDebugConfig();
|
|
848
|
-
debugConfigRef.current = debugConfig;
|
|
224
|
+
// Internal chartInit that wraps the user's callback
|
|
225
|
+
const handleChartInit = useCallback(
|
|
226
|
+
(instance: any) => {
|
|
227
|
+
chartInstanceRef.current = instance;
|
|
228
|
+
adapterRef.current = instance;
|
|
849
229
|
|
|
850
|
-
//
|
|
230
|
+
// Performance monitoring init
|
|
851
231
|
if (enablePerformanceMonitoring) {
|
|
852
232
|
performanceAnalyzerRef.current = PerformanceAnalyzer.getInstance({
|
|
853
233
|
enabled: true,
|
|
@@ -858,249 +238,89 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
858
238
|
autoStart: true,
|
|
859
239
|
});
|
|
860
240
|
}
|
|
241
|
+
performanceRef.current.initStartTime = Date.now();
|
|
861
242
|
|
|
862
|
-
//
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
// 处理虚拟滚动
|
|
866
|
-
const processedOption = processVirtualScroll(option as EChartsOption);
|
|
867
|
-
|
|
868
|
-
// 获取适配器实例
|
|
869
|
-
const chartAdapter = getAdapter({
|
|
870
|
-
width,
|
|
871
|
-
height,
|
|
872
|
-
theme,
|
|
873
|
-
option: processedOption,
|
|
874
|
-
onInit,
|
|
875
|
-
containerRef: chartRef,
|
|
876
|
-
direction,
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
// 设置组件实例(针对小程序环境)
|
|
880
|
-
if (typeof chartAdapter.setComponent === 'function') {
|
|
881
|
-
chartAdapter.setComponent({
|
|
882
|
-
createChart: (_config: Record<string, any>) => {
|
|
883
|
-
// 这里应该根据具体平台实现创建图表的逻辑
|
|
884
|
-
return {};
|
|
885
|
-
},
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// 开始记录渲染时间
|
|
890
|
-
recordPerformance('render');
|
|
891
|
-
|
|
892
|
-
// 初始化图表
|
|
893
|
-
const instance = chartAdapter.init();
|
|
894
|
-
|
|
895
|
-
// 结束记录渲染时间
|
|
896
|
-
recordPerformance('render');
|
|
897
|
-
|
|
898
|
-
// 注册图表实例
|
|
899
|
-
if (chartId && instance) {
|
|
900
|
-
registerChart(chartId, instance);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// 更新调试信息
|
|
904
|
-
if (debugConfig?.enabled) {
|
|
905
|
-
// 更新实例信息
|
|
906
|
-
updateDebugInfo({
|
|
907
|
-
instance: {
|
|
908
|
-
id: chartId,
|
|
909
|
-
type: 'ECharts',
|
|
910
|
-
renderer: 'canvas', // 简化处理,使用默认值
|
|
911
|
-
width: typeof width === 'number' ? width : undefined,
|
|
912
|
-
height: typeof height === 'number' ? height : undefined,
|
|
913
|
-
platform: 'web', // 简化处理,使用默认值
|
|
914
|
-
},
|
|
915
|
-
config: processedOption,
|
|
916
|
-
data: {
|
|
917
|
-
series: Array.isArray(processedOption.series) ? processedOption.series : [],
|
|
918
|
-
totalDataCount: calculateDataLength(processedOption),
|
|
919
|
-
currentDataCount: calculateDataLength(processedOption),
|
|
920
|
-
},
|
|
921
|
-
performance: {
|
|
922
|
-
initTime: performanceRef.current.initEndTime - performanceRef.current.initStartTime,
|
|
923
|
-
renderTime:
|
|
924
|
-
performanceRef.current.renderEndTime - performanceRef.current.renderStartTime,
|
|
925
|
-
dataSize: JSON.stringify(processedOption).length,
|
|
926
|
-
},
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// 绑定事件
|
|
931
|
-
if (instance && onClick) {
|
|
932
|
-
instance.on('click', (params: any) => {
|
|
933
|
-
onClick(params as any);
|
|
243
|
+
// Register for linkage
|
|
244
|
+
if (chartId) registerChart(chartId, instance);
|
|
934
245
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
type: 'highlight',
|
|
944
|
-
name: params.name,
|
|
945
|
-
});
|
|
946
|
-
}
|
|
246
|
+
// Setup internal event handlers for linkage + virtual scroll
|
|
247
|
+
if (instance) {
|
|
248
|
+
// Click linkage
|
|
249
|
+
if (linkageConfig.enableClickLinkage && chartId && linkageConfig.linkedChartIds) {
|
|
250
|
+
instance.on('click', (params: any) => {
|
|
251
|
+
linkageConfig.linkedChartIds!.forEach((lid) => {
|
|
252
|
+
const linked = getChart(lid);
|
|
253
|
+
if (linked) linked.dispatchAction({ type: 'highlight', name: params.name });
|
|
947
254
|
});
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
951
257
|
|
|
952
|
-
|
|
953
|
-
if (instance) {
|
|
258
|
+
// Zoom + zoom linkage + virtual scroll page update
|
|
954
259
|
instance.on('datazoom', (params: any) => {
|
|
955
|
-
|
|
956
|
-
if (onDataZoom) {
|
|
957
|
-
onDataZoom(params as any);
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// 触发缩放事件
|
|
961
|
-
if (onZoom) {
|
|
260
|
+
if (onZoom)
|
|
962
261
|
onZoom({
|
|
963
262
|
start: params.start || 0,
|
|
964
263
|
end: params.end || 100,
|
|
965
264
|
dataZoomIndex: params.dataZoomIndex || 0,
|
|
966
265
|
});
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
// 缩放联动
|
|
970
|
-
if (linkageConfig.enableZoomLinkage && chartId && linkageConfig.linkedChartIds) {
|
|
971
|
-
linkageConfig.linkedChartIds.forEach((linkedChartId) => {
|
|
972
|
-
const linkedChart = getChart(linkedChartId);
|
|
973
|
-
if (linkedChart) {
|
|
974
|
-
linkedChart.dispatchAction({
|
|
975
|
-
type: 'dataZoom',
|
|
976
|
-
start: params.start,
|
|
977
|
-
end: params.end,
|
|
978
|
-
dataZoomIndex: params.dataZoomIndex,
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// 处理虚拟滚动
|
|
985
|
-
if (virtualScroll) {
|
|
986
|
-
if (virtualScrollRef.current.isScrolling) {
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
|
|
266
|
+
if (virtualScroll && !virtualScrollRef.current.isScrolling) {
|
|
990
267
|
virtualScrollRef.current.isScrolling = true;
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const newPage = Math.floor((scrollPercent / 100) * virtualScrollRef.current.totalPages);
|
|
995
|
-
|
|
268
|
+
const newPage = Math.floor(
|
|
269
|
+
((params.start || 0) / 100) * virtualScrollRef.current.totalPages
|
|
270
|
+
);
|
|
996
271
|
if (newPage !== virtualScrollRef.current.currentPage) {
|
|
997
272
|
virtualScrollRef.current.currentPage = newPage;
|
|
998
|
-
|
|
999
|
-
// 更新图表数据
|
|
1000
|
-
const updatedOption = processVirtualScroll(option as EChartsOption);
|
|
1001
|
-
chartAdapter.setOption(updatedOption);
|
|
273
|
+
// Trigger re-render via option update
|
|
1002
274
|
}
|
|
1003
|
-
|
|
1004
|
-
// 延迟重置滚动状态,避免频繁触发
|
|
1005
275
|
setTimeout(() => {
|
|
1006
276
|
virtualScrollRef.current.isScrolling = false;
|
|
1007
277
|
}, 100);
|
|
1008
278
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
if (instance && enableLegendInteraction) {
|
|
1020
|
-
// 图例选择事件
|
|
1021
|
-
instance.on('legendselectchanged', (params: any) => {
|
|
1022
|
-
const { name, selected } = params;
|
|
1023
|
-
|
|
1024
|
-
// 图例联动
|
|
1025
|
-
if (linkageConfig.enableLegendLinkage && chartId && linkageConfig.linkedChartIds) {
|
|
1026
|
-
linkageConfig.linkedChartIds.forEach((linkedChartId) => {
|
|
1027
|
-
const linkedChart = getChart(linkedChartId);
|
|
1028
|
-
if (linkedChart) {
|
|
1029
|
-
linkedChart.setOption({ legend: { selected } });
|
|
1030
|
-
}
|
|
279
|
+
if (linkageConfig.enableZoomLinkage && chartId && linkageConfig.linkedChartIds) {
|
|
280
|
+
linkageConfig.linkedChartIds!.forEach((lid) => {
|
|
281
|
+
const linked = getChart(lid);
|
|
282
|
+
if (linked)
|
|
283
|
+
linked.dispatchAction({
|
|
284
|
+
type: 'dataZoom',
|
|
285
|
+
start: params.start,
|
|
286
|
+
end: params.end,
|
|
287
|
+
dataZoomIndex: params.dataZoomIndex,
|
|
288
|
+
});
|
|
1031
289
|
});
|
|
1032
290
|
}
|
|
291
|
+
});
|
|
1033
292
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
// 触发回调
|
|
1044
|
-
if (onLegendSelect) {
|
|
1045
|
-
onLegendSelect({ name, selected: newSelected });
|
|
293
|
+
// Legend interaction
|
|
294
|
+
if (enableLegendInteraction) {
|
|
295
|
+
instance.on('legendselectchanged', (params: any) => {
|
|
296
|
+
const { name, selected } = params;
|
|
297
|
+
if (linkageConfig.enableLegendLinkage && chartId && linkageConfig.linkedChartIds) {
|
|
298
|
+
linkageConfig.linkedChartIds!.forEach((lid) => {
|
|
299
|
+
const linked = getChart(lid);
|
|
300
|
+
if (linked) linked.setOption({ legend: { selected } });
|
|
301
|
+
});
|
|
1046
302
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
}
|
|
303
|
+
if (legendInteractionMode === 'single') {
|
|
304
|
+
const newSelected: Record<string, boolean> = {};
|
|
305
|
+
Object.keys(selected).forEach((k) => {
|
|
306
|
+
newSelected[k] = k === name;
|
|
307
|
+
});
|
|
308
|
+
instance.setOption({ legend: { selected: newSelected } });
|
|
309
|
+
onLegendSelect?.({ name, selected: newSelected });
|
|
1054
310
|
} else {
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
onLegendUnselect({ name, selected });
|
|
1058
|
-
}
|
|
311
|
+
if (selected[name]) onLegendSelect?.({ name, selected });
|
|
312
|
+
else onLegendUnselect?.({ name, selected });
|
|
1059
313
|
}
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
|
-
// 图例全选功能
|
|
1064
|
-
if (legendInteractionMode === 'all') {
|
|
1065
|
-
// 这里可以添加全选/反选的逻辑
|
|
1066
|
-
// 例如:监听特定事件或添加自定义按钮
|
|
314
|
+
});
|
|
1067
315
|
}
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
// 自定义提示框功能
|
|
1071
|
-
if (instance && enableCustomTooltip) {
|
|
1072
|
-
// 提示框显示事件
|
|
1073
|
-
instance.on('tooltipshow', (params: any) => {
|
|
1074
|
-
// 触发提示框显示回调
|
|
1075
|
-
if (onTooltipShow) {
|
|
1076
|
-
onTooltipShow(params);
|
|
1077
|
-
}
|
|
1078
|
-
});
|
|
1079
316
|
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
onTooltipHide(params);
|
|
1085
|
-
}
|
|
1086
|
-
});
|
|
1087
|
-
|
|
1088
|
-
// 提示框格式化功能
|
|
1089
|
-
if (customTooltipContent) {
|
|
1090
|
-
// 这里可以添加自定义提示框的格式化逻辑
|
|
1091
|
-
// 例如:使用ECharts的tooltip.formatter选项
|
|
317
|
+
// Custom tooltip
|
|
318
|
+
if (enableCustomTooltip && customTooltipContent) {
|
|
319
|
+
instance.on('tooltipshow', (params: any) => onTooltipShow?.(params));
|
|
320
|
+
instance.on('tooltiphide', (params: any) => onTooltipHide?.(params));
|
|
1092
321
|
instance.setOption({
|
|
1093
322
|
tooltip: {
|
|
1094
|
-
formatter: (params: any) =>
|
|
1095
|
-
// 这里可以返回自定义的HTML内容
|
|
1096
|
-
// 由于ECharts的tooltip.formatter只支持返回字符串,我们需要将React组件转换为HTML字符串
|
|
1097
|
-
// 注意:这种方式有局限性,更复杂的自定义提示框可能需要使用其他方案
|
|
1098
|
-
return (
|
|
1099
|
-
'<div style="background: white; padding: 10px; border: 1px solid #ccc;">' +
|
|
1100
|
-
JSON.stringify(params) +
|
|
1101
|
-
'</div>'
|
|
1102
|
-
);
|
|
1103
|
-
},
|
|
323
|
+
formatter: (params: any) => String(customTooltipContent(params)),
|
|
1104
324
|
...(customTooltipStyle && {
|
|
1105
325
|
backgroundColor: 'transparent',
|
|
1106
326
|
borderColor: 'transparent',
|
|
@@ -1111,203 +331,158 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
1111
331
|
}
|
|
1112
332
|
}
|
|
1113
333
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
334
|
+
// Update debug panel
|
|
335
|
+
if (debugConfigRef.current?.enabled) {
|
|
336
|
+
updateDebugInfo({
|
|
337
|
+
instance: {
|
|
338
|
+
id: chartId,
|
|
339
|
+
type: 'ECharts',
|
|
340
|
+
renderer: 'canvas',
|
|
341
|
+
width: typeof width === 'number' ? width : undefined,
|
|
342
|
+
height: typeof height === 'number' ? height : undefined,
|
|
343
|
+
platform: 'web',
|
|
344
|
+
},
|
|
345
|
+
config: wrappedOption,
|
|
346
|
+
data: {
|
|
347
|
+
series: Array.isArray(wrappedOption?.series) ? wrappedOption.series : [],
|
|
348
|
+
totalDataCount: calculateDataLength(wrappedOption),
|
|
349
|
+
currentDataCount: calculateDataLength(wrappedOption),
|
|
350
|
+
},
|
|
351
|
+
performance: {
|
|
352
|
+
initTime: 0,
|
|
353
|
+
renderTime: 0,
|
|
354
|
+
dataSize: JSON.stringify(wrappedOption).length,
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
}
|
|
1131
358
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
359
|
+
onInit?.(instance);
|
|
360
|
+
performanceRef.current.initEndTime = Date.now();
|
|
361
|
+
},
|
|
362
|
+
[
|
|
363
|
+
chartId,
|
|
364
|
+
enablePerformanceMonitoring,
|
|
365
|
+
onInit,
|
|
366
|
+
linkageConfig,
|
|
367
|
+
virtualScroll,
|
|
368
|
+
onZoom,
|
|
369
|
+
enableLegendInteraction,
|
|
370
|
+
legendInteractionMode,
|
|
371
|
+
onLegendSelect,
|
|
372
|
+
onLegendUnselect,
|
|
373
|
+
enableCustomTooltip,
|
|
374
|
+
customTooltipContent,
|
|
375
|
+
customTooltipStyle,
|
|
376
|
+
onTooltipShow,
|
|
377
|
+
onTooltipHide,
|
|
378
|
+
wrappedOption,
|
|
379
|
+
width,
|
|
380
|
+
height,
|
|
381
|
+
]
|
|
382
|
+
);
|
|
1139
383
|
|
|
1140
|
-
|
|
1141
|
-
* 更新图表尺寸的 useEffect
|
|
1142
|
-
*
|
|
1143
|
-
* @description 当图表宽度或高度变化时,调整图表大小
|
|
1144
|
-
*/
|
|
384
|
+
// Update performance record
|
|
1145
385
|
useEffect(() => {
|
|
1146
|
-
if (
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
* @param newOption 新的配置
|
|
1155
|
-
* @returns 是否相同
|
|
1156
|
-
*/
|
|
1157
|
-
const isOptionEqual = (
|
|
1158
|
-
oldOption: EChartsOption | undefined,
|
|
1159
|
-
newOption: EChartsOption | undefined
|
|
1160
|
-
): boolean => {
|
|
1161
|
-
if (oldOption === newOption) {
|
|
1162
|
-
return true;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
if (!oldOption || !newOption) {
|
|
1166
|
-
return false;
|
|
386
|
+
if (chartInstanceRef.current && onPerformance) {
|
|
387
|
+
const p = performanceRef.current;
|
|
388
|
+
onPerformance({
|
|
389
|
+
renderTime: p.renderEndTime - p.renderStartTime,
|
|
390
|
+
initTime: p.initEndTime - p.initStartTime,
|
|
391
|
+
updateTime: p.updateEndTime - p.updateStartTime,
|
|
392
|
+
dataSize: JSON.stringify(option).length,
|
|
393
|
+
});
|
|
1167
394
|
}
|
|
395
|
+
}, [option, onPerformance]);
|
|
1168
396
|
|
|
1169
|
-
|
|
1170
|
-
// 深度比较
|
|
1171
|
-
return JSON.stringify(oldOption) === JSON.stringify(newOption);
|
|
1172
|
-
} else {
|
|
1173
|
-
// 浅比较,只比较引用
|
|
1174
|
-
return oldOption === newOption;
|
|
1175
|
-
}
|
|
1176
|
-
};
|
|
1177
|
-
|
|
1178
|
-
/**
|
|
1179
|
-
* 更新图表配置的 useEffect
|
|
1180
|
-
*
|
|
1181
|
-
* @description 当图表配置项变化时,更新图表
|
|
1182
|
-
*/
|
|
397
|
+
// Data update callback
|
|
1183
398
|
useEffect(() => {
|
|
1184
|
-
if (
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
// 处理虚拟滚动
|
|
1189
|
-
const processedOption = processVirtualScroll(option);
|
|
1190
|
-
adapterRef.current.setOption(processedOption);
|
|
1191
|
-
|
|
1192
|
-
// 结束记录更新时间
|
|
1193
|
-
recordPerformance('update');
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
// 数据更新监听
|
|
1197
|
-
if (onDataUpdate && dataUpdateOptions.enabled !== false) {
|
|
1198
|
-
const oldOption = oldOptionRef.current;
|
|
1199
|
-
if (!isOptionEqual(oldOption, option)) {
|
|
1200
|
-
onDataUpdate(oldOption, option);
|
|
1201
|
-
// 更新旧的配置引用
|
|
399
|
+
if (onDataUpdate && dataUpdateOptions?.enabled !== false) {
|
|
400
|
+
const oldOpt = oldOptionRef.current;
|
|
401
|
+
if (oldOpt !== option) {
|
|
402
|
+
onDataUpdate(oldOpt, option);
|
|
1202
403
|
oldOptionRef.current = option;
|
|
1203
404
|
}
|
|
1204
405
|
}
|
|
1205
406
|
}, [option, onDataUpdate, dataUpdateOptions]);
|
|
1206
407
|
|
|
1207
|
-
|
|
1208
|
-
* 更新图表主题的 useEffect
|
|
1209
|
-
*
|
|
1210
|
-
* @description 当图表主题变化时,更新图表主题
|
|
1211
|
-
*/
|
|
1212
|
-
useEffect(() => {
|
|
1213
|
-
if (adapterRef.current && theme) {
|
|
1214
|
-
adapterRef.current.setTheme(theme);
|
|
1215
|
-
}
|
|
1216
|
-
}, [theme]);
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
* 处理窗口大小变化的 useEffect
|
|
1220
|
-
*
|
|
1221
|
-
* @description 当窗口大小变化时,如果开启了自动调整大小,则调整图表大小
|
|
1222
|
-
*/
|
|
408
|
+
// Cleanup on unmount
|
|
1223
409
|
useEffect(() => {
|
|
1224
|
-
if (!autoResize || !adapterRef.current) {
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
const handleResize = () => {
|
|
1229
|
-
adapterRef.current.resize();
|
|
1230
|
-
};
|
|
1231
|
-
|
|
1232
|
-
window.addEventListener('resize', handleResize);
|
|
1233
|
-
|
|
1234
410
|
return () => {
|
|
1235
|
-
|
|
411
|
+
if (chartId) removeChart(chartId);
|
|
412
|
+
if (performanceAnalyzerRef.current) {
|
|
413
|
+
performanceAnalyzerRef.current.dispose();
|
|
414
|
+
performanceAnalyzerRef.current = null;
|
|
415
|
+
}
|
|
416
|
+
if (adapterRef.current) adapterRef.current.dispose();
|
|
1236
417
|
};
|
|
1237
|
-
}, [
|
|
418
|
+
}, [chartId]);
|
|
1238
419
|
|
|
1239
|
-
/**
|
|
1240
|
-
* 合并后的样式对象
|
|
1241
|
-
*
|
|
1242
|
-
* @description 合并用户传入的样式和组件默认样式
|
|
1243
|
-
*/
|
|
1244
420
|
const mergedStyle = {
|
|
1245
|
-
width
|
|
1246
|
-
height: typeof height === 'number' ? `${height}px` : height,
|
|
1247
|
-
direction,
|
|
1248
|
-
...style,
|
|
421
|
+
...normalizeSizeObject(width, height, direction, style),
|
|
1249
422
|
};
|
|
1250
423
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
424
|
+
const wrapperProps: BaseChartProps & { chartType: string } = {
|
|
425
|
+
option: wrappedOption as any,
|
|
426
|
+
width,
|
|
427
|
+
height,
|
|
428
|
+
theme: typeof theme === 'string' ? theme : (theme as Record<string, unknown>),
|
|
429
|
+
autoResize,
|
|
430
|
+
loading: false,
|
|
431
|
+
onChartInit: handleChartInit,
|
|
432
|
+
renderer: 'canvas',
|
|
433
|
+
onEvents: {},
|
|
434
|
+
chartType: 'base',
|
|
435
|
+
style: mergedStyle,
|
|
436
|
+
className,
|
|
437
|
+
};
|
|
1258
438
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
439
|
+
return (
|
|
440
|
+
<>
|
|
441
|
+
<BaseChartWrapper {...wrapperProps} />
|
|
442
|
+
{debugConfig?.enabled && (
|
|
443
|
+
<DebugPanel
|
|
444
|
+
options={debugConfig}
|
|
445
|
+
debugInfo={{
|
|
446
|
+
instance: {
|
|
447
|
+
id: chartId,
|
|
448
|
+
type: 'ECharts',
|
|
449
|
+
renderer: 'canvas',
|
|
450
|
+
width: typeof width === 'number' ? width : undefined,
|
|
451
|
+
height: typeof height === 'number' ? height : undefined,
|
|
452
|
+
platform: 'web',
|
|
453
|
+
},
|
|
454
|
+
config: option,
|
|
455
|
+
data: {
|
|
456
|
+
series: Array.isArray(option?.series) ? option.series : [],
|
|
457
|
+
totalDataCount: calculateDataLength(option),
|
|
458
|
+
currentDataCount: calculateDataLength(option),
|
|
459
|
+
},
|
|
460
|
+
performance: {
|
|
461
|
+
initTime: performanceRef.current.initEndTime - performanceRef.current.initStartTime,
|
|
462
|
+
renderTime:
|
|
463
|
+
performanceRef.current.renderEndTime - performanceRef.current.renderStartTime,
|
|
464
|
+
updateTime: 0,
|
|
465
|
+
dataSize: JSON.stringify(option).length,
|
|
466
|
+
},
|
|
467
|
+
}}
|
|
468
|
+
/>
|
|
469
|
+
)}
|
|
470
|
+
</>
|
|
1268
471
|
);
|
|
1269
|
-
|
|
1270
|
-
// 如果启用了调试面板,渲染调试面板
|
|
1271
|
-
if (debugConfig?.enabled) {
|
|
1272
|
-
return React.createElement(
|
|
1273
|
-
React.Fragment,
|
|
1274
|
-
null,
|
|
1275
|
-
chartContainer,
|
|
1276
|
-
React.createElement(DebugPanel, {
|
|
1277
|
-
options: debugConfig,
|
|
1278
|
-
debugInfo: {
|
|
1279
|
-
instance: {
|
|
1280
|
-
id: chartId,
|
|
1281
|
-
type: 'ECharts',
|
|
1282
|
-
renderer: 'canvas', // 简化处理,使用默认值
|
|
1283
|
-
width: typeof width === 'number' ? width : undefined,
|
|
1284
|
-
height: typeof height === 'number' ? height : undefined,
|
|
1285
|
-
platform: 'web', // 简化处理,使用默认值
|
|
1286
|
-
},
|
|
1287
|
-
config: option,
|
|
1288
|
-
data: {
|
|
1289
|
-
series: Array.isArray(option?.series) ? option.series : [],
|
|
1290
|
-
totalDataCount: calculateDataLength(option as EChartsOption),
|
|
1291
|
-
currentDataCount: calculateDataLength(option as EChartsOption),
|
|
1292
|
-
},
|
|
1293
|
-
performance: {
|
|
1294
|
-
initTime: performanceRef.current.initEndTime - performanceRef.current.initStartTime,
|
|
1295
|
-
renderTime:
|
|
1296
|
-
performanceRef.current.renderEndTime - performanceRef.current.renderStartTime,
|
|
1297
|
-
updateTime:
|
|
1298
|
-
performanceRef.current.updateEndTime - performanceRef.current.updateStartTime,
|
|
1299
|
-
dataSize: JSON.stringify(option).length,
|
|
1300
|
-
},
|
|
1301
|
-
},
|
|
1302
|
-
})
|
|
1303
|
-
);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
// 否则只渲染图表容器
|
|
1307
|
-
return chartContainer;
|
|
1308
472
|
};
|
|
1309
473
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
474
|
+
function normalizeSizeObject(
|
|
475
|
+
width: number | string,
|
|
476
|
+
height: number | string,
|
|
477
|
+
direction: 'ltr' | 'rtl',
|
|
478
|
+
style?: React.CSSProperties
|
|
479
|
+
): React.CSSProperties {
|
|
480
|
+
return {
|
|
481
|
+
width: normalizeSize(width, '100%'),
|
|
482
|
+
height: normalizeSize(height, '300px'),
|
|
483
|
+
direction,
|
|
484
|
+
...style,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
1313
488
|
export default BaseChart;
|