@agions/taroviz 1.11.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +245 -0
- package/README.md +104 -302
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/vendors.js +1 -0
- package/dist/cjs/vendors~echarts.js +1 -0
- package/dist/esm/index.js +1 -58151
- package/dist/esm/vendors.js +1 -0
- package/dist/esm/vendors~echarts.js +1 -0
- package/package.json +19 -25
- package/src/adapters/MiniAppAdapter.ts +136 -0
- package/src/adapters/__tests__/index.test.ts +1 -1
- package/src/adapters/h5/__tests__/index.test.ts +4 -2
- package/src/adapters/h5/index.ts +63 -64
- package/src/adapters/harmony/index.ts +23 -245
- package/src/adapters/index.ts +49 -45
- package/src/adapters/swan/index.ts +6 -69
- package/src/adapters/tt/index.ts +7 -70
- package/src/adapters/types.ts +25 -58
- package/src/adapters/weapp/index.ts +6 -69
- package/src/charts/__tests__/testUtils.tsx +87 -0
- package/src/charts/boxplot/__tests__/index.test.tsx +49 -103
- package/src/charts/boxplot/index.tsx +2 -1
- package/src/charts/boxplot/types.ts +17 -16
- package/src/charts/common/BaseChartWrapper.tsx +90 -82
- package/src/charts/common/__mocks__/BaseChartWrapper.tsx +17 -0
- package/src/charts/createChartComponent.tsx +36 -0
- package/src/charts/createOptionChartComponent.tsx +32 -0
- package/src/charts/funnel/__tests__/index.test.tsx +99 -0
- package/src/charts/funnel/index.tsx +60 -10
- package/src/charts/funnel/types.ts +6 -0
- package/src/charts/graph/__tests__/index.test.tsx +102 -33
- package/src/charts/graph/index.tsx +66 -9
- package/src/charts/graph/types.ts +6 -0
- package/src/charts/heatmap/__tests__/index.test.tsx +139 -0
- package/src/charts/heatmap/index.tsx +103 -10
- package/src/charts/heatmap/types.ts +6 -0
- package/src/charts/index.ts +74 -26
- package/src/charts/liquid/__tests__/index.test.tsx +52 -0
- package/src/charts/liquid/index.tsx +239 -182
- package/src/charts/liquid/types.ts +11 -11
- package/src/charts/parallel/__tests__/index.test.tsx +40 -67
- package/src/charts/parallel/index.tsx +2 -1
- package/src/charts/parallel/types.ts +19 -18
- package/src/charts/radar/__tests__/index.test.tsx +210 -0
- package/src/charts/radar/index.tsx +143 -10
- package/src/charts/radar/types.ts +13 -0
- package/src/charts/sankey/__tests__/index.test.tsx +124 -0
- package/src/charts/sankey/index.tsx +62 -10
- package/src/charts/sankey/types.ts +6 -0
- package/src/charts/tree/__tests__/index.test.tsx +71 -0
- package/src/charts/tree/index.tsx +5 -2
- package/src/charts/tree/types.ts +9 -9
- package/src/charts/types.ts +208 -106
- package/src/charts/utils.ts +9 -7
- package/src/charts/wordcloud/__tests__/index.test.tsx +98 -31
- package/src/charts/wordcloud/index.tsx +75 -9
- package/src/charts/wordcloud/types.ts +6 -0
- package/src/components/DataFilter/index.tsx +32 -10
- package/src/core/animation/types.ts +6 -6
- package/src/core/components/Annotation.tsx +6 -7
- package/src/core/components/BaseChart.tsx +110 -168
- package/src/core/components/ErrorBoundary.tsx +17 -4
- package/src/core/components/LazyChart.tsx +54 -55
- package/src/core/components/hooks/index.ts +6 -2
- package/src/core/components/hooks/useChartInit.ts +6 -3
- package/src/core/components/hooks/usePerformance.ts +8 -2
- package/src/core/components/hooks/useVirtualScroll.ts +2 -1
- package/src/core/index.ts +1 -1
- package/src/core/themes/ThemeManager.ts +1 -1
- package/src/core/types/common.ts +2 -1
- package/src/core/types/index.ts +0 -12
- package/src/core/types/platform.ts +3 -5
- package/src/core/utils/__tests__/deepClone.test.ts +317 -0
- package/src/core/utils/__tests__/index.test.ts +2 -1
- package/src/core/utils/chartInstances.ts +13 -0
- package/src/core/utils/common.ts +20 -29
- package/src/core/utils/deepClone.ts +114 -0
- package/src/core/utils/download.ts +128 -0
- package/src/core/utils/drillDown.ts +34 -353
- package/src/core/utils/drillDownHelpers.ts +426 -0
- package/src/core/utils/events.ts +12 -0
- package/src/core/utils/export/ExportUtils.ts +36 -67
- package/src/core/utils/format.ts +44 -0
- package/src/core/utils/index.ts +21 -154
- package/src/core/utils/merge.ts +25 -0
- package/src/core/utils/performance/PerformanceAnalyzer.ts +38 -21
- package/src/core/utils/performance/hooks.ts +7 -0
- package/src/core/utils/performance/index.ts +2 -0
- package/src/{hooks → core/utils/performance}/useAnimation.ts +45 -41
- package/src/core/utils/performance/useDataZoom.ts +324 -0
- package/src/{hooks → core/utils/performance}/usePerformance.ts +49 -41
- package/src/core/utils/performance/usePerformanceHooks.ts +278 -0
- package/src/core/utils/performanceUtils.ts +310 -0
- package/src/core/utils/runtime.ts +190 -0
- package/src/core/utils/setOptionUtils.ts +59 -0
- package/src/core/version.ts +14 -0
- package/src/editor/EnhancedThemeEditor.tsx +362 -540
- package/src/editor/ThemeEditor.tsx +55 -321
- package/src/editor/components/ThemeBasicSettings.tsx +113 -0
- package/src/editor/components/ThemeColorEditor.tsx +105 -0
- package/src/editor/components/ThemeSelector.tsx +70 -0
- package/src/editor/hooks/useThemeEditorState.ts +201 -0
- package/src/editor/index.ts +10 -2
- package/src/hooks/__tests__/index.test.tsx +3 -1
- package/src/hooks/chartConnectHelpers.ts +341 -0
- package/src/hooks/index.ts +55 -660
- package/src/hooks/types.ts +189 -0
- package/src/hooks/useChartAutoResize.ts +73 -0
- package/src/hooks/useChartConnect.ts +92 -238
- package/src/hooks/useChartDownload.ts +25 -27
- package/src/hooks/useChartHistory.ts +34 -49
- package/src/hooks/useChartInit.ts +59 -0
- package/src/hooks/useChartOptions.ts +259 -0
- package/src/hooks/useChartPerformance.ts +109 -0
- package/src/hooks/useChartSelection.ts +52 -49
- package/src/hooks/useChartTheme.ts +51 -0
- package/src/hooks/useDataTransform.ts +19 -4
- package/src/hooks/utils/chartDownloadUtils.ts +40 -53
- package/src/hooks/utils/dataTransformUtils.ts +22 -0
- package/src/index.ts +48 -34
- package/src/main.tsx +4 -9
- package/src/react-dom.d.ts +3 -3
- package/src/themes/index.ts +30 -855
- package/src/themes/palettes/blue-green.ts +13 -0
- package/src/themes/palettes/chalk.ts +13 -0
- package/src/themes/palettes/cyber.ts +44 -0
- package/src/themes/palettes/dark.ts +52 -0
- package/src/themes/palettes/default.ts +52 -0
- package/src/themes/palettes/elegant.ts +34 -0
- package/src/themes/palettes/forest.ts +13 -0
- package/src/themes/palettes/glass.ts +49 -0
- package/src/themes/palettes/golden.ts +13 -0
- package/src/themes/palettes/neon.ts +43 -0
- package/src/themes/palettes/ocean.ts +39 -0
- package/src/themes/palettes/pastel.ts +37 -0
- package/src/themes/palettes/purple-passion.ts +13 -0
- package/src/themes/palettes/retro.ts +33 -0
- package/src/themes/palettes/sunset.ts +40 -0
- package/src/themes/palettes/walden.ts +13 -0
- package/src/themes/registry.ts +184 -0
- package/src/themes/types.ts +213 -0
- package/src/charts/bar/__tests__/index.test.tsx +0 -113
- package/src/charts/bar/index.tsx +0 -14
- package/src/charts/candlestick/__tests__/index.test.tsx +0 -40
- package/src/charts/candlestick/index.tsx +0 -13
- package/src/charts/gauge/index.tsx +0 -14
- package/src/charts/line/__tests__/index.test.tsx +0 -107
- package/src/charts/line/index.tsx +0 -15
- package/src/charts/pie/__tests__/index.test.tsx +0 -112
- package/src/charts/pie/index.tsx +0 -14
- package/src/charts/scatter/index.tsx +0 -14
- package/src/charts/sunburst/index.tsx +0 -18
- package/src/charts/treemap/index.tsx +0 -18
- package/src/core/utils/codeGenerator/CodeGenerator.ts +0 -669
- package/src/core/utils/codeGenerator/index.ts +0 -13
- package/src/core/utils/codeGenerator/types.ts +0 -198
- package/src/core/utils/configGenerator/ConfigGenerator.ts +0 -583
- package/src/core/utils/configGenerator/index.ts +0 -13
- package/src/core/utils/configGenerator/types.ts +0 -445
- package/src/core/utils/debug/DebugPanel.tsx +0 -637
- package/src/core/utils/debug/debugger.ts +0 -322
- package/src/core/utils/debug/index.ts +0 -21
- package/src/core/utils/debug/types.ts +0 -142
- package/src/hooks/useDataZoom.ts +0 -323
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1
2
|
/**
|
|
2
3
|
* DataFilter - 数据筛选器 UI 组件
|
|
3
4
|
* 提供独立的数据筛选交互界面,支持多种筛选类型
|
|
@@ -32,7 +33,15 @@ export interface FilterField {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/** 筛选值类型 */
|
|
35
|
-
export type FilterValue =
|
|
36
|
+
export type FilterValue =
|
|
37
|
+
| string
|
|
38
|
+
| number
|
|
39
|
+
| boolean
|
|
40
|
+
| [number, number]
|
|
41
|
+
| [string, string]
|
|
42
|
+
| string[]
|
|
43
|
+
| number[]
|
|
44
|
+
| undefined;
|
|
36
45
|
|
|
37
46
|
/** 筛选器完整值 */
|
|
38
47
|
export interface FilterValues {
|
|
@@ -74,7 +83,8 @@ export interface DataFilterProps {
|
|
|
74
83
|
// ============================================================================
|
|
75
84
|
|
|
76
85
|
const BASE_STYLE: React.CSSProperties = {
|
|
77
|
-
fontFamily:
|
|
86
|
+
fontFamily:
|
|
87
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
78
88
|
fontSize: '14px',
|
|
79
89
|
color: '#333',
|
|
80
90
|
};
|
|
@@ -122,7 +132,13 @@ interface SelectFilterProps {
|
|
|
122
132
|
compact?: boolean;
|
|
123
133
|
}
|
|
124
134
|
|
|
125
|
-
const SelectFilter: React.FC<SelectFilterProps> = ({
|
|
135
|
+
const SelectFilter: React.FC<SelectFilterProps> = ({
|
|
136
|
+
field,
|
|
137
|
+
value,
|
|
138
|
+
onChange,
|
|
139
|
+
disabled,
|
|
140
|
+
compact,
|
|
141
|
+
}) => {
|
|
126
142
|
const handleChange = useCallback(
|
|
127
143
|
(e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
128
144
|
const val = e.target.value;
|
|
@@ -241,7 +257,13 @@ interface CheckboxFilterProps {
|
|
|
241
257
|
compact?: boolean;
|
|
242
258
|
}
|
|
243
259
|
|
|
244
|
-
const CheckboxFilter: React.FC<CheckboxFilterProps> = ({
|
|
260
|
+
const CheckboxFilter: React.FC<CheckboxFilterProps> = ({
|
|
261
|
+
field,
|
|
262
|
+
value,
|
|
263
|
+
onChange,
|
|
264
|
+
disabled,
|
|
265
|
+
compact,
|
|
266
|
+
}) => {
|
|
245
267
|
const selectedValues = useMemo(() => {
|
|
246
268
|
if (Array.isArray(value)) {
|
|
247
269
|
return new Set(value.map(String));
|
|
@@ -380,13 +402,13 @@ const DateFilter: React.FC<DateFilterProps> = ({ field, value, onChange, disable
|
|
|
380
402
|
* ```tsx
|
|
381
403
|
* <DataFilter
|
|
382
404
|
* fields={[
|
|
383
|
-
* {
|
|
384
|
-
* {
|
|
385
|
-
* {
|
|
386
|
-
* {
|
|
405
|
+
* { _key: 'category', label: '分类', type: 'select', options: [{ label: '苹果', _value: 'apple' }] },
|
|
406
|
+
* { _key: 'price', label: '价格区间', type: 'range', min: 0, max: 1000 },
|
|
407
|
+
* { _key: 'tags', label: '标签', type: 'checkbox', options: [{ label: '热门', _value: 'hot' }] },
|
|
408
|
+
* { _key: 'date', label: '日期', type: 'date' },
|
|
387
409
|
* ]}
|
|
388
|
-
*
|
|
389
|
-
* onChange={(
|
|
410
|
+
* _value={_filters}
|
|
411
|
+
* onChange={(_filters) => setFilters(_filters)}
|
|
390
412
|
* layout="horizontal"
|
|
391
413
|
* showReset
|
|
392
414
|
* />
|
|
@@ -241,33 +241,33 @@ export enum AnimationEventType {
|
|
|
241
241
|
/**
|
|
242
242
|
* 动画开始事件
|
|
243
243
|
*/
|
|
244
|
-
|
|
244
|
+
_ANIMATION_START = 'animationStart',
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
247
|
* 动画更新事件
|
|
248
248
|
*/
|
|
249
|
-
|
|
249
|
+
_ANIMATION_UPDATE = 'animationUpdate',
|
|
250
250
|
|
|
251
251
|
/**
|
|
252
252
|
* 动画结束事件
|
|
253
253
|
*/
|
|
254
|
-
|
|
254
|
+
_ANIMATION_END = 'animationEnd',
|
|
255
255
|
|
|
256
256
|
/**
|
|
257
257
|
* 动画取消事件
|
|
258
258
|
*/
|
|
259
|
-
|
|
259
|
+
_ANIMATION_CANCEL = 'animationCancel',
|
|
260
260
|
|
|
261
261
|
/**
|
|
262
262
|
* 动画重复事件
|
|
263
263
|
*/
|
|
264
|
-
|
|
264
|
+
_ANIMATION_REPEAT = 'animationRepeat',
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
/**
|
|
268
268
|
* 动画事件回调类型
|
|
269
269
|
*/
|
|
270
|
-
export type AnimationEventHandler = (
|
|
270
|
+
export type AnimationEventHandler = (_event: {
|
|
271
271
|
type: AnimationEventType;
|
|
272
272
|
animationType: AnimationType;
|
|
273
273
|
chartId?: string;
|
|
@@ -57,7 +57,7 @@ export interface MarkLineConfig {
|
|
|
57
57
|
label?: {
|
|
58
58
|
show?: boolean;
|
|
59
59
|
position?: 'start' | 'middle' | 'end' | 'insideStartBottom' | 'insideStartTop';
|
|
60
|
-
formatter?: string | ((
|
|
60
|
+
formatter?: string | ((_value: unknown) => string);
|
|
61
61
|
color?: string;
|
|
62
62
|
fontSize?: number;
|
|
63
63
|
};
|
|
@@ -81,7 +81,7 @@ export interface MarkAreaConfig {
|
|
|
81
81
|
label?: {
|
|
82
82
|
show?: boolean;
|
|
83
83
|
position?: 'top' | 'bottom' | 'left' | 'right' | 'inside';
|
|
84
|
-
formatter?: string | ((
|
|
84
|
+
formatter?: string | ((_value: unknown) => string);
|
|
85
85
|
color?: string;
|
|
86
86
|
fontSize?: number;
|
|
87
87
|
};
|
|
@@ -98,7 +98,7 @@ export interface ScatterAnnotationConfig {
|
|
|
98
98
|
/** 数据点 */
|
|
99
99
|
data: Array<{
|
|
100
100
|
coord: [number | string, number];
|
|
101
|
-
|
|
101
|
+
_value?: number;
|
|
102
102
|
name: string;
|
|
103
103
|
}>;
|
|
104
104
|
/** 符号类型 */
|
|
@@ -111,7 +111,7 @@ export interface ScatterAnnotationConfig {
|
|
|
111
111
|
label?: {
|
|
112
112
|
show?: boolean;
|
|
113
113
|
position?: string;
|
|
114
|
-
formatter?: string | ((
|
|
114
|
+
formatter?: string | ((_value: unknown) => string);
|
|
115
115
|
color?: string;
|
|
116
116
|
};
|
|
117
117
|
}
|
|
@@ -243,7 +243,6 @@ export function useAnnotation(props: AnnotationProps): EChartsOption {
|
|
|
243
243
|
const { type, markLine, markArea, scatter } = props;
|
|
244
244
|
|
|
245
245
|
return useMemo(() => {
|
|
246
|
-
// 使用 any 避免类型复杂性问题
|
|
247
246
|
const series: any[] = [];
|
|
248
247
|
|
|
249
248
|
if (type === 'line' && markLine) {
|
|
@@ -291,8 +290,8 @@ export const AnnotationPresets = {
|
|
|
291
290
|
}),
|
|
292
291
|
|
|
293
292
|
/** 警戒线 */
|
|
294
|
-
thresholdLine: (
|
|
295
|
-
data: [{ yAxis:
|
|
293
|
+
thresholdLine: (_value: number, color = '#faad14'): MarkLineConfig => ({
|
|
294
|
+
data: [{ yAxis: _value, name: '警戒线' }],
|
|
296
295
|
lineStyle: { color, type: 'solid', width: 2 },
|
|
297
296
|
label: { show: true, position: 'start', color },
|
|
298
297
|
}),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1
2
|
/**
|
|
2
3
|
* TaroViz 基础图表组件
|
|
3
4
|
* 所有图表组件的基类
|
|
@@ -6,21 +7,22 @@
|
|
|
6
7
|
* 所有具体的图表组件(如折线图、柱状图等)都继承自该组件
|
|
7
8
|
*/
|
|
8
9
|
import React, { useEffect, useRef, useMemo, useCallback } from 'react';
|
|
10
|
+
import { deepClone } from '../utils/deepClone';
|
|
9
11
|
|
|
10
12
|
import { generateEChartsAnimationConfig } from '../animation';
|
|
11
13
|
import { EChartsOption, EChartsType, AnimationConfig } from '../types';
|
|
12
14
|
import type { DataZoomComponentOption } from 'echarts';
|
|
13
15
|
import { registerChart, removeChart, getChart } from '../utils/chartInstances';
|
|
14
|
-
import { DebugPanel, DebugPanelOptions, updateDebugInfo } from '../utils/debug';
|
|
15
16
|
import { PerformanceAnalyzer } from '../utils/performance';
|
|
16
17
|
import { normalizeSize, calculateDataLength, filterDataByKeys } from '../utils/chartUtils';
|
|
17
18
|
import BaseChartWrapper from '../../charts/common/BaseChartWrapper';
|
|
18
19
|
import type { BaseChartProps } from '../../charts/types';
|
|
19
20
|
import type {
|
|
20
21
|
EChartsMouseEventParams,
|
|
21
|
-
EChartsDataZoomEventParams,
|
|
22
|
-
EChartsLegendEventParams,
|
|
23
22
|
EChartsTooltipEventParams,
|
|
23
|
+
ChartEventParams,
|
|
24
|
+
ChartExportOptions,
|
|
25
|
+
ChartLinkageConfig,
|
|
24
26
|
} from '../types/common';
|
|
25
27
|
import type { ECElementEvent } from 'echarts';
|
|
26
28
|
|
|
@@ -28,46 +30,8 @@ import type { ECElementEvent } from 'echarts';
|
|
|
28
30
|
// 接口定义
|
|
29
31
|
// ============================================================================
|
|
30
32
|
|
|
31
|
-
/**
|
|
32
|
-
export
|
|
33
|
-
componentType?: string;
|
|
34
|
-
componentSubType?: string;
|
|
35
|
-
componentIndex?: number;
|
|
36
|
-
seriesType?: string;
|
|
37
|
-
seriesIndex?: number;
|
|
38
|
-
seriesId?: string;
|
|
39
|
-
seriesName?: string;
|
|
40
|
-
name?: string;
|
|
41
|
-
dataIndex?: number;
|
|
42
|
-
data?: unknown;
|
|
43
|
-
dataType?: string;
|
|
44
|
-
value?: unknown;
|
|
45
|
-
color?: string;
|
|
46
|
-
borderColor?: string;
|
|
47
|
-
dimensionNames?: string[];
|
|
48
|
-
encode?: Record<string, number[]>;
|
|
49
|
-
marker?: string;
|
|
50
|
-
status?: string;
|
|
51
|
-
dimensionIndex?: number;
|
|
52
|
-
percent?: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** 图表导出选项 */
|
|
56
|
-
export interface ChartExportOptions {
|
|
57
|
-
type?: 'png' | 'jpeg' | 'svg';
|
|
58
|
-
filename?: string;
|
|
59
|
-
pixelRatio?: number;
|
|
60
|
-
backgroundColor?: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** 图表联动配置 */
|
|
64
|
-
export interface ChartLinkageConfig {
|
|
65
|
-
linkedChartIds?: string[];
|
|
66
|
-
enableClickLinkage?: boolean;
|
|
67
|
-
enableZoomLinkage?: boolean;
|
|
68
|
-
enableLegendLinkage?: boolean;
|
|
69
|
-
enableFilterLinkage?: boolean;
|
|
70
|
-
}
|
|
33
|
+
/** 从 core/types 导入共享类型 */
|
|
34
|
+
export { ChartEventParams, ChartExportOptions, ChartLinkageConfig } from '../types/common';
|
|
71
35
|
|
|
72
36
|
// ============================================================================
|
|
73
37
|
// ChartProps - 与原有接口保持完全兼容
|
|
@@ -77,15 +41,14 @@ export interface ChartProps {
|
|
|
77
41
|
chartId?: string;
|
|
78
42
|
option?: EChartsOption;
|
|
79
43
|
animation?: AnimationConfig;
|
|
80
|
-
debug?: boolean | DebugPanelOptions;
|
|
81
44
|
width?: number | string;
|
|
82
45
|
height?: number | string;
|
|
83
46
|
theme?: string | object;
|
|
84
47
|
autoResize?: boolean;
|
|
85
48
|
direction?: 'ltr' | 'rtl';
|
|
86
|
-
onInit?: (
|
|
87
|
-
onClick?: (
|
|
88
|
-
onDataZoom?: (
|
|
49
|
+
onInit?: (_instance: EChartsType) => void;
|
|
50
|
+
onClick?: (_params: ChartEventParams) => void;
|
|
51
|
+
onDataZoom?: (_params: ChartEventParams) => void;
|
|
89
52
|
style?: React.CSSProperties;
|
|
90
53
|
className?: string;
|
|
91
54
|
children?: React.ReactNode;
|
|
@@ -100,28 +63,30 @@ export interface ChartProps {
|
|
|
100
63
|
dataSize: number;
|
|
101
64
|
}) => void;
|
|
102
65
|
enableZoom?: boolean;
|
|
103
|
-
onZoom?: (data: { start: number; end: number;
|
|
66
|
+
onZoom?: (data: { start: number; end: number; _dataZoomIndex: number }) => void;
|
|
104
67
|
enableDataFiltering?: boolean;
|
|
105
|
-
|
|
106
|
-
onDataFiltered?: (
|
|
68
|
+
_filters?: Record<string, string | number | boolean | string[] | null>;
|
|
69
|
+
onDataFiltered?: (_filteredData: unknown[], _filters: Record<string, unknown>) => void;
|
|
107
70
|
enableLegendInteraction?: boolean;
|
|
108
71
|
legendInteractionMode?: 'single' | 'multiple' | 'all';
|
|
109
|
-
onLegendSelect?: (
|
|
110
|
-
onLegendUnselect?: (
|
|
111
|
-
onLegendSelectAll?: (
|
|
112
|
-
onLegendInverseSelect?: (
|
|
72
|
+
onLegendSelect?: (_params: { name: string; selected: Record<string, boolean> }) => void;
|
|
73
|
+
onLegendUnselect?: (_params: { name: string; selected: Record<string, boolean> }) => void;
|
|
74
|
+
onLegendSelectAll?: (_params: { selected: Record<string, boolean> }) => void;
|
|
75
|
+
onLegendInverseSelect?: (_params: { selected: Record<string, boolean> }) => void;
|
|
113
76
|
enableCustomTooltip?: boolean;
|
|
114
|
-
customTooltipContent?: (
|
|
77
|
+
customTooltipContent?: (
|
|
78
|
+
_params: EChartsMouseEventParams | EChartsMouseEventParams[]
|
|
79
|
+
) => React.ReactNode;
|
|
115
80
|
customTooltipStyle?: React.CSSProperties;
|
|
116
|
-
onTooltipShow?: (
|
|
117
|
-
onTooltipHide?: (
|
|
118
|
-
onExport?: (
|
|
81
|
+
onTooltipShow?: (_params: EChartsTooltipEventParams) => void;
|
|
82
|
+
onTooltipHide?: (_params: EChartsTooltipEventParams) => void;
|
|
83
|
+
onExport?: (_dataURL: string, options: ChartExportOptions) => void;
|
|
119
84
|
linkageConfig?: ChartLinkageConfig;
|
|
120
85
|
onDataUpdate?: (
|
|
121
86
|
oldOption: EChartsOption | undefined,
|
|
122
87
|
newOption: EChartsOption | undefined
|
|
123
88
|
) => void;
|
|
124
|
-
|
|
89
|
+
_dataUpdateOptions?: { enabled?: boolean; deepCompare?: boolean; debounceDelay?: number };
|
|
125
90
|
}
|
|
126
91
|
|
|
127
92
|
// ============================================================================
|
|
@@ -133,7 +98,6 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
133
98
|
chartId,
|
|
134
99
|
option,
|
|
135
100
|
animation,
|
|
136
|
-
debug,
|
|
137
101
|
width = '100%',
|
|
138
102
|
height = '300px',
|
|
139
103
|
theme,
|
|
@@ -153,7 +117,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
153
117
|
enableZoom: _enableZoom = false,
|
|
154
118
|
onZoom,
|
|
155
119
|
enableDataFiltering = false,
|
|
156
|
-
|
|
120
|
+
_filters = {},
|
|
157
121
|
onDataFiltered,
|
|
158
122
|
enableLegendInteraction = false,
|
|
159
123
|
legendInteractionMode = 'single',
|
|
@@ -169,7 +133,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
169
133
|
onExport: _onExport,
|
|
170
134
|
linkageConfig = {},
|
|
171
135
|
onDataUpdate,
|
|
172
|
-
|
|
136
|
+
_dataUpdateOptions = {},
|
|
173
137
|
} = props;
|
|
174
138
|
|
|
175
139
|
// Refs
|
|
@@ -190,31 +154,22 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
190
154
|
});
|
|
191
155
|
const oldOptionRef = useRef<EChartsOption | undefined>(option);
|
|
192
156
|
const adapterRef = useRef<unknown>(null);
|
|
193
|
-
const debugConfigRef = useRef<DebugPanelOptions | null>(null);
|
|
194
157
|
const performanceAnalyzerRef = useRef<PerformanceAnalyzer | null>(null);
|
|
195
158
|
|
|
196
|
-
//
|
|
197
|
-
const debugConfig = useMemo(() => {
|
|
198
|
-
if (!debug) return null;
|
|
199
|
-
return typeof debug === 'boolean'
|
|
200
|
-
? { enabled: debug, autoExpand: false }
|
|
201
|
-
: { enabled: true, ...debug };
|
|
202
|
-
}, [debug]);
|
|
203
|
-
|
|
204
|
-
// Wrapper option that applies virtual scroll + data filtering
|
|
159
|
+
// Wrapper option that applies virtual scroll + _data filtering
|
|
205
160
|
const wrappedOption = useMemo(() => {
|
|
206
161
|
if (!option) return undefined;
|
|
207
162
|
let processed: Record<string, unknown> = { ...option };
|
|
208
163
|
|
|
209
|
-
// Apply
|
|
210
|
-
if (enableDataFiltering &&
|
|
211
|
-
processed =
|
|
164
|
+
// Apply _data filtering
|
|
165
|
+
if (enableDataFiltering && _filters && Object.keys(_filters).length > 0) {
|
|
166
|
+
processed = deepClone(processed);
|
|
212
167
|
if (processed.series && Array.isArray(processed.series)) {
|
|
213
168
|
processed.series = (processed.series as unknown[]).map((s: unknown) => {
|
|
214
|
-
const seriesItem = s as {
|
|
215
|
-
if (seriesItem.
|
|
216
|
-
const filtered = filterDataByKeys(seriesItem.
|
|
217
|
-
if (onDataFiltered) onDataFiltered(filtered,
|
|
169
|
+
const seriesItem = s as { _data?: unknown[]; [key: string]: unknown };
|
|
170
|
+
if (seriesItem._data && Array.isArray(seriesItem._data)) {
|
|
171
|
+
const filtered = filterDataByKeys(seriesItem._data, _filters);
|
|
172
|
+
if (onDataFiltered) onDataFiltered(filtered, _filters);
|
|
218
173
|
if (virtualScroll) {
|
|
219
174
|
virtualScrollRef.current.totalDataCount = filtered.length;
|
|
220
175
|
virtualScrollRef.current.totalPages = Math.ceil(
|
|
@@ -225,24 +180,26 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
225
180
|
start + virtualScrollPageSize + virtualScrollPreloadSize,
|
|
226
181
|
filtered.length
|
|
227
182
|
);
|
|
228
|
-
return { ...seriesItem,
|
|
183
|
+
return { ...seriesItem, _data: filtered.slice(start, end) };
|
|
229
184
|
}
|
|
230
|
-
return { ...seriesItem,
|
|
185
|
+
return { ...seriesItem, _data: filtered };
|
|
231
186
|
}
|
|
232
187
|
return seriesItem;
|
|
233
188
|
});
|
|
234
189
|
}
|
|
235
190
|
}
|
|
236
191
|
|
|
237
|
-
// Inject
|
|
192
|
+
// Inject _dataZoom when enableZoom is true (keyboard-accessible zoom)
|
|
238
193
|
if (_enableZoom) {
|
|
239
|
-
processed =
|
|
240
|
-
// Avoid duplicate
|
|
241
|
-
const existingDzArr = Array.isArray(processed.
|
|
242
|
-
? processed.
|
|
243
|
-
: processed.
|
|
194
|
+
processed = deepClone(processed);
|
|
195
|
+
// Avoid duplicate _dataZoom entries
|
|
196
|
+
const existingDzArr = Array.isArray(processed._dataZoom)
|
|
197
|
+
? (processed._dataZoom as DataZoomComponentOption[])
|
|
198
|
+
: processed._dataZoom
|
|
199
|
+
? [processed._dataZoom as DataZoomComponentOption]
|
|
200
|
+
: [];
|
|
244
201
|
if (!existingDzArr.some((dz) => dz?.type === 'inside')) {
|
|
245
|
-
processed.
|
|
202
|
+
processed._dataZoom = [
|
|
246
203
|
...(existingDzArr || []),
|
|
247
204
|
// Inside (mouse wheel + keyboard) — wired to keyboard nav in BaseChartWrapper
|
|
248
205
|
{ type: 'inside', start: 0, end: 100, zoomOnMouseWheel: true, moveOnMouseMove: false },
|
|
@@ -251,15 +208,15 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
251
208
|
}
|
|
252
209
|
|
|
253
210
|
// Apply animation config
|
|
254
|
-
const
|
|
255
|
-
const animConfig = generateEChartsAnimationConfig(animation,
|
|
211
|
+
const _dataLength = calculateDataLength(processed);
|
|
212
|
+
const animConfig = generateEChartsAnimationConfig(animation, _dataLength);
|
|
256
213
|
return { ...processed, ...animConfig } as EChartsOption;
|
|
257
214
|
}, [
|
|
258
215
|
option,
|
|
259
216
|
animation,
|
|
260
217
|
_enableZoom,
|
|
261
218
|
enableDataFiltering,
|
|
262
|
-
|
|
219
|
+
_filters,
|
|
263
220
|
virtualScroll,
|
|
264
221
|
virtualScrollPageSize,
|
|
265
222
|
virtualScrollPreloadSize,
|
|
@@ -268,9 +225,9 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
268
225
|
|
|
269
226
|
// Internal chartInit that wraps the user's callback
|
|
270
227
|
const handleChartInit = useCallback(
|
|
271
|
-
(
|
|
272
|
-
chartInstanceRef.current =
|
|
273
|
-
adapterRef.current =
|
|
228
|
+
(_instance: EChartsType) => {
|
|
229
|
+
chartInstanceRef.current = _instance;
|
|
230
|
+
adapterRef.current = _instance as unknown;
|
|
274
231
|
|
|
275
232
|
// Performance monitoring init
|
|
276
233
|
if (enablePerformanceMonitoring) {
|
|
@@ -287,28 +244,33 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
287
244
|
performanceRef.current.initStartTime = Date.now();
|
|
288
245
|
|
|
289
246
|
// Register for linkage
|
|
290
|
-
if (chartId) registerChart(chartId,
|
|
247
|
+
if (chartId) registerChart(chartId, _instance);
|
|
291
248
|
|
|
292
249
|
// Setup internal event handlers for linkage + virtual scroll
|
|
293
|
-
if (
|
|
250
|
+
if (_instance) {
|
|
294
251
|
// Click linkage
|
|
295
252
|
if (linkageConfig.enableClickLinkage && chartId && linkageConfig.linkedChartIds) {
|
|
296
|
-
|
|
253
|
+
_instance.on('click', (_params: ECElementEvent) => {
|
|
297
254
|
linkageConfig.linkedChartIds!.forEach((lid) => {
|
|
298
255
|
const linked = getChart(lid);
|
|
299
|
-
if (linked) linked.dispatchAction({ type: 'highlight', name:
|
|
256
|
+
if (linked) linked.dispatchAction({ type: 'highlight', name: _params.name });
|
|
300
257
|
});
|
|
301
258
|
});
|
|
302
259
|
}
|
|
303
260
|
|
|
304
261
|
// Zoom + zoom linkage + virtual scroll page update
|
|
305
|
-
|
|
306
|
-
const p =
|
|
262
|
+
_instance.on('_datazoom', (_params: unknown) => {
|
|
263
|
+
const p = _params as {
|
|
264
|
+
start?: number;
|
|
265
|
+
end?: number;
|
|
266
|
+
_dataZoomIndex?: number;
|
|
267
|
+
batch?: Array<{ start?: number; end?: number; _dataZoomIndex?: number }>;
|
|
268
|
+
};
|
|
307
269
|
if (onZoom)
|
|
308
270
|
onZoom({
|
|
309
271
|
start: p.start || 0,
|
|
310
272
|
end: p.end || 100,
|
|
311
|
-
|
|
273
|
+
_dataZoomIndex: p._dataZoomIndex || 0,
|
|
312
274
|
});
|
|
313
275
|
if (virtualScroll && !virtualScrollRef.current.isScrolling) {
|
|
314
276
|
virtualScrollRef.current.isScrolling = true;
|
|
@@ -328,10 +290,10 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
328
290
|
const linked = getChart(lid);
|
|
329
291
|
if (linked)
|
|
330
292
|
linked.dispatchAction({
|
|
331
|
-
type: '
|
|
293
|
+
type: '_dataZoom',
|
|
332
294
|
start: p.start,
|
|
333
295
|
end: p.end,
|
|
334
|
-
|
|
296
|
+
_dataZoomIndex: p._dataZoomIndex,
|
|
335
297
|
});
|
|
336
298
|
});
|
|
337
299
|
}
|
|
@@ -339,8 +301,8 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
339
301
|
|
|
340
302
|
// Legend interaction
|
|
341
303
|
if (enableLegendInteraction) {
|
|
342
|
-
|
|
343
|
-
const p =
|
|
304
|
+
_instance.on('legendselectchanged', (_params: unknown) => {
|
|
305
|
+
const p = _params as { name?: string; selected: Record<string, boolean> };
|
|
344
306
|
const { name, selected } = p;
|
|
345
307
|
if (linkageConfig.enableLegendLinkage && chartId && linkageConfig.linkedChartIds) {
|
|
346
308
|
linkageConfig.linkedChartIds!.forEach((lid) => {
|
|
@@ -353,7 +315,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
353
315
|
Object.keys(selected).forEach((k) => {
|
|
354
316
|
newSelected[k] = k === name;
|
|
355
317
|
});
|
|
356
|
-
|
|
318
|
+
_instance.setOption({ legend: { selected: newSelected } });
|
|
357
319
|
if (name !== undefined) onLegendSelect?.({ name, selected: newSelected });
|
|
358
320
|
} else {
|
|
359
321
|
if (name !== undefined && selected[name]) onLegendSelect?.({ name, selected });
|
|
@@ -364,11 +326,16 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
364
326
|
|
|
365
327
|
// Custom tooltip
|
|
366
328
|
if (enableCustomTooltip && customTooltipContent) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
329
|
+
_instance.on('tooltipshow', (_params: unknown) =>
|
|
330
|
+
onTooltipShow?.(_params as EChartsTooltipEventParams)
|
|
331
|
+
);
|
|
332
|
+
_instance.on('tooltiphide', (_params: unknown) =>
|
|
333
|
+
onTooltipHide?.(_params as EChartsTooltipEventParams)
|
|
334
|
+
);
|
|
335
|
+
_instance.setOption({
|
|
370
336
|
tooltip: {
|
|
371
|
-
formatter: (
|
|
337
|
+
formatter: (_params: unknown) =>
|
|
338
|
+
String(customTooltipContent(_params as EChartsMouseEventParams)),
|
|
372
339
|
...(customTooltipStyle && {
|
|
373
340
|
backgroundColor: 'transparent',
|
|
374
341
|
borderColor: 'transparent',
|
|
@@ -379,32 +346,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
379
346
|
}
|
|
380
347
|
}
|
|
381
348
|
|
|
382
|
-
|
|
383
|
-
if (debugConfigRef.current?.enabled) {
|
|
384
|
-
updateDebugInfo({
|
|
385
|
-
instance: {
|
|
386
|
-
id: chartId,
|
|
387
|
-
type: 'ECharts',
|
|
388
|
-
renderer: 'canvas',
|
|
389
|
-
width: typeof width === 'number' ? width : undefined,
|
|
390
|
-
height: typeof height === 'number' ? height : undefined,
|
|
391
|
-
platform: 'web',
|
|
392
|
-
},
|
|
393
|
-
config: wrappedOption,
|
|
394
|
-
data: {
|
|
395
|
-
series: Array.isArray(wrappedOption?.series) ? wrappedOption.series : [],
|
|
396
|
-
totalDataCount: calculateDataLength(wrappedOption),
|
|
397
|
-
currentDataCount: calculateDataLength(wrappedOption),
|
|
398
|
-
},
|
|
399
|
-
performance: {
|
|
400
|
-
initTime: 0,
|
|
401
|
-
renderTime: 0,
|
|
402
|
-
dataSize: JSON.stringify(wrappedOption).length,
|
|
403
|
-
},
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
onInit?.(instance);
|
|
349
|
+
onInit?.(_instance);
|
|
408
350
|
performanceRef.current.initEndTime = Date.now();
|
|
409
351
|
},
|
|
410
352
|
[
|
|
@@ -437,7 +379,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
437
379
|
renderTime: p.renderEndTime - p.renderStartTime,
|
|
438
380
|
initTime: p.initEndTime - p.initStartTime,
|
|
439
381
|
updateTime: p.updateEndTime - p.updateStartTime,
|
|
440
|
-
dataSize:
|
|
382
|
+
dataSize: estimateDataSize(option),
|
|
441
383
|
});
|
|
442
384
|
}
|
|
443
385
|
}, [option, onPerformance]);
|
|
@@ -446,9 +388,9 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
446
388
|
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
447
389
|
|
|
448
390
|
useEffect(() => {
|
|
449
|
-
if (!onDataUpdate ||
|
|
391
|
+
if (!onDataUpdate || _dataUpdateOptions?.enabled === false) return;
|
|
450
392
|
|
|
451
|
-
const delay =
|
|
393
|
+
const delay = _dataUpdateOptions?.debounceDelay ?? 0;
|
|
452
394
|
|
|
453
395
|
if (debounceTimerRef.current) {
|
|
454
396
|
clearTimeout(debounceTimerRef.current);
|
|
@@ -476,7 +418,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
476
418
|
debounceTimerRef.current = null;
|
|
477
419
|
}
|
|
478
420
|
};
|
|
479
|
-
}, [option, onDataUpdate,
|
|
421
|
+
}, [option, onDataUpdate, _dataUpdateOptions]);
|
|
480
422
|
|
|
481
423
|
// Cleanup on unmount
|
|
482
424
|
useEffect(() => {
|
|
@@ -512,38 +454,38 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
512
454
|
return (
|
|
513
455
|
<>
|
|
514
456
|
<BaseChartWrapper {...wrapperProps} />
|
|
515
|
-
{debugConfig?.enabled && (
|
|
516
|
-
<DebugPanel
|
|
517
|
-
options={debugConfig}
|
|
518
|
-
debugInfo={{
|
|
519
|
-
instance: {
|
|
520
|
-
id: chartId,
|
|
521
|
-
type: 'ECharts',
|
|
522
|
-
renderer: 'canvas',
|
|
523
|
-
width: typeof width === 'number' ? width : undefined,
|
|
524
|
-
height: typeof height === 'number' ? height : undefined,
|
|
525
|
-
platform: 'web',
|
|
526
|
-
},
|
|
527
|
-
config: option,
|
|
528
|
-
data: {
|
|
529
|
-
series: Array.isArray(option?.series) ? option.series : [],
|
|
530
|
-
totalDataCount: calculateDataLength(option),
|
|
531
|
-
currentDataCount: calculateDataLength(option),
|
|
532
|
-
},
|
|
533
|
-
performance: {
|
|
534
|
-
initTime: performanceRef.current.initEndTime - performanceRef.current.initStartTime,
|
|
535
|
-
renderTime:
|
|
536
|
-
performanceRef.current.renderEndTime - performanceRef.current.renderStartTime,
|
|
537
|
-
updateTime: 0,
|
|
538
|
-
dataSize: JSON.stringify(option).length,
|
|
539
|
-
},
|
|
540
|
-
}}
|
|
541
|
-
/>
|
|
542
|
-
)}
|
|
543
457
|
</>
|
|
544
458
|
);
|
|
545
459
|
};
|
|
546
460
|
|
|
461
|
+
/**
|
|
462
|
+
* Lightweight estimation of option data size without expensive JSON.stringify.
|
|
463
|
+
* Counts approximate character length by summing string values and array lengths.
|
|
464
|
+
*/
|
|
465
|
+
function estimateDataSize(option: unknown): number {
|
|
466
|
+
if (option == null) return 0;
|
|
467
|
+
if (typeof option === 'string') return option.length;
|
|
468
|
+
if (typeof option !== 'object') return 8; // number/boolean approximation
|
|
469
|
+
|
|
470
|
+
let size = 0;
|
|
471
|
+
const obj = option as Record<string, unknown>;
|
|
472
|
+
for (const key in obj) {
|
|
473
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
|
|
474
|
+
size += key.length + 1; // key + colon
|
|
475
|
+
const val = obj[key];
|
|
476
|
+
if (typeof val === 'string') {
|
|
477
|
+
size += val.length + 2; // quotes
|
|
478
|
+
} else if (Array.isArray(val)) {
|
|
479
|
+
size += val.length * 4; // rough per-element estimate
|
|
480
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
481
|
+
size += estimateDataSize(val);
|
|
482
|
+
} else {
|
|
483
|
+
size += 8; // number/boolean
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return size;
|
|
487
|
+
}
|
|
488
|
+
|
|
547
489
|
function normalizeSizeObject(
|
|
548
490
|
width: number | string,
|
|
549
491
|
height: number | string,
|