import { BitmapCoordinatesRenderingScope, CanvasRenderingTarget2D } from 'fancy-canvas'; import { AutoscaleInfo, Coordinate, IChartApi, ISeriesApi, ISeriesPrimitive, IPrimitivePaneRenderer, IPrimitivePaneView, Logical, SeriesOptionsMap, SeriesType, Time, } from 'lightweight-charts'; class TrendLinePaneRenderer implements IPrimitivePaneRenderer { _p1: ViewPoint; _p2: ViewPoint; _text1: string; _text2: string; _options: TrendLineOptions; constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: TrendLineOptions) { this._p1 = p1; this._p2 = p2; this._text1 = text1; this._text2 = text2; this._options = options; } draw(target: CanvasRenderingTarget2D) { target.useBitmapCoordinateSpace(scope => { if ( this._p1.x === null || this._p1.y === null || this._p2.x === null || this._p2.y === null ) return; const ctx = scope.context; const x1Scaled = Math.round(this._p1.x * scope.horizontalPixelRatio); const y1Scaled = Math.round(this._p1.y * scope.verticalPixelRatio); const x2Scaled = Math.round(this._p2.x * scope.horizontalPixelRatio); const y2Scaled = Math.round(this._p2.y * scope.verticalPixelRatio); ctx.lineWidth = this._options.width; ctx.strokeStyle = this._options.lineColor; ctx.beginPath(); ctx.moveTo(x1Scaled, y1Scaled); ctx.lineTo(x2Scaled, y2Scaled); ctx.stroke(); if (this._options.showLabels) { this._drawTextLabel(scope, this._text1, x1Scaled, y1Scaled, true); this._drawTextLabel(scope, this._text2, x2Scaled, y2Scaled, false); } }); } _drawTextLabel(scope: BitmapCoordinatesRenderingScope, text: string, x: number, y: number, left: boolean) { scope.context.font = '24px Arial'; scope.context.beginPath(); const offset = 5 * scope.horizontalPixelRatio; const textWidth = scope.context.measureText(text); const leftAdjustment = left ? textWidth.width + offset * 4 : 0; scope.context.fillStyle = this._options.labelBackgroundColor; scope.context.roundRect(x + offset - leftAdjustment, y - 24, textWidth.width + offset * 2, 24 + offset, 5); scope.context.fill(); scope.context.beginPath(); scope.context.fillStyle = this._options.labelTextColor; scope.context.fillText(text, x + offset * 2 - leftAdjustment, y); } } interface ViewPoint { x: Coordinate | null; y: Coordinate | null; } class TrendLinePaneView implements IPrimitivePaneView { _source: TrendLine; _p1: ViewPoint = { x: null, y: null }; _p2: ViewPoint = { x: null, y: null }; constructor(source: TrendLine) { this._source = source; } update() { const series = this._source._series; const y1 = series.priceToCoordinate(this._source._p1.price); const y2 = series.priceToCoordinate(this._source._p2.price); const timeScale = this._source._chart.timeScale(); const x1 = timeScale.timeToCoordinate(this._source._p1.time); const x2 = timeScale.timeToCoordinate(this._source._p2.time); this._p1 = { x: x1, y: y1 }; this._p2 = { x: x2, y: y2 }; } renderer() { return new TrendLinePaneRenderer( this._p1, this._p2, '' + this._source._p1.price.toFixed(1), '' + this._source._p2.price.toFixed(1), this._source._options ); } } interface Point { time: Time; price: number; } export interface TrendLineOptions { lineColor: string; width: number; showLabels: boolean; labelBackgroundColor: string; labelTextColor: string; } const defaultOptions: TrendLineOptions = { lineColor: 'rgb(0, 0, 0)', width: 2, showLabels: false, labelBackgroundColor: 'rgba(255, 255, 255, 0.85)', labelTextColor: 'rgb(0, 0, 0)', }; export class TrendLine implements ISeriesPrimitive