Загрузка данных
import { BehaviorSubject } from 'rxjs';
enum DOMObjectType {
Drawing = 'Drawing',
Indicator = 'Indicator',
}
export interface IDOMObject {
id: string;
hidden: BehaviorSubject<boolean>;
zIndex: number;
type: DOMObjectType;
name: string;
delete(): void;
hide(): void;
show(): void;
lastUpdated(): void;
moveUp(): void;
moveDown(): void;
setZIndex(next: number): void;
}
export interface DOMObjectParams {
id: string;
zIndex: number;
onDelete: (id: string) => void;
moveUp: (id: string) => void;
moveDown: (id: string) => void;
}
export class DOMObject implements IDOMObject {
public readonly id: string;
public zIndex: number;
public hidden = new BehaviorSubject(false);
public moveUp: () => void;
public moveDown: () => void;
protected onDelete: (id: string) => void;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
name: string;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
type: DOMObjectType;
constructor({ id, zIndex, onDelete, moveUp, moveDown }: DOMObjectParams) {
this.id = id;
this.zIndex = zIndex;
this.onDelete = onDelete;
this.moveUp = () => moveUp(this.id);
this.moveDown = () => moveDown(this.id);
}
delete(): void {
this.onDelete(this.id);
}
hide(): void {
this.hidden.next(true);
}
show(): void {
this.hidden.next(false);
}
lastUpdated(): void {}
setZIndex(next: number): void {
this.zIndex = next;
}
}
import { IChartApi, ISeriesApi, SeriesType } from 'lightweight-charts';
import { DOMObject, DOMObjectParams } from '@core/DOMObject';
import { ISeriesDrawing } from '@core/Drawings/common';
import { SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
type IDrawing = DOMObject;
interface DrawingParams extends DOMObjectParams {
lwcChart: IChartApi;
mainSeries: SeriesStrategies;
onDelete: (id: string) => void;
construct: (chart: IChartApi, series: ISeriesApi<SeriesType>) => ISeriesDrawing;
}
export class Drawing extends DOMObject implements IDrawing {
private lwcDrawing: ISeriesDrawing;
private mainSeries: SeriesStrategies;
constructor({ lwcChart, mainSeries, id, onDelete, zIndex, moveUp, moveDown, construct }: DrawingParams) {
super({ id, zIndex, onDelete, moveUp, moveDown });
this.lwcDrawing = construct(lwcChart, mainSeries);
this.onDelete = onDelete;
this.mainSeries = mainSeries;
}
public delete() {
this.destroy();
super.delete();
}
public getLwcDrawing() {
return this.lwcDrawing;
}
public show() {
this.lwcDrawing.show();
super.show();
}
public hide() {
this.lwcDrawing.hide();
super.hide();
}
public rebind = (nextMainSeries: SeriesStrategies) => {
this.lwcDrawing.rebind(nextMainSeries);
this.mainSeries = nextMainSeries;
};
public destroy() {
this.mainSeries.detachPrimitive(this.lwcDrawing);
this.lwcDrawing.destroy();
}
}
import { IChartApi, ISeriesApi, SeriesType } from 'lightweight-charts';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { EventManager } from '@core';
import { DOMModel } from '@core/DOMModel';
import { Drawing } from '@core/Drawings';
import { ISeriesDrawing } from '@core/Drawings/common';
import { HorzLine } from '@core/Drawings/horzLine';
import { Ray } from '@core/Drawings/ray';
import { TrendLine } from '@core/Drawings/trendLine';
import { VertLine } from '@core/Drawings/vertLine';
import { VolumeProfile } from '@core/Drawings/volumeProfile';
import { Diapson } from '@src/core/Drawings/diapson';
import { Rectangle } from '@src/core/Drawings/rectangle';
import { Ruler } from '@src/core/Drawings/ruler';
import { SliderPosition } from '@src/core/Drawings/sliderPosition';
import { Traectory } from '@src/core/Drawings/traectory';
import { SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
import { ChartTypeOptions } from '@src/types';
interface DrawingsManagerParams {
eventManager: EventManager;
mainSeries$: Observable<SeriesStrategies | null>;
lwcChart: IChartApi;
chartOptions?: ChartTypeOptions;
DOM: DOMModel;
container: HTMLElement;
}
export enum DrawingsNames {
'trendLine' = 'trendLine',
'ray' = 'ray',
'horizontalLine' = 'horizontalLine',
'horizontalRay' = 'horizontalRay',
'verticalLine' = 'verticalLine',
'ruler' = 'ruler',
'sliderLong' = 'sliderLong',
'sliderShort' = 'sliderShort',
'diapsonDates' = 'diapsonDates',
'diapsonPrices' = 'diapsonPrices',
'fixedProfile' = 'fixedProfile',
'rectangle' = 'rectangle',
'traectory' = 'traectory',
}
export interface DrawingParams {
chart: IChartApi;
series: ISeriesApi<SeriesType>;
eventManager: EventManager;
container: HTMLElement;
removeSelf: () => void;
}
export interface DrawingConfig {
singleInstance?: boolean;
construct: (params: DrawingParams) => ISeriesDrawing;
}
export const drawingsMap: Record<DrawingsNames, DrawingConfig> = {
[DrawingsNames.trendLine]: {
construct: ({ chart, series }) => {
return new TrendLine(chart, series);
},
},
[DrawingsNames.ray]: {
construct: ({ chart, series }) => {
return new Ray(chart, series);
},
},
[DrawingsNames.horizontalLine]: {
construct: ({ chart, series }) => {
return new HorzLine(chart, series);
},
},
[DrawingsNames.horizontalRay]: {
construct: ({ chart, series }) => {
return new TrendLine(chart, series); // fixme TrendLine is just stub
},
},
[DrawingsNames.verticalLine]: {
construct: ({ chart, series }) => {
return new VertLine(chart, series);
},
},
[DrawingsNames.sliderLong]: {
construct: ({ chart, series, eventManager, container, removeSelf }) => {
return new SliderPosition(chart, series, {
side: 'long',
container,
formatObservable: eventManager.getChartOptionsModel(),
resetTriggers: [eventManager.getTimeframeObs(), eventManager.getInterval()],
removeSelf,
});
},
},
[DrawingsNames.sliderShort]: {
construct: ({ chart, series, eventManager, container, removeSelf }) => {
return new SliderPosition(chart, series, {
side: 'short',
container,
formatObservable: eventManager.getChartOptionsModel(),
resetTriggers: [eventManager.getTimeframeObs(), eventManager.getInterval()],
removeSelf,
});
},
},
[DrawingsNames.diapsonDates]: {
construct: ({ chart, series, container, eventManager, removeSelf }) => {
return new Diapson(chart, series, {
rangeMode: 'date',
container,
formatObservable: eventManager.getChartOptionsModel(),
removeSelf,
});
},
},
[DrawingsNames.diapsonPrices]: {
construct: ({ chart, series, container, eventManager, removeSelf }) => {
return new Diapson(chart, series, {
rangeMode: 'price',
container,
formatObservable: eventManager.getChartOptionsModel(),
removeSelf,
});
},
},
[DrawingsNames.fixedProfile]: {
construct: ({ chart, series }) => {
return new VolumeProfile(chart, series); // fixme TrendLine is just stub
},
},
[DrawingsNames.rectangle]: {
construct: ({ chart, series, eventManager, container, removeSelf }) => {
return new Rectangle(chart, series, {
container,
formatObservable: eventManager.getChartOptionsModel(),
removeSelf,
});
},
},
[DrawingsNames.ruler]: {
singleInstance: true,
construct: ({ chart, series, eventManager, removeSelf }) => {
return new Ruler(chart, series, {
formatObservable: eventManager.getChartOptionsModel(),
resetTriggers: [eventManager.getTimeframeObs(), eventManager.getInterval()],
removeSelf,
});
},
},
[DrawingsNames.traectory]: {
construct: ({ chart, series, eventManager, removeSelf, container }) => {
return new Traectory(chart, series, {
formatObservable: eventManager.getChartOptionsModel(),
container,
removeSelf,
});
},
},
};
export class DrawingsManager {
private eventManager: EventManager;
private lwcChart: IChartApi;
private chartOptions?: ChartTypeOptions;
private drawingsQty = 0; // todo: replace with hash
private DOM: DOMModel;
private container: HTMLElement;
private mainSeries: SeriesStrategies | null = null;
private subscriptions = new Subscription();
private drawings$: BehaviorSubject<Drawing[]> = new BehaviorSubject<Drawing[]>([]);
constructor({ eventManager, mainSeries$, lwcChart, chartOptions, DOM, container }: DrawingsManagerParams) {
this.DOM = DOM;
this.eventManager = eventManager;
this.lwcChart = lwcChart;
this.chartOptions = chartOptions;
this.container = container;
this.subscriptions.add(
mainSeries$.subscribe((s) => {
if (!s) return;
this.mainSeries = s;
this.drawings$.value.forEach((drawing) => drawing.rebind(s));
}),
);
}
public addDrawing(name: DrawingsNames) {
if (!this.mainSeries) {
throw new Error('[Drawings] main series is not defined');
}
const config = drawingsMap[name];
if (config.singleInstance) {
this.removeDrawingsByName(name);
}
const drawingId = `${name}${this.drawingsQty}`;
const construct = (chart: IChartApi, series: ISeriesApi<SeriesType>) =>
config.construct({
chart,
series,
eventManager: this.eventManager,
container: this.container,
removeSelf: () => this.removeDrawing(drawingId),
});
const drawing = (zIndex: number, moveUp: (id: string) => void, moveDown: (id: string) => void) =>
new Drawing({
lwcChart: this.lwcChart,
mainSeries: this.mainSeries as SeriesStrategies,
id: drawingId,
onDelete: this.removeDrawing,
zIndex,
moveDown,
moveUp,
construct,
});
const trendLine = this.DOM.setEntity<Drawing>(drawing);
this.drawingsQty++;
this.drawings$.next([...this.drawings$.value, trendLine]);
}
private removeDrawing = (id: string) => {
const drawings = this.drawings$.value;
const filtered = drawings.filter((d) => d.id !== id);
const objectToRemove = drawings.find((d) => d.id === id);
if (!objectToRemove) {
return;
}
objectToRemove.destroy();
this.DOM.removeEntity(objectToRemove);
this.drawings$.next(filtered);
};
private removeDrawingsByName(name: DrawingsNames): void {
const drawingsToRemove = this.drawings$.value.filter((drawing) => drawing.id.startsWith(name));
if (!drawingsToRemove.length) {
return;
}
drawingsToRemove.forEach((drawing) => {
drawing.destroy();
this.DOM.removeEntity(drawing);
});
this.drawings$.next(this.drawings$.value.filter((drawing) => !drawing.id.startsWith(name)));
}
public entities(): Observable<Drawing[]> {
return this.drawings$.asObservable();
}
public getDrawings() {}
public hideAll() {}
}