Загрузка данных
import { IChartApi, ISeriesApi, MouseEventParams, SeriesOptionsMap, SeriesType, Time } from 'lightweight-charts';
import { ISeriesDrawing } from '@core/Drawings/common';
import { getPriceFromYCoordinate, getYCoordinateFromPrice } from '@core/Drawings/helpers';
import { CustomPriceAxisView } from '@src/core/Drawings/axis';
import { HorzLinePaneView } from './paneView';
import type { AxisLabel } from '@core/Drawings/types';
export interface HorzLineOptions {
color: string;
labelText: string;
height: number;
labelBackgroundColor: string;
labelTextColor: string;
showLabel: boolean;
}
const defaultOptions: HorzLineOptions = {
color: 'gray',
labelText: '',
height: 3,
labelBackgroundColor: 'green',
labelTextColor: 'gray',
showLabel: false,
};
export class HorzLine implements ISeriesDrawing {
private _attached = false;
_chart: IChartApi;
_series: ISeriesApi<keyof SeriesOptionsMap>;
_price!: number;
_paneViews: HorzLinePaneView[];
_priceAxisViews: CustomPriceAxisView[];
_options: HorzLineOptions;
_visible = true;
constructor(chart: IChartApi, series: ISeriesApi<SeriesType>, options?: Partial<HorzLineOptions>) {
const horzLineOptions: HorzLineOptions = {
...defaultOptions,
...options,
};
this._chart = chart;
this._series = series;
this._options = horzLineOptions;
chart.subscribeClick(this.startDrawing);
this._paneViews = [new HorzLinePaneView(this, horzLineOptions)];
this._priceAxisViews = [
new CustomPriceAxisView({
getAxisLabel: (labelKind) => this.getPriceAxisLabel(labelKind),
labelKind: 'main',
}),
];
}
private startDrawing = (param: MouseEventParams<Time>) => {
if (param.point) {
const price = getPriceFromYCoordinate(this._series, param.point.y);
if (price == null) {
return;
}
this._price = price;
this._series.attachPrimitive(this);
this._attached = true;
this.updateAllViews();
}
this._chart.unsubscribeClick(this.startDrawing);
};
public getPriceAxisLabel(labelKind: string): AxisLabel | null {
if (labelKind !== 'main') {
return null;
}
if (!this._visible || !this._options.showLabel) {
return null;
}
const coordinate = getYCoordinateFromPrice(this._series, this._price);
if (coordinate === null) {
return null;
}
return {
coordinate,
text: this._options.labelText,
textColor: this._options.labelTextColor,
backgroundColor: this._options.labelBackgroundColor,
};
}
public updateAllViews() {
this._paneViews.forEach((pw) => pw.update());
this._priceAxisViews.forEach((pw) => pw.update());
}
public priceAxisViews() {
return this._priceAxisViews;
}
public paneViews() {
return this._paneViews;
}
public hide(): void {
this._visible = false;
this.updateAllViews();
}
public show(): void {
this._visible = true;
this.updateAllViews();
}
public rebind(series: ISeriesApi<SeriesType>): void {
if (this._attached) {
this._series.detachPrimitive(this);
}
this._series = series;
this.updateAllViews();
if (this._attached) {
this._series.attachPrimitive(this);
}
}
public destroy(): void {
this._chart.unsubscribeClick(this.startDrawing);
if (this._attached) {
this._series.detachPrimitive(this);
this._attached = false;
}
}
}
import { CanvasRenderingTarget2D } from 'fancy-canvas';
import { Coordinate, IPrimitivePaneRenderer } from 'lightweight-charts';
import { BitmapPositionLength } from '@core/Drawings/common';
import type { HorzLineOptions } from './horzLine';
export class HorzLinePaneRenderer implements IPrimitivePaneRenderer {
private _y: Coordinate | null = null;
private _options: HorzLineOptions;
private _visible: boolean;
constructor(y: Coordinate | null, options: HorzLineOptions, visible: boolean) {
this._y = y;
this._options = options;
this._visible = visible;
}
public draw(target: CanvasRenderingTarget2D): void {
if (!this._visible) {
return;
}
target.useBitmapCoordinateSpace((scope) => {
if (this._y === null) {
return;
}
const ctx = scope.context;
const position = getHorizontalLineBitmapPosition(this._y, scope.verticalPixelRatio, this._options.height);
ctx.fillStyle = this._options.color;
ctx.fillRect(0, position.position, scope.bitmapSize.width, position.length);
});
}
}
function getHorizontalLineBitmapPosition(
positionMedia: number,
pixelRatio: number,
desiredHeightMedia: number,
heightIsBitmap?: boolean,
): BitmapPositionLength {
const scaledPosition = Math.round(pixelRatio * positionMedia);
const lineBitmapHeight = heightIsBitmap ? desiredHeightMedia : Math.round(desiredHeightMedia * pixelRatio);
const offset = Math.floor(lineBitmapHeight * 0.5);
const position = scaledPosition - offset;
return { position, length: lineBitmapHeight };
}
import { Coordinate, IPrimitivePaneRenderer, IPrimitivePaneView } from 'lightweight-charts';
import { getYCoordinateFromPrice } from '@core/Drawings/helpers';
import { HorzLinePaneRenderer } from './paneRenderer';
import type { HorzLine, HorzLineOptions } from './horzLine';
export class HorzLinePaneView implements IPrimitivePaneView {
_source: HorzLine;
_y: Coordinate | null = null;
_options: HorzLineOptions;
constructor(source: HorzLine, options: HorzLineOptions) {
this._source = source;
this._options = options;
}
public update(): void {
this._y = getYCoordinateFromPrice(this._source._series, this._source._price);
}
public renderer(): IPrimitivePaneRenderer {
return new HorzLinePaneRenderer(this._y, this._options, this._source._visible);
}
}
import { CanvasRenderingTarget2D } from 'fancy-canvas';
import { Coordinate, IPrimitivePaneRenderer } from 'lightweight-charts';
import { BitmapPositionLength } from '@core/Drawings/common';
import type { VertLineOptions } from './vertLine';
export class VertLinePaneRenderer implements IPrimitivePaneRenderer {
private _x: Coordinate | null = null;
private _options: VertLineOptions;
private _visible: boolean;
constructor(x: Coordinate | null, options: VertLineOptions, visible: boolean) {
this._x = x;
this._visible = visible;
this._options = options;
}
public draw(target: CanvasRenderingTarget2D): void {
if (!this._visible) {
return;
}
target.useBitmapCoordinateSpace((scope) => {
if (this._x === null) {
return;
}
const ctx = scope.context;
const position = getVerticalLineBitmapPosition(this._x, scope.horizontalPixelRatio, this._options.width);
ctx.fillStyle = this._options.color;
ctx.fillRect(position.position, 0, position.length, scope.bitmapSize.height);
});
}
}
function getVerticalLineBitmapPosition(
positionMedia: number,
pixelRatio: number,
desiredWidthMedia: number,
widthIsBitmap?: boolean,
): BitmapPositionLength {
const scaledPosition = Math.round(pixelRatio * positionMedia);
const lineBitmapWidth = widthIsBitmap ? desiredWidthMedia : Math.round(desiredWidthMedia * pixelRatio);
const offset = Math.floor(lineBitmapWidth * 0.5);
const position = scaledPosition - offset;
return { position, length: lineBitmapWidth };
}
import { Coordinate, IPrimitivePaneRenderer, IPrimitivePaneView } from 'lightweight-charts';
import { getXCoordinateFromTime } from '@core/Drawings/helpers';
import { VertLinePaneRenderer } from './paneRenderer';
import type { VertLine, VertLineOptions } from './vertLine';
export class VertLinePaneView implements IPrimitivePaneView {
private _source: VertLine;
private _x: Coordinate | null = null;
private _options: VertLineOptions;
constructor(source: VertLine, options: VertLineOptions) {
this._source = source;
this._options = options;
}
public update(): void {
const { time } = this._source;
if (!time) {
this._x = null;
return;
}
this._x = getXCoordinateFromTime(this._source.chart, time);
}
public renderer(): IPrimitivePaneRenderer {
return new VertLinePaneRenderer(this._x, this._options, this._source.visible);
}
}
import { IChartApi, ISeriesApi, MouseEventParams, SeriesOptionsMap, SeriesType, Time } from 'lightweight-charts';
import { ISeriesDrawing } from '@core/Drawings/common';
import { getTimeFromXCoordinate, getXCoordinateFromTime } from '@core/Drawings/helpers';
import { CustomTimeAxisView } from '@src/core/Drawings/axis';
import { VertLinePaneView } from './paneView';
import type { AxisLabel } from '@core/Drawings/types';
export interface VertLineOptions {
color: string;
labelText: string;
width: number;
labelBackgroundColor: string;
labelTextColor: string;
showLabel: boolean;
}
const defaultOptions: VertLineOptions = {
color: 'gray',
labelText: '',
width: 3,
labelBackgroundColor: 'green',
labelTextColor: 'white',
showLabel: false,
};
export class VertLine implements ISeriesDrawing {
private _series: ISeriesApi<keyof SeriesOptionsMap>;
private _paneViews: VertLinePaneView[];
private _timeAxisViews: CustomTimeAxisView[];
private _attached = false;
private _options: VertLineOptions;
time: Time | null = null;
chart: IChartApi;
visible = true;
constructor(chart: IChartApi, series: ISeriesApi<SeriesType>, options?: Partial<VertLineOptions>) {
const vertLineOptions: VertLineOptions = {
...defaultOptions,
...options,
};
this.chart = chart;
this._series = series;
this._options = vertLineOptions;
chart.subscribeClick(this.startDrawing);
this._paneViews = [new VertLinePaneView(this, vertLineOptions)];
this._timeAxisViews = [
new CustomTimeAxisView({
getAxisLabel: (labelKind) => this.getTimeAxisLabel(labelKind),
labelKind: 'main',
}),
];
}
private startDrawing = (param: MouseEventParams<Time>) => {
if (param.point) {
const time = getTimeFromXCoordinate(this.chart, param.point.x);
if (time == null) {
return;
}
this.time = time;
this._series.attachPrimitive(this);
this._attached = true;
this.updateAllViews();
}
this.chart.unsubscribeClick(this.startDrawing);
};
public getTimeAxisLabel(labelKind: string): AxisLabel | null {
if (labelKind !== 'main') {
return null;
}
if (!this.visible || !this._options.showLabel || !this.time) {
return null;
}
const coordinate = getXCoordinateFromTime(this.chart, this.time);
if (coordinate === null) {
return null;
}
return {
coordinate,
text: this._options.labelText,
textColor: this._options.labelTextColor,
backgroundColor: this._options.labelBackgroundColor,
};
}
public updateAllViews() {
this._paneViews.forEach((pw) => pw.update());
this._timeAxisViews.forEach((tw) => tw.update());
}
public timeAxisViews() {
return this._timeAxisViews;
}
public paneViews() {
return this._paneViews;
}
public hide(): void {
this.visible = false;
this.updateAllViews();
}
public show(): void {
this.visible = true;
this.updateAllViews();
}
public rebind(series: ISeriesApi<SeriesType>): void {
if (this._attached) {
this._series.detachPrimitive(this);
}
this._series = series;
this.updateAllViews();
if (this._attached) {
this._series.attachPrimitive(this);
}
}
public destroy(): void {
this.chart.unsubscribeClick(this.startDrawing);
if (this._attached) {
this._series.detachPrimitive(this);
this._attached = false;
}
}
}