implement drawing methods, fix horizontal line bug, continue refactor

This commit is contained in:
louisnw
2024-04-14 16:29:15 +01:00
parent 3ead45f858
commit 3fdd19e3ce
33 changed files with 732 additions and 365 deletions

View File

@ -3,7 +3,7 @@ import {
} from 'lightweight-charts';
import { Point } from '../drawing/data-source';
import { Drawing, InteractionState } from '../drawing/drawing';
import { InteractionState } from '../drawing/drawing';
import { DrawingOptions, defaultOptions } from '../drawing/options';
import { BoxPaneView } from './pane-view';
import { TwoPointDrawing } from '../drawing/two-point-drawing';
@ -54,13 +54,13 @@ export class Box extends TwoPointDrawing {
switch(state) {
case InteractionState.NONE:
document.body.style.cursor = "default";
this.applyOptions({showCircles: false});
this._hovered = false;
this._unsubscribe("mousedown", this._handleMouseDownInteraction);
break;
case InteractionState.HOVERING:
document.body.style.cursor = "pointer";
this.applyOptions({showCircles: true});
this._hovered = true;
this._unsubscribe("mouseup", this._handleMouseUpInteraction);
this._subscribe("mousedown", this._handleMouseDownInteraction)
this.chart.applyOptions({handleScroll: true});
@ -82,19 +82,19 @@ export class Box extends TwoPointDrawing {
_onDrag(diff: any) {
if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP1) {
Drawing._addDiffToPoint(this._p1, diff.time, diff.logical, diff.price);
this._addDiffToPoint(this.p1, diff.logical, diff.price);
}
if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP2) {
Drawing._addDiffToPoint(this._p2, diff.time, diff.logical, diff.price);
this._addDiffToPoint(this.p2, diff.logical, diff.price);
}
if (this._state != InteractionState.DRAGGING) {
if (this._state == InteractionState.DRAGGINGP3) {
Drawing._addDiffToPoint(this._p1, diff.time, diff.logical, 0);
Drawing._addDiffToPoint(this._p2, 0, 0, diff.price);
this._addDiffToPoint(this.p1, diff.logical, 0);
this._addDiffToPoint(this.p2, 0, diff.price);
}
if (this._state == InteractionState.DRAGGINGP4) {
Drawing._addDiffToPoint(this._p1, 0, 0, diff.price);
Drawing._addDiffToPoint(this._p2, diff.time, diff.logical, 0);
this._addDiffToPoint(this.p1, 0, diff.price);
this._addDiffToPoint(this.p2, diff.logical, 0);
}
}
}

View File

@ -2,12 +2,13 @@ import { ViewPoint } from "../drawing/pane-view";
import { CanvasRenderingTarget2D } from "fancy-canvas";
import { TwoPointDrawingPaneRenderer } from "../drawing/pane-renderer";
import { BoxOptions } from "./box";
import { setLineStyle } from "../helpers/canvas-rendering";
export class BoxPaneRenderer extends TwoPointDrawingPaneRenderer {
declare _options: BoxOptions;
constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: BoxOptions) {
super(p1, p2, text1, text2, options)
constructor(p1: ViewPoint, p2: ViewPoint, options: BoxOptions, showCircles: boolean) {
super(p1, p2, options, showCircles)
}
draw(target: CanvasRenderingTarget2D) {
@ -21,6 +22,7 @@ export class BoxPaneRenderer extends TwoPointDrawingPaneRenderer {
ctx.lineWidth = this._options.width;
ctx.strokeStyle = this._options.lineColor;
setLineStyle(ctx, this._options.lineStyle)
ctx.fillStyle = this._options.fillColor;
const mainX = Math.min(scaled.x1, scaled.x2);
@ -31,7 +33,7 @@ export class BoxPaneRenderer extends TwoPointDrawingPaneRenderer {
ctx.strokeRect(mainX, mainY, width, height);
ctx.fillRect(mainX, mainY, width, height);
if (!this._options.showCircles) return;
if (!this._hovered) return;
this._drawEndCircle(scope, mainX, mainY);
this._drawEndCircle(scope, mainX+width, mainY);
this._drawEndCircle(scope, mainX+width, mainY+height);

View File

@ -11,9 +11,8 @@ export class BoxPaneView extends TwoPointDrawingPaneView {
return new BoxPaneRenderer(
this._p1,
this._p2,
'' + this._source._p1.price.toFixed(1),
'' + this._source._p2.price.toFixed(1),
this._source._options as BoxOptions,
this._source.hovered,
);
}
}

View File

@ -1,23 +1,15 @@
import {
IChartApi,
ISeriesApi,
Logical,
SeriesOptionsMap,
Time,
} from 'lightweight-charts';
import { DrawingOptions } from './options';
export interface Point {
time: Time | null;
logical: Logical;
price: number;
}
export interface DrawingDataSource {
chart: IChartApi;
series: ISeriesApi<keyof SeriesOptionsMap>;
options: DrawingOptions;
p1: Point;
p2: Point;
export interface DiffPoint {
logical: number;
price: number;
}

View File

@ -5,6 +5,7 @@ import {
SeriesType,
} from 'lightweight-charts';
import { Drawing } from './drawing';
import { HorizontalLine } from '../horizontal-line/horizontal-line';
export class DrawingTool {
@ -69,9 +70,10 @@ export class DrawingTool {
if (this._activeDrawing == null) {
if (this._drawingType == null) return;
// TODO this line wont work for horizontals ?
this._activeDrawing = new this._drawingType(point, point);
this._series.attachPrimitive(this._activeDrawing);
if (this._drawingType == HorizontalLine) this._onClick(param);
}
else {
this._drawings.push(this._activeDrawing);

View File

@ -1,10 +1,14 @@
import { ISeriesApi, MouseEventParams, SeriesType, Time, Logical } from 'lightweight-charts';
import {
ISeriesApi,
Logical,
MouseEventParams,
SeriesType
} from 'lightweight-charts';
import { PluginBase } from '../plugin-base';
import { Point } from './data-source';
import { DrawingPaneView } from './pane-view';
import { DiffPoint, Point } from './data-source';
import { DrawingOptions, defaultOptions } from './options';
import { convertTime } from '../helpers/time';
import { DrawingPaneView } from './pane-view';
export enum InteractionState {
NONE,
@ -16,17 +20,12 @@ export enum InteractionState {
DRAGGINGP4,
}
interface DiffPoint {
time: number | null;
logical: number;
price: number;
}
export abstract class Drawing extends PluginBase {
_paneViews: DrawingPaneView[] = [];
_options: DrawingOptions;
abstract _type: string;
protected _points: (Point|null)[] = [];
protected _state: InteractionState = InteractionState.NONE;
@ -66,7 +65,13 @@ export abstract class Drawing extends PluginBase {
this.requestUpdate();
}
public abstract updatePoints(...points: (Point | null)[]): void;
public updatePoints(...points: (Point | null)[]) {
for (let i=0; i<this.points.length; i++) {
if (points[i] == null) continue;
this.points[i] = points[i] as Point;
}
this.requestUpdate();
}
detach() {
this._options.lineColor = 'transparent';
@ -78,6 +83,10 @@ export abstract class Drawing extends PluginBase {
}
get points() {
return this._points;
}
protected _subscribe(name: keyof DocumentEventMap, listener: any) {
document.body.addEventListener(name, listener);
this._listeners.push({name: name, listener: listener});
@ -92,20 +101,19 @@ export abstract class Drawing extends PluginBase {
_handleHoverInteraction(param: MouseEventParams) {
this._latestHoverPoint = param.point;
if (!Drawing._mouseIsDown) {
if (Drawing._mouseIsDown) {
this._handleDragInteraction(param);
} else {
if (this._mouseIsOverDrawing(param)) {
if (this._state != InteractionState.NONE) return;
this._moveToState(InteractionState.HOVERING);
Drawing.hoveredObject = Drawing.lastHoveredObject = this;
}
else {
} else {
if (this._state == InteractionState.NONE) return;
this._moveToState(InteractionState.NONE);
if (Drawing.hoveredObject === this) Drawing.hoveredObject = null;
}
return;
}
this._handleDragInteraction(param);
}
public static _eventToPoint(param: MouseEventParams, series: ISeriesApi<SeriesType>) {
@ -121,35 +129,27 @@ export abstract class Drawing extends PluginBase {
protected static _getDiff(p1: Point, p2: Point): DiffPoint {
const diff: DiffPoint = {
time: null,
logical: p1.logical-p2.logical,
price: p1.price-p2.price,
}
if (p1.time && p2.time) {
diff.time = convertTime(p1.time)-convertTime(p2.time);
}
return diff;
}
protected static _addDiffToPoint(point: Point, timeDiff: number | null, logicalDiff: number, priceDiff: number) {
if (timeDiff != null && point.time != null) {
point.time = (convertTime(point.time)+timeDiff)/1000 as Time;
}
else {
point.time = null;
}
protected _addDiffToPoint(point: Point | null, logicalDiff: number, priceDiff: number) {
if (!point) return;
point.logical = point.logical + logicalDiff as Logical;
point.price = point.price+priceDiff;
point.time = this.series.dataByIndex(point.logical)?.time || null;
}
protected _handleMouseDownInteraction = () => {
if (Drawing._mouseIsDown) return;
// if (Drawing._mouseIsDown) return;
Drawing._mouseIsDown = true;
this._onMouseDown();
}
protected _handleMouseUpInteraction = () => {
if (!Drawing._mouseIsDown) return;
// if (!Drawing._mouseIsDown) return;
Drawing._mouseIsDown = false;
this._moveToState(InteractionState.HOVERING);
}
@ -174,12 +174,7 @@ export abstract class Drawing extends PluginBase {
}
protected abstract _onMouseDown(): void;
protected abstract _onDrag(diff: any): void; // TODO any?
protected abstract _onDrag(diff: DiffPoint): void;
protected abstract _moveToState(state: InteractionState): void;
protected abstract _mouseIsOverDrawing(param: MouseEventParams): boolean;
// toJSON() {
// const {series, chart, ...serialized} = this;
// return serialized;
// }
}

View File

@ -1,18 +1,14 @@
import { LineStyle } from "lightweight-charts";
export interface DrawingOptions {
lineColor: string;
lineStyle: LineStyle
width: number;
showLabels: boolean;
showCircles: boolean,
}
export const defaultOptions: DrawingOptions = {
lineColor: 'rgb(255, 255, 255)',
lineColor: '#1E80F0',
lineStyle: LineStyle.Solid,
width: 4,
showLabels: true,
showCircles: false,
};
};

View File

@ -17,15 +17,13 @@ export abstract class DrawingPaneRenderer implements ISeriesPrimitivePaneRendere
export abstract class TwoPointDrawingPaneRenderer extends DrawingPaneRenderer {
_p1: ViewPoint;
_p2: ViewPoint;
_text1: string;
_text2: string;
protected _hovered: boolean;
constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: DrawingOptions) {
constructor(p1: ViewPoint, p2: ViewPoint, options: DrawingOptions, hovered: boolean) {
super(options);
this._p1 = p1;
this._p2 = p2;
this._text1 = text1;
this._text2 = text2;
this._hovered = hovered;
}
abstract draw(target: CanvasRenderingTarget2D): void;

View File

@ -33,11 +33,12 @@ export abstract class TwoPointDrawingPaneView extends DrawingPaneView {
}
update() {
if (!this._source.p1 || !this._source.p2) return;
const series = this._source.series;
const y1 = series.priceToCoordinate(this._source._p1.price);
const y2 = series.priceToCoordinate(this._source._p2.price);
const x1 = this._getX(this._source._p1);
const x2 = this._getX(this._source._p2);
const y1 = series.priceToCoordinate(this._source.p1.price);
const y2 = series.priceToCoordinate(this._source.p2.price);
const x1 = this._getX(this._source.p1);
const x2 = this._getX(this._source.p2);
this._p1 = { x: x1, y: y1 };
this._p2 = { x: x2, y: y2 };
if (!x1 || !x2 || !y1 || !y2) return;
@ -47,9 +48,6 @@ export abstract class TwoPointDrawingPaneView extends DrawingPaneView {
_getX(p: Point) {
const timeScale = this._source.chart.timeScale();
if (!p.time) {
return timeScale.logicalToCoordinate(p.logical);
}
return timeScale.timeToCoordinate(p.time);
return timeScale.logicalToCoordinate(p.logical);
}
}

View File

@ -5,18 +5,18 @@ import { TwoPointDrawingPaneView } from './pane-view';
export abstract class TwoPointDrawing extends Drawing {
_p1: Point;
_p2: Point;
_paneViews: TwoPointDrawingPaneView[] = [];
protected _hovered: boolean = false;
constructor(
p1: Point,
p2: Point,
options?: Partial<DrawingOptions>
) {
super()
this._p1 = p1;
this._p2 = p2;
this.points.push(p1);
this.points.push(p2);
this._options = {
...defaultOptions,
...options,
@ -31,9 +31,8 @@ export abstract class TwoPointDrawing extends Drawing {
this.updatePoints(null, point);
}
public updatePoints(...points: (Point|null)[]) {
this._p1 = points[0] || this._p1;
this._p2 = points[1] || this._p2;
this.requestUpdate();
}
get p1() { return this.points[0]; }
get p2() { return this.points[1]; }
get hovered() { return this._hovered; }
}

View File

@ -1,4 +1,8 @@
import { Box } from "../box/box";
import { HorizontalLine } from "../horizontal-line/horizontal-line";
import { RayLine } from "../horizontal-line/ray-line";
import { TrendLine } from "../trend-line/trend-line";
import { VerticalLine } from "../vertical-line/vertical-line";
import { Table } from "./table";
export interface GlobalParams extends Window {
@ -10,7 +14,13 @@ export interface GlobalParams extends Window {
cursor: string;
Handler: any;
Table: typeof Table;
HorizontalLine: typeof HorizontalLine;
TrendLine: typeof TrendLine;
Box: typeof Box;
RayLine: typeof RayLine;
VerticalLine: typeof VerticalLine;
}
interface paneStyle {
@ -48,7 +58,12 @@ export function globalParamInit() {
}
window.cursor = 'default';
window.Table = Table;
window.HorizontalLine = HorizontalLine;
window.TrendLine = TrendLine;
window.Box = Box;
window.RayLine = RayLine;
window.VerticalLine = VerticalLine;
}

View File

@ -52,8 +52,14 @@ export class Handler {
public _seriesList: ISeriesApi<SeriesType>[] = [];
constructor(chartId: string, innerWidth: number, innerHeight: number, position: string, autoSize: boolean) {
// TODO make some subcharts in the vite dev window and mess with the CSS to see if you can not need the position param. also see if you can remove resizing each time the window resizes?
constructor(
chartId: string,
innerWidth: number,
innerHeight: number,
position: string,
autoSize: boolean
) {
this.reSize = this.reSize.bind(this)
this.id = chartId
@ -224,8 +230,15 @@ export class Handler {
return param.seriesData.get(series) || null;
}
const setChildRange = (timeRange: LogicalRange | null) => { if(timeRange) childChart.chart.timeScale().setVisibleLogicalRange(timeRange); }
const setParentRange = (timeRange: LogicalRange | null) => { if(timeRange) parentChart.chart.timeScale().setVisibleLogicalRange(timeRange); }
const childTimeScale = childChart.chart.timeScale();
const parentTimeScale = parentChart.chart.timeScale();
const setChildRange = (timeRange: LogicalRange | null) => {
if(timeRange) childTimeScale.setVisibleLogicalRange(timeRange);
}
const setParentRange = (timeRange: LogicalRange | null) => {
if(timeRange) parentTimeScale.setVisibleLogicalRange(timeRange);
}
const setParentCrosshair = (param: MouseEventParams) => {
crosshairHandler(parentChart, getPoint(childChart.series, param))
@ -235,7 +248,14 @@ export class Handler {
}
let selected = parentChart
function addMouseOverListener(thisChart: Handler, otherChart: Handler, thisCrosshair: MouseEventHandler<Time>, otherCrosshair: MouseEventHandler<Time>, thisRange: LogicalRangeChangeEventHandler, otherRange: LogicalRangeChangeEventHandler) {
function addMouseOverListener(
thisChart: Handler,
otherChart: Handler,
thisCrosshair: MouseEventHandler<Time>,
otherCrosshair: MouseEventHandler<Time>,
thisRange: LogicalRangeChangeEventHandler,
otherRange: LogicalRangeChangeEventHandler)
{
thisChart.wrapper.addEventListener('mouseover', () => {
if (selected === thisChart) return
selected = thisChart
@ -246,10 +266,28 @@ export class Handler {
thisChart.chart.timeScale().subscribeVisibleLogicalRangeChange(otherRange)
})
}
addMouseOverListener(parentChart, childChart, setParentCrosshair, setChildCrosshair, setParentRange, setChildRange)
addMouseOverListener(childChart, parentChart, setChildCrosshair, setParentCrosshair, setChildRange, setParentRange)
addMouseOverListener(
parentChart,
childChart,
setParentCrosshair,
setChildCrosshair,
setParentRange,
setChildRange
)
addMouseOverListener(
childChart,
parentChart,
setChildCrosshair,
setParentCrosshair,
setChildRange,
setParentRange
)
parentChart.chart.subscribeCrosshairMove(setChildCrosshair)
const parentRange = parentTimeScale.getVisibleLogicalRange()
if (parentRange) childTimeScale.setVisibleLogicalRange(parentRange)
if (crosshairOnly) return;
parentChart.chart.timeScale().subscribeVisibleLogicalRangeChange(setChildRange)
}

View File

@ -7,9 +7,9 @@ import { GlobalParams } from "./global-params";
import { StylePicker } from "../context-menu/style-picker";
import { ColorPicker } from "../context-menu/color-picker";
import { IChartApi, ISeriesApi, SeriesType } from "lightweight-charts";
import { TwoPointDrawing } from "../drawing/two-point-drawing";
import { HorizontalLine } from "../horizontal-line/horizontal-line";
import { RayLine } from "../horizontal-line/ray-line";
import { VerticalLine } from "../vertical-line/vertical-line";
interface Icon {
@ -67,6 +67,7 @@ export class ToolBox {
this.buttons.push(this._makeToolBoxElement(HorizontalLine, 'KeyH', ToolBox.HORZ_SVG));
this.buttons.push(this._makeToolBoxElement(RayLine, 'KeyR', ToolBox.RAY_SVG));
this.buttons.push(this._makeToolBoxElement(Box, 'KeyB', ToolBox.BOX_SVG));
this.buttons.push(this._makeToolBoxElement(VerticalLine, 'KeyV', ToolBox.RAY_SVG));
for (const button of this.buttons) {
div.appendChild(button);
}
@ -122,7 +123,7 @@ export class ToolBox {
this._drawingTool?.beginDrawing(this.activeIcon.type);
}
removeActiveAndSave() {
removeActiveAndSave = () => {
window.setCursor('default');
if (this.activeIcon) this.activeIcon.div.classList.remove('active-toolbox-button')
this.activeIcon = null
@ -168,39 +169,36 @@ export class ToolBox {
this._drawingTool.clearDrawings();
}
saveDrawings() {
saveDrawings = () => {
const drawingMeta = []
for (const d of this._drawingTool.drawings) {
if (d instanceof TwoPointDrawing) {
drawingMeta.push({
type: d._type,
p1: d._p1,
p2: d._p2,
color: d._options.lineColor,
style: d._options.lineStyle, // TODO should push all options, just dont have showcircles/ non public stuff as actual options
// would also fix the instanceOf in loadDrawings
})
}
// TODO else if d instanceof Drawing
drawingMeta.push({
points: d.points,
options: d._options
});
}
const string = JSON.stringify(drawingMeta);
window.callbackFunction(`save_drawings${this._handlerID}_~_${string}`)
}
loadDrawings(drawings: any[]) { // TODO any?
loadDrawings(drawings: any[]) { // TODO any
drawings.forEach((d) => {
const options = {
lineColor: d.color,
lineStyle: d.style,
}
switch (d.type) {
case "Box":
this._drawingTool.addNewDrawing(new Box(d.p1, d.p2, options));
this._drawingTool.addNewDrawing(new Box(d.points[0], d.points[1], d.options));
break;
case "TrendLine":
this._drawingTool.addNewDrawing(new TrendLine(d.p1, d.p2, options));
this._drawingTool.addNewDrawing(new TrendLine(d.points[0], d.points[1], d.options));
break;
case "HorizontalLine":
this._drawingTool.addNewDrawing(new HorizontalLine(d.points[0], d.options));
break;
case "RayLine":
this._drawingTool.addNewDrawing(new RayLine(d.points[0], d.options));
break;
case "VerticalLine":
this._drawingTool.addNewDrawing(new VerticalLine(d.points[0], d.options));
break;
// TODO case HorizontalLine
}
})
}

View File

@ -0,0 +1,14 @@
import { LineStyle } from "lightweight-charts";
export function setLineStyle(ctx: CanvasRenderingContext2D, style: LineStyle): void {
const dashPatterns = {
[LineStyle.Solid]: [],
[LineStyle.Dotted]: [ctx.lineWidth, ctx.lineWidth],
[LineStyle.Dashed]: [2 * ctx.lineWidth, 2 * ctx.lineWidth],
[LineStyle.LargeDashed]: [6 * ctx.lineWidth, 6 * ctx.lineWidth],
[LineStyle.SparseDotted]: [ctx.lineWidth, 4 * ctx.lineWidth],
};
const dashPattern = dashPatterns[style];
ctx.setLineDash(dashPattern);
}

View File

@ -57,7 +57,7 @@ export class HorizontalLine extends Drawing {
}
_onDrag(diff: any) {
Drawing._addDiffToPoint(this._point, 0, 0, diff.price);
this._addDiffToPoint(this._point, 0, diff.price);
this.requestUpdate();
}

View File

@ -2,6 +2,7 @@ import { CanvasRenderingTarget2D } from "fancy-canvas";
import { DrawingOptions } from "../drawing/options";
import { DrawingPaneRenderer } from "../drawing/pane-renderer";
import { ViewPoint } from "../drawing/pane-view";
import { setLineStyle } from "../helpers/canvas-rendering";
export class HorizontalLinePaneRenderer extends DrawingPaneRenderer {
_point: ViewPoint = {x: null, y: null};
@ -21,6 +22,7 @@ export class HorizontalLinePaneRenderer extends DrawingPaneRenderer {
ctx.lineWidth = this._options.width;
ctx.strokeStyle = this._options.lineColor;
setLineStyle(ctx, this._options.lineStyle);
ctx.beginPath();
ctx.moveTo(scaledX, scaledY);

View File

@ -16,7 +16,9 @@ export class HorizontalLinePaneView extends DrawingPaneView {
const point = this._source._point;
const timeScale = this._source.chart.timeScale()
const series = this._source.series;
this._point.x = point.time ? timeScale.timeToCoordinate(point.time) : null;
if (this._source._type == "RayLine") {
this._point.x = timeScale.logicalToCoordinate(point.logical);
}
this._point.y = series.priceToCoordinate(point.price);
}

View File

@ -2,8 +2,7 @@ import {
DeepPartial,
MouseEventParams
} from "lightweight-charts";
import { Point } from "../drawing/data-source";
import { Drawing } from "../drawing/drawing";
import { DiffPoint, Point } from "../drawing/data-source";
import { DrawingOptions } from "../drawing/options";
import { HorizontalLine } from "./horizontal-line";
@ -20,8 +19,8 @@ export class RayLine extends HorizontalLine {
this.requestUpdate();
}
_onDrag(diff: any) {
Drawing._addDiffToPoint(this._point, diff.time, diff.logical, diff.price);
_onDrag(diff: DiffPoint) {
this._addDiffToPoint(this._point, diff.logical, diff.price);
this.requestUpdate();
}

View File

@ -3,10 +3,11 @@ import { ViewPoint } from "./pane-view";
import { CanvasRenderingTarget2D } from "fancy-canvas";
import { TwoPointDrawingPaneRenderer } from "../drawing/pane-renderer";
import { DrawingOptions } from "../drawing/options";
import { setLineStyle } from "../helpers/canvas-rendering";
export class TrendLinePaneRenderer extends TwoPointDrawingPaneRenderer {
constructor(p1: ViewPoint, p2: ViewPoint, text1: string, text2: string, options: DrawingOptions) {
super(p1, p2, text1, text2, options);
constructor(p1: ViewPoint, p2: ViewPoint, options: DrawingOptions, hovered: boolean) {
super(p1, p2, options, hovered);
}
draw(target: CanvasRenderingTarget2D) {
@ -25,6 +26,7 @@ export class TrendLinePaneRenderer extends TwoPointDrawingPaneRenderer {
ctx.lineWidth = this._options.width;
ctx.strokeStyle = this._options.lineColor;
setLineStyle(ctx, this._options.lineStyle);
ctx.beginPath();
ctx.moveTo(scaled.x1, scaled.y1);
ctx.lineTo(scaled.x2, scaled.y2);
@ -32,7 +34,7 @@ export class TrendLinePaneRenderer extends TwoPointDrawingPaneRenderer {
// this._drawTextLabel(scope, this._text1, x1Scaled, y1Scaled, true);
// this._drawTextLabel(scope, this._text2, x2Scaled, y2Scaled, false);
if (!this._options.showCircles) return;
if (!this._hovered) return;
this._drawEndCircle(scope, scaled.x1, scaled.y1);
this._drawEndCircle(scope, scaled.x2, scaled.y2);
});

View File

@ -17,9 +17,8 @@ export class TrendLinePaneView extends TwoPointDrawingPaneView {
return new TrendLinePaneRenderer(
this._p1,
this._p2,
'' + this._source._p1.price.toFixed(1),
'' + this._source._p2.price.toFixed(1),
this._source._options
this._source._options,
this._source.hovered,
);
}
}

View File

@ -5,7 +5,7 @@ import {
import { TrendLinePaneView } from './pane-view';
import { Point } from '../drawing/data-source';
import { Drawing, InteractionState } from '../drawing/drawing';
import { InteractionState } from '../drawing/drawing';
import { DrawingOptions } from '../drawing/options';
import { TwoPointDrawing } from '../drawing/two-point-drawing';
@ -27,14 +27,14 @@ export class TrendLine extends TwoPointDrawing {
case InteractionState.NONE:
document.body.style.cursor = "default";
this._options.showCircles = false;
this._hovered = false;
this.requestUpdate();
this._unsubscribe("mousedown", this._handleMouseDownInteraction);
break;
case InteractionState.HOVERING:
document.body.style.cursor = "pointer";
this._options.showCircles = true;
this._hovered = true;
this.requestUpdate();
this._subscribe("mousedown", this._handleMouseDownInteraction);
this._unsubscribe("mouseup", this._handleMouseDownInteraction);
@ -55,10 +55,10 @@ export class TrendLine extends TwoPointDrawing {
_onDrag(diff: any) {
if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP1) {
Drawing._addDiffToPoint(this._p1, diff.time, diff.logical, diff.price);
this._addDiffToPoint(this.p1, diff.logical, diff.price);
}
if (this._state == InteractionState.DRAGGING || this._state == InteractionState.DRAGGINGP2) {
Drawing._addDiffToPoint(this._p2, diff.time, diff.logical, diff.price);
this._addDiffToPoint(this.p2, diff.logical, diff.price);
}
}

View File

@ -0,0 +1,32 @@
import { CanvasRenderingTarget2D } from "fancy-canvas";
import { DrawingOptions } from "../drawing/options";
import { DrawingPaneRenderer } from "../drawing/pane-renderer";
import { ViewPoint } from "../drawing/pane-view";
import { setLineStyle } from "../helpers/canvas-rendering";
export class VerticalLinePaneRenderer extends DrawingPaneRenderer {
_point: ViewPoint = {x: null, y: null};
constructor(point: ViewPoint, options: DrawingOptions) {
super(options);
this._point = point;
}
draw(target: CanvasRenderingTarget2D) {
target.useBitmapCoordinateSpace(scope => {
if (this._point.x == null) return;
const ctx = scope.context;
const scaledX = this._point.x * scope.horizontalPixelRatio;
ctx.lineWidth = this._options.width;
ctx.strokeStyle = this._options.lineColor;
setLineStyle(ctx, this._options.lineStyle);
ctx.beginPath();
ctx.moveTo(scaledX, 0);
ctx.lineTo(scaledX, scope.bitmapSize.height);
ctx.stroke();
});
}
}

View File

@ -0,0 +1,29 @@
import { VerticalLinePaneRenderer } from './pane-renderer';
import { VerticalLine } from './vertical-line';
import { DrawingPaneView, ViewPoint } from '../drawing/pane-view';
export class VerticalLinePaneView extends DrawingPaneView {
_source: VerticalLine;
_point: ViewPoint = {x: null, y: null};
constructor(source: VerticalLine) {
super(source);
this._source = source;
}
update() {
const point = this._source._point;
const timeScale = this._source.chart.timeScale()
const series = this._source.series;
this._point.x = timeScale.logicalToCoordinate(point.logical)
this._point.y = series.priceToCoordinate(point.price);
}
renderer() {
return new VerticalLinePaneRenderer(
this._point,
this._source._options
);
}
}

View File

@ -0,0 +1,89 @@
import {
DeepPartial,
MouseEventParams
} from "lightweight-charts";
import { Point } from "../drawing/data-source";
import { Drawing, InteractionState } from "../drawing/drawing";
import { DrawingOptions } from "../drawing/options";
import { VerticalLinePaneView } from "./pane-view";
import { GlobalParams } from "../general/global-params";
declare const window: GlobalParams;
export class VerticalLine extends Drawing {
_type = 'VerticalLine';
_paneViews: VerticalLinePaneView[];
_point: Point;
private _callbackName: string | null;
protected _startDragPoint: Point | null = null;
constructor(point: Point, options: DeepPartial<DrawingOptions>, callbackName=null) {
super(options)
this._point = point;
this._paneViews = [new VerticalLinePaneView(this)];
this._callbackName = callbackName;
}
public updatePoints(...points: (Point | null)[]) {
for (const p of points) if (p) this._point = p;
this.requestUpdate();
}
_moveToState(state: InteractionState) {
switch(state) {
case InteractionState.NONE:
document.body.style.cursor = "default";
this._unsubscribe("mousedown", this._handleMouseDownInteraction);
break;
case InteractionState.HOVERING:
document.body.style.cursor = "pointer";
this._unsubscribe("mouseup", this._childHandleMouseUpInteraction);
this._subscribe("mousedown", this._handleMouseDownInteraction)
this.chart.applyOptions({handleScroll: true});
break;
case InteractionState.DRAGGING:
document.body.style.cursor = "grabbing";
this._subscribe("mouseup", this._childHandleMouseUpInteraction);
this.chart.applyOptions({handleScroll: false});
break;
}
this._state = state;
}
_onDrag(diff: any) {
this._addDiffToPoint(this._point, diff.logical, 0);
this.requestUpdate();
}
_mouseIsOverDrawing(param: MouseEventParams, tolerance = 4) {
if (!param.point) return false;
const timeScale = this.chart.timeScale()
let x;
if (this._point.time) {
x = timeScale.timeToCoordinate(this._point.time);
}
else {
x = timeScale.logicalToCoordinate(this._point.logical);
}
if (!x) return false;
return (Math.abs(x-param.point.x) < tolerance);
}
protected _onMouseDown() {
this._startDragPoint = null;
const hoverPoint = this._latestHoverPoint;
if (!hoverPoint) return;
return this._moveToState(InteractionState.DRAGGING);
}
protected _childHandleMouseUpInteraction = () => {
this._handleMouseUpInteraction();
if (!this._callbackName) return;
console.log(window.callbackFunction);
window.callbackFunction(`${this._callbackName}_~_${this._point.price.toFixed(8)}`);
}
}