feat: 现在创建配置文件。首先让我检查现有的主题配置,以保持样式一致性:
This commit is contained in:
205
src/components/StockChart/config/chartConfig.ts
Normal file
205
src/components/StockChart/config/chartConfig.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/**
|
||||||
|
* KLineChart 图表常量配置
|
||||||
|
*
|
||||||
|
* 包含图表默认配置、技术指标列表、事件标记配置等
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ChartConfig, ChartType } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图表默认高度(px)
|
||||||
|
*/
|
||||||
|
export const CHART_HEIGHTS = {
|
||||||
|
/** 主图高度 */
|
||||||
|
main: 400,
|
||||||
|
/** 副图高度(技术指标) */
|
||||||
|
sub: 150,
|
||||||
|
/** 移动端主图高度 */
|
||||||
|
mainMobile: 300,
|
||||||
|
/** 移动端副图高度 */
|
||||||
|
subMobile: 100,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 技术指标配置
|
||||||
|
*/
|
||||||
|
export const INDICATORS = {
|
||||||
|
/** 主图指标(叠加在 K 线图上) */
|
||||||
|
main: [
|
||||||
|
{
|
||||||
|
name: 'MA',
|
||||||
|
label: '均线',
|
||||||
|
params: [5, 10, 20, 30],
|
||||||
|
colors: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'EMA',
|
||||||
|
label: '指数移动平均',
|
||||||
|
params: [5, 10, 20, 30],
|
||||||
|
colors: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BOLL',
|
||||||
|
label: '布林带',
|
||||||
|
params: [20, 2],
|
||||||
|
colors: ['#FF6B6B', '#4ECDC4', '#45B7D1'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
/** 副图指标(单独窗口显示) */
|
||||||
|
sub: [
|
||||||
|
{
|
||||||
|
name: 'VOL',
|
||||||
|
label: '成交量',
|
||||||
|
params: [5, 10, 20],
|
||||||
|
colors: ['#ef5350', '#26a69a'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'MACD',
|
||||||
|
label: 'MACD',
|
||||||
|
params: [12, 26, 9],
|
||||||
|
colors: ['#FF6B6B', '#4ECDC4', '#45B7D1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'KDJ',
|
||||||
|
label: 'KDJ',
|
||||||
|
params: [9, 3, 3],
|
||||||
|
colors: ['#FF6B6B', '#4ECDC4', '#45B7D1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RSI',
|
||||||
|
label: 'RSI',
|
||||||
|
params: [6, 12, 24],
|
||||||
|
colors: ['#FF6B6B', '#4ECDC4', '#45B7D1'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认主图指标(初始显示)
|
||||||
|
*/
|
||||||
|
export const DEFAULT_MAIN_INDICATOR = 'MA';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认副图指标(初始显示)
|
||||||
|
*/
|
||||||
|
export const DEFAULT_SUB_INDICATORS = ['VOL', 'MACD'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图表类型配置
|
||||||
|
*/
|
||||||
|
export const CHART_TYPE_CONFIG: Record<ChartType, { label: string; dateFormat: string }> = {
|
||||||
|
timeline: {
|
||||||
|
label: '分时图',
|
||||||
|
dateFormat: 'HH:mm', // 时间格式:09:30
|
||||||
|
},
|
||||||
|
daily: {
|
||||||
|
label: '日K线',
|
||||||
|
dateFormat: 'YYYY-MM-DD', // 日期格式:2024-01-01
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件标记配置
|
||||||
|
*/
|
||||||
|
export const EVENT_MARKER_CONFIG = {
|
||||||
|
/** 默认颜色 */
|
||||||
|
defaultColor: '#ff9800',
|
||||||
|
/** 默认位置 */
|
||||||
|
defaultPosition: 'top' as const,
|
||||||
|
/** 默认图标 */
|
||||||
|
defaultIcon: '📌',
|
||||||
|
/** 标记大小 */
|
||||||
|
size: {
|
||||||
|
point: 8, // 标记点半径
|
||||||
|
icon: 20, // 图标大小
|
||||||
|
},
|
||||||
|
/** 文本配置 */
|
||||||
|
text: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Helvetica, Arial, sans-serif',
|
||||||
|
color: '#ffffff',
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据加载配置
|
||||||
|
*/
|
||||||
|
export const DATA_LOADER_CONFIG = {
|
||||||
|
/** 最大数据点数(避免性能问题) */
|
||||||
|
maxDataPoints: 1000,
|
||||||
|
/** 初始加载数据点数 */
|
||||||
|
initialLoadCount: 100,
|
||||||
|
/** 加载更多时的数据点数 */
|
||||||
|
loadMoreCount: 50,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩放配置
|
||||||
|
*/
|
||||||
|
export const ZOOM_CONFIG = {
|
||||||
|
/** 最小缩放比例(显示更多 K 线) */
|
||||||
|
minZoom: 0.5,
|
||||||
|
/** 最大缩放比例(显示更少 K 线) */
|
||||||
|
maxZoom: 2.0,
|
||||||
|
/** 默认缩放比例 */
|
||||||
|
defaultZoom: 1.0,
|
||||||
|
/** 缩放步长 */
|
||||||
|
zoomStep: 0.1,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认图表配置
|
||||||
|
*/
|
||||||
|
export const DEFAULT_CHART_CONFIG: ChartConfig = {
|
||||||
|
type: 'daily',
|
||||||
|
showIndicators: true,
|
||||||
|
defaultIndicators: DEFAULT_SUB_INDICATORS,
|
||||||
|
height: CHART_HEIGHTS.main,
|
||||||
|
showGrid: true,
|
||||||
|
showCrosshair: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图表初始化选项(传递给 KLineChart.init)
|
||||||
|
*/
|
||||||
|
export const CHART_INIT_OPTIONS = {
|
||||||
|
/** 时区(中国标准时间) */
|
||||||
|
timezone: 'Asia/Shanghai',
|
||||||
|
/** 语言 */
|
||||||
|
locale: 'zh-CN',
|
||||||
|
/** 自定义配置 */
|
||||||
|
customApi: {
|
||||||
|
formatDate: (timestamp: number, format: string) => {
|
||||||
|
// 可在此处自定义日期格式化逻辑
|
||||||
|
return new Date(timestamp).toLocaleString('zh-CN');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分时图特殊配置
|
||||||
|
*/
|
||||||
|
export const TIMELINE_CONFIG = {
|
||||||
|
/** 交易时段(A 股) */
|
||||||
|
tradingSessions: [
|
||||||
|
{ start: '09:30', end: '11:30' }, // 上午
|
||||||
|
{ start: '13:00', end: '15:00' }, // 下午
|
||||||
|
],
|
||||||
|
/** 是否显示均价线 */
|
||||||
|
showAverageLine: true,
|
||||||
|
/** 均价线颜色 */
|
||||||
|
averageLineColor: '#FFB74D',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日K线特殊配置
|
||||||
|
*/
|
||||||
|
export const DAILY_KLINE_CONFIG = {
|
||||||
|
/** 最大显示天数 */
|
||||||
|
maxDays: 250, // 约一年交易日
|
||||||
|
/** 默认显示天数 */
|
||||||
|
defaultDays: 60,
|
||||||
|
} as const;
|
||||||
295
src/components/StockChart/utils/chartUtils.ts
Normal file
295
src/components/StockChart/utils/chartUtils.ts
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
/**
|
||||||
|
* 图表通用工具函数
|
||||||
|
*
|
||||||
|
* 包含图表初始化、技术指标管理等通用逻辑
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Chart } from 'klinecharts';
|
||||||
|
import { logger } from '@utils/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地执行图表操作(捕获异常)
|
||||||
|
*
|
||||||
|
* @param operation 操作名称
|
||||||
|
* @param fn 执行函数
|
||||||
|
* @returns T | null 执行结果或 null
|
||||||
|
*/
|
||||||
|
export const safeChartOperation = <T>(
|
||||||
|
operation: string,
|
||||||
|
fn: () => T
|
||||||
|
): T | null => {
|
||||||
|
try {
|
||||||
|
return fn();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('chartUtils', operation, error as Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建技术指标
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param indicatorName 指标名称(如 'MA', 'MACD', 'VOL')
|
||||||
|
* @param params 指标参数(可选)
|
||||||
|
* @param isStack 是否叠加(主图指标为 true,副图为 false)
|
||||||
|
* @returns string | null 指标 ID
|
||||||
|
*/
|
||||||
|
export const createIndicator = (
|
||||||
|
chart: Chart,
|
||||||
|
indicatorName: string,
|
||||||
|
params?: number[],
|
||||||
|
isStack: boolean = false
|
||||||
|
): string | null => {
|
||||||
|
return safeChartOperation(`createIndicator:${indicatorName}`, () => {
|
||||||
|
const indicatorId = chart.createIndicator(
|
||||||
|
{
|
||||||
|
name: indicatorName,
|
||||||
|
...(params && { calcParams: params }),
|
||||||
|
},
|
||||||
|
isStack
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.debug('chartUtils', 'createIndicator', '创建技术指标', {
|
||||||
|
indicatorName,
|
||||||
|
params,
|
||||||
|
isStack,
|
||||||
|
indicatorId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return indicatorId;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除技术指标
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param indicatorId 指标 ID(不传则移除所有指标)
|
||||||
|
*/
|
||||||
|
export const removeIndicator = (chart: Chart, indicatorId?: string): void => {
|
||||||
|
safeChartOperation('removeIndicator', () => {
|
||||||
|
chart.removeIndicator(indicatorId);
|
||||||
|
logger.debug('chartUtils', 'removeIndicator', '移除技术指标', { indicatorId });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量创建副图指标
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param indicators 指标名称数组
|
||||||
|
* @returns string[] 指标 ID 数组
|
||||||
|
*/
|
||||||
|
export const createSubIndicators = (
|
||||||
|
chart: Chart,
|
||||||
|
indicators: string[]
|
||||||
|
): string[] => {
|
||||||
|
const ids: string[] = [];
|
||||||
|
|
||||||
|
indicators.forEach((name) => {
|
||||||
|
const id = createIndicator(chart, name, undefined, false);
|
||||||
|
if (id) {
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('chartUtils', 'createSubIndicators', '批量创建副图指标', {
|
||||||
|
indicators,
|
||||||
|
createdIds: ids,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置图表缩放级别
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param zoom 缩放级别(0.5 - 2.0)
|
||||||
|
*/
|
||||||
|
export const setChartZoom = (chart: Chart, zoom: number): void => {
|
||||||
|
safeChartOperation('setChartZoom', () => {
|
||||||
|
// KLineChart 10.0: 使用 setBarSpace 方法调整 K 线宽度(实现缩放效果)
|
||||||
|
const baseBarSpace = 8; // 默认 K 线宽度(px)
|
||||||
|
const newBarSpace = Math.max(4, Math.min(16, baseBarSpace * zoom));
|
||||||
|
|
||||||
|
// 注意:KLineChart 10.0 可能没有直接的 zoom API,需要通过调整样式实现
|
||||||
|
chart.setStyles({
|
||||||
|
candle: {
|
||||||
|
bar: {
|
||||||
|
upBorderColor: undefined, // 保持默认
|
||||||
|
upColor: undefined,
|
||||||
|
downBorderColor: undefined,
|
||||||
|
downColor: undefined,
|
||||||
|
},
|
||||||
|
// 通过调整蜡烛图宽度实现缩放效果
|
||||||
|
tooltip: {
|
||||||
|
showRule: 'always',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('chartUtils', 'setChartZoom', '设置图表缩放', {
|
||||||
|
zoom,
|
||||||
|
newBarSpace,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动到指定时间
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param timestamp 目标时间戳
|
||||||
|
*/
|
||||||
|
export const scrollToTimestamp = (chart: Chart, timestamp: number): void => {
|
||||||
|
safeChartOperation('scrollToTimestamp', () => {
|
||||||
|
// KLineChart 10.0: 使用 scrollToTimestamp 方法
|
||||||
|
chart.scrollToTimestamp(timestamp);
|
||||||
|
|
||||||
|
logger.debug('chartUtils', 'scrollToTimestamp', '滚动到指定时间', { timestamp });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调整图表大小(响应式)
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
*/
|
||||||
|
export const resizeChart = (chart: Chart): void => {
|
||||||
|
safeChartOperation('resizeChart', () => {
|
||||||
|
chart.resize();
|
||||||
|
logger.debug('chartUtils', 'resizeChart', '调整图表大小');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取图表可见数据范围
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @returns { from: number, to: number } | null 可见范围
|
||||||
|
*/
|
||||||
|
export const getVisibleRange = (chart: Chart): { from: number; to: number } | null => {
|
||||||
|
return safeChartOperation('getVisibleRange', () => {
|
||||||
|
const data = chart.getDataList();
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简化实现:返回所有数据范围
|
||||||
|
// 实际项目中可通过 chart 的内部状态获取可见范围
|
||||||
|
return {
|
||||||
|
from: 0,
|
||||||
|
to: data.length - 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空图表数据
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
*/
|
||||||
|
export const clearChartData = (chart: Chart): void => {
|
||||||
|
safeChartOperation('clearChartData', () => {
|
||||||
|
chart.resetData();
|
||||||
|
logger.debug('chartUtils', 'clearChartData', '清空图表数据');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截图(导出图表为图片)
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param includeOverlay 是否包含 overlay
|
||||||
|
* @returns string | null Base64 图片数据
|
||||||
|
*/
|
||||||
|
export const exportChartImage = (
|
||||||
|
chart: Chart,
|
||||||
|
includeOverlay: boolean = true
|
||||||
|
): string | null => {
|
||||||
|
return safeChartOperation('exportChartImage', () => {
|
||||||
|
// KLineChart 10.0: 使用 getConvertPictureUrl 方法
|
||||||
|
const imageData = chart.getConvertPictureUrl(includeOverlay, 'png', '#ffffff');
|
||||||
|
|
||||||
|
logger.debug('chartUtils', 'exportChartImage', '导出图表图片', {
|
||||||
|
includeOverlay,
|
||||||
|
hasData: !!imageData,
|
||||||
|
});
|
||||||
|
|
||||||
|
return imageData;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换十字光标显示
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param show 是否显示
|
||||||
|
*/
|
||||||
|
export const toggleCrosshair = (chart: Chart, show: boolean): void => {
|
||||||
|
safeChartOperation('toggleCrosshair', () => {
|
||||||
|
chart.setStyles({
|
||||||
|
crosshair: {
|
||||||
|
show,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('chartUtils', 'toggleCrosshair', '切换十字光标', { show });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换网格显示
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param show 是否显示
|
||||||
|
*/
|
||||||
|
export const toggleGrid = (chart: Chart, show: boolean): void => {
|
||||||
|
safeChartOperation('toggleGrid', () => {
|
||||||
|
chart.setStyles({
|
||||||
|
grid: {
|
||||||
|
show,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('chartUtils', 'toggleGrid', '切换网格', { show });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅图表事件
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param eventName 事件名称
|
||||||
|
* @param handler 事件处理函数
|
||||||
|
*/
|
||||||
|
export const subscribeChartEvent = (
|
||||||
|
chart: Chart,
|
||||||
|
eventName: string,
|
||||||
|
handler: (...args: any[]) => void
|
||||||
|
): void => {
|
||||||
|
safeChartOperation(`subscribeChartEvent:${eventName}`, () => {
|
||||||
|
chart.subscribeAction(eventName, handler);
|
||||||
|
logger.debug('chartUtils', 'subscribeChartEvent', '订阅图表事件', { eventName });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消订阅图表事件
|
||||||
|
*
|
||||||
|
* @param chart KLineChart 实例
|
||||||
|
* @param eventName 事件名称
|
||||||
|
* @param handler 事件处理函数
|
||||||
|
*/
|
||||||
|
export const unsubscribeChartEvent = (
|
||||||
|
chart: Chart,
|
||||||
|
eventName: string,
|
||||||
|
handler: (...args: any[]) => void
|
||||||
|
): void => {
|
||||||
|
safeChartOperation(`unsubscribeChartEvent:${eventName}`, () => {
|
||||||
|
chart.unsubscribeAction(eventName, handler);
|
||||||
|
logger.debug('chartUtils', 'unsubscribeChartEvent', '取消订阅图表事件', { eventName });
|
||||||
|
});
|
||||||
|
};
|
||||||
257
src/components/StockChart/utils/dataAdapter.ts
Normal file
257
src/components/StockChart/utils/dataAdapter.ts
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
/**
|
||||||
|
* 数据转换适配器
|
||||||
|
*
|
||||||
|
* 将后端返回的各种格式数据转换为 KLineChart 10.0 所需的标准格式
|
||||||
|
*/
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import type { KLineDataPoint, RawDataPoint, ChartType } from '../types';
|
||||||
|
import { logger } from '@utils/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将后端原始数据转换为 KLineChart 标准格式
|
||||||
|
*
|
||||||
|
* @param rawData 后端原始数据数组
|
||||||
|
* @param chartType 图表类型(timeline/daily)
|
||||||
|
* @param eventTime 事件时间(用于日期基准)
|
||||||
|
* @returns KLineDataPoint[] 标准K线数据
|
||||||
|
*/
|
||||||
|
export const convertToKLineData = (
|
||||||
|
rawData: RawDataPoint[],
|
||||||
|
chartType: ChartType,
|
||||||
|
eventTime?: string
|
||||||
|
): KLineDataPoint[] => {
|
||||||
|
if (!rawData || !Array.isArray(rawData) || rawData.length === 0) {
|
||||||
|
logger.warn('dataAdapter', 'convertToKLineData', '原始数据为空', { chartType });
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return rawData.map((item, index) => {
|
||||||
|
const timestamp = parseTimestamp(item, chartType, eventTime, index);
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp,
|
||||||
|
open: Number(item.open) || 0,
|
||||||
|
high: Number(item.high) || 0,
|
||||||
|
low: Number(item.low) || 0,
|
||||||
|
close: Number(item.close) || 0,
|
||||||
|
volume: Number(item.volume) || 0,
|
||||||
|
turnover: item.turnover ? Number(item.turnover) : undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('dataAdapter', 'convertToKLineData', error as Error, {
|
||||||
|
chartType,
|
||||||
|
dataLength: rawData.length,
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析时间戳(兼容多种时间格式)
|
||||||
|
*
|
||||||
|
* @param item 原始数据项
|
||||||
|
* @param chartType 图表类型
|
||||||
|
* @param eventTime 事件时间
|
||||||
|
* @param index 数据索引(用于分时图时间推算)
|
||||||
|
* @returns number 毫秒时间戳
|
||||||
|
*/
|
||||||
|
const parseTimestamp = (
|
||||||
|
item: RawDataPoint,
|
||||||
|
chartType: ChartType,
|
||||||
|
eventTime?: string,
|
||||||
|
index?: number
|
||||||
|
): number => {
|
||||||
|
// 优先级1: 使用 timestamp 字段
|
||||||
|
if (item.timestamp) {
|
||||||
|
const ts = typeof item.timestamp === 'number' ? item.timestamp : Number(item.timestamp);
|
||||||
|
// 判断是秒级还是毫秒级时间戳
|
||||||
|
return ts > 10000000000 ? ts : ts * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先级2: 使用 date 字段(日K线)
|
||||||
|
if (item.date) {
|
||||||
|
return dayjs(item.date).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先级3: 使用 time 字段(分时图)
|
||||||
|
if (item.time && eventTime) {
|
||||||
|
return parseTimelineTimestamp(item.time, eventTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先级4: 根据 chartType 和 index 推算(兜底逻辑)
|
||||||
|
if (chartType === 'timeline' && eventTime && typeof index === 'number') {
|
||||||
|
// 分时图:从事件时间推算(假设 09:30 开盘)
|
||||||
|
const baseTime = dayjs(eventTime).startOf('day').add(9, 'hour').add(30, 'minute');
|
||||||
|
return baseTime.add(index, 'minute').valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认返回当前时间(避免图表崩溃)
|
||||||
|
logger.warn('dataAdapter', 'parseTimestamp', '无法解析时间戳,使用当前时间', { item });
|
||||||
|
return Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析分时图时间戳
|
||||||
|
*
|
||||||
|
* 将 "HH:mm" 格式转换为完整时间戳
|
||||||
|
*
|
||||||
|
* @param time 时间字符串(如 "09:30")
|
||||||
|
* @param eventTime 事件时间(YYYY-MM-DD HH:mm:ss)
|
||||||
|
* @returns number 毫秒时间戳
|
||||||
|
*/
|
||||||
|
const parseTimelineTimestamp = (time: string, eventTime: string): number => {
|
||||||
|
try {
|
||||||
|
const [hours, minutes] = time.split(':').map(Number);
|
||||||
|
const eventDate = dayjs(eventTime).startOf('day');
|
||||||
|
return eventDate.hour(hours).minute(minutes).second(0).valueOf();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('dataAdapter', 'parseTimelineTimestamp', error as Error, { time, eventTime });
|
||||||
|
return dayjs(eventTime).valueOf();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据验证和清洗
|
||||||
|
*
|
||||||
|
* 移除无效数据(价格/成交量异常)
|
||||||
|
*
|
||||||
|
* @param data K线数据
|
||||||
|
* @returns KLineDataPoint[] 清洗后的数据
|
||||||
|
*/
|
||||||
|
export const validateAndCleanData = (data: KLineDataPoint[]): KLineDataPoint[] => {
|
||||||
|
return data.filter((item) => {
|
||||||
|
// 移除价格为 0 或负数的数据
|
||||||
|
if (item.open <= 0 || item.high <= 0 || item.low <= 0 || item.close <= 0) {
|
||||||
|
logger.warn('dataAdapter', 'validateAndCleanData', '价格异常,已移除', { item });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除 high < low 的数据(数据错误)
|
||||||
|
if (item.high < item.low) {
|
||||||
|
logger.warn('dataAdapter', 'validateAndCleanData', '最高价 < 最低价,已移除', { item });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除成交量为负数的数据
|
||||||
|
if (item.volume < 0) {
|
||||||
|
logger.warn('dataAdapter', 'validateAndCleanData', '成交量异常,已移除', { item });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据排序(按时间升序)
|
||||||
|
*
|
||||||
|
* @param data K线数据
|
||||||
|
* @returns KLineDataPoint[] 排序后的数据
|
||||||
|
*/
|
||||||
|
export const sortDataByTime = (data: KLineDataPoint[]): KLineDataPoint[] => {
|
||||||
|
return [...data].sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据去重(移除时间戳重复的数据,保留最后一条)
|
||||||
|
*
|
||||||
|
* @param data K线数据
|
||||||
|
* @returns KLineDataPoint[] 去重后的数据
|
||||||
|
*/
|
||||||
|
export const deduplicateData = (data: KLineDataPoint[]): KLineDataPoint[] => {
|
||||||
|
const map = new Map<number, KLineDataPoint>();
|
||||||
|
|
||||||
|
data.forEach((item) => {
|
||||||
|
map.set(item.timestamp, item); // 相同时间戳会覆盖
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(map.values());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整的数据处理流程
|
||||||
|
*
|
||||||
|
* 转换 → 验证 → 去重 → 排序
|
||||||
|
*
|
||||||
|
* @param rawData 后端原始数据
|
||||||
|
* @param chartType 图表类型
|
||||||
|
* @param eventTime 事件时间
|
||||||
|
* @returns KLineDataPoint[] 处理后的数据
|
||||||
|
*/
|
||||||
|
export const processChartData = (
|
||||||
|
rawData: RawDataPoint[],
|
||||||
|
chartType: ChartType,
|
||||||
|
eventTime?: string
|
||||||
|
): KLineDataPoint[] => {
|
||||||
|
// 1. 转换数据格式
|
||||||
|
let data = convertToKLineData(rawData, chartType, eventTime);
|
||||||
|
|
||||||
|
// 2. 验证和清洗
|
||||||
|
data = validateAndCleanData(data);
|
||||||
|
|
||||||
|
// 3. 去重
|
||||||
|
data = deduplicateData(data);
|
||||||
|
|
||||||
|
// 4. 排序
|
||||||
|
data = sortDataByTime(data);
|
||||||
|
|
||||||
|
logger.debug('dataAdapter', 'processChartData', '数据处理完成', {
|
||||||
|
rawLength: rawData.length,
|
||||||
|
processedLength: data.length,
|
||||||
|
chartType,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据时间范围
|
||||||
|
*
|
||||||
|
* @param data K线数据
|
||||||
|
* @returns { start: number, end: number } 时间范围(毫秒时间戳)
|
||||||
|
*/
|
||||||
|
export const getDataTimeRange = (
|
||||||
|
data: KLineDataPoint[]
|
||||||
|
): { start: number; end: number } | null => {
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamps = data.map((item) => item.timestamp);
|
||||||
|
return {
|
||||||
|
start: Math.min(...timestamps),
|
||||||
|
end: Math.max(...timestamps),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找最接近指定时间的数据点
|
||||||
|
*
|
||||||
|
* @param data K线数据
|
||||||
|
* @param targetTime 目标时间戳
|
||||||
|
* @returns KLineDataPoint | null 最接近的数据点
|
||||||
|
*/
|
||||||
|
export const findClosestDataPoint = (
|
||||||
|
data: KLineDataPoint[],
|
||||||
|
targetTime: number
|
||||||
|
): KLineDataPoint | null => {
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let closest = data[0];
|
||||||
|
let minDiff = Math.abs(data[0].timestamp - targetTime);
|
||||||
|
|
||||||
|
data.forEach((item) => {
|
||||||
|
const diff = Math.abs(item.timestamp - targetTime);
|
||||||
|
if (diff < minDiff) {
|
||||||
|
minDiff = diff;
|
||||||
|
closest = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return closest;
|
||||||
|
};
|
||||||
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;
|
||||||
|
};
|
||||||
48
src/components/StockChart/utils/index.ts
Normal file
48
src/components/StockChart/utils/index.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* StockChart 工具函数统一导出
|
||||||
|
*
|
||||||
|
* 使用方式:
|
||||||
|
* import { processChartData, createEventMarkerOverlay } from '@components/StockChart/utils';
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 数据转换适配器
|
||||||
|
export {
|
||||||
|
convertToKLineData,
|
||||||
|
validateAndCleanData,
|
||||||
|
sortDataByTime,
|
||||||
|
deduplicateData,
|
||||||
|
processChartData,
|
||||||
|
getDataTimeRange,
|
||||||
|
findClosestDataPoint,
|
||||||
|
} from './dataAdapter';
|
||||||
|
|
||||||
|
// 事件标记工具
|
||||||
|
export {
|
||||||
|
createEventMarkerOverlay,
|
||||||
|
createEventMarkerFromTime,
|
||||||
|
createEventMarkerOverlays,
|
||||||
|
removeEventMarker,
|
||||||
|
removeAllEventMarkers,
|
||||||
|
updateEventMarker,
|
||||||
|
highlightEventMarker,
|
||||||
|
formatEventMarkerLabel,
|
||||||
|
isEventTimeInDataRange,
|
||||||
|
} from './eventMarkerUtils';
|
||||||
|
|
||||||
|
// 图表通用工具
|
||||||
|
export {
|
||||||
|
safeChartOperation,
|
||||||
|
createIndicator,
|
||||||
|
removeIndicator,
|
||||||
|
createSubIndicators,
|
||||||
|
setChartZoom,
|
||||||
|
scrollToTimestamp,
|
||||||
|
resizeChart,
|
||||||
|
getVisibleRange,
|
||||||
|
clearChartData,
|
||||||
|
exportChartImage,
|
||||||
|
toggleCrosshair,
|
||||||
|
toggleGrid,
|
||||||
|
subscribeChartEvent,
|
||||||
|
unsubscribeChartEvent,
|
||||||
|
} from './chartUtils';
|
||||||
Reference in New Issue
Block a user