@agions/taroviz 1.11.5 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +245 -0
- package/README.md +31 -46
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/vendors.js +1 -1
- package/dist/cjs/vendors~echarts.js +1 -1
- package/dist/esm/index.js +1 -14270
- package/dist/esm/vendors.js +1 -16770
- package/dist/esm/vendors~echarts.js +1 -59417
- package/package.json +10 -15
- package/src/adapters/h5/index.ts +38 -38
- package/src/adapters/index.ts +32 -34
- package/src/adapters/types.ts +23 -55
- package/src/charts/boxplot/types.ts +2 -2
- package/src/charts/common/BaseChartWrapper.tsx +9 -7
- package/src/charts/createChartComponent.tsx +9 -21
- package/src/charts/createOptionChartComponent.tsx +32 -0
- package/src/charts/funnel/__tests__/index.test.tsx +99 -0
- package/src/charts/funnel/index.tsx +64 -0
- package/src/charts/funnel/types.ts +6 -0
- package/src/charts/graph/__tests__/index.test.tsx +116 -0
- package/src/charts/graph/index.tsx +70 -0
- package/src/charts/graph/types.ts +6 -0
- package/src/charts/heatmap/__tests__/index.test.tsx +139 -0
- package/src/charts/heatmap/index.tsx +107 -0
- package/src/charts/heatmap/types.ts +6 -0
- package/src/charts/index.ts +47 -57
- package/src/charts/liquid/__tests__/index.test.tsx +52 -0
- package/src/charts/liquid/index.tsx +7 -133
- package/src/charts/liquid/types.ts +6 -6
- package/src/charts/parallel/types.ts +3 -3
- package/src/charts/radar/__tests__/index.test.tsx +210 -0
- package/src/charts/radar/index.tsx +147 -0
- package/src/charts/radar/types.ts +13 -0
- package/src/charts/sankey/__tests__/index.test.tsx +124 -0
- package/src/charts/sankey/index.tsx +70 -0
- package/src/charts/sankey/types.ts +6 -0
- package/src/charts/tree/__tests__/index.test.tsx +71 -0
- package/src/charts/tree/index.tsx +1 -1
- package/src/charts/tree/types.ts +8 -8
- package/src/charts/types.ts +208 -106
- package/src/charts/wordcloud/__tests__/index.test.tsx +106 -0
- package/src/charts/wordcloud/index.tsx +79 -0
- package/src/charts/wordcloud/types.ts +6 -0
- package/src/components/DataFilter/index.tsx +7 -6
- package/src/core/animation/types.ts +6 -6
- package/src/core/components/Annotation.tsx +6 -6
- package/src/core/components/BaseChart.tsx +97 -133
- package/src/core/components/LazyChart.tsx +3 -8
- package/src/core/components/hooks/index.ts +6 -2
- package/src/core/components/hooks/usePerformance.ts +8 -2
- package/src/core/components/hooks/useVirtualScroll.ts +2 -1
- package/src/core/types/common.ts +2 -1
- package/src/core/types/platform.ts +1 -0
- 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 -36
- package/src/core/utils/deepClone.ts +114 -0
- package/src/core/utils/download.ts +22 -28
- package/src/core/utils/drillDown.ts +1 -0
- package/src/core/utils/events.ts +12 -0
- package/src/core/utils/export/ExportUtils.ts +2 -1
- package/src/core/utils/format.ts +44 -0
- package/src/core/utils/index.ts +18 -159
- package/src/core/utils/merge.ts +25 -0
- package/src/core/utils/performance/PerformanceAnalyzer.ts +3 -1
- 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 +6 -5
- package/src/{hooks → core/utils/performance}/useDataZoom.ts +7 -2
- package/src/{hooks → core/utils/performance}/usePerformance.ts +39 -39
- package/src/{hooks → core/utils/performance}/usePerformanceHooks.ts +39 -39
- package/src/core/utils/runtime.ts +190 -0
- package/src/editor/components/ThemeSelector.tsx +3 -3
- package/src/hooks/chartConnectHelpers.ts +6 -0
- package/src/hooks/index.ts +54 -626
- package/src/hooks/types.ts +27 -0
- package/src/hooks/useChartAutoResize.ts +73 -0
- package/src/hooks/useChartConnect.ts +5 -1
- package/src/hooks/useChartDownload.ts +1 -1
- package/src/hooks/useChartHistory.ts +1 -3
- 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 +23 -12
- package/src/hooks/useChartTheme.ts +51 -0
- package/src/hooks/useDataTransform.ts +19 -4
- package/src/index.ts +5 -10
- 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/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 -449
- package/src/core/utils/debug/DebugPanel.tsx +0 -640
- 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/types.ts
CHANGED
|
@@ -160,3 +160,30 @@ export interface LoadingOptions {
|
|
|
160
160
|
maskColor?: string;
|
|
161
161
|
zlevel?: number;
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// 图表配置类型
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
/** 图表配置 */
|
|
169
|
+
export interface ChartConfig {
|
|
170
|
+
width?: number | string;
|
|
171
|
+
height?: number | string;
|
|
172
|
+
renderer?: 'canvas' | 'svg';
|
|
173
|
+
theme?: string | Record<string, unknown>;
|
|
174
|
+
[key: string]: unknown;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** 数据转换器 */
|
|
178
|
+
export type DataTransformer<T = unknown> = (data: T) => EChartsOption;
|
|
179
|
+
|
|
180
|
+
/** 响应式断点 */
|
|
181
|
+
export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
182
|
+
|
|
183
|
+
/** 断点配置 */
|
|
184
|
+
export interface BreakpointConfig {
|
|
185
|
+
width: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 主题切换回调 */
|
|
189
|
+
export type ThemeChangeCallback = (theme: string | Record<string, unknown>) => void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useChartAutoResize - 图表自适应 Hook
|
|
3
|
+
* 监听容器尺寸变化并自动调整图表大小
|
|
4
|
+
*/
|
|
5
|
+
import { useEffect, useRef } from 'react';
|
|
6
|
+
import type { ChartInstance } from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 图表自适应 Hook
|
|
10
|
+
* @param instance 图表实例
|
|
11
|
+
* @param options 配置选项
|
|
12
|
+
*/
|
|
13
|
+
export function useResize(
|
|
14
|
+
instance: ChartInstance | null,
|
|
15
|
+
options?: {
|
|
16
|
+
/** 延迟时间 (ms) */
|
|
17
|
+
delay?: number;
|
|
18
|
+
/** 最小宽度 */
|
|
19
|
+
minWidth?: number;
|
|
20
|
+
/** 最小高度 */
|
|
21
|
+
minHeight?: number;
|
|
22
|
+
/** 是否启用 */
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
) {
|
|
26
|
+
const { delay = 300, minWidth, minHeight, enabled = true } = options || {};
|
|
27
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!instance || !enabled) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handleResize = () => {
|
|
35
|
+
if (timeoutRef.current) {
|
|
36
|
+
clearTimeout(timeoutRef.current);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
timeoutRef.current = setTimeout(() => {
|
|
40
|
+
try {
|
|
41
|
+
const dom = instance.getDom?.();
|
|
42
|
+
if (dom) {
|
|
43
|
+
const { clientWidth, clientHeight } = dom;
|
|
44
|
+
if (minWidth && clientWidth < minWidth) return;
|
|
45
|
+
if (minHeight && clientHeight < minHeight) return;
|
|
46
|
+
}
|
|
47
|
+
instance.resize?.();
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.warn('Failed to resize chart:', e);
|
|
50
|
+
}
|
|
51
|
+
}, delay);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
window.addEventListener('resize', handleResize);
|
|
55
|
+
|
|
56
|
+
// 创建一个 ResizeObserver 来监听容器大小变化
|
|
57
|
+
const dom = instance.getDom?.();
|
|
58
|
+
if (dom && typeof ResizeObserver !== 'undefined') {
|
|
59
|
+
const observer = new ResizeObserver(handleResize);
|
|
60
|
+
observer.observe(dom);
|
|
61
|
+
return () => {
|
|
62
|
+
observer.disconnect();
|
|
63
|
+
window.removeEventListener('resize', handleResize);
|
|
64
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
window.removeEventListener('resize', handleResize);
|
|
70
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
71
|
+
};
|
|
72
|
+
}, [instance, delay, minWidth, minHeight, enabled]);
|
|
73
|
+
}
|
|
@@ -42,9 +42,13 @@ export type { UseChartConnectOptions, UseChartConnectReturn } from './types';
|
|
|
42
42
|
* @returns 图表联动操作接口
|
|
43
43
|
*/
|
|
44
44
|
export function useChartConnect(options: UseChartConnectOptions): UseChartConnectReturn {
|
|
45
|
+
const defaultEvents = useMemo<ConnectEventType[]>(
|
|
46
|
+
() => ['click', 'hover', 'select', 'dataZoom'],
|
|
47
|
+
[]
|
|
48
|
+
);
|
|
45
49
|
const {
|
|
46
50
|
chartIds = [],
|
|
47
|
-
events =
|
|
51
|
+
events = defaultEvents,
|
|
48
52
|
autoBind = false,
|
|
49
53
|
groupName,
|
|
50
54
|
disabled = false,
|
|
@@ -144,11 +144,9 @@ export function useChartHistory(
|
|
|
144
144
|
newStack.push(option);
|
|
145
145
|
if (newStack.length > maxHistorySize) {
|
|
146
146
|
newStack.shift();
|
|
147
|
-
} else {
|
|
148
|
-
setCurrentIndex(newStack.length - 1);
|
|
149
147
|
}
|
|
150
|
-
|
|
151
148
|
historyStack.current = newStack;
|
|
149
|
+
setCurrentIndex(newStack.length - 1);
|
|
152
150
|
return originalSetOption(option, notMerge, lazyUpdate);
|
|
153
151
|
};
|
|
154
152
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useChartInit - 图表初始化 Hook
|
|
3
|
+
* 负责初始化 ECharts 图表实例
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useEffect, useRef } from 'react';
|
|
6
|
+
import { getAdapter } from '../adapters';
|
|
7
|
+
import type { ChartInstance, ChartConfig } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 使用图表 Hook
|
|
11
|
+
* @param chartRef 图表容器的引用
|
|
12
|
+
* @param config 图表配置
|
|
13
|
+
* @returns [图表实例, 设置实例函数, 是否已初始化]
|
|
14
|
+
*/
|
|
15
|
+
export function useChart(
|
|
16
|
+
chartRef: React.RefObject<HTMLElement>,
|
|
17
|
+
config?: ChartConfig
|
|
18
|
+
): [ChartInstance | null, React.Dispatch<React.SetStateAction<ChartInstance | null>>, boolean] {
|
|
19
|
+
const [instance, setInstance] = useState<ChartInstance | null>(null);
|
|
20
|
+
const [initialized, setInitialized] = useState(false);
|
|
21
|
+
const configRef = useRef(config);
|
|
22
|
+
configRef.current = config;
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!chartRef.current || instance) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const initAdapter = async () => {
|
|
30
|
+
try {
|
|
31
|
+
const adapter = await getAdapter(configRef.current || {});
|
|
32
|
+
const chartInstance = adapter as unknown as ChartInstance;
|
|
33
|
+
setInstance(chartInstance);
|
|
34
|
+
setInitialized(true);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Failed to initialize chart:', error);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
initAdapter();
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
if (instance) {
|
|
44
|
+
try {
|
|
45
|
+
const inst = instance as ChartInstance;
|
|
46
|
+
if (!inst.isDisposed?.()) {
|
|
47
|
+
inst.dispose();
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.warn('Failed to dispose chart instance:', e);
|
|
51
|
+
}
|
|
52
|
+
setInstance(null);
|
|
53
|
+
setInitialized(false);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}, [chartRef]);
|
|
57
|
+
|
|
58
|
+
return [instance, setInstance, initialized];
|
|
59
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useChartOptions - 图表选项与工具相关 Hooks
|
|
3
|
+
* 提供图表选项设置、事件绑定、加载状态、响应式配置、全屏、导出、工具等
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
6
|
+
import type { EChartsOption } from 'echarts';
|
|
7
|
+
import type { ChartInstance, EventHandler, LoadingOptions, Breakpoint } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 设置图表选项 Hook
|
|
11
|
+
* @param instance 图表实例
|
|
12
|
+
* @param option 图表选项
|
|
13
|
+
* @param options 配置选项
|
|
14
|
+
*/
|
|
15
|
+
export function useOption(
|
|
16
|
+
instance: ChartInstance | null,
|
|
17
|
+
option: EChartsOption | null,
|
|
18
|
+
options?: {
|
|
19
|
+
/** 是否不合并 */
|
|
20
|
+
notMerge?: boolean;
|
|
21
|
+
/** 是否延迟更新 */
|
|
22
|
+
lazyUpdate?: boolean;
|
|
23
|
+
/** 是否在数据变化时替换 */
|
|
24
|
+
replaceMerge?: string[];
|
|
25
|
+
/** 依赖数组 */
|
|
26
|
+
deps?: unknown[];
|
|
27
|
+
}
|
|
28
|
+
) {
|
|
29
|
+
const { notMerge = false, lazyUpdate = false, replaceMerge, deps = [] } = options || {};
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (instance && option) {
|
|
33
|
+
try {
|
|
34
|
+
instance.setOption(option, notMerge, lazyUpdate);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.warn('Failed to set chart option:', e);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, [instance, option, notMerge, lazyUpdate, replaceMerge, ...deps]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 图表事件 Hook
|
|
44
|
+
* @param instance 图表实例
|
|
45
|
+
* @param events 事件对象
|
|
46
|
+
*/
|
|
47
|
+
export function useEvents(instance: ChartInstance | null, events: Record<string, EventHandler>) {
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!instance || !events) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const eventEntries = Object.entries(events);
|
|
54
|
+
|
|
55
|
+
// 绑定事件
|
|
56
|
+
eventEntries.forEach(([eventName, handler]) => {
|
|
57
|
+
try {
|
|
58
|
+
instance.on(eventName, handler);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.warn(`Failed to bind event ${eventName}:`, e);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// 清理事件
|
|
65
|
+
return () => {
|
|
66
|
+
eventEntries.forEach(([eventName, handler]) => {
|
|
67
|
+
try {
|
|
68
|
+
instance.off(eventName, handler);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.warn(`Failed to unbind event ${eventName}:`, e);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
}, [instance, events]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 图表加载状态 Hook
|
|
79
|
+
* @param instance 图表实例
|
|
80
|
+
* @param loading 是否加载中
|
|
81
|
+
* @param options 加载选项
|
|
82
|
+
*/
|
|
83
|
+
export function useLoading(
|
|
84
|
+
instance: ChartInstance | null,
|
|
85
|
+
loading: boolean,
|
|
86
|
+
options?: LoadingOptions
|
|
87
|
+
) {
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!instance) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
if (loading) {
|
|
95
|
+
instance.showLoading(options);
|
|
96
|
+
} else {
|
|
97
|
+
instance.hideLoading();
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.warn('Failed to set chart loading state:', e);
|
|
101
|
+
}
|
|
102
|
+
}, [instance, loading, options]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 使用响应式图表配置
|
|
107
|
+
* @param config 响应式配置
|
|
108
|
+
* @returns 当前断点和配置
|
|
109
|
+
*/
|
|
110
|
+
export function useResponsive(config?: {
|
|
111
|
+
/** 断点配置 */
|
|
112
|
+
breakpoints?: Record<Breakpoint, number>;
|
|
113
|
+
/** 默认断点 */
|
|
114
|
+
defaultBreakpoint?: Breakpoint;
|
|
115
|
+
}) {
|
|
116
|
+
const { breakpoints = { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200 }, defaultBreakpoint = 'md' } =
|
|
117
|
+
config || {};
|
|
118
|
+
|
|
119
|
+
const [breakpoint, setBreakpoint] = useState<Breakpoint>(defaultBreakpoint);
|
|
120
|
+
const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
const handleResize = () => {
|
|
124
|
+
const width = window.innerWidth;
|
|
125
|
+
setWindowSize({ width, height: window.innerHeight });
|
|
126
|
+
|
|
127
|
+
// 确定当前断点
|
|
128
|
+
let current: Breakpoint = 'xs';
|
|
129
|
+
if (width >= breakpoints.xl) current = 'xl';
|
|
130
|
+
else if (width >= breakpoints.lg) current = 'lg';
|
|
131
|
+
else if (width >= breakpoints.md) current = 'md';
|
|
132
|
+
else if (width >= breakpoints.sm) current = 'sm';
|
|
133
|
+
|
|
134
|
+
setBreakpoint(current);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
handleResize();
|
|
138
|
+
window.addEventListener('resize', handleResize);
|
|
139
|
+
|
|
140
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
141
|
+
}, [breakpoints]);
|
|
142
|
+
|
|
143
|
+
return { breakpoint, windowSize };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 使用图表全屏
|
|
148
|
+
* @param chartRef 图表容器引用
|
|
149
|
+
* @returns [是否全屏, 进入/退出全屏函数]
|
|
150
|
+
*/
|
|
151
|
+
export function useFullscreen(chartRef: React.RefObject<HTMLElement>) {
|
|
152
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
153
|
+
|
|
154
|
+
const toggle = useCallback(() => {
|
|
155
|
+
if (!chartRef.current) return;
|
|
156
|
+
|
|
157
|
+
if (!isFullscreen) {
|
|
158
|
+
if (chartRef.current.requestFullscreen) {
|
|
159
|
+
chartRef.current.requestFullscreen();
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
if (document.exitFullscreen) {
|
|
163
|
+
document.exitFullscreen();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}, [chartRef, isFullscreen]);
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
const handleChange = () => {
|
|
170
|
+
setIsFullscreen(!!document.fullscreenElement);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
document.addEventListener('fullscreenchange', handleChange);
|
|
174
|
+
return () => document.removeEventListener('fullscreenchange', handleChange);
|
|
175
|
+
}, []);
|
|
176
|
+
|
|
177
|
+
return { isFullscreen, toggle };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 使用图表导出
|
|
182
|
+
* @param instance 图表实例
|
|
183
|
+
* @returns 导出函数
|
|
184
|
+
*/
|
|
185
|
+
export function useExport(instance: ChartInstance | null) {
|
|
186
|
+
const inst = instance as ChartInstance;
|
|
187
|
+
const exportImage = useCallback(
|
|
188
|
+
(options?: { type?: 'png' | 'jpeg'; pixelRatio?: number; backgroundColor?: string }) => {
|
|
189
|
+
if (!inst) return null;
|
|
190
|
+
const { type = 'png', pixelRatio = 2, backgroundColor } = options || {};
|
|
191
|
+
return inst.getDataURL?.({ type, pixelRatio, backgroundColor });
|
|
192
|
+
},
|
|
193
|
+
[inst]
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const exportSVG = useCallback(() => {
|
|
197
|
+
if (!inst) return null;
|
|
198
|
+
return inst.getSvgData?.();
|
|
199
|
+
}, [inst]);
|
|
200
|
+
|
|
201
|
+
const exportCSV = useCallback(
|
|
202
|
+
(options?: { seriesIndex?: number; dimension?: number }) => {
|
|
203
|
+
if (!inst) return null;
|
|
204
|
+
return inst.getCompressedDataURL?.(options);
|
|
205
|
+
},
|
|
206
|
+
[inst]
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return { exportImage, exportSVG, exportCSV };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 使用图表工具
|
|
214
|
+
* @param instance 图表实例
|
|
215
|
+
* @returns 工具函数
|
|
216
|
+
*/
|
|
217
|
+
export function useChartTools(instance: ChartInstance | null) {
|
|
218
|
+
const inst = instance as ChartInstance;
|
|
219
|
+
const getInstance = useCallback(() => instance, [instance]);
|
|
220
|
+
|
|
221
|
+
const clear = useCallback(() => {
|
|
222
|
+
inst?.clear?.();
|
|
223
|
+
}, [inst]);
|
|
224
|
+
|
|
225
|
+
const repaint = useCallback(() => {
|
|
226
|
+
inst?.resize?.();
|
|
227
|
+
}, [inst]);
|
|
228
|
+
|
|
229
|
+
const dispatchAction = useCallback(
|
|
230
|
+
(action: { type: string; [key: string]: unknown }) => {
|
|
231
|
+
inst?.dispatchAction?.(action);
|
|
232
|
+
},
|
|
233
|
+
[inst]
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const showTip = useCallback(
|
|
237
|
+
(seriesIndex?: number, dataIndex?: number) => {
|
|
238
|
+
inst?.dispatchAction?.({ type: 'showTip', seriesIndex, dataIndex });
|
|
239
|
+
},
|
|
240
|
+
[inst]
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const hideTip = useCallback(() => {
|
|
244
|
+
inst?.dispatchAction?.({ type: 'hideTip' });
|
|
245
|
+
}, [inst]);
|
|
246
|
+
|
|
247
|
+
const zoom = useCallback(
|
|
248
|
+
(start?: number, end?: number) => {
|
|
249
|
+
inst?.dispatchAction?.({
|
|
250
|
+
type: 'dataZoom',
|
|
251
|
+
start: start ?? 0,
|
|
252
|
+
end: end ?? 100,
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
[inst]
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
return { getInstance, clear, repaint, dispatchAction, showTip, hideTip, zoom };
|
|
259
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useChartPerformance - 图表性能相关 Hooks
|
|
3
|
+
* 提供数据轮询等功能
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 使用数据轮询
|
|
9
|
+
* @param fetchFn 数据获取函数
|
|
10
|
+
* @param options 配置选项
|
|
11
|
+
* @returns [数据, 加载状态, 错误, 刷新函数]
|
|
12
|
+
*/
|
|
13
|
+
export function useDataPolling<T>(
|
|
14
|
+
fetchFn: () => Promise<T>,
|
|
15
|
+
options?: {
|
|
16
|
+
/** 轮询间隔 (ms) */
|
|
17
|
+
interval?: number;
|
|
18
|
+
/** 是否自动开始 */
|
|
19
|
+
autoStart?: boolean;
|
|
20
|
+
/** 错误重试次数 */
|
|
21
|
+
retryCount?: number;
|
|
22
|
+
/** 重试延迟 (ms) */
|
|
23
|
+
retryDelay?: number;
|
|
24
|
+
}
|
|
25
|
+
) {
|
|
26
|
+
const { interval = 5000, autoStart = false, retryCount = 3, retryDelay = 1000 } = options || {};
|
|
27
|
+
|
|
28
|
+
const [data, setData] = useState<T | null>(null);
|
|
29
|
+
const [loading, setLoading] = useState(autoStart);
|
|
30
|
+
const [error, setError] = useState<Error | null>(null);
|
|
31
|
+
|
|
32
|
+
// 用于取消进行中的请求
|
|
33
|
+
const abortRef = useRef<{ cancelled: boolean }>({ cancelled: false });
|
|
34
|
+
|
|
35
|
+
const fetchData = useCallback(async () => {
|
|
36
|
+
// 取消之前的请求
|
|
37
|
+
abortRef.current.cancelled = true;
|
|
38
|
+
// 创建新的取消标记
|
|
39
|
+
abortRef.current = { cancelled: false };
|
|
40
|
+
const currentAbort = abortRef.current;
|
|
41
|
+
|
|
42
|
+
let retries = retryCount;
|
|
43
|
+
setLoading(true);
|
|
44
|
+
setError(null);
|
|
45
|
+
|
|
46
|
+
// retryCount < 0 表示不重试,直接一次请求
|
|
47
|
+
if (retryCount < 0) {
|
|
48
|
+
try {
|
|
49
|
+
const result = await fetchFn();
|
|
50
|
+
if (!currentAbort.cancelled) {
|
|
51
|
+
setData(result);
|
|
52
|
+
setLoading(false);
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
if (!currentAbort.cancelled) {
|
|
56
|
+
setError(e as Error);
|
|
57
|
+
setLoading(false);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// retryCount >= 0:循环重试
|
|
64
|
+
while (retries > 0 && !currentAbort.cancelled) {
|
|
65
|
+
try {
|
|
66
|
+
const result = await fetchFn();
|
|
67
|
+
if (!currentAbort.cancelled) {
|
|
68
|
+
setData(result);
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
} catch (e) {
|
|
73
|
+
retries--;
|
|
74
|
+
if (retries <= 0 || currentAbort.cancelled) {
|
|
75
|
+
if (!currentAbort.cancelled) {
|
|
76
|
+
setError(e as Error);
|
|
77
|
+
}
|
|
78
|
+
setLoading(false);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}, [fetchFn, retryCount, retryDelay]);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (autoStart) {
|
|
88
|
+
fetchData();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (interval > 0) {
|
|
92
|
+
const timer = setInterval(fetchData, interval);
|
|
93
|
+
return () => {
|
|
94
|
+
clearInterval(timer);
|
|
95
|
+
abortRef.current.cancelled = true;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
abortRef.current.cancelled = true;
|
|
101
|
+
};
|
|
102
|
+
}, [interval, autoStart, fetchData]);
|
|
103
|
+
|
|
104
|
+
const refresh = useCallback(() => {
|
|
105
|
+
fetchData();
|
|
106
|
+
}, [fetchData]);
|
|
107
|
+
|
|
108
|
+
return { data, loading, error, refresh };
|
|
109
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1
2
|
/**
|
|
2
3
|
* useChartSelection - 图表数据点选择/高亮 Hook
|
|
3
4
|
* 支持单个/批量选择、反选、清除选择,配合 ECharts select 事件
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
* - 支持多选模式(multi)
|
|
10
11
|
* - 自动绑定图表 select/unselect 事件
|
|
11
12
|
*/
|
|
12
|
-
import { useEffect, useRef, useCallback, useState
|
|
13
|
+
import { useEffect, useRef, useCallback, useState } from 'react';
|
|
13
14
|
import type { ChartInstance } from './types';
|
|
14
15
|
|
|
15
16
|
// ============================================================================
|
|
@@ -44,7 +45,7 @@ export interface UseChartSelectionOptions {
|
|
|
44
45
|
/** 是否启用 Shift+Click 范围选择,默认 true */
|
|
45
46
|
enableShiftRangeSelect?: boolean;
|
|
46
47
|
/** 选择变化时的回调 */
|
|
47
|
-
onSelectionChange?: (
|
|
48
|
+
onSelectionChange?: (_event: SelectionEvent) => void;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/** 选择返回值 */
|
|
@@ -214,13 +215,22 @@ export function useChartSelection(
|
|
|
214
215
|
const toggle = useCallback(
|
|
215
216
|
(key: DataPointKey) => {
|
|
216
217
|
const str = keyToString(key);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
let isCurrentlySelected = false;
|
|
219
|
+
setSelectedPoints((prev) => {
|
|
220
|
+
isCurrentlySelected = prev.some((p) => keyToString(p) === str);
|
|
221
|
+
if (isCurrentlySelected) {
|
|
222
|
+
dispatchSelect(key, false);
|
|
223
|
+
notifyChange([], [key]);
|
|
224
|
+
return prev.filter((p) => keyToString(p) !== str);
|
|
225
|
+
} else {
|
|
226
|
+
dispatchSelect(key, true);
|
|
227
|
+
const next = mode === 'single' ? [key] : [...prev, key];
|
|
228
|
+
notifyChange(next, []);
|
|
229
|
+
return next;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
222
232
|
},
|
|
223
|
-
[
|
|
233
|
+
[mode, notifyChange, dispatchSelect]
|
|
224
234
|
);
|
|
225
235
|
|
|
226
236
|
const selectMultiple = useCallback(
|
|
@@ -312,10 +322,11 @@ export function useChartSelection(
|
|
|
312
322
|
if (chart?.dispatchAction) {
|
|
313
323
|
chart.dispatchAction({ type: 'unselect' });
|
|
314
324
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
325
|
+
setSelectedPoints((prev) => {
|
|
326
|
+
notifyChange([], prev);
|
|
327
|
+
return [];
|
|
328
|
+
});
|
|
329
|
+
}, [notifyChange]);
|
|
319
330
|
|
|
320
331
|
const isSelected = useCallback(
|
|
321
332
|
(key: DataPointKey) => {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useChartTheme - 图表主题相关 Hooks
|
|
3
|
+
* 提供主题解析和主题切换功能
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useMemo, useCallback } from 'react';
|
|
6
|
+
import { getThemeByName } from '../themes';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 使用图表主题
|
|
10
|
+
* @param theme 主题名称或配置
|
|
11
|
+
* @param darkMode 是否为暗色模式
|
|
12
|
+
* @returns 处理后的主题
|
|
13
|
+
*/
|
|
14
|
+
export function useChartTheme(theme: string | Record<string, unknown>, darkMode = false) {
|
|
15
|
+
return useMemo(() => {
|
|
16
|
+
if (typeof theme === 'string') {
|
|
17
|
+
// 如果是字符串,尝试获取内置主题配置
|
|
18
|
+
try {
|
|
19
|
+
const builtinTheme = getThemeByName(theme);
|
|
20
|
+
return builtinTheme || (darkMode ? 'dark' : theme);
|
|
21
|
+
} catch {
|
|
22
|
+
return darkMode ? 'dark' : theme;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return theme;
|
|
26
|
+
}, [theme, darkMode]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 使用主题切换
|
|
31
|
+
* @param initialTheme 初始主题
|
|
32
|
+
* @returns [当前主题, 切换主题函数]
|
|
33
|
+
*/
|
|
34
|
+
export function useThemeSwitcher(initialTheme = 'default') {
|
|
35
|
+
const [theme, setTheme] = useState<string | Record<string, unknown>>(initialTheme);
|
|
36
|
+
const [isDark, setIsDark] = useState(false);
|
|
37
|
+
|
|
38
|
+
const switchTheme = useCallback((newTheme: string | Record<string, unknown>) => {
|
|
39
|
+
setTheme(newTheme);
|
|
40
|
+
if (typeof newTheme === 'string') {
|
|
41
|
+
setIsDark(newTheme === 'dark' || newTheme.includes('dark'));
|
|
42
|
+
}
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const toggleDark = useCallback(() => {
|
|
46
|
+
setIsDark((prev) => !prev);
|
|
47
|
+
setTheme((prev) => (prev === 'dark' ? 'default' : 'dark'));
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return { theme, isDark, switchTheme, toggleDark, setTheme };
|
|
51
|
+
}
|