@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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 主题编辑器共享状态管理 Hook
|
|
3
|
+
* 提取 ThemeEditor 和 EnhancedThemeEditor 的公共状态逻辑
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
6
|
+
import type { ThemeOptions } from '../../themes';
|
|
7
|
+
import { getRegisteredThemes, registerTheme, switchTheme } from '../../themes';
|
|
8
|
+
|
|
9
|
+
export interface UseThemeEditorStateOptions {
|
|
10
|
+
selectedTheme?: string;
|
|
11
|
+
enableLivePreview?: boolean;
|
|
12
|
+
onThemeChange?: (theme: ThemeOptions) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseThemeEditorStateReturn {
|
|
16
|
+
/** 当前主题 */
|
|
17
|
+
currentTheme: ThemeOptions;
|
|
18
|
+
/** 已注册的主题列表 */
|
|
19
|
+
registeredThemes: ThemeOptions[];
|
|
20
|
+
/** 颜色列表 */
|
|
21
|
+
colors: string[];
|
|
22
|
+
/** 背景色 */
|
|
23
|
+
backgroundColor: string;
|
|
24
|
+
/** 文本颜色 */
|
|
25
|
+
textColor: string;
|
|
26
|
+
/** 深色模式 */
|
|
27
|
+
darkMode: boolean;
|
|
28
|
+
/** 是否处于编辑模式 */
|
|
29
|
+
isEditing: boolean;
|
|
30
|
+
/** 新主题名称 */
|
|
31
|
+
newThemeName: string;
|
|
32
|
+
/** 设置当前主题 */
|
|
33
|
+
setCurrentTheme: (theme: ThemeOptions) => void;
|
|
34
|
+
/** 更新主题(部分更新) */
|
|
35
|
+
updateTheme: (updates: Partial<ThemeOptions>) => void;
|
|
36
|
+
/** 处理颜色变化 */
|
|
37
|
+
handleColorChange: (index: number, color: string) => void;
|
|
38
|
+
/** 添加颜色 */
|
|
39
|
+
handleAddColor: () => void;
|
|
40
|
+
/** 删除颜色 */
|
|
41
|
+
handleRemoveColor: (index: number) => void;
|
|
42
|
+
/** 开始编辑新主题 */
|
|
43
|
+
handleStartEdit: () => void;
|
|
44
|
+
/** 设置新主题名称 */
|
|
45
|
+
setNewThemeName: (name: string) => void;
|
|
46
|
+
/** 保存主题 */
|
|
47
|
+
saveTheme: () => ThemeOptions;
|
|
48
|
+
/** 取消编辑 */
|
|
49
|
+
cancelEdit: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 带有必填字段的默认主题 */
|
|
53
|
+
const DEFAULT_THEME: Required<
|
|
54
|
+
Pick<ThemeOptions, 'colors' | 'backgroundColor' | 'textColor' | 'darkMode'>
|
|
55
|
+
> = {
|
|
56
|
+
colors: ['#1890ff'],
|
|
57
|
+
backgroundColor: '#ffffff',
|
|
58
|
+
textColor: '#333333',
|
|
59
|
+
darkMode: false,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 主题编辑器状态管理 Hook
|
|
64
|
+
*/
|
|
65
|
+
export function useThemeEditorState(
|
|
66
|
+
options: UseThemeEditorStateOptions = {}
|
|
67
|
+
): UseThemeEditorStateReturn {
|
|
68
|
+
const { selectedTheme, enableLivePreview = true, onThemeChange } = options;
|
|
69
|
+
|
|
70
|
+
const registeredThemes = getRegisteredThemes();
|
|
71
|
+
|
|
72
|
+
// 计算初始主题
|
|
73
|
+
const getInitialTheme = useCallback((): ThemeOptions => {
|
|
74
|
+
if (selectedTheme) {
|
|
75
|
+
const theme = registeredThemes.find((t) => t.name === selectedTheme);
|
|
76
|
+
if (theme) return theme;
|
|
77
|
+
}
|
|
78
|
+
return registeredThemes[0] || DEFAULT_THEME;
|
|
79
|
+
}, [selectedTheme, registeredThemes]);
|
|
80
|
+
|
|
81
|
+
const [currentTheme, setCurrentThemeState] = useState<ThemeOptions>(getInitialTheme);
|
|
82
|
+
const [newThemeName, setNewThemeName] = useState<string>('');
|
|
83
|
+
const [isEditing, setIsEditing] = useState<boolean>(false);
|
|
84
|
+
|
|
85
|
+
// 派生状态
|
|
86
|
+
const colors: string[] = currentTheme.colors || DEFAULT_THEME.colors;
|
|
87
|
+
const backgroundColor: string = currentTheme.backgroundColor || DEFAULT_THEME.backgroundColor;
|
|
88
|
+
const textColor: string = currentTheme.textColor || DEFAULT_THEME.textColor;
|
|
89
|
+
const darkMode: boolean = currentTheme.darkMode ?? DEFAULT_THEME.darkMode;
|
|
90
|
+
|
|
91
|
+
// 当外部 selectedTheme 变化时同步
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (selectedTheme) {
|
|
94
|
+
const theme = registeredThemes.find((t) => t.name === selectedTheme);
|
|
95
|
+
if (theme) {
|
|
96
|
+
setCurrentThemeState(theme);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}, [selectedTheme, registeredThemes]);
|
|
100
|
+
|
|
101
|
+
// 设置当前主题并触发回调
|
|
102
|
+
const setCurrentTheme = useCallback(
|
|
103
|
+
(theme: ThemeOptions) => {
|
|
104
|
+
setCurrentThemeState(theme);
|
|
105
|
+
if (enableLivePreview) {
|
|
106
|
+
switchTheme(theme);
|
|
107
|
+
}
|
|
108
|
+
onThemeChange?.(theme);
|
|
109
|
+
},
|
|
110
|
+
[enableLivePreview, onThemeChange]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// 更新主题(部分更新)
|
|
114
|
+
const updateTheme = useCallback(
|
|
115
|
+
(updates: Partial<ThemeOptions>) => {
|
|
116
|
+
const updated = { ...currentTheme, ...updates };
|
|
117
|
+
setCurrentThemeState(updated);
|
|
118
|
+
if (enableLivePreview) {
|
|
119
|
+
switchTheme(updated);
|
|
120
|
+
}
|
|
121
|
+
onThemeChange?.(updated);
|
|
122
|
+
},
|
|
123
|
+
[currentTheme, enableLivePreview, onThemeChange]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// 处理颜色变化
|
|
127
|
+
const handleColorChange = useCallback(
|
|
128
|
+
(index: number, color: string) => {
|
|
129
|
+
const newColors = [...colors];
|
|
130
|
+
newColors[index] = color;
|
|
131
|
+
updateTheme({ colors: newColors });
|
|
132
|
+
},
|
|
133
|
+
[colors, updateTheme]
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// 添加颜色
|
|
137
|
+
const handleAddColor = useCallback(() => {
|
|
138
|
+
updateTheme({ colors: [...colors, '#000000'] });
|
|
139
|
+
}, [colors, updateTheme]);
|
|
140
|
+
|
|
141
|
+
// 删除颜色
|
|
142
|
+
const handleRemoveColor = useCallback(
|
|
143
|
+
(index: number) => {
|
|
144
|
+
if (colors.length <= 1) return;
|
|
145
|
+
const newColors = colors.filter((_, i) => i !== index);
|
|
146
|
+
updateTheme({ colors: newColors });
|
|
147
|
+
},
|
|
148
|
+
[colors, updateTheme]
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// 开始编辑新主题
|
|
152
|
+
const handleStartEdit = useCallback(() => {
|
|
153
|
+
setIsEditing(true);
|
|
154
|
+
setNewThemeName('');
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
// 取消编辑
|
|
158
|
+
const cancelEdit = useCallback(() => {
|
|
159
|
+
setIsEditing(false);
|
|
160
|
+
setNewThemeName('');
|
|
161
|
+
}, []);
|
|
162
|
+
|
|
163
|
+
// 保存主题
|
|
164
|
+
const saveTheme = useCallback((): ThemeOptions => {
|
|
165
|
+
const name = isEditing && newThemeName ? newThemeName : currentTheme.name || 'custom';
|
|
166
|
+
const themeToSave: ThemeOptions = {
|
|
167
|
+
...currentTheme,
|
|
168
|
+
name,
|
|
169
|
+
colors,
|
|
170
|
+
backgroundColor,
|
|
171
|
+
textColor,
|
|
172
|
+
darkMode,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
registerTheme(name, themeToSave);
|
|
176
|
+
setIsEditing(false);
|
|
177
|
+
setNewThemeName('');
|
|
178
|
+
|
|
179
|
+
return themeToSave;
|
|
180
|
+
}, [isEditing, newThemeName, currentTheme, colors, backgroundColor, textColor, darkMode]);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
currentTheme,
|
|
184
|
+
registeredThemes,
|
|
185
|
+
colors,
|
|
186
|
+
backgroundColor,
|
|
187
|
+
textColor,
|
|
188
|
+
darkMode,
|
|
189
|
+
isEditing,
|
|
190
|
+
newThemeName,
|
|
191
|
+
setCurrentTheme,
|
|
192
|
+
updateTheme,
|
|
193
|
+
handleColorChange,
|
|
194
|
+
handleAddColor,
|
|
195
|
+
handleRemoveColor,
|
|
196
|
+
handleStartEdit,
|
|
197
|
+
setNewThemeName,
|
|
198
|
+
saveTheme,
|
|
199
|
+
cancelEdit,
|
|
200
|
+
};
|
|
201
|
+
}
|
package/src/editor/index.ts
CHANGED
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
* 提供可视化的主题编辑功能
|
|
4
4
|
*/
|
|
5
5
|
import ThemeEditor from './ThemeEditor';
|
|
6
|
-
import
|
|
6
|
+
import EnhancedThemeEditor from './EnhancedThemeEditor';
|
|
7
7
|
|
|
8
|
-
export {
|
|
8
|
+
export type { ThemeEditorProps } from './ThemeEditor';
|
|
9
|
+
export type { EnhancedThemeEditorProps, ThemeExportOptions } from './EnhancedThemeEditor';
|
|
10
|
+
export { useThemeEditorState } from './hooks/useThemeEditorState';
|
|
11
|
+
export type {
|
|
12
|
+
UseThemeEditorStateOptions,
|
|
13
|
+
UseThemeEditorStateReturn,
|
|
14
|
+
} from './hooks/useThemeEditorState';
|
|
15
|
+
|
|
16
|
+
export { ThemeEditor, EnhancedThemeEditor };
|
|
9
17
|
|
|
10
18
|
export default ThemeEditor;
|
|
@@ -249,7 +249,9 @@ describe('React Hooks', () => {
|
|
|
249
249
|
);
|
|
250
250
|
|
|
251
251
|
// getThemeByName('default') returns the defaultTheme object
|
|
252
|
-
expect(result.current).toEqual(
|
|
252
|
+
expect(result.current).toEqual(
|
|
253
|
+
expect.objectContaining({ backgroundColor: '#ffffff', textColor: '#333333' })
|
|
254
|
+
);
|
|
253
255
|
});
|
|
254
256
|
|
|
255
257
|
it('should return original theme when darkMode is false', () => {
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
/**
|
|
3
|
+
* useChartConnect 辅助函数
|
|
4
|
+
* 将 useChartConnect 中的逻辑拆分为独立函数,提高代码可维护性
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
ChartInstance,
|
|
9
|
+
ConnectEventType,
|
|
10
|
+
ChartPointerEventParams,
|
|
11
|
+
ChartSelectEventParams,
|
|
12
|
+
ChartDataZoomEventParams,
|
|
13
|
+
EventHandler,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// 导出类型
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
export type {
|
|
21
|
+
ConnectEventType,
|
|
22
|
+
ChartPointerEventParams,
|
|
23
|
+
ChartSelectEventParams,
|
|
24
|
+
ChartDataZoomEventParams,
|
|
25
|
+
} from './types';
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// 内部类型
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
/** 图表联动项 */
|
|
32
|
+
export interface ChartConnectItem {
|
|
33
|
+
instance: ChartInstance;
|
|
34
|
+
id: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 事件处理器映射 */
|
|
38
|
+
export type EventHandlersMap = Map<string, Map<ConnectEventType, EventHandler>>;
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// 事件处理器创建
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 创建联动事件处理器
|
|
46
|
+
*/
|
|
47
|
+
export function createConnectHandler(
|
|
48
|
+
sourceId: string,
|
|
49
|
+
|
|
50
|
+
eventType: ConnectEventType,
|
|
51
|
+
disabled: boolean,
|
|
52
|
+
|
|
53
|
+
eventFilter: ((eventType: ConnectEventType, params: unknown) => boolean) | undefined,
|
|
54
|
+
chartsRef: React.MutableRefObject<Map<string, ChartConnectItem>>,
|
|
55
|
+
optionsRef: React.MutableRefObject<{
|
|
56
|
+
onConnect?: (
|
|
57
|
+
sourceId: string,
|
|
58
|
+
targetId: string,
|
|
59
|
+
|
|
60
|
+
payload: { eventType: ConnectEventType; params: unknown }
|
|
61
|
+
) => void;
|
|
62
|
+
}>
|
|
63
|
+
): EventHandler {
|
|
64
|
+
return (params: unknown) => {
|
|
65
|
+
if (disabled) return;
|
|
66
|
+
|
|
67
|
+
// 应用事件过滤器
|
|
68
|
+
if (eventFilter && !eventFilter(eventType, params)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 分发联动事件到所有其他图表
|
|
73
|
+
dispatchToOthers(sourceId, eventType, params, chartsRef);
|
|
74
|
+
|
|
75
|
+
// 触发回调
|
|
76
|
+
const currentOptions = optionsRef.current;
|
|
77
|
+
if (currentOptions.onConnect) {
|
|
78
|
+
chartsRef.current.forEach((item, targetId) => {
|
|
79
|
+
if (targetId !== sourceId) {
|
|
80
|
+
currentOptions.onConnect?.(sourceId, targetId, { eventType, params });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 分发事件到其他图表
|
|
89
|
+
*/
|
|
90
|
+
export function dispatchToOthers(
|
|
91
|
+
sourceId: string,
|
|
92
|
+
|
|
93
|
+
eventType: ConnectEventType,
|
|
94
|
+
params: unknown,
|
|
95
|
+
chartsRef: React.MutableRefObject<Map<string, ChartConnectItem>>
|
|
96
|
+
): void {
|
|
97
|
+
chartsRef.current.forEach((item, targetId) => {
|
|
98
|
+
if (targetId === sourceId) return;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
switch (eventType) {
|
|
102
|
+
case 'click':
|
|
103
|
+
case 'hover': {
|
|
104
|
+
const p = params as ChartPointerEventParams;
|
|
105
|
+
item.instance.dispatchAction?.({
|
|
106
|
+
type: 'showTip',
|
|
107
|
+
seriesIndex: p.seriesIndex,
|
|
108
|
+
dataIndex: p.dataIndex,
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case 'select': {
|
|
113
|
+
const p = params as ChartSelectEventParams;
|
|
114
|
+
item.instance.dispatchAction?.({
|
|
115
|
+
type: 'toggleSelect',
|
|
116
|
+
seriesIndex: p.seriesIndex,
|
|
117
|
+
dataIndex: p.dataIndex,
|
|
118
|
+
});
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'dataZoom': {
|
|
122
|
+
const p = params as ChartDataZoomEventParams;
|
|
123
|
+
item.instance.dispatchAction?.({
|
|
124
|
+
type: 'dataZoom',
|
|
125
|
+
start: p.start,
|
|
126
|
+
end: p.end,
|
|
127
|
+
dataZoomIndex: p.dataZoomIndex,
|
|
128
|
+
dataZoomIndexs: p.dataZoomIndexs,
|
|
129
|
+
});
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
default:
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
} catch (e) {
|
|
136
|
+
console.warn(`[useChartConnect] Failed to dispatch ${eventType} to ${targetId}:`, e);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// 事件绑定管理
|
|
143
|
+
// ============================================================================
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 绑定单个图表的联动事件
|
|
147
|
+
*/
|
|
148
|
+
export function bindChartEvents(
|
|
149
|
+
chartInstance: ChartInstance,
|
|
150
|
+
chartId: string,
|
|
151
|
+
events: ConnectEventType[],
|
|
152
|
+
createHandler: (chartId: string, eventType: ConnectEventType) => EventHandler,
|
|
153
|
+
eventHandlersRef: React.MutableRefObject<EventHandlersMap>
|
|
154
|
+
): void {
|
|
155
|
+
if (!chartInstance) return;
|
|
156
|
+
|
|
157
|
+
const handlers = new Map<ConnectEventType, EventHandler>();
|
|
158
|
+
|
|
159
|
+
// 为每个事件类型创建并绑定处理器
|
|
160
|
+
events.forEach((eventType) => {
|
|
161
|
+
const handler = createHandler(chartId, eventType);
|
|
162
|
+
handlers.set(eventType, handler);
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
chartInstance.on(eventType, handler);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
console.warn(`[useChartConnect] Failed to bind ${eventType} event:`, e);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
eventHandlersRef.current.set(chartId, handlers);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 解绑单个图表的联动事件
|
|
176
|
+
*/
|
|
177
|
+
export function unbindChartEvents(
|
|
178
|
+
chartId: string,
|
|
179
|
+
eventHandlersRef: React.MutableRefObject<EventHandlersMap>,
|
|
180
|
+
chartsRef: React.MutableRefObject<Map<string, ChartConnectItem>>
|
|
181
|
+
): void {
|
|
182
|
+
const handlers = eventHandlersRef.current.get(chartId);
|
|
183
|
+
if (!handlers) return;
|
|
184
|
+
|
|
185
|
+
const chartItem = chartsRef.current.get(chartId);
|
|
186
|
+
if (!chartItem) return;
|
|
187
|
+
|
|
188
|
+
handlers.forEach((handler, eventType) => {
|
|
189
|
+
try {
|
|
190
|
+
chartItem.instance.off(eventType, handler);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.warn(`[useChartConnect] Failed to unbind ${eventType} event:`, e);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
eventHandlersRef.current.delete(chartId);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// 连接管理
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 生成唯一图表 ID
|
|
205
|
+
*/
|
|
206
|
+
export function generateChartId(): string {
|
|
207
|
+
return `chart_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 绑定图表到联动组
|
|
212
|
+
*/
|
|
213
|
+
export function connectChart(
|
|
214
|
+
chartInstance: ChartInstance,
|
|
215
|
+
chartId: string | undefined,
|
|
216
|
+
groupName: string | undefined,
|
|
217
|
+
chartsRef: React.MutableRefObject<Map<string, ChartConnectItem>>,
|
|
218
|
+
connectedRef: React.MutableRefObject<boolean>,
|
|
219
|
+
eventHandlersRef: React.MutableRefObject<EventHandlersMap>,
|
|
220
|
+
bindEvents: (chartInstance: ChartInstance, chartId: string) => void
|
|
221
|
+
): string {
|
|
222
|
+
const id = chartId || generateChartId();
|
|
223
|
+
|
|
224
|
+
// 检查是否已连接
|
|
225
|
+
if (chartsRef.current.has(id)) {
|
|
226
|
+
console.warn(`[useChartConnect] Chart ${id} is already connected`);
|
|
227
|
+
return id;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 存储图表实例
|
|
231
|
+
chartsRef.current.set(id, {
|
|
232
|
+
instance: chartInstance,
|
|
233
|
+
id,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// 绑定事件
|
|
237
|
+
bindEvents(chartInstance, id);
|
|
238
|
+
connectedRef.current = true;
|
|
239
|
+
|
|
240
|
+
// 如果有 groupName,设置图表组
|
|
241
|
+
if (groupName && 'group' in chartInstance) {
|
|
242
|
+
try {
|
|
243
|
+
(chartInstance as ChartInstance & { group?: string }).group = groupName;
|
|
244
|
+
} catch (e) {
|
|
245
|
+
console.warn('[useChartConnect] Failed to set chart group:', e);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return id;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 从联动组移除图表
|
|
254
|
+
*/
|
|
255
|
+
export function disconnectChart(
|
|
256
|
+
chartInstance: ChartInstance,
|
|
257
|
+
chartId: string | undefined,
|
|
258
|
+
chartsRef: React.MutableRefObject<Map<string, ChartConnectItem>>,
|
|
259
|
+
connectedRef: React.MutableRefObject<boolean>,
|
|
260
|
+
unbindEvents: (chartId: string) => void
|
|
261
|
+
): void {
|
|
262
|
+
// 查找图表 ID
|
|
263
|
+
let targetId = chartId;
|
|
264
|
+
if (!targetId) {
|
|
265
|
+
chartsRef.current.forEach((item, id) => {
|
|
266
|
+
if (item.instance === chartInstance) {
|
|
267
|
+
targetId = id;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!targetId || !chartsRef.current.has(targetId)) {
|
|
273
|
+
console.warn(`[useChartConnect] Chart ${targetId} not found in connection group`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 解绑事件
|
|
278
|
+
unbindEvents(targetId);
|
|
279
|
+
|
|
280
|
+
// 移除图表
|
|
281
|
+
chartsRef.current.delete(targetId);
|
|
282
|
+
|
|
283
|
+
// 更新连接状态
|
|
284
|
+
connectedRef.current = chartsRef.current.size > 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 批量连接图表
|
|
289
|
+
*/
|
|
290
|
+
export function connectAllCharts(
|
|
291
|
+
charts: Array<{ instance: ChartInstance; id: string }>,
|
|
292
|
+
connect: (instance: ChartInstance, id?: string) => void
|
|
293
|
+
): void {
|
|
294
|
+
charts.forEach(({ instance, id }) => {
|
|
295
|
+
connect(instance, id);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 批量断开所有图表
|
|
301
|
+
*/
|
|
302
|
+
export function disconnectAllCharts(
|
|
303
|
+
chartsRef: React.MutableRefObject<Map<string, ChartConnectItem>>,
|
|
304
|
+
connectedRef: React.MutableRefObject<boolean>,
|
|
305
|
+
unbindEvents: (chartId: string) => void
|
|
306
|
+
): void {
|
|
307
|
+
chartsRef.current.forEach((item, id) => {
|
|
308
|
+
unbindEvents(id);
|
|
309
|
+
});
|
|
310
|
+
chartsRef.current.clear();
|
|
311
|
+
connectedRef.current = false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 触发联动事件
|
|
316
|
+
*/
|
|
317
|
+
export function dispatchConnectEvent(
|
|
318
|
+
sourceId: string,
|
|
319
|
+
|
|
320
|
+
payload: { eventType: ConnectEventType; params: unknown } | undefined,
|
|
321
|
+
disabled: boolean,
|
|
322
|
+
chartsRef: React.MutableRefObject<Map<string, ChartConnectItem>>,
|
|
323
|
+
dispatchToOthers: (sourceId: string, eventType: ConnectEventType, params: unknown) => void
|
|
324
|
+
): void {
|
|
325
|
+
if (disabled) return;
|
|
326
|
+
|
|
327
|
+
const chartItem = chartsRef.current.get(sourceId);
|
|
328
|
+
if (!chartItem) {
|
|
329
|
+
console.warn(`[useChartConnect] Source chart ${sourceId} not found`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const { eventType, params } = payload || {};
|
|
334
|
+
if (!eventType) {
|
|
335
|
+
console.warn('[useChartConnect] Payload must include eventType');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// 分发事件到其他图表
|
|
340
|
+
dispatchToOthers(sourceId, eventType, params);
|
|
341
|
+
}
|