@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
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 箱线图类型定义
|
|
3
3
|
*/
|
|
4
|
+
import type { EChartsType, ECElementEvent } from 'echarts';
|
|
5
|
+
import type { LoadingOptions } from '../types';
|
|
4
6
|
|
|
5
7
|
export type BoxplotChartProps = {
|
|
6
8
|
option?: BoxplotOption;
|
|
@@ -8,11 +10,11 @@ export type BoxplotChartProps = {
|
|
|
8
10
|
height?: string | number;
|
|
9
11
|
className?: string;
|
|
10
12
|
style?: React.CSSProperties;
|
|
11
|
-
onEvents?: Record<string, (params:
|
|
13
|
+
onEvents?: Record<string, (params: ECElementEvent) => void>;
|
|
12
14
|
loading?: boolean;
|
|
13
|
-
loadingOption?:
|
|
15
|
+
loadingOption?: LoadingOptions;
|
|
14
16
|
theme?: string;
|
|
15
|
-
onChartReady?: (chart:
|
|
17
|
+
onChartReady?: (chart: EChartsType) => void;
|
|
16
18
|
opts?: {
|
|
17
19
|
devicePixelRatio?: number;
|
|
18
20
|
renderer?: 'canvas' | 'svg';
|
|
@@ -14,7 +14,10 @@ describe('CandlestickChart', () => {
|
|
|
14
14
|
|
|
15
15
|
it('renders with custom width and height', () => {
|
|
16
16
|
const { container } = render(<CandlestickChart width={500} height={400} />);
|
|
17
|
-
|
|
17
|
+
// BaseChartWrapper renders a fragment with a hidden accessibility table (first child)
|
|
18
|
+
// and the actual chart div (last child), so we query the last child for styles
|
|
19
|
+
const chartDiv = container.lastChild;
|
|
20
|
+
expect(chartDiv).toHaveStyle({ width: '500px', height: '400px' });
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
it('renders with stock data', () => {
|
|
@@ -14,12 +14,18 @@ describe('GraphChart', () => {
|
|
|
14
14
|
|
|
15
15
|
it('renders with custom className', () => {
|
|
16
16
|
const { container } = render(<GraphChart className="test-graph" />);
|
|
17
|
-
|
|
17
|
+
// BaseChartWrapper renders a fragment with a hidden accessibility table (first child)
|
|
18
|
+
// and the actual chart div (last child), so we query the last child for className
|
|
19
|
+
const chartDiv = container.lastChild;
|
|
20
|
+
expect(chartDiv).toHaveClass('test-graph');
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
it('renders with custom width and height', () => {
|
|
21
24
|
const { container } = render(<GraphChart width={500} height={400} />);
|
|
22
|
-
|
|
25
|
+
// BaseChartWrapper renders a fragment with a hidden accessibility table (first child)
|
|
26
|
+
// and the actual chart div (last child), so we query the last child for styles
|
|
27
|
+
const chartDiv = container.lastChild;
|
|
28
|
+
expect(chartDiv).toHaveStyle({ width: '500px', height: '400px' });
|
|
23
29
|
});
|
|
24
30
|
|
|
25
31
|
it('renders with basic option', () => {
|
|
@@ -2,17 +2,20 @@
|
|
|
2
2
|
* 平行坐标图类型定义
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type { EChartsType, ECElementEvent } from 'echarts';
|
|
6
|
+
import type { LoadingOptions } from '../types';
|
|
7
|
+
|
|
5
8
|
export type ParallelChartProps = {
|
|
6
9
|
option?: ParallelOption;
|
|
7
10
|
width?: string | number;
|
|
8
11
|
height?: string | number;
|
|
9
12
|
className?: string;
|
|
10
13
|
style?: React.CSSProperties;
|
|
11
|
-
onEvents?: Record<string, (params:
|
|
14
|
+
onEvents?: Record<string, (params: ECElementEvent) => void>;
|
|
12
15
|
loading?: boolean;
|
|
13
|
-
loadingOption?:
|
|
16
|
+
loadingOption?: LoadingOptions;
|
|
14
17
|
theme?: string;
|
|
15
|
-
onChartReady?: (chart:
|
|
18
|
+
onChartReady?: (chart: EChartsType) => void;
|
|
16
19
|
opts?: {
|
|
17
20
|
devicePixelRatio?: number;
|
|
18
21
|
renderer?: 'canvas' | 'svg';
|
package/src/charts/tree/types.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 树图类型定义
|
|
3
3
|
* ECharts 内置 tree 类型
|
|
4
4
|
*/
|
|
5
|
-
import type { EChartsOption } from 'echarts';
|
|
5
|
+
import type { EChartsOption, EChartsType, ECElementEvent } from 'echarts';
|
|
6
6
|
|
|
7
7
|
// ============================================================================
|
|
8
8
|
// 树图数据节点
|
|
@@ -166,9 +166,9 @@ export interface TreeChartProps {
|
|
|
166
166
|
/** 加载配置 */
|
|
167
167
|
loadingOption?: Record<string, unknown>;
|
|
168
168
|
/** 图表初始化回调 */
|
|
169
|
-
onChartInit?: (chart:
|
|
169
|
+
onChartInit?: (chart: EChartsType) => void;
|
|
170
170
|
/** 图表就绪回调 */
|
|
171
|
-
onChartReady?: (chart:
|
|
171
|
+
onChartReady?: (chart: EChartsType) => void;
|
|
172
172
|
/** 事件回调 */
|
|
173
|
-
onEvents?: Record<string, (params:
|
|
173
|
+
onEvents?: Record<string, (params: ECElementEvent) => void>;
|
|
174
174
|
}
|
|
@@ -14,7 +14,10 @@ describe('WordCloudChart', () => {
|
|
|
14
14
|
|
|
15
15
|
it('renders with custom width and height', () => {
|
|
16
16
|
const { container } = render(<WordCloudChart width={600} height={400} />);
|
|
17
|
-
|
|
17
|
+
// BaseChartWrapper renders a fragment with a hidden accessibility table (first child)
|
|
18
|
+
// and the actual chart div (last child), so we query the last child for styles
|
|
19
|
+
const chartDiv = container.lastChild;
|
|
20
|
+
expect(chartDiv).toHaveStyle({ width: '600px', height: '400px' });
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
it('renders with word data', () => {
|
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
AnimationManagerConfig,
|
|
11
11
|
AnimationType,
|
|
12
12
|
AnimationEventType,
|
|
13
|
+
AnimationEventHandler,
|
|
14
|
+
AnimationEventData,
|
|
15
|
+
EChartsAnimationConfigResult,
|
|
13
16
|
} from './types';
|
|
14
17
|
|
|
15
18
|
/**
|
|
@@ -131,7 +134,7 @@ export class AnimationManager {
|
|
|
131
134
|
private templates: Map<string, AnimationTemplate> = new Map();
|
|
132
135
|
private defaultConfig: AnimationConfig;
|
|
133
136
|
private performanceConfig: AnimationManagerConfig['performance'];
|
|
134
|
-
private eventHandlers: Map<string, Set<
|
|
137
|
+
private eventHandlers: Map<string, Set<AnimationEventHandler>> = new Map();
|
|
135
138
|
|
|
136
139
|
/**
|
|
137
140
|
* 私有构造函数
|
|
@@ -304,7 +307,7 @@ export class AnimationManager {
|
|
|
304
307
|
public generateEChartsAnimationConfig(
|
|
305
308
|
config: Partial<AnimationConfig> = {},
|
|
306
309
|
dataLength: number = 0
|
|
307
|
-
):
|
|
310
|
+
): EChartsAnimationConfigResult {
|
|
308
311
|
const optimizedConfig = this.getOptimizedConfig(config, dataLength);
|
|
309
312
|
|
|
310
313
|
if (!optimizedConfig.enabled) {
|
|
@@ -331,7 +334,7 @@ export class AnimationManager {
|
|
|
331
334
|
/**
|
|
332
335
|
* 绑定动画事件
|
|
333
336
|
*/
|
|
334
|
-
public on(eventType: AnimationEventType, handler:
|
|
337
|
+
public on(eventType: AnimationEventType, handler: AnimationEventHandler): void {
|
|
335
338
|
if (!this.eventHandlers.has(eventType)) {
|
|
336
339
|
this.eventHandlers.set(eventType, new Set());
|
|
337
340
|
}
|
|
@@ -341,7 +344,7 @@ export class AnimationManager {
|
|
|
341
344
|
/**
|
|
342
345
|
* 解绑动画事件
|
|
343
346
|
*/
|
|
344
|
-
public off(eventType: AnimationEventType, handler?:
|
|
347
|
+
public off(eventType: AnimationEventType, handler?: AnimationEventHandler): void {
|
|
345
348
|
if (!handler) {
|
|
346
349
|
this.eventHandlers.delete(eventType);
|
|
347
350
|
return;
|
|
@@ -352,7 +355,7 @@ export class AnimationManager {
|
|
|
352
355
|
/**
|
|
353
356
|
* 触发动画事件
|
|
354
357
|
*/
|
|
355
|
-
public emit(eventType: AnimationEventType, data:
|
|
358
|
+
public emit(eventType: AnimationEventType, data: AnimationEventData): void {
|
|
356
359
|
const handlers = this.eventHandlers.get(eventType);
|
|
357
360
|
if (handlers) {
|
|
358
361
|
handlers.forEach((handler) => {
|
|
@@ -427,7 +430,7 @@ export function getAnimationPreset(name: string): AnimationPreset | undefined {
|
|
|
427
430
|
export function generateEChartsAnimationConfig(
|
|
428
431
|
config: Partial<AnimationConfig> = {},
|
|
429
432
|
dataLength: number = 0
|
|
430
|
-
):
|
|
433
|
+
): EChartsAnimationConfigResult {
|
|
431
434
|
const manager = AnimationManager.getInstance();
|
|
432
435
|
return manager.generateEChartsAnimationConfig(config, dataLength);
|
|
433
436
|
}
|
|
@@ -174,6 +174,36 @@ export interface AnimationTemplate {
|
|
|
174
174
|
scenarios?: string[];
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* ECharts 动画配置返回类型
|
|
179
|
+
*/
|
|
180
|
+
export interface EChartsAnimationConfigResult {
|
|
181
|
+
animation: boolean;
|
|
182
|
+
animationDuration?: number;
|
|
183
|
+
animationEasing?: string;
|
|
184
|
+
animationDelay?: number;
|
|
185
|
+
animationDurationUpdate?: number;
|
|
186
|
+
animationEasingUpdate?: string;
|
|
187
|
+
animationDelayUpdate?: number;
|
|
188
|
+
animationThreshold?: number;
|
|
189
|
+
progressive?: boolean;
|
|
190
|
+
progressiveThreshold?: number;
|
|
191
|
+
progressiveChunkMode?: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 动画事件数据
|
|
196
|
+
*/
|
|
197
|
+
export interface AnimationEventData {
|
|
198
|
+
type: AnimationEventType;
|
|
199
|
+
animationType: AnimationType;
|
|
200
|
+
chartId?: string;
|
|
201
|
+
seriesIndex?: number;
|
|
202
|
+
dataIndex?: number;
|
|
203
|
+
timestamp: number;
|
|
204
|
+
duration: number;
|
|
205
|
+
}
|
|
206
|
+
|
|
177
207
|
/**
|
|
178
208
|
* 动画管理器配置
|
|
179
209
|
*/
|
|
@@ -32,7 +32,7 @@ export interface MarkAreaStyle {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* 标记线数据点
|
|
35
|
+
* 标记线数据点 — 支持常规坐标和 ECharts 统计类型
|
|
36
36
|
*/
|
|
37
37
|
export interface MarkLineDataPoint {
|
|
38
38
|
/** X轴值 */
|
|
@@ -41,6 +41,8 @@ export interface MarkLineDataPoint {
|
|
|
41
41
|
yAxis?: string | number;
|
|
42
42
|
/** 数据点名称 */
|
|
43
43
|
name?: string;
|
|
44
|
+
/** ECharts 统计/特殊类型('average' | 'max' | 'min' | 'median') */
|
|
45
|
+
type?: string;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/**
|
|
@@ -97,7 +99,7 @@ export interface ScatterAnnotationConfig {
|
|
|
97
99
|
data: Array<{
|
|
98
100
|
coord: [number | string, number];
|
|
99
101
|
value?: number;
|
|
100
|
-
name
|
|
102
|
+
name: string;
|
|
101
103
|
}>;
|
|
102
104
|
/** 符号类型 */
|
|
103
105
|
symbol?: string;
|
|
@@ -109,7 +111,7 @@ export interface ScatterAnnotationConfig {
|
|
|
109
111
|
label?: {
|
|
110
112
|
show?: boolean;
|
|
111
113
|
position?: string;
|
|
112
|
-
formatter?: string | ((value:
|
|
114
|
+
formatter?: string | ((value: unknown) => string);
|
|
113
115
|
color?: string;
|
|
114
116
|
};
|
|
115
117
|
}
|
|
@@ -223,14 +225,14 @@ export function convertAnnotationToScatter(
|
|
|
223
225
|
itemStyle,
|
|
224
226
|
label: {
|
|
225
227
|
show: label?.show !== false,
|
|
226
|
-
position: label?.position || 'top',
|
|
228
|
+
position: label?.position || ('top' as const),
|
|
227
229
|
formatter: label?.formatter,
|
|
228
230
|
color: label?.color || '#333',
|
|
229
231
|
},
|
|
230
232
|
data,
|
|
231
233
|
},
|
|
232
|
-
}
|
|
233
|
-
];
|
|
234
|
+
},
|
|
235
|
+
] as unknown as EChartsOption['series'];
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
/**
|
|
@@ -269,28 +271,28 @@ export function useAnnotation(props: AnnotationProps): EChartsOption {
|
|
|
269
271
|
export const AnnotationPresets = {
|
|
270
272
|
/** 平均线 */
|
|
271
273
|
averageLine: (color = '#1890ff'): MarkLineConfig => ({
|
|
272
|
-
data: [{ type: 'average', name: '平均值' }]
|
|
274
|
+
data: [{ type: 'average', name: '平均值' }],
|
|
273
275
|
lineStyle: { color, type: 'dashed', width: 2 },
|
|
274
276
|
label: { show: true, position: 'end', color },
|
|
275
277
|
}),
|
|
276
278
|
|
|
277
279
|
/** 最大值线 */
|
|
278
280
|
maxLine: (color = '#f5222d'): MarkLineConfig => ({
|
|
279
|
-
data: [{ type: 'max', name: '最大值' }]
|
|
281
|
+
data: [{ type: 'max', name: '最大值' }],
|
|
280
282
|
lineStyle: { color, type: 'dashed', width: 2 },
|
|
281
283
|
label: { show: true, position: 'end', color },
|
|
282
284
|
}),
|
|
283
285
|
|
|
284
286
|
/** 最小值线 */
|
|
285
287
|
minLine: (color = '#52c41a'): MarkLineConfig => ({
|
|
286
|
-
data: [{ type: 'min', name: '最小值' }]
|
|
288
|
+
data: [{ type: 'min', name: '最小值' }],
|
|
287
289
|
lineStyle: { color, type: 'dashed', width: 2 },
|
|
288
290
|
label: { show: true, position: 'end', color },
|
|
289
291
|
}),
|
|
290
292
|
|
|
291
293
|
/** 警戒线 */
|
|
292
294
|
thresholdLine: (value: number, color = '#faad14'): MarkLineConfig => ({
|
|
293
|
-
data: [{ yAxis: value, name: '警戒线' }]
|
|
295
|
+
data: [{ yAxis: value, name: '警戒线' }],
|
|
294
296
|
lineStyle: { color, type: 'solid', width: 2 },
|
|
295
297
|
label: { show: true, position: 'start', color },
|
|
296
298
|
}),
|
|
@@ -16,6 +16,13 @@ import { PerformanceAnalyzer } from '../utils/performance';
|
|
|
16
16
|
import { normalizeSize, calculateDataLength, filterDataByKeys } from '../utils/chartUtils';
|
|
17
17
|
import BaseChartWrapper from '../../charts/common/BaseChartWrapper';
|
|
18
18
|
import type { BaseChartProps } from '../../charts/types';
|
|
19
|
+
import type {
|
|
20
|
+
EChartsMouseEventParams,
|
|
21
|
+
EChartsDataZoomEventParams,
|
|
22
|
+
EChartsLegendEventParams,
|
|
23
|
+
EChartsTooltipEventParams,
|
|
24
|
+
} from '../types/common';
|
|
25
|
+
import type { ECElementEvent } from 'echarts';
|
|
19
26
|
|
|
20
27
|
// ============================================================================
|
|
21
28
|
// 接口定义
|
|
@@ -95,8 +102,8 @@ export interface ChartProps {
|
|
|
95
102
|
enableZoom?: boolean;
|
|
96
103
|
onZoom?: (data: { start: number; end: number; dataZoomIndex: number }) => void;
|
|
97
104
|
enableDataFiltering?: boolean;
|
|
98
|
-
filters?: Record<string,
|
|
99
|
-
onDataFiltered?: (filteredData:
|
|
105
|
+
filters?: Record<string, string | number | boolean | string[] | null>;
|
|
106
|
+
onDataFiltered?: (filteredData: unknown[], filters: Record<string, unknown>) => void;
|
|
100
107
|
enableLegendInteraction?: boolean;
|
|
101
108
|
legendInteractionMode?: 'single' | 'multiple' | 'all';
|
|
102
109
|
onLegendSelect?: (params: { name: string; selected: Record<string, boolean> }) => void;
|
|
@@ -104,10 +111,10 @@ export interface ChartProps {
|
|
|
104
111
|
onLegendSelectAll?: (params: { selected: Record<string, boolean> }) => void;
|
|
105
112
|
onLegendInverseSelect?: (params: { selected: Record<string, boolean> }) => void;
|
|
106
113
|
enableCustomTooltip?: boolean;
|
|
107
|
-
customTooltipContent?: (params:
|
|
114
|
+
customTooltipContent?: (params: EChartsMouseEventParams | EChartsMouseEventParams[]) => React.ReactNode;
|
|
108
115
|
customTooltipStyle?: React.CSSProperties;
|
|
109
|
-
onTooltipShow?: (params:
|
|
110
|
-
onTooltipHide?: (params:
|
|
116
|
+
onTooltipShow?: (params: EChartsTooltipEventParams) => void;
|
|
117
|
+
onTooltipHide?: (params: EChartsTooltipEventParams) => void;
|
|
111
118
|
onExport?: (dataURL: string, options: ChartExportOptions) => void;
|
|
112
119
|
linkageConfig?: ChartLinkageConfig;
|
|
113
120
|
onDataUpdate?: (
|
|
@@ -197,15 +204,16 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
197
204
|
// Wrapper option that applies virtual scroll + data filtering
|
|
198
205
|
const wrappedOption = useMemo(() => {
|
|
199
206
|
if (!option) return undefined;
|
|
200
|
-
let processed = { ...option };
|
|
207
|
+
let processed: Record<string, unknown> = { ...option };
|
|
201
208
|
|
|
202
209
|
// Apply data filtering
|
|
203
210
|
if (enableDataFiltering && filters && Object.keys(filters).length > 0) {
|
|
204
|
-
processed = JSON.parse(JSON.stringify(processed));
|
|
211
|
+
processed = JSON.parse(JSON.stringify(processed)) as typeof processed;
|
|
205
212
|
if (processed.series && Array.isArray(processed.series)) {
|
|
206
|
-
processed.series = processed.series.map((s:
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
processed.series = (processed.series as unknown[]).map((s: unknown) => {
|
|
214
|
+
const seriesItem = s as { data?: unknown[]; [key: string]: unknown };
|
|
215
|
+
if (seriesItem.data && Array.isArray(seriesItem.data)) {
|
|
216
|
+
const filtered = filterDataByKeys(seriesItem.data, filters);
|
|
209
217
|
if (onDataFiltered) onDataFiltered(filtered, filters);
|
|
210
218
|
if (virtualScroll) {
|
|
211
219
|
virtualScrollRef.current.totalDataCount = filtered.length;
|
|
@@ -217,11 +225,11 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
217
225
|
start + virtualScrollPageSize + virtualScrollPreloadSize,
|
|
218
226
|
filtered.length
|
|
219
227
|
);
|
|
220
|
-
return { ...
|
|
228
|
+
return { ...seriesItem, data: filtered.slice(start, end) };
|
|
221
229
|
}
|
|
222
|
-
return { ...
|
|
230
|
+
return { ...seriesItem, data: filtered };
|
|
223
231
|
}
|
|
224
|
-
return
|
|
232
|
+
return seriesItem;
|
|
225
233
|
});
|
|
226
234
|
}
|
|
227
235
|
}
|
|
@@ -285,7 +293,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
285
293
|
if (instance) {
|
|
286
294
|
// Click linkage
|
|
287
295
|
if (linkageConfig.enableClickLinkage && chartId && linkageConfig.linkedChartIds) {
|
|
288
|
-
instance.on('click', (params:
|
|
296
|
+
instance.on('click', (params: ECElementEvent) => {
|
|
289
297
|
linkageConfig.linkedChartIds!.forEach((lid) => {
|
|
290
298
|
const linked = getChart(lid);
|
|
291
299
|
if (linked) linked.dispatchAction({ type: 'highlight', name: params.name });
|
|
@@ -294,17 +302,18 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
294
302
|
}
|
|
295
303
|
|
|
296
304
|
// Zoom + zoom linkage + virtual scroll page update
|
|
297
|
-
instance.on('datazoom', (params:
|
|
305
|
+
instance.on('datazoom', (params: unknown) => {
|
|
306
|
+
const p = params as { start?: number; end?: number; dataZoomIndex?: number; batch?: Array<{ start?: number; end?: number; dataZoomIndex?: number }> };
|
|
298
307
|
if (onZoom)
|
|
299
308
|
onZoom({
|
|
300
|
-
start:
|
|
301
|
-
end:
|
|
302
|
-
dataZoomIndex:
|
|
309
|
+
start: p.start || 0,
|
|
310
|
+
end: p.end || 100,
|
|
311
|
+
dataZoomIndex: p.dataZoomIndex || 0,
|
|
303
312
|
});
|
|
304
313
|
if (virtualScroll && !virtualScrollRef.current.isScrolling) {
|
|
305
314
|
virtualScrollRef.current.isScrolling = true;
|
|
306
315
|
const newPage = Math.floor(
|
|
307
|
-
((
|
|
316
|
+
((p.start || 0) / 100) * virtualScrollRef.current.totalPages
|
|
308
317
|
);
|
|
309
318
|
if (newPage !== virtualScrollRef.current.currentPage) {
|
|
310
319
|
virtualScrollRef.current.currentPage = newPage;
|
|
@@ -320,9 +329,9 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
320
329
|
if (linked)
|
|
321
330
|
linked.dispatchAction({
|
|
322
331
|
type: 'dataZoom',
|
|
323
|
-
start:
|
|
324
|
-
end:
|
|
325
|
-
dataZoomIndex:
|
|
332
|
+
start: p.start,
|
|
333
|
+
end: p.end,
|
|
334
|
+
dataZoomIndex: p.dataZoomIndex,
|
|
326
335
|
});
|
|
327
336
|
});
|
|
328
337
|
}
|
|
@@ -330,8 +339,9 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
330
339
|
|
|
331
340
|
// Legend interaction
|
|
332
341
|
if (enableLegendInteraction) {
|
|
333
|
-
instance.on('legendselectchanged', (params:
|
|
334
|
-
const { name
|
|
342
|
+
instance.on('legendselectchanged', (params: unknown) => {
|
|
343
|
+
const p = params as { name?: string; selected: Record<string, boolean> };
|
|
344
|
+
const { name, selected } = p;
|
|
335
345
|
if (linkageConfig.enableLegendLinkage && chartId && linkageConfig.linkedChartIds) {
|
|
336
346
|
linkageConfig.linkedChartIds!.forEach((lid) => {
|
|
337
347
|
const linked = getChart(lid);
|
|
@@ -344,21 +354,21 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
344
354
|
newSelected[k] = k === name;
|
|
345
355
|
});
|
|
346
356
|
instance.setOption({ legend: { selected: newSelected } });
|
|
347
|
-
onLegendSelect?.({ name, selected: newSelected });
|
|
357
|
+
if (name !== undefined) onLegendSelect?.({ name, selected: newSelected });
|
|
348
358
|
} else {
|
|
349
|
-
if (selected[name]) onLegendSelect?.({ name, selected });
|
|
350
|
-
else onLegendUnselect?.({ name, selected });
|
|
359
|
+
if (name !== undefined && selected[name]) onLegendSelect?.({ name, selected });
|
|
360
|
+
else if (name !== undefined) onLegendUnselect?.({ name, selected });
|
|
351
361
|
}
|
|
352
362
|
});
|
|
353
363
|
}
|
|
354
364
|
|
|
355
365
|
// Custom tooltip
|
|
356
366
|
if (enableCustomTooltip && customTooltipContent) {
|
|
357
|
-
instance.on('tooltipshow', (params:
|
|
358
|
-
instance.on('tooltiphide', (params:
|
|
367
|
+
instance.on('tooltipshow', (params: unknown) => onTooltipShow?.(params as EChartsTooltipEventParams));
|
|
368
|
+
instance.on('tooltiphide', (params: unknown) => onTooltipHide?.(params as EChartsTooltipEventParams));
|
|
359
369
|
instance.setOption({
|
|
360
370
|
tooltip: {
|
|
361
|
-
formatter: (params:
|
|
371
|
+
formatter: (params: unknown) => String(customTooltipContent(params as EChartsMouseEventParams)),
|
|
362
372
|
...(customTooltipStyle && {
|
|
363
373
|
backgroundColor: 'transparent',
|
|
364
374
|
borderColor: 'transparent',
|
|
@@ -485,7 +495,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
|
|
|
485
495
|
};
|
|
486
496
|
|
|
487
497
|
const wrapperProps: BaseChartProps & { chartType: string } = {
|
|
488
|
-
option: wrappedOption as
|
|
498
|
+
option: wrappedOption as unknown as Record<string, unknown>,
|
|
489
499
|
width,
|
|
490
500
|
height,
|
|
491
501
|
theme: typeof theme === 'string' ? theme : (theme as Record<string, unknown>),
|
|
@@ -65,25 +65,30 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
65
65
|
return fallback(error, this.handleReset);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
//
|
|
68
|
+
// 默认错误展示(使用 CSS 变量,与 ThemeManager 对齐)
|
|
69
69
|
return (
|
|
70
70
|
<div
|
|
71
|
+
role="alert"
|
|
72
|
+
aria-live="assertive"
|
|
71
73
|
style={{
|
|
72
74
|
display: 'flex',
|
|
73
75
|
flexDirection: 'column',
|
|
74
76
|
alignItems: 'center',
|
|
75
77
|
justifyContent: 'center',
|
|
76
|
-
padding: '
|
|
77
|
-
backgroundColor: '#fff',
|
|
78
|
-
border: '1px solid #ff4d4f',
|
|
79
|
-
borderRadius: '8px',
|
|
80
|
-
color: '#333',
|
|
78
|
+
padding: 'var(--tv-border-radius, 16px)',
|
|
79
|
+
backgroundColor: 'var(--tv-bg-color, #fff)',
|
|
80
|
+
border: '1px solid var(--tv-error-color, #ff4d4f)',
|
|
81
|
+
borderRadius: 'var(--tv-border-radius, 8px)',
|
|
82
|
+
color: 'var(--tv-text-color, #333)',
|
|
81
83
|
minHeight: '200px',
|
|
84
|
+
fontFamily: 'var(--tv-font-family, sans-serif)',
|
|
82
85
|
}}
|
|
83
86
|
>
|
|
84
|
-
<div style={{ fontSize: '48px', marginBottom: '16px' }}>⚠️</div>
|
|
85
|
-
<h3 style={{ margin: '0 0 12px', color: '#ff4d4f' }}
|
|
86
|
-
|
|
87
|
+
<div style={{ fontSize: '48px', marginBottom: '16px' }} aria-hidden="true">⚠️</div>
|
|
88
|
+
<h3 style={{ margin: '0 0 12px', color: 'var(--tv-error-color, #ff4d4f)', fontWeight: 700 }}>
|
|
89
|
+
图表渲染失败
|
|
90
|
+
</h3>
|
|
91
|
+
<p style={{ margin: '0 0 16px', color: 'var(--tv-text-color-secondary, #666)', textAlign: 'center', maxWidth: '320px' }}>
|
|
87
92
|
图表在渲染过程中遇到错误,请检查数据配置是否正确
|
|
88
93
|
</p>
|
|
89
94
|
{showDetails && (
|
|
@@ -91,15 +96,15 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
91
96
|
style={{
|
|
92
97
|
width: '100%',
|
|
93
98
|
padding: '12px',
|
|
94
|
-
backgroundColor: '#f5f5f5',
|
|
95
|
-
borderRadius: '4px',
|
|
96
|
-
fontSize: '12px',
|
|
97
|
-
fontFamily: 'monospace',
|
|
99
|
+
backgroundColor: 'var(--tv-bg-color-secondary, #f5f5f5)',
|
|
100
|
+
borderRadius: 'var(--tv-border-radius-small, 4px)',
|
|
101
|
+
fontSize: 'var(--tv-font-size-small, 12px)',
|
|
102
|
+
fontFamily: 'var(--tv-font-family, monospace)',
|
|
98
103
|
overflow: 'auto',
|
|
99
104
|
maxHeight: '150px',
|
|
100
105
|
}}
|
|
101
106
|
>
|
|
102
|
-
<summary style={{ cursor: 'pointer', marginBottom: '8px' }}>错误详情</summary>
|
|
107
|
+
<summary style={{ cursor: 'pointer', marginBottom: '8px', fontWeight: 600 }}>错误详情</summary>
|
|
103
108
|
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
|
|
104
109
|
{error.message}
|
|
105
110
|
{'\n\n'}
|
|
@@ -112,12 +117,20 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
112
117
|
style={{
|
|
113
118
|
marginTop: '16px',
|
|
114
119
|
padding: '8px 24px',
|
|
115
|
-
backgroundColor: '#1890ff',
|
|
120
|
+
backgroundColor: 'var(--tv-primary-color, #1890ff)',
|
|
116
121
|
color: '#fff',
|
|
117
122
|
border: 'none',
|
|
118
|
-
borderRadius: '4px',
|
|
123
|
+
borderRadius: 'var(--tv-border-radius-small, 4px)',
|
|
119
124
|
cursor: 'pointer',
|
|
120
|
-
fontSize: '14px',
|
|
125
|
+
fontSize: 'var(--tv-font-size, 14px)',
|
|
126
|
+
fontWeight: 600,
|
|
127
|
+
transition: 'background-color var(--tv-transition-duration, 0.3s)',
|
|
128
|
+
}}
|
|
129
|
+
onMouseEnter={(e) => {
|
|
130
|
+
e.currentTarget.style.backgroundColor = 'var(--tv-primary-color-hover, #40a9ff)';
|
|
131
|
+
}}
|
|
132
|
+
onMouseLeave={(e) => {
|
|
133
|
+
e.currentTarget.style.backgroundColor = 'var(--tv-primary-color, #1890ff)';
|
|
121
134
|
}}
|
|
122
135
|
>
|
|
123
136
|
重试
|