@agions/taroviz 1.2.1 → 1.3.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.
@@ -0,0 +1,346 @@
1
+ /**
2
+ * TaroViz 图表标注组件
3
+ * 支持在图表上添加标记线、标记区域、散点等标注
4
+ */
5
+ import React, { useMemo } from 'react';
6
+ import type { EChartsOption } from 'echarts';
7
+
8
+ /**
9
+ * 标注类型
10
+ */
11
+ export type AnnotationType = 'line' | 'area' | 'scatter' | 'rect' | 'image' | 'html';
12
+
13
+ /**
14
+ * 标记线样式
15
+ */
16
+ export interface MarkLineStyle {
17
+ color?: string;
18
+ width?: number;
19
+ type?: 'solid' | 'dashed' | 'dotted';
20
+ opacity?: number;
21
+ }
22
+
23
+ /**
24
+ * 标记区域样式
25
+ */
26
+ export interface MarkAreaStyle {
27
+ color?: string;
28
+ opacity?: number;
29
+ borderColor?: string;
30
+ borderWidth?: number;
31
+ borderType?: 'solid' | 'dashed' | 'dotted';
32
+ }
33
+
34
+ /**
35
+ * 标记线数据点
36
+ */
37
+ export interface MarkLineDataPoint {
38
+ /** X轴值 */
39
+ xAxis?: string | number;
40
+ /** Y轴值 */
41
+ yAxis?: string | number;
42
+ /** 数据点名称 */
43
+ name?: string;
44
+ }
45
+
46
+ /**
47
+ * 标记线配置
48
+ */
49
+ export interface MarkLineConfig {
50
+ /** 标记线数据 */
51
+ data: MarkLineDataPoint[];
52
+ /** 标记线样式 */
53
+ lineStyle?: MarkLineStyle;
54
+ /** 标签配置 */
55
+ label?: {
56
+ show?: boolean;
57
+ position?: 'start' | 'middle' | 'end' | 'insideStartBottom' | 'insideStartTop';
58
+ formatter?: string | ((value: any) => string);
59
+ color?: string;
60
+ fontSize?: number;
61
+ };
62
+ /** 是否启用动画 */
63
+ animation?: boolean;
64
+ /** 动画时长 */
65
+ animationDuration?: number;
66
+ /** 精度 */
67
+ precision?: number;
68
+ }
69
+
70
+ /**
71
+ * 标记区域配置
72
+ */
73
+ export interface MarkAreaConfig {
74
+ /** 区域数据,每个区域由两个点定义 */
75
+ data: [MarkLineDataPoint, MarkLineDataPoint][];
76
+ /** 区域样式 */
77
+ style?: MarkAreaStyle;
78
+ /** 标签配置 */
79
+ label?: {
80
+ show?: boolean;
81
+ position?: 'top' | 'bottom' | 'left' | 'right' | 'inside';
82
+ formatter?: string | ((value: any) => string);
83
+ color?: string;
84
+ fontSize?: number;
85
+ };
86
+ /** 是否启用动画 */
87
+ animation?: boolean;
88
+ /** 动画时长 */
89
+ animationDuration?: number;
90
+ }
91
+
92
+ /**
93
+ * 散点标注配置
94
+ */
95
+ export interface ScatterAnnotationConfig {
96
+ /** 数据点 */
97
+ data: Array<{
98
+ coord: [number | string, number];
99
+ value?: number;
100
+ name?: string;
101
+ }>;
102
+ /** 符号类型 */
103
+ symbol?: string;
104
+ /** 符号大小 */
105
+ symbolSize?: number | [number, number];
106
+ /** 样式 */
107
+ itemStyle?: Record<string, unknown>;
108
+ /** 标签 */
109
+ label?: {
110
+ show?: boolean;
111
+ position?: string;
112
+ formatter?: string | ((value: any) => string);
113
+ color?: string;
114
+ };
115
+ }
116
+
117
+ /**
118
+ * 标注组件Props
119
+ */
120
+ export interface AnnotationProps {
121
+ /** 标注类型 */
122
+ type: AnnotationType;
123
+ /** 序列索引 */
124
+ seriesIndex?: number;
125
+ /** 标记线配置 */
126
+ markLine?: MarkLineConfig;
127
+ /** 标记区域配置 */
128
+ markArea?: MarkAreaConfig;
129
+ /** 散点标注配置 */
130
+ scatter?: ScatterAnnotationConfig;
131
+ /** 自定义标注数据 */
132
+ customData?: unknown[];
133
+ }
134
+
135
+ /**
136
+ * 将标注配置转换为 ECharts 标注格式
137
+ */
138
+ export function convertAnnotationToMarkLine(config: MarkLineConfig): EChartsOption['series'] {
139
+ const { data, lineStyle, label, animation, animationDuration, precision = 2 } = config;
140
+
141
+ return [
142
+ {
143
+ type: 'line',
144
+ markLine: {
145
+ symbol: ['circle', 'arrow'],
146
+ silent: true,
147
+ animation: animation !== false,
148
+ animationDuration: animationDuration || 300,
149
+ precision,
150
+ lineStyle: {
151
+ color: lineStyle?.color || '#333',
152
+ width: lineStyle?.width || 2,
153
+ type: lineStyle?.type || 'dashed',
154
+ opacity: lineStyle?.opacity,
155
+ },
156
+ label: {
157
+ show: label?.show !== false,
158
+ position: label?.position || 'end',
159
+ formatter: label?.formatter,
160
+ color: label?.color || '#333',
161
+ fontSize: label?.fontSize || 12,
162
+ },
163
+ data: data.map((item) => ({
164
+ xAxis: item.xAxis,
165
+ yAxis: item.yAxis,
166
+ name: item.name,
167
+ })),
168
+ },
169
+ },
170
+ ];
171
+ }
172
+
173
+ /**
174
+ * 将标注区域配置转换为 ECharts 格式
175
+ */
176
+ export function convertAnnotationToMarkArea(config: MarkAreaConfig): EChartsOption['series'] {
177
+ const { data, style, label, animation, animationDuration } = config;
178
+
179
+ return [
180
+ {
181
+ type: 'bar',
182
+ markArea: {
183
+ silent: true,
184
+ animation: animation !== false,
185
+ animationDuration: animationDuration || 300,
186
+ itemStyle: {
187
+ color: style?.color || 'rgba(24, 144, 255, 0.1)',
188
+ opacity: style?.opacity || 0.3,
189
+ borderColor: style?.borderColor || 'transparent',
190
+ borderWidth: style?.borderWidth || 0,
191
+ borderType: style?.borderType,
192
+ },
193
+ label: {
194
+ show: label?.show !== false,
195
+ position: label?.position || 'inside',
196
+ formatter: label?.formatter,
197
+ color: label?.color || '#333',
198
+ fontSize: label?.fontSize || 12,
199
+ },
200
+ data: data.map(([start, end]) => [
201
+ { xAxis: start.xAxis, yAxis: start.yAxis },
202
+ { xAxis: end.xAxis, yAxis: end.yAxis },
203
+ ]),
204
+ },
205
+ },
206
+ ];
207
+ }
208
+
209
+ /**
210
+ * 将散点标注配置转换为 ECharts 格式
211
+ */
212
+ export function convertAnnotationToScatter(config: ScatterAnnotationConfig): EChartsOption['series'] {
213
+ const { data, symbol, symbolSize, itemStyle, label } = config;
214
+
215
+ return [
216
+ {
217
+ type: 'scatter',
218
+ markPoint: {
219
+ symbol,
220
+ symbolSize,
221
+ itemStyle,
222
+ label: {
223
+ show: label?.show !== false,
224
+ position: label?.position || 'top',
225
+ formatter: label?.formatter,
226
+ color: label?.color || '#333',
227
+ },
228
+ data,
229
+ },
230
+ },
231
+ ];
232
+ }
233
+
234
+ /**
235
+ * 标注 Hook
236
+ * 用于生成标注配置
237
+ */
238
+ export function useAnnotation(props: AnnotationProps): EChartsOption {
239
+ const { type, markLine, markArea, scatter } = props;
240
+
241
+ return useMemo(() => {
242
+ const series: EChartsOption['series'] = [];
243
+
244
+ if (type === 'line' && markLine) {
245
+ series.push(...convertAnnotationToMarkLine(markLine));
246
+ }
247
+
248
+ if (type === 'area' && markArea) {
249
+ series.push(...convertAnnotationToMarkArea(markArea));
250
+ }
251
+
252
+ if (type === 'scatter' && scatter) {
253
+ series.push(...convertAnnotationToScatter(scatter));
254
+ }
255
+
256
+ return { series };
257
+ }, [type, markLine, markArea, scatter]);
258
+ }
259
+
260
+ /**
261
+ * 预定义标注样式
262
+ */
263
+ export const AnnotationPresets = {
264
+ /** 平均线 */
265
+ averageLine: (color = '#1890ff'): MarkLineConfig => ({
266
+ data: [{ type: 'average', name: '平均值' }],
267
+ lineStyle: { color, type: 'dashed', width: 2 },
268
+ label: { show: true, position: 'end', color },
269
+ }),
270
+
271
+ /** 最大值线 */
272
+ maxLine: (color = '#f5222d'): MarkLineConfig => ({
273
+ data: [{ type: 'max', name: '最大值' }],
274
+ lineStyle: { color, type: 'dashed', width: 2 },
275
+ label: { show: true, position: 'end', color },
276
+ }),
277
+
278
+ /** 最小值线 */
279
+ minLine: (color = '#52c41a'): MarkLineConfig => ({
280
+ data: [{ type: 'min', name: '最小值' }],
281
+ lineStyle: { color, type: 'dashed', width: 2 },
282
+ label: { show: true, position: 'end', color },
283
+ }),
284
+
285
+ /** 警戒线 */
286
+ thresholdLine: (value: number, color = '#faad14'): MarkLineConfig => ({
287
+ data: [{ yAxis: value, name: '警戒线' }],
288
+ lineStyle: { color, type: 'solid', width: 2 },
289
+ label: { show: true, position: 'start', color },
290
+ }),
291
+
292
+ /** 目标区域 */
293
+ targetArea: (min: number, max: number, color = 'rgba(82, 196, 26, 0.1)'): MarkAreaConfig => ({
294
+ data: [
295
+ [{ yAxis: min }, { yAxis: max }],
296
+ ],
297
+ style: { color, opacity: 0.3 },
298
+ label: { show: true, position: 'inside', color: '#52c41a' },
299
+ }),
300
+
301
+ /** 预警区域 */
302
+ warningArea: (min: number, max: number, color = 'rgba(250, 173, 20, 0.1)'): MarkAreaConfig => ({
303
+ data: [
304
+ [{ yAxis: min }, { yAxis: max }],
305
+ ],
306
+ style: { color, opacity: 0.3 },
307
+ label: { show: true, position: 'inside', color: '#faad14' },
308
+ }),
309
+ };
310
+
311
+ /**
312
+ * 创建组合标注
313
+ */
314
+ export function createCompositeAnnotation(
315
+ annotations: Array<{
316
+ type: AnnotationType;
317
+ markLine?: MarkLineConfig;
318
+ markArea?: MarkAreaConfig;
319
+ scatter?: ScatterAnnotationConfig;
320
+ }>
321
+ ): EChartsOption {
322
+ const allSeries: EChartsOption['series'] = [];
323
+
324
+ annotations.forEach((annotation) => {
325
+ if (annotation.type === 'line' && annotation.markLine) {
326
+ allSeries.push(...convertAnnotationToMarkLine(annotation.markLine));
327
+ }
328
+ if (annotation.type === 'area' && annotation.markArea) {
329
+ allSeries.push(...convertAnnotationToMarkArea(annotation.markArea));
330
+ }
331
+ if (annotation.type === 'scatter' && annotation.scatter) {
332
+ allSeries.push(...convertAnnotationToScatter(annotation.scatter));
333
+ }
334
+ });
335
+
336
+ return { series: allSeries };
337
+ }
338
+
339
+ export default {
340
+ useAnnotation,
341
+ convertAnnotationToMarkLine,
342
+ convertAnnotationToMarkArea,
343
+ convertAnnotationToScatter,
344
+ AnnotationPresets,
345
+ createCompositeAnnotation,
346
+ };