@agions/taroviz 1.9.0 → 1.11.1
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 +35 -5
- package/dist/cjs/index.js +1 -1
- package/dist/esm/index.js +1128 -432
- package/package.json +1 -1
- package/src/charts/boxplot/types.ts +5 -3
- package/src/charts/candlestick/__tests__/index.test.tsx +4 -1
- package/src/charts/graph/__tests__/index.test.tsx +8 -2
- package/src/charts/parallel/types.ts +6 -3
- package/src/charts/tree/types.ts +4 -4
- package/src/charts/wordcloud/__tests__/index.test.tsx +4 -1
- package/src/core/animation/AnimationManager.ts +9 -6
- package/src/core/animation/types.ts +30 -0
- package/src/core/components/Annotation.tsx +12 -10
- package/src/core/components/BaseChart.tsx +41 -31
- package/src/core/components/ErrorBoundary.tsx +30 -17
- package/src/core/components/LazyChart.tsx +36 -9
- package/src/core/themes/ThemeManager.ts +33 -0
- package/src/core/types/common.ts +100 -5
- package/src/core/utils/chartUtils.ts +8 -3
- package/src/core/utils/export/ExportUtils.ts +39 -9
- package/src/core/utils/performance/PerformanceAnalyzer.ts +15 -5
- package/src/core/utils/performance/types.ts +10 -1
- package/src/hooks/index.ts +53 -0
- package/src/hooks/useChartDownload.ts +17 -261
- package/src/hooks/useChartHistory.ts +273 -0
- package/src/hooks/useChartSelection.ts +350 -0
- package/src/hooks/useDataTransform.ts +39 -286
- package/src/hooks/utils/chartDownloadUtils.ts +273 -0
- package/src/hooks/utils/dataTransformUtils.ts +287 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 用于按需加载图表,减少首屏体积
|
|
4
4
|
*/
|
|
5
5
|
import React, { Suspense, lazy, ComponentType } from 'react';
|
|
6
|
+
import type { BaseChartProps } from '../../charts/types';
|
|
6
7
|
|
|
7
8
|
// 懒加载各个图表组件
|
|
8
9
|
const LazyLineChart = lazy(() => import('../../charts/line'));
|
|
@@ -18,7 +19,7 @@ const LazySunburstChart = lazy(() => import('../../charts/sunburst'));
|
|
|
18
19
|
const LazySankeyChart = lazy(() => import('../../charts/sankey'));
|
|
19
20
|
|
|
20
21
|
// 统一的图表类型到懒加载组件映射
|
|
21
|
-
const LAZY_CHART_MODULES: Record<string, () => Promise<{ default: ComponentType<
|
|
22
|
+
const LAZY_CHART_MODULES: Record<string, () => Promise<{ default: ComponentType<BaseChartProps> }>> = {
|
|
22
23
|
line: () => import('../../charts/line'),
|
|
23
24
|
bar: () => import('../../charts/bar'),
|
|
24
25
|
pie: () => import('../../charts/pie'),
|
|
@@ -35,10 +36,14 @@ const LAZY_CHART_MODULES: Record<string, () => Promise<{ default: ComponentType<
|
|
|
35
36
|
export const LAZY_CHART_TYPES = Object.keys(LAZY_CHART_MODULES);
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
+
* 默认加载状态组件(使用 CSS 变量,与 ThemeManager 对齐)
|
|
40
|
+
* 包含完整的无障碍支持
|
|
39
41
|
*/
|
|
40
42
|
const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中...' }) => (
|
|
41
43
|
<div
|
|
44
|
+
role="status"
|
|
45
|
+
aria-label={text}
|
|
46
|
+
aria-busy="true"
|
|
42
47
|
style={{
|
|
43
48
|
display: 'flex',
|
|
44
49
|
alignItems: 'center',
|
|
@@ -46,8 +51,8 @@ const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中
|
|
|
46
51
|
width: '100%',
|
|
47
52
|
height: '100%',
|
|
48
53
|
minHeight: '200px',
|
|
49
|
-
backgroundColor: '#f5f5f5',
|
|
50
|
-
borderRadius: '8px',
|
|
54
|
+
backgroundColor: 'var(--tv-bg-color-secondary, #f5f5f5)',
|
|
55
|
+
borderRadius: 'var(--tv-border-radius, 8px)',
|
|
51
56
|
}}
|
|
52
57
|
>
|
|
53
58
|
<div style={{ textAlign: 'center' }}>
|
|
@@ -55,12 +60,13 @@ const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中
|
|
|
55
60
|
style={{
|
|
56
61
|
width: '40px',
|
|
57
62
|
height: '40px',
|
|
58
|
-
border: '3px solid #1890ff',
|
|
63
|
+
border: '3px solid var(--tv-primary-color, #1890ff)',
|
|
59
64
|
borderTopColor: 'transparent',
|
|
60
65
|
borderRadius: '50%',
|
|
61
66
|
animation: 'taroviz-spin 1s linear infinite',
|
|
62
67
|
margin: '0 auto 12px',
|
|
63
68
|
}}
|
|
69
|
+
aria-hidden="true"
|
|
64
70
|
/>
|
|
65
71
|
<style>
|
|
66
72
|
{`
|
|
@@ -69,7 +75,28 @@ const DefaultLoadingFallback: React.FC<{ text?: string }> = ({ text = '加载中
|
|
|
69
75
|
}
|
|
70
76
|
`}
|
|
71
77
|
</style>
|
|
72
|
-
|
|
78
|
+
{/*
|
|
79
|
+
视觉隐藏文本,屏幕阅读器可读取加载状态
|
|
80
|
+
避免干扰视觉布局
|
|
81
|
+
*/}
|
|
82
|
+
<span
|
|
83
|
+
style={{
|
|
84
|
+
position: 'absolute',
|
|
85
|
+
width: '1px',
|
|
86
|
+
height: '1px',
|
|
87
|
+
padding: 0,
|
|
88
|
+
margin: '-1px',
|
|
89
|
+
overflow: 'hidden',
|
|
90
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
91
|
+
whiteSpace: 'nowrap',
|
|
92
|
+
border: 0,
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
{text}
|
|
96
|
+
</span>
|
|
97
|
+
<span style={{ color: 'var(--tv-text-color-secondary, #666)', fontSize: 'var(--tv-font-size, 14px)' }}>
|
|
98
|
+
{text}
|
|
99
|
+
</span>
|
|
73
100
|
</div>
|
|
74
101
|
</div>
|
|
75
102
|
);
|
|
@@ -136,8 +163,8 @@ export function preloadAllCharts(): Promise<void[]> {
|
|
|
136
163
|
* 创建懒加载图表映射
|
|
137
164
|
* 用于动态导入图表
|
|
138
165
|
*/
|
|
139
|
-
export function createLazyChart(chartType: string): ComponentType<
|
|
140
|
-
const lazyCharts: Record<string, ComponentType<
|
|
166
|
+
export function createLazyChart(chartType: string): ComponentType<BaseChartProps> | null {
|
|
167
|
+
const lazyCharts: Record<string, ComponentType<BaseChartProps>> = {
|
|
141
168
|
line: LazyLineChart,
|
|
142
169
|
bar: LazyBarChart,
|
|
143
170
|
pie: LazyPieChart,
|
|
@@ -159,7 +186,7 @@ export function createLazyChart(chartType: string): ComponentType<any> | null {
|
|
|
159
186
|
* 用于按名称动态获取懒加载图表组件
|
|
160
187
|
*/
|
|
161
188
|
export const LazyChartRegistry = {
|
|
162
|
-
get(chartType: string): ComponentType<
|
|
189
|
+
get(chartType: string): ComponentType<BaseChartProps> | null {
|
|
163
190
|
return createLazyChart(chartType);
|
|
164
191
|
},
|
|
165
192
|
|
|
@@ -578,6 +578,39 @@ class ThemeManager {
|
|
|
578
578
|
return this.currentTheme.isDark || this.currentTheme.type === 'dark';
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
+
/**
|
|
582
|
+
* 检测系统 prefers-color-scheme 是否为暗色
|
|
583
|
+
*/
|
|
584
|
+
public getSystemPrefersDark(): boolean {
|
|
585
|
+
if (typeof window === 'undefined') return false;
|
|
586
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* 订阅系统主题变化,自动切换匹配的主题
|
|
591
|
+
* @returns 取消订阅的函数
|
|
592
|
+
*/
|
|
593
|
+
public watchSystemTheme(): () => void {
|
|
594
|
+
if (typeof window === 'undefined') return () => {};
|
|
595
|
+
|
|
596
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
597
|
+
const handler = (e: MediaQueryListEvent) => {
|
|
598
|
+
// 当系统主题切换时,自动应用对应主题
|
|
599
|
+
this.setTheme(e.matches ? 'dark' : 'default');
|
|
600
|
+
};
|
|
601
|
+
mq.addEventListener('change', handler);
|
|
602
|
+
return () => mq.removeEventListener('change', handler);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* 应用初始主题(根据系统偏好自动选择 dark/default)
|
|
607
|
+
* 必须在 DOM 加载后调用
|
|
608
|
+
*/
|
|
609
|
+
public applyInitialTheme(): void {
|
|
610
|
+
const prefersDark = this.getSystemPrefersDark();
|
|
611
|
+
this.setTheme(prefersDark ? 'dark' : 'default');
|
|
612
|
+
}
|
|
613
|
+
|
|
581
614
|
/**
|
|
582
615
|
* 注册主题变更监听器
|
|
583
616
|
*/
|
package/src/core/types/common.ts
CHANGED
|
@@ -4,24 +4,119 @@
|
|
|
4
4
|
export type { EChartsOption } from 'echarts';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* ECharts 鼠标事件参数类型
|
|
8
|
+
* 基于 ECharts CallbackDataParams,用于 click、mousemove 等鼠标事件
|
|
9
9
|
*/
|
|
10
|
-
export interface
|
|
11
|
-
componentType
|
|
10
|
+
export interface EChartsMouseEventParams {
|
|
11
|
+
componentType: string;
|
|
12
|
+
componentSubType?: string;
|
|
13
|
+
componentIndex?: number;
|
|
12
14
|
seriesType?: string;
|
|
13
15
|
seriesIndex?: number;
|
|
16
|
+
seriesId?: string;
|
|
14
17
|
seriesName?: string;
|
|
15
18
|
name?: string;
|
|
16
19
|
dataIndex?: number;
|
|
17
20
|
data?: unknown;
|
|
21
|
+
dataType?: string;
|
|
18
22
|
value?: unknown;
|
|
19
23
|
color?: string;
|
|
20
24
|
borderColor?: string;
|
|
21
25
|
borderWidth?: number;
|
|
22
|
-
|
|
26
|
+
dimensionNames?: string[];
|
|
27
|
+
encode?: Record<string, number[]>;
|
|
28
|
+
marker?: string;
|
|
29
|
+
status?: string;
|
|
30
|
+
dimensionIndex?: number;
|
|
31
|
+
percent?: number;
|
|
23
32
|
}
|
|
24
33
|
|
|
34
|
+
/**
|
|
35
|
+
* ECharts DataZoom 事件参数类型
|
|
36
|
+
*/
|
|
37
|
+
export interface EChartsDataZoomEventParams {
|
|
38
|
+
type: 'datazoom';
|
|
39
|
+
start?: number;
|
|
40
|
+
end?: number;
|
|
41
|
+
startValue?: number;
|
|
42
|
+
endValue?: number;
|
|
43
|
+
dataZoomIndex?: number;
|
|
44
|
+
batch?: Array<{
|
|
45
|
+
start?: number;
|
|
46
|
+
end?: number;
|
|
47
|
+
startValue?: number;
|
|
48
|
+
endValue?: number;
|
|
49
|
+
dataZoomIndex?: number;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* ECharts Legend 事件参数类型
|
|
55
|
+
*/
|
|
56
|
+
export interface EChartsLegendEventParams {
|
|
57
|
+
type: 'legendselectchanged' | 'legendselected' | 'legendunselected';
|
|
58
|
+
name: string;
|
|
59
|
+
selected: Record<string, boolean>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* ECharts Tooltip 事件参数类型
|
|
64
|
+
*/
|
|
65
|
+
export interface EChartsTooltipEventParams {
|
|
66
|
+
type: 'tooltipshow' | 'tooltiphide';
|
|
67
|
+
data?: unknown;
|
|
68
|
+
dataIndex?: number;
|
|
69
|
+
dataType?: string;
|
|
70
|
+
name?: string;
|
|
71
|
+
value?: unknown;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 图表事件参数类型(向后兼容别名)
|
|
76
|
+
*/
|
|
77
|
+
export type ChartEventParams = EChartsMouseEventParams;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 图表导出选项
|
|
81
|
+
*/
|
|
82
|
+
export interface ChartExportOptions {
|
|
83
|
+
type?: 'png' | 'jpeg' | 'svg';
|
|
84
|
+
filename?: string;
|
|
85
|
+
pixelRatio?: number;
|
|
86
|
+
backgroundColor?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 图表联动配置
|
|
91
|
+
*/
|
|
92
|
+
export interface ChartLinkageConfig {
|
|
93
|
+
linkedChartIds?: string[];
|
|
94
|
+
enableClickLinkage?: boolean;
|
|
95
|
+
enableZoomLinkage?: boolean;
|
|
96
|
+
enableLegendLinkage?: boolean;
|
|
97
|
+
enableFilterLinkage?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* ECharts 系列数据基本类型
|
|
102
|
+
*/
|
|
103
|
+
export interface EChartsSeriesData {
|
|
104
|
+
name?: string;
|
|
105
|
+
type?: string;
|
|
106
|
+
data?: unknown[];
|
|
107
|
+
[key: string]: unknown;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* ECharts 事件参数联合类型
|
|
112
|
+
*/
|
|
113
|
+
export type EChartsEventParams =
|
|
114
|
+
| EChartsMouseEventParams
|
|
115
|
+
| EChartsDataZoomEventParams
|
|
116
|
+
| EChartsLegendEventParams
|
|
117
|
+
| EChartsTooltipEventParams
|
|
118
|
+
| Record<string, unknown>;
|
|
119
|
+
|
|
25
120
|
/**
|
|
26
121
|
* 图表事件监听器
|
|
27
122
|
*/
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { EChartsOption } from 'echarts';
|
|
5
5
|
|
|
6
|
+
interface SeriesItem {
|
|
7
|
+
data?: unknown[] | Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
/**
|
|
7
11
|
* Normalize size value to CSS string
|
|
8
12
|
*/
|
|
@@ -19,7 +23,7 @@ export function calculateDataLength(option: { series?: unknown } | undefined): n
|
|
|
19
23
|
let count = 0;
|
|
20
24
|
if (option.series) {
|
|
21
25
|
const series = Array.isArray(option.series) ? option.series : [option.series];
|
|
22
|
-
for (const seriesItem of series as
|
|
26
|
+
for (const seriesItem of series as SeriesItem[]) {
|
|
23
27
|
if (seriesItem.data) {
|
|
24
28
|
if (Array.isArray(seriesItem.data)) {
|
|
25
29
|
count += seriesItem.data.length;
|
|
@@ -35,11 +39,12 @@ export function calculateDataLength(option: { series?: unknown } | undefined): n
|
|
|
35
39
|
/**
|
|
36
40
|
* Filter data by filter conditions
|
|
37
41
|
*/
|
|
38
|
-
export function filterDataByKeys(data:
|
|
42
|
+
export function filterDataByKeys<T>(data: T[], filters: Record<string, unknown>): T[] {
|
|
39
43
|
if (!filters || Object.keys(filters).length === 0) return data;
|
|
40
44
|
return data.filter((item) => {
|
|
41
45
|
for (const [key, value] of Object.entries(filters)) {
|
|
42
|
-
|
|
46
|
+
const itemVal = (item as Record<string, unknown>)[key];
|
|
47
|
+
if (itemVal !== value && !(Array.isArray(itemVal) && itemVal.includes(value))) return false;
|
|
43
48
|
}
|
|
44
49
|
return true;
|
|
45
50
|
});
|
|
@@ -8,12 +8,41 @@ import type { ECharts } from 'echarts';
|
|
|
8
8
|
// 类型定义
|
|
9
9
|
// ============================================================================
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* ECharts 支持的图片导出类型
|
|
13
|
+
* 注意: webp/gif 类型为扩展支持,不在官方类型中
|
|
14
|
+
*/
|
|
15
|
+
type ExportImageType = 'png' | 'jpeg' | 'svg' | 'webp' | 'gif';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* getDataURL 方法参数(基于 ECharts 官方类型,扩展 webp/gif 支持)
|
|
19
|
+
*/
|
|
20
|
+
interface EChartsDataURLOptions {
|
|
21
|
+
type?: ExportImageType;
|
|
22
|
+
pixelRatio?: number;
|
|
23
|
+
backgroundColor?: string;
|
|
24
|
+
quality?: number;
|
|
25
|
+
excludeComponents?: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* jsPDF 库类型(简化版)
|
|
30
|
+
*/
|
|
31
|
+
interface JSPDFInstance {
|
|
32
|
+
setProperties(props: Record<string, string>): void;
|
|
33
|
+
setFontSize(size: number): void;
|
|
34
|
+
setTextColor(r: number, g: number, b: number): void;
|
|
35
|
+
text(text: string, x: number, y: number): void;
|
|
36
|
+
addImage(imageData: string, format: string, x: number, y: number, width: number, height: number): void;
|
|
37
|
+
output(type: 'blob'): Blob;
|
|
38
|
+
}
|
|
39
|
+
|
|
11
40
|
/**
|
|
12
41
|
* 图片导出选项
|
|
13
42
|
*/
|
|
14
43
|
export interface ExportImageOptions {
|
|
15
44
|
/** 图片类型 */
|
|
16
|
-
type?:
|
|
45
|
+
type?: ExportImageType;
|
|
17
46
|
/** 设备像素比 */
|
|
18
47
|
pixelRatio?: number;
|
|
19
48
|
/** 背景色 */
|
|
@@ -155,13 +184,13 @@ class ChartExporter {
|
|
|
155
184
|
const { type = 'png', pixelRatio = 2, backgroundColor = '#ffffff', quality = 0.8 } = options;
|
|
156
185
|
|
|
157
186
|
const mimeType = `image/${type}`;
|
|
158
|
-
//
|
|
159
|
-
const data =
|
|
187
|
+
// 使用类型断言:ECharts 实际支持 webp/gif,但官方类型未声明
|
|
188
|
+
const data = chart.getDataURL({
|
|
160
189
|
type,
|
|
161
190
|
pixelRatio,
|
|
162
191
|
backgroundColor,
|
|
163
192
|
quality,
|
|
164
|
-
});
|
|
193
|
+
} as unknown as Parameters<typeof chart.getDataURL>[0]);
|
|
165
194
|
|
|
166
195
|
return {
|
|
167
196
|
data,
|
|
@@ -177,7 +206,7 @@ class ChartExporter {
|
|
|
177
206
|
const { compress = false } = options;
|
|
178
207
|
|
|
179
208
|
// ECharts 5.x 使用 getDataURL 获取 SVG
|
|
180
|
-
const svgData =
|
|
209
|
+
const svgData = chart.getDataURL({ type: 'svg' });
|
|
181
210
|
if (!svgData || svgData === 'data:image/svg+xml;charset=utf8,') {
|
|
182
211
|
throw new Error('SVG export is not supported. Please use canvas renderer.');
|
|
183
212
|
}
|
|
@@ -219,11 +248,12 @@ class ChartExporter {
|
|
|
219
248
|
});
|
|
220
249
|
|
|
221
250
|
// 动态导入 jspdf
|
|
222
|
-
let
|
|
251
|
+
let JSPDFClass: new (options: Record<string, string>) => JSPDFInstance;
|
|
223
252
|
try {
|
|
224
253
|
// 尝试使用动态导入,使用 webpackIgnore 注释避免预解析
|
|
225
254
|
// @ts-expect-error - 动态导入
|
|
226
|
-
|
|
255
|
+
const module = await import(/* webpackIgnore: true */ 'jspdf');
|
|
256
|
+
JSPDFClass = module.default as new (options: Record<string, string>) => JSPDFInstance;
|
|
227
257
|
} catch {
|
|
228
258
|
// 如果没有 jspdf,提供备选方案
|
|
229
259
|
console.warn('[TaroViz] jspdf not found, falling back to image download');
|
|
@@ -246,7 +276,7 @@ class ChartExporter {
|
|
|
246
276
|
const isLandscape = orientation === 'landscape';
|
|
247
277
|
|
|
248
278
|
// 创建 PDF
|
|
249
|
-
const doc = new
|
|
279
|
+
const doc = new JSPDFClass({
|
|
250
280
|
orientation,
|
|
251
281
|
unit: 'mm',
|
|
252
282
|
format: pageSize,
|
|
@@ -289,7 +319,7 @@ class ChartExporter {
|
|
|
289
319
|
doc.text(`Generated by TaroViz on ${new Date().toLocaleDateString()}`, marginLeft, footerY);
|
|
290
320
|
|
|
291
321
|
// 导出为 Blob
|
|
292
|
-
const pdfBlob = doc.output('blob');
|
|
322
|
+
const pdfBlob = doc.output('blob') as Blob;
|
|
293
323
|
|
|
294
324
|
return {
|
|
295
325
|
data: pdfBlob,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
PerformanceAnalysisResult,
|
|
10
10
|
PerformanceEventType,
|
|
11
11
|
PerformanceEventHandler,
|
|
12
|
+
PerformanceEventData,
|
|
12
13
|
PerformanceReportConfig,
|
|
13
14
|
} from './types';
|
|
14
15
|
|
|
@@ -118,11 +119,20 @@ export class PerformanceAnalyzer {
|
|
|
118
119
|
/**
|
|
119
120
|
* 触发事件
|
|
120
121
|
*/
|
|
121
|
-
private emit(eventType: PerformanceEventType
|
|
122
|
+
private emit(eventType: PerformanceEventType.MONITORING_START): void;
|
|
123
|
+
private emit(eventType: PerformanceEventType.MONITORING_END): void;
|
|
124
|
+
private emit(eventType: PerformanceEventType.METRIC_UPDATE, data: PerformanceMetric): void;
|
|
125
|
+
private emit(eventType: PerformanceEventType.ANALYSIS_COMPLETE, data: PerformanceAnalysisResult): void;
|
|
126
|
+
private emit(eventType: PerformanceEventType, data?: PerformanceMetric | PerformanceAnalysisResult): void {
|
|
122
127
|
const handlers = this.eventHandlers.get(eventType);
|
|
128
|
+
const eventData: PerformanceEventData = data === undefined
|
|
129
|
+
? { type: eventType as PerformanceEventType.MONITORING_START | PerformanceEventType.MONITORING_END }
|
|
130
|
+
: eventType === PerformanceEventType.METRIC_UPDATE
|
|
131
|
+
? { type: eventType, data: data as PerformanceMetric }
|
|
132
|
+
: { type: eventType, data: data as PerformanceAnalysisResult };
|
|
123
133
|
handlers?.forEach((handler) => {
|
|
124
134
|
try {
|
|
125
|
-
handler(
|
|
135
|
+
handler(eventData);
|
|
126
136
|
} catch (error) {
|
|
127
137
|
console.error('Error in performance event handler:', error);
|
|
128
138
|
}
|
|
@@ -324,7 +334,7 @@ export class PerformanceAnalyzer {
|
|
|
324
334
|
/**
|
|
325
335
|
* 记录数据大小
|
|
326
336
|
*/
|
|
327
|
-
public recordDataSize(data:
|
|
337
|
+
public recordDataSize(data: unknown): void {
|
|
328
338
|
try {
|
|
329
339
|
const dataSize = new Blob([JSON.stringify(data)]).size / 1024;
|
|
330
340
|
this.recordMetric('dataSize', dataSize, 'KB', '图表数据大小');
|
|
@@ -450,8 +460,8 @@ export class PerformanceAnalyzer {
|
|
|
450
460
|
private generateJsonReport(
|
|
451
461
|
result: PerformanceAnalysisResult,
|
|
452
462
|
_config: PerformanceReportConfig
|
|
453
|
-
):
|
|
454
|
-
const report:
|
|
463
|
+
): Record<string, unknown> {
|
|
464
|
+
const report: Record<string, unknown> = {
|
|
455
465
|
timestamp: new Date().toISOString(),
|
|
456
466
|
duration: result.duration,
|
|
457
467
|
score: result.score,
|
|
@@ -154,10 +154,19 @@ export enum PerformanceEventType {
|
|
|
154
154
|
ANALYSIS_COMPLETE = 'performanceAnalysisComplete',
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* 性能事件数据
|
|
159
|
+
*/
|
|
160
|
+
export type PerformanceEventData =
|
|
161
|
+
| { type: PerformanceEventType.MONITORING_START }
|
|
162
|
+
| { type: PerformanceEventType.MONITORING_END }
|
|
163
|
+
| { type: PerformanceEventType.METRIC_UPDATE; data: PerformanceMetric }
|
|
164
|
+
| { type: PerformanceEventType.ANALYSIS_COMPLETE; data: PerformanceAnalysisResult };
|
|
165
|
+
|
|
157
166
|
/**
|
|
158
167
|
* 性能事件回调类型
|
|
159
168
|
*/
|
|
160
|
-
export type PerformanceEventHandler = (event:
|
|
169
|
+
export type PerformanceEventHandler = (event: PerformanceEventData) => void;
|
|
161
170
|
|
|
162
171
|
/**
|
|
163
172
|
* 性能分析报告配置
|
package/src/hooks/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ import type { EChartsOption } from 'echarts';
|
|
|
9
9
|
import { useDataZoom } from './useDataZoom';
|
|
10
10
|
import { useChartConnect } from './useChartConnect';
|
|
11
11
|
import { useChartDownload } from './useChartDownload';
|
|
12
|
+
import { useChartHistory } from './useChartHistory';
|
|
13
|
+
import { useChartSelection } from './useChartSelection';
|
|
12
14
|
|
|
13
15
|
// ============================================================================
|
|
14
16
|
// 类型定义
|
|
@@ -651,6 +653,55 @@ export {
|
|
|
651
653
|
type DownloadDataOptions,
|
|
652
654
|
} from './useChartDownload';
|
|
653
655
|
|
|
656
|
+
// 图表下载工具函数
|
|
657
|
+
export {
|
|
658
|
+
generateFilename,
|
|
659
|
+
downloadBlob,
|
|
660
|
+
downloadDataUrl,
|
|
661
|
+
csvToBlob,
|
|
662
|
+
jsonToBlob,
|
|
663
|
+
convertToCSV,
|
|
664
|
+
convertToJSON,
|
|
665
|
+
createPdfFromImage,
|
|
666
|
+
} from './utils/chartDownloadUtils';
|
|
667
|
+
|
|
668
|
+
// 数据转换工具函数(类型导出)
|
|
669
|
+
export type {
|
|
670
|
+
DataItem,
|
|
671
|
+
DataSource,
|
|
672
|
+
AggregationType,
|
|
673
|
+
TimePeriod,
|
|
674
|
+
TransformMapping,
|
|
675
|
+
} from './utils/dataTransformUtils';
|
|
676
|
+
|
|
677
|
+
// 数据转换工具函数(值导出)
|
|
678
|
+
export {
|
|
679
|
+
transformLineOrBar,
|
|
680
|
+
transformPie,
|
|
681
|
+
transformScatter,
|
|
682
|
+
transformRadar,
|
|
683
|
+
transformHeatmap,
|
|
684
|
+
groupByTime,
|
|
685
|
+
aggregateValues,
|
|
686
|
+
} from './utils/dataTransformUtils';
|
|
687
|
+
|
|
688
|
+
// 图表历史记录 Hook (Undo/Redo)
|
|
689
|
+
export {
|
|
690
|
+
useChartHistory,
|
|
691
|
+
type UseChartHistoryOptions,
|
|
692
|
+
type UseChartHistoryReturn,
|
|
693
|
+
} from './useChartHistory';
|
|
694
|
+
|
|
695
|
+
// 图表选择 Hook
|
|
696
|
+
export {
|
|
697
|
+
useChartSelection,
|
|
698
|
+
type UseChartSelectionOptions,
|
|
699
|
+
type UseChartSelectionReturn,
|
|
700
|
+
type DataPointKey,
|
|
701
|
+
type SelectionMode,
|
|
702
|
+
type SelectionEvent,
|
|
703
|
+
} from './useChartSelection';
|
|
704
|
+
|
|
654
705
|
// ============================================================================
|
|
655
706
|
// 导出
|
|
656
707
|
// ============================================================================
|
|
@@ -683,4 +734,6 @@ export default {
|
|
|
683
734
|
useDataZoom,
|
|
684
735
|
useChartConnect,
|
|
685
736
|
useChartDownload,
|
|
737
|
+
useChartHistory,
|
|
738
|
+
useChartSelection,
|
|
686
739
|
};
|