@agions/taroviz 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -21
- package/dist/cjs/index.js +1 -1
- package/dist/esm/index.js +49185 -2194
- package/package.json +2 -1
- package/src/charts/index.ts +5 -1
- package/src/charts/liquid/index.tsx +226 -0
- package/src/charts/liquid/types.ts +130 -0
- package/src/charts/tree/index.tsx +117 -0
- package/src/charts/tree/types.ts +174 -0
- package/src/components/DataFilter/index.tsx +587 -0
- package/src/core/utils/drillDown.ts +643 -0
- package/src/hooks/index.ts +39 -1
- package/src/hooks/useChartConnect.ts +362 -0
- package/src/hooks/useChartDownload.ts +692 -0
- package/src/hooks/useDataZoom.ts +323 -0
- package/src/index.ts +25 -2
- package/src/themes/index.ts +3 -0
- package/src/themes/useAutoTheme.ts +66 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDataZoom - 数据缩放 Hook
|
|
3
|
+
* 控制图表的数据缩放区域(dataZoom),支持拖拽和滚轮缩放
|
|
4
|
+
*/
|
|
5
|
+
import { useRef, useCallback, useEffect } from 'react';
|
|
6
|
+
import type { RefObject } from 'react';
|
|
7
|
+
import type { ChartInstance } from './index';
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// 类型定义
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/** dataZoom 类型 */
|
|
14
|
+
export type DataZoomType = 'inside' | 'slider';
|
|
15
|
+
|
|
16
|
+
/** dataZoom 配置选项 */
|
|
17
|
+
export interface UseDataZoomOptions {
|
|
18
|
+
/** dataZoom 类型: inside=内置型(滚轮/双指), slider=滑块型 */
|
|
19
|
+
type?: DataZoomType;
|
|
20
|
+
/** 起始位置 (0-100) */
|
|
21
|
+
start?: number;
|
|
22
|
+
/** 结束位置 (0-100) */
|
|
23
|
+
end?: number;
|
|
24
|
+
/** 最小跨度 (0-100) */
|
|
25
|
+
minSpan?: number;
|
|
26
|
+
/** 最大跨度 (0-100) */
|
|
27
|
+
maxSpan?: number;
|
|
28
|
+
/** 是否锁定缩放 */
|
|
29
|
+
zoomLock?: boolean;
|
|
30
|
+
/** 节流时间 (ms) */
|
|
31
|
+
throttle?: number;
|
|
32
|
+
/** 是否禁用 */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
/** 是否显示缩放痕迹 */
|
|
35
|
+
brushSelect?: boolean;
|
|
36
|
+
/** 缩放模式: 'scale' | 'mix' */
|
|
37
|
+
zoomMode?: 'scale' | 'mix';
|
|
38
|
+
/** 是否在组件卸载时重置缩放 */
|
|
39
|
+
resetOnUnmount?: boolean;
|
|
40
|
+
/** 缩放变化回调 */
|
|
41
|
+
onZoomChange?: (range: { start: number; end: number }) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 缩放范围 */
|
|
45
|
+
export interface ZoomRange {
|
|
46
|
+
start: number;
|
|
47
|
+
end: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** dataZoom 返回值 */
|
|
51
|
+
export interface UseDataZoomReturn {
|
|
52
|
+
/** 绑定 dataZoom 到图表实例 */
|
|
53
|
+
bindDataZoom: (chartInstance: ChartInstance) => void;
|
|
54
|
+
/** 设置缩放范围 */
|
|
55
|
+
setZoomRange: (start: number, end: number) => void;
|
|
56
|
+
/** 重置缩放到初始状态 */
|
|
57
|
+
resetZoom: () => void;
|
|
58
|
+
/** 获取当前缩放范围 */
|
|
59
|
+
getZoomRange: () => ZoomRange;
|
|
60
|
+
/** 起始位置的原始值 (Ref) */
|
|
61
|
+
startValue: RefObject<number | string | Date | undefined>;
|
|
62
|
+
/** 结束位置的原始值 (Ref) */
|
|
63
|
+
endValue: RefObject<number | string | Date | undefined>;
|
|
64
|
+
/** 绑定事件处理 */
|
|
65
|
+
bindEvents: (chartInstance: ChartInstance) => void;
|
|
66
|
+
/** 缩放变化回调 */
|
|
67
|
+
onZoomChange?: (range: ZoomRange) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Hook 实现
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 使用图表数据缩放
|
|
76
|
+
* @param options 配置选项
|
|
77
|
+
* @returns dataZoom 操作接口
|
|
78
|
+
*/
|
|
79
|
+
export function useDataZoom(options: UseDataZoomOptions = {}): UseDataZoomReturn {
|
|
80
|
+
const {
|
|
81
|
+
type = 'inside',
|
|
82
|
+
start = 0,
|
|
83
|
+
end = 100,
|
|
84
|
+
minSpan,
|
|
85
|
+
maxSpan,
|
|
86
|
+
zoomLock = false,
|
|
87
|
+
throttle = 16,
|
|
88
|
+
disabled = false,
|
|
89
|
+
brushSelect = false,
|
|
90
|
+
zoomMode = 'scale',
|
|
91
|
+
resetOnUnmount = false,
|
|
92
|
+
onZoomChange,
|
|
93
|
+
} = options;
|
|
94
|
+
|
|
95
|
+
// Refs
|
|
96
|
+
const chartRef = useRef<ChartInstance | null>(null);
|
|
97
|
+
const startValueRef = useRef<number | string | Date | undefined>(undefined);
|
|
98
|
+
const endValueRef = useRef<number | string | Date | undefined>(undefined);
|
|
99
|
+
const isBindingRef = useRef(false);
|
|
100
|
+
const throttledHandlerRef = useRef<((...args: any[]) => void) | null>(null);
|
|
101
|
+
|
|
102
|
+
// 节流处理
|
|
103
|
+
const throttledCallback = useCallback(
|
|
104
|
+
<T extends (...args: any[]) => void>(fn: T): T => {
|
|
105
|
+
let lastCall = 0;
|
|
106
|
+
return ((...args: any[]) => {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
if (now - lastCall >= throttle) {
|
|
109
|
+
lastCall = now;
|
|
110
|
+
fn(...args);
|
|
111
|
+
}
|
|
112
|
+
}) as T;
|
|
113
|
+
},
|
|
114
|
+
[throttle]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// 构建 dataZoom 配置
|
|
118
|
+
const buildDataZoomConfig = useCallback(() => {
|
|
119
|
+
const config: any[] = [];
|
|
120
|
+
|
|
121
|
+
// 内置型 dataZoom
|
|
122
|
+
if (type === 'inside' || zoomMode === 'mix') {
|
|
123
|
+
config.push({
|
|
124
|
+
type: 'inside',
|
|
125
|
+
start,
|
|
126
|
+
end,
|
|
127
|
+
zoomLock,
|
|
128
|
+
disabled,
|
|
129
|
+
zoomOnMouseWheel: true,
|
|
130
|
+
moveOnMouseMove: false,
|
|
131
|
+
moveOnMouseWheel: false,
|
|
132
|
+
preventDefaultMouseMove: true,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 滑块型 dataZoom
|
|
137
|
+
if (type === 'slider' || zoomMode === 'mix') {
|
|
138
|
+
config.push({
|
|
139
|
+
type: 'slider',
|
|
140
|
+
start,
|
|
141
|
+
end,
|
|
142
|
+
minSpan,
|
|
143
|
+
maxSpan,
|
|
144
|
+
zoomLock,
|
|
145
|
+
disabled,
|
|
146
|
+
show: true,
|
|
147
|
+
brushSelect,
|
|
148
|
+
throttle,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return config;
|
|
153
|
+
}, [type, start, end, minSpan, maxSpan, zoomLock, disabled, brushSelect, throttle]);
|
|
154
|
+
|
|
155
|
+
// 绑定 dataZoom 到图表
|
|
156
|
+
const bindDataZoom = useCallback(
|
|
157
|
+
(chartInstance: ChartInstance) => {
|
|
158
|
+
if (!chartInstance || isBindingRef.current) return;
|
|
159
|
+
|
|
160
|
+
isBindingRef.current = true;
|
|
161
|
+
chartRef.current = chartInstance;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
// 设置 dataZoom 配置
|
|
165
|
+
const dataZoomConfig = buildDataZoomConfig();
|
|
166
|
+
chartInstance.setOption(
|
|
167
|
+
{
|
|
168
|
+
dataZoom: dataZoomConfig,
|
|
169
|
+
},
|
|
170
|
+
false,
|
|
171
|
+
true
|
|
172
|
+
);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.warn('[useDataZoom] Failed to bind dataZoom:', e);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
isBindingRef.current = false;
|
|
178
|
+
},
|
|
179
|
+
[buildDataZoomConfig]
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// 解绑 dataZoom
|
|
183
|
+
const unbindDataZoom = useCallback(
|
|
184
|
+
(chartInstance: ChartInstance) => {
|
|
185
|
+
if (!chartInstance) return;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// 通过 dispatchAction 关闭 dataZoom
|
|
189
|
+
chartInstance.dispatchAction?.({
|
|
190
|
+
type: 'dataZoom',
|
|
191
|
+
start: 0,
|
|
192
|
+
end: 100,
|
|
193
|
+
});
|
|
194
|
+
} catch (e) {
|
|
195
|
+
console.warn('[useDataZoom] Failed to unbind dataZoom:', e);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
[]
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// 设置缩放范围
|
|
202
|
+
const setZoomRange = useCallback((newStart: number, newEnd: number) => {
|
|
203
|
+
const chart = chartRef.current;
|
|
204
|
+
if (!chart) return;
|
|
205
|
+
|
|
206
|
+
const clampedStart = Math.max(0, Math.min(100, newStart));
|
|
207
|
+
const clampedEnd = Math.max(0, Math.min(100, newEnd));
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
chart.dispatchAction?.({
|
|
211
|
+
type: 'dataZoom',
|
|
212
|
+
start: clampedStart,
|
|
213
|
+
end: clampedEnd,
|
|
214
|
+
});
|
|
215
|
+
} catch (e) {
|
|
216
|
+
console.warn('[useDataZoom] Failed to set zoom range:', e);
|
|
217
|
+
}
|
|
218
|
+
}, []);
|
|
219
|
+
|
|
220
|
+
// 重置缩放
|
|
221
|
+
const resetZoom = useCallback(() => {
|
|
222
|
+
setZoomRange(start, end);
|
|
223
|
+
}, [setZoomRange, start, end]);
|
|
224
|
+
|
|
225
|
+
// 获取当前缩放范围
|
|
226
|
+
const getZoomRange = useCallback((): ZoomRange => {
|
|
227
|
+
const chart = chartRef.current;
|
|
228
|
+
if (!chart) {
|
|
229
|
+
return { start, end };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const option = chart.getOption?.();
|
|
234
|
+
const dataZoom = option?.dataZoom as any[];
|
|
235
|
+
if (Array.isArray(dataZoom) && dataZoom.length > 0) {
|
|
236
|
+
// 优先获取 inside 类型的范围
|
|
237
|
+
const insideZoom = dataZoom.find((dz: any) => dz.type === 'inside');
|
|
238
|
+
const sliderZoom = dataZoom.find((dz: any) => dz.type === 'slider');
|
|
239
|
+
const zoom = insideZoom || sliderZoom || dataZoom[0];
|
|
240
|
+
return {
|
|
241
|
+
start: zoom.start ?? start,
|
|
242
|
+
end: zoom.end ?? end,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
} catch (e) {
|
|
246
|
+
console.warn('[useDataZoom] Failed to get zoom range:', e);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { start, end };
|
|
250
|
+
}, [start, end]);
|
|
251
|
+
|
|
252
|
+
// 绑定事件
|
|
253
|
+
const bindEvents = useCallback(
|
|
254
|
+
(chartInstance: ChartInstance) => {
|
|
255
|
+
if (!chartInstance) return;
|
|
256
|
+
|
|
257
|
+
// 创建缩放变化处理器
|
|
258
|
+
const handleZoomChange = (params: any) => {
|
|
259
|
+
if (disabled) return;
|
|
260
|
+
|
|
261
|
+
const { start: newStart, end: newEnd } = params || {};
|
|
262
|
+
if (newStart !== undefined) startValueRef.current = newStart;
|
|
263
|
+
if (newEnd !== undefined) endValueRef.current = newEnd;
|
|
264
|
+
|
|
265
|
+
onZoomChange?.({ start: newStart ?? start, end: newEnd ?? end });
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// 使用节流包装
|
|
269
|
+
throttledHandlerRef.current = throttledCallback(handleZoomChange);
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
// 监听 dataZoom 事件
|
|
273
|
+
chartInstance.on('datazoom', throttledHandlerRef.current);
|
|
274
|
+
} catch (e) {
|
|
275
|
+
console.warn('[useDataZoom] Failed to bind events:', e);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
[disabled, onZoomChange, throttledCallback]
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// 解绑事件
|
|
282
|
+
const unbindEvents = useCallback(
|
|
283
|
+
(chartInstance: ChartInstance) => {
|
|
284
|
+
if (!chartInstance || !throttledHandlerRef.current) return;
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
chartInstance.off('datazoom', throttledHandlerRef.current);
|
|
288
|
+
} catch (e) {
|
|
289
|
+
console.warn('[useDataZoom] Failed to unbind events:', e);
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
[]
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// 清理:组件卸载时
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
return () => {
|
|
298
|
+
const chart = chartRef.current;
|
|
299
|
+
if (chart) {
|
|
300
|
+
if (resetOnUnmount) {
|
|
301
|
+
unbindDataZoom(chart);
|
|
302
|
+
}
|
|
303
|
+
unbindEvents(chart);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}, [resetOnUnmount, unbindDataZoom, unbindEvents]);
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
bindDataZoom,
|
|
310
|
+
setZoomRange,
|
|
311
|
+
resetZoom,
|
|
312
|
+
getZoomRange,
|
|
313
|
+
startValue: startValueRef,
|
|
314
|
+
endValue: endValueRef,
|
|
315
|
+
bindEvents,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// 导出
|
|
321
|
+
// ============================================================================
|
|
322
|
+
|
|
323
|
+
export default useDataZoom;
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TaroViz - 基于 Taro 和 ECharts 的多端图表组件库
|
|
3
|
-
* @version 1.
|
|
3
|
+
* @version 1.7.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// 核心组件
|
|
@@ -59,6 +59,25 @@ export { default as WordCloudChart } from './charts/wordcloud';
|
|
|
59
59
|
export { default as BoxplotChart } from './charts/boxplot';
|
|
60
60
|
export { default as ParallelChart } from './charts/parallel';
|
|
61
61
|
|
|
62
|
+
// v1.7.0 新增组件
|
|
63
|
+
export { DataFilter, type DataFilterProps, type FilterField, type FilterValues } from './components/DataFilter';
|
|
64
|
+
export {
|
|
65
|
+
createDrillDown,
|
|
66
|
+
canDrillDown,
|
|
67
|
+
buildHierarchy,
|
|
68
|
+
createRegionDrillDown,
|
|
69
|
+
createCategoryDrillDown,
|
|
70
|
+
type DrillDownConfig,
|
|
71
|
+
type DrillDownSource,
|
|
72
|
+
type DrillDownReturn,
|
|
73
|
+
type DrillDownEventParams,
|
|
74
|
+
type DrillUpEventParams,
|
|
75
|
+
} from './core/utils/drillDown';
|
|
76
|
+
|
|
77
|
+
// v1.7.0 新增图表组件
|
|
78
|
+
export { default as LiquidChart } from './charts/liquid';
|
|
79
|
+
export { default as TreeChart } from './charts/tree';
|
|
80
|
+
|
|
62
81
|
// 适配器
|
|
63
82
|
export { getAdapter, detectPlatform, getEnv } from './adapters';
|
|
64
83
|
export { default as H5Adapter } from './adapters/h5';
|
|
@@ -152,10 +171,14 @@ export {
|
|
|
152
171
|
useDataTransform,
|
|
153
172
|
useTableTransform,
|
|
154
173
|
useTimeSeriesTransform,
|
|
174
|
+
// v1.7.0 新增 Hooks
|
|
175
|
+
useDataZoom,
|
|
176
|
+
useChartConnect,
|
|
177
|
+
useChartDownload,
|
|
155
178
|
} from './hooks';
|
|
156
179
|
|
|
157
180
|
/**
|
|
158
181
|
* 库信息
|
|
159
182
|
*/
|
|
160
183
|
export const name = 'taroviz';
|
|
161
|
-
export const version = '1.
|
|
184
|
+
export const version = '1.7.0';
|
package/src/themes/index.ts
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAutoTheme - 自动跟随系统暗色模式 Hook
|
|
3
|
+
* 当用户操作系统级别的暗色模式时,图表自动切换到对应的主题
|
|
4
|
+
*/
|
|
5
|
+
import { useEffect, useRef } from 'react';
|
|
6
|
+
import { switchTheme } from './index';
|
|
7
|
+
|
|
8
|
+
/** 自动主题配置选项 */
|
|
9
|
+
export interface UseAutoThemeOptions {
|
|
10
|
+
/** 是否启用自动跟随 */
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
/** 暗色主题名称 */
|
|
13
|
+
darkThemeName?: string;
|
|
14
|
+
/** 亮色主题名称 */
|
|
15
|
+
lightThemeName?: string;
|
|
16
|
+
/** 延迟切换的防抖时间 ms */
|
|
17
|
+
debounceMs?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 自动跟随系统暗色模式
|
|
22
|
+
* @param options 配置选项
|
|
23
|
+
*/
|
|
24
|
+
export function useAutoTheme(options?: UseAutoThemeOptions): void {
|
|
25
|
+
const {
|
|
26
|
+
enabled = true,
|
|
27
|
+
darkThemeName = 'dark',
|
|
28
|
+
lightThemeName = 'light',
|
|
29
|
+
debounceMs = 0,
|
|
30
|
+
} = options || {};
|
|
31
|
+
|
|
32
|
+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!enabled) return;
|
|
36
|
+
|
|
37
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
38
|
+
|
|
39
|
+
const handleChange = (e: MediaQueryListEvent) => {
|
|
40
|
+
// 清除之前的定时器
|
|
41
|
+
if (timeoutRef.current) {
|
|
42
|
+
clearTimeout(timeoutRef.current);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 防抖处理
|
|
46
|
+
timeoutRef.current = setTimeout(() => {
|
|
47
|
+
const isDark = e.matches;
|
|
48
|
+
switchTheme(isDark ? darkThemeName : lightThemeName);
|
|
49
|
+
}, debounceMs);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// 初始设置
|
|
53
|
+
const isDark = mediaQuery.matches;
|
|
54
|
+
switchTheme(isDark ? darkThemeName : lightThemeName);
|
|
55
|
+
|
|
56
|
+
// 监听变化
|
|
57
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
mediaQuery.removeEventListener('change', handleChange);
|
|
61
|
+
if (timeoutRef.current) {
|
|
62
|
+
clearTimeout(timeoutRef.current);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}, [enabled, darkThemeName, lightThemeName, debounceMs]);
|
|
66
|
+
}
|