implement drawing methods, fix horizontal line bug, continue refactor
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
// }
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
14
src/helpers/canvas-rendering.ts
Normal file
14
src/helpers/canvas-rendering.ts
Normal 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);
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
src/vertical-line/pane-renderer.ts
Normal file
32
src/vertical-line/pane-renderer.ts
Normal 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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
29
src/vertical-line/pane-view.ts
Normal file
29
src/vertical-line/pane-view.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
89
src/vertical-line/vertical-line.ts
Normal file
89
src/vertical-line/vertical-line.ts
Normal 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)}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user