feat: 现在创建配置文件。首先让我检查现有的主题配置,以保持样式一致性:
This commit is contained in:
305
src/components/StockChart/utils/eventMarkerUtils.ts
Normal file
305
src/components/StockChart/utils/eventMarkerUtils.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* 事件标记工具函数
|
||||
*
|
||||
* 用于在 K 线图上创建、管理事件标记(Overlay)
|
||||
*/
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import type { OverlayCreate } from 'klinecharts';
|
||||
import type { EventMarker, KLineDataPoint } from '../types';
|
||||
import { EVENT_MARKER_CONFIG } from '../config';
|
||||
import { findClosestDataPoint } from './dataAdapter';
|
||||
import { logger } from '@utils/logger';
|
||||
|
||||
/**
|
||||
* 创建事件标记 Overlay(KLineChart 10.0 格式)
|
||||
*
|
||||
* @param marker 事件标记配置
|
||||
* @param data K线数据(用于定位标记位置)
|
||||
* @returns OverlayCreate | null Overlay 配置对象
|
||||
*/
|
||||
export const createEventMarkerOverlay = (
|
||||
marker: EventMarker,
|
||||
data: KLineDataPoint[]
|
||||
): OverlayCreate | null => {
|
||||
try {
|
||||
// 查找最接近事件时间的数据点
|
||||
const closestPoint = findClosestDataPoint(data, marker.timestamp);
|
||||
|
||||
if (!closestPoint) {
|
||||
logger.warn('eventMarkerUtils', 'createEventMarkerOverlay', '未找到匹配的数据点', {
|
||||
markerId: marker.id,
|
||||
timestamp: marker.timestamp,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据位置计算 Y 坐标
|
||||
const yValue = calculateMarkerYPosition(closestPoint, marker.position);
|
||||
|
||||
// 创建 Overlay 配置(KLineChart 10.0 规范)
|
||||
const overlay: OverlayCreate = {
|
||||
name: 'simpleAnnotation', // 使用内置的简单标注类型
|
||||
id: marker.id,
|
||||
points: [
|
||||
{
|
||||
timestamp: closestPoint.timestamp,
|
||||
value: yValue,
|
||||
},
|
||||
],
|
||||
styles: {
|
||||
point: {
|
||||
color: marker.color,
|
||||
borderColor: marker.color,
|
||||
borderSize: 2,
|
||||
radius: EVENT_MARKER_CONFIG.size.point,
|
||||
},
|
||||
text: {
|
||||
color: EVENT_MARKER_CONFIG.text.color,
|
||||
size: EVENT_MARKER_CONFIG.text.fontSize,
|
||||
family: EVENT_MARKER_CONFIG.text.fontFamily,
|
||||
weight: 'bold',
|
||||
},
|
||||
rect: {
|
||||
style: 'fill',
|
||||
color: marker.color,
|
||||
borderRadius: EVENT_MARKER_CONFIG.text.borderRadius,
|
||||
paddingLeft: EVENT_MARKER_CONFIG.text.padding,
|
||||
paddingRight: EVENT_MARKER_CONFIG.text.padding,
|
||||
paddingTop: EVENT_MARKER_CONFIG.text.padding,
|
||||
paddingBottom: EVENT_MARKER_CONFIG.text.padding,
|
||||
},
|
||||
},
|
||||
// 标记文本内容
|
||||
extendData: {
|
||||
label: marker.label,
|
||||
icon: marker.icon,
|
||||
},
|
||||
};
|
||||
|
||||
logger.debug('eventMarkerUtils', 'createEventMarkerOverlay', '创建事件标记', {
|
||||
markerId: marker.id,
|
||||
timestamp: closestPoint.timestamp,
|
||||
label: marker.label,
|
||||
});
|
||||
|
||||
return overlay;
|
||||
} catch (error) {
|
||||
logger.error('eventMarkerUtils', 'createEventMarkerOverlay', error as Error, {
|
||||
markerId: marker.id,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算标记的 Y 轴位置
|
||||
*
|
||||
* @param dataPoint K线数据点
|
||||
* @param position 标记位置(top/middle/bottom)
|
||||
* @returns number Y轴数值
|
||||
*/
|
||||
const calculateMarkerYPosition = (
|
||||
dataPoint: KLineDataPoint,
|
||||
position: 'top' | 'middle' | 'bottom'
|
||||
): number => {
|
||||
switch (position) {
|
||||
case 'top':
|
||||
return dataPoint.high * 1.02; // 在最高价上方 2%
|
||||
case 'bottom':
|
||||
return dataPoint.low * 0.98; // 在最低价下方 2%
|
||||
case 'middle':
|
||||
default:
|
||||
return (dataPoint.high + dataPoint.low) / 2; // 中间位置
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 从事件时间创建标记配置
|
||||
*
|
||||
* @param eventTime 事件时间字符串(ISO 格式)
|
||||
* @param label 标记标签(可选,默认为"事件发生")
|
||||
* @param color 标记颜色(可选,使用默认颜色)
|
||||
* @returns EventMarker 事件标记配置
|
||||
*/
|
||||
export const createEventMarkerFromTime = (
|
||||
eventTime: string,
|
||||
label: string = '事件发生',
|
||||
color: string = EVENT_MARKER_CONFIG.defaultColor
|
||||
): EventMarker => {
|
||||
const timestamp = dayjs(eventTime).valueOf();
|
||||
|
||||
return {
|
||||
id: `event-${timestamp}`,
|
||||
timestamp,
|
||||
label,
|
||||
position: EVENT_MARKER_CONFIG.defaultPosition,
|
||||
color,
|
||||
icon: EVENT_MARKER_CONFIG.defaultIcon,
|
||||
draggable: false,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量创建事件标记 Overlays
|
||||
*
|
||||
* @param markers 事件标记配置数组
|
||||
* @param data K线数据
|
||||
* @returns OverlayCreate[] Overlay 配置数组
|
||||
*/
|
||||
export const createEventMarkerOverlays = (
|
||||
markers: EventMarker[],
|
||||
data: KLineDataPoint[]
|
||||
): OverlayCreate[] => {
|
||||
if (!markers || markers.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const overlays: OverlayCreate[] = [];
|
||||
|
||||
markers.forEach((marker) => {
|
||||
const overlay = createEventMarkerOverlay(marker, data);
|
||||
if (overlay) {
|
||||
overlays.push(overlay);
|
||||
}
|
||||
});
|
||||
|
||||
logger.debug('eventMarkerUtils', 'createEventMarkerOverlays', '批量创建事件标记', {
|
||||
totalMarkers: markers.length,
|
||||
createdOverlays: overlays.length,
|
||||
});
|
||||
|
||||
return overlays;
|
||||
};
|
||||
|
||||
/**
|
||||
* 移除事件标记
|
||||
*
|
||||
* @param chart KLineChart 实例
|
||||
* @param markerId 标记 ID
|
||||
*/
|
||||
export const removeEventMarker = (chart: any, markerId: string): void => {
|
||||
try {
|
||||
chart.removeOverlay(markerId);
|
||||
logger.debug('eventMarkerUtils', 'removeEventMarker', '移除事件标记', { markerId });
|
||||
} catch (error) {
|
||||
logger.error('eventMarkerUtils', 'removeEventMarker', error as Error, { markerId });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 移除所有事件标记
|
||||
*
|
||||
* @param chart KLineChart 实例
|
||||
*/
|
||||
export const removeAllEventMarkers = (chart: any): void => {
|
||||
try {
|
||||
// KLineChart 10.0 API: removeOverlay() 不传参数时移除所有 overlays
|
||||
chart.removeOverlay();
|
||||
logger.debug('eventMarkerUtils', 'removeAllEventMarkers', '移除所有事件标记');
|
||||
} catch (error) {
|
||||
logger.error('eventMarkerUtils', 'removeAllEventMarkers', error as Error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新事件标记
|
||||
*
|
||||
* @param chart KLineChart 实例
|
||||
* @param markerId 标记 ID
|
||||
* @param updates 更新内容(部分字段)
|
||||
*/
|
||||
export const updateEventMarker = (
|
||||
chart: any,
|
||||
markerId: string,
|
||||
updates: Partial<EventMarker>
|
||||
): void => {
|
||||
try {
|
||||
// 先移除旧标记
|
||||
removeEventMarker(chart, markerId);
|
||||
|
||||
// 重新创建标记(KLineChart 10.0 不支持直接更新 overlay)
|
||||
// 注意:需要在调用方重新创建并添加 overlay
|
||||
|
||||
logger.debug('eventMarkerUtils', 'updateEventMarker', '更新事件标记', {
|
||||
markerId,
|
||||
updates,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('eventMarkerUtils', 'updateEventMarker', error as Error, { markerId });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 高亮事件标记(改变样式)
|
||||
*
|
||||
* @param chart KLineChart 实例
|
||||
* @param markerId 标记 ID
|
||||
* @param highlight 是否高亮
|
||||
*/
|
||||
export const highlightEventMarker = (
|
||||
chart: any,
|
||||
markerId: string,
|
||||
highlight: boolean
|
||||
): void => {
|
||||
try {
|
||||
// KLineChart 10.0: 通过 overrideOverlay 修改样式
|
||||
chart.overrideOverlay({
|
||||
id: markerId,
|
||||
styles: {
|
||||
point: {
|
||||
activeRadius: highlight ? 10 : EVENT_MARKER_CONFIG.size.point,
|
||||
activeBorderSize: highlight ? 3 : 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
logger.debug('eventMarkerUtils', 'highlightEventMarker', '高亮事件标记', {
|
||||
markerId,
|
||||
highlight,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('eventMarkerUtils', 'highlightEventMarker', error as Error, { markerId });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化事件标记标签
|
||||
*
|
||||
* @param eventTitle 事件标题
|
||||
* @param maxLength 最大长度(默认 10)
|
||||
* @returns string 格式化后的标签
|
||||
*/
|
||||
export const formatEventMarkerLabel = (eventTitle: string, maxLength: number = 10): string => {
|
||||
if (!eventTitle) {
|
||||
return '事件';
|
||||
}
|
||||
|
||||
if (eventTitle.length <= maxLength) {
|
||||
return eventTitle;
|
||||
}
|
||||
|
||||
return `${eventTitle.substring(0, maxLength)}...`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断事件时间是否在数据范围内
|
||||
*
|
||||
* @param eventTime 事件时间戳
|
||||
* @param data K线数据
|
||||
* @returns boolean 是否在范围内
|
||||
*/
|
||||
export const isEventTimeInDataRange = (
|
||||
eventTime: number,
|
||||
data: KLineDataPoint[]
|
||||
): boolean => {
|
||||
if (!data || data.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const timestamps = data.map((item) => item.timestamp);
|
||||
const minTime = Math.min(...timestamps);
|
||||
const maxTime = Math.max(...timestamps);
|
||||
|
||||
return eventTime >= minTime && eventTime <= maxTime;
|
||||
};
|
||||
Reference in New Issue
Block a user