Загрузка данных
import type {
BarData,
CustomData,
HistogramData,
LineData,
SeriesDataItemTypeMap,
SingleValueData,
Time,
} from 'lightweight-charts';
import type { Ohlc } from '@core/Legend';
import { BaseSeries, BaseSeriesParams, calcCandleChange } from '@core/Series/BaseSeries';
import type { ISeries } from '@src/modules/series-strategies';
import { t } from '@src/translations';
import type { Candle, LineCandle } from '@src/types';
import { formatCompactNumber } from '@src/utils';
import { isSingleValueData } from '@src/utils/isSingleValueData';
import { removeAlphaFromHex } from '@src/utils/removeAlphaFromHex';
type SingleValueSeriesType = 'Line' | 'Histogram' | 'Baseline';
type SourceData = Partial<Candle> & Partial<LineCandle>;
type LegendData = BarData | LineData | HistogramData | CustomData | null;
type LegendValues = Partial<
Record<
keyof Ohlc,
{
value: number | string | Time;
color: string;
name: string;
}
>
>;
export abstract class SingleValueSeriesStrategy<TSeries extends SingleValueSeriesType>
extends BaseSeries<TSeries>
implements ISeries<TSeries>
{
constructor(params: BaseSeriesParams<TSeries>) {
super(params);
this.subscribeDataSource(params.dataSource);
}
public validateData(data: SourceData[]): boolean {
return Array.isArray(data) && data.every((point) => typeof point.time === 'number');
}
protected dataSourceSubscription = (dataToSet: Candle[]): void => {
if (!this.validateData(dataToSet)) {
console.error(`LightweightAPI: Invalid data format for ${this.getTypeName()} chart`);
return;
}
this.setData(this.formatData(dataToSet));
};
protected dataSourceRealtimeSubscription = (dataToSet: Candle): void => {
if (!this.validateData([dataToSet])) {
console.error(`LightweightAPI: Invalid data format for ${this.getTypeName()} chart`);
return;
}
const [formattedData] = this.formatData([dataToSet]);
if (!formattedData) {
return;
}
this.update(formattedData);
};
protected formatMainSerie(inputData: Candle[]): SeriesDataItemTypeMap<Time>[TSeries][] {
return inputData.map((point) => this.formatPoint(point));
}
protected formatLegendValues(currentBar: LegendData, prevBar: LegendData): LegendValues {
if (!isSingleValueData(currentBar)) {
return {};
}
const change = calcCandleChange(prevBar, currentBar);
if (!change) {
return {};
}
const color = this.getLegendColor(currentBar);
return {
value: {
value: formatCompactNumber(currentBar.value) ?? '',
name: '',
color,
},
absoluteChange: {
value: formatCompactNumber(change.absoluteChange ?? 0),
name: t('Change'),
color,
},
percentageChange: {
value:
change.percentageChange !== undefined
? `${formatCompactNumber(change.percentageChange)}%`
: '',
name: t('Change'),
color,
},
time: {
value: change.time,
name: t('Time'),
color,
},
};
}
protected getLegendColor(currentBar: SingleValueData<Time>): string {
if ('color' in currentBar && typeof currentBar.color === 'string') {
return removeAlphaFromHex(currentBar.color);
}
const options = this.options();
if ('color' in options && typeof options.color === 'string') {
return options.color;
}
return '';
}
private formatPoint(point: SourceData): SeriesDataItemTypeMap<Time>[TSeries] {
const time = point.time as Time;
const value = typeof point.close === 'number' ? point.close : point.value;
if (typeof value !== 'number') {
return { time } as SeriesDataItemTypeMap<Time>[TSeries];
}
return {
time,
value,
customValues: point as unknown as Record<string, unknown>,
} as SeriesDataItemTypeMap<Time>[TSeries];
}
public abstract getTypeName(): string;
}