implement toggleable buttons; menu items can now be changed; add horizontal line and vertical line labels

This commit is contained in:
louisnw
2024-05-31 17:25:55 +01:00
parent ca93ddbcb1
commit 7915863a64
13 changed files with 233 additions and 59 deletions

View File

@ -29,7 +29,7 @@ export class Legend {
this.legendHandler = this.legendHandler.bind(this)
this.handler = handler;
this.ohlcEnabled = true;
this.ohlcEnabled = false;
this.percentEnabled = false
this.linesEnabled = false
this.colorBasedOnCandle = false

59
src/general/menu.ts Normal file
View File

@ -0,0 +1,59 @@
import { GlobalParams } from "./global-params";
declare const window: GlobalParams
export class Menu {
private div: HTMLDivElement;
private isOpen: boolean = false;
private widget: any;
constructor(
private makeButton: Function,
private callbackName: string,
items: string[],
activeItem: string,
separator: boolean,
align: 'right'|'left') {
this.div = document.createElement('div')
this.div.classList.add('topbar-menu');
this.widget = this.makeButton(activeItem+' ↓', null, separator, true, align)
this.updateMenuItems(items)
this.widget.elem.addEventListener('click', () => {
this.isOpen = !this.isOpen;
if (!this.isOpen) {
this.div.style.display = 'none';
return;
}
let rect = this.widget.elem.getBoundingClientRect()
this.div.style.display = 'flex'
this.div.style.flexDirection = 'column'
let center = rect.x+(rect.width/2)
this.div.style.left = center-(this.div.clientWidth/2)+'px'
this.div.style.top = rect.y+rect.height+'px'
})
document.body.appendChild(this.div)
}
updateMenuItems(items: string[]) {
this.div.innerHTML = '';
items.forEach(text => {
let button = this.makeButton(text, null, false, false)
button.elem.addEventListener('click', () => {
this.widget.elem.innerText = button.elem.innerText+' ↓'
window.callbackFunction(`${this.callbackName}_~_${button.elem.innerText}`)
this.div.style.display = 'none'
this.isOpen = false
});
button.elem.style.margin = '4px 4px'
button.elem.style.padding = '2px 2px'
this.div.appendChild(button.elem)
})
this.widget.elem.innerText = items[0]+' ↓';
}
}

View File

@ -1,5 +1,6 @@
import { GlobalParams } from "./global-params";
import { Handler } from "./handler";
import { Menu } from "./menu";
declare const window: GlobalParams
@ -85,44 +86,11 @@ export class TopBar {
return textBox
}
makeMenu(items: string[], activeItem: string, separator: boolean, callbackName: string, align='right') {
let menu = document.createElement('div')
menu.classList.add('topbar-menu');
let menuOpen = false;
items.forEach(text => {
let button = this.makeButton(text, null, false, false)
button.elem.addEventListener('click', () => {
widget.elem.innerText = button.elem.innerText+' ↓'
window.callbackFunction(`${callbackName}_~_${button.elem.innerText}`)
menu.style.display = 'none'
menuOpen = false
});
button.elem.style.margin = '4px 4px'
button.elem.style.padding = '2px 2px'
menu.appendChild(button.elem)
})
let widget =
this.makeButton(activeItem+' ↓', null, separator, true, align)
widget.elem.addEventListener('click', () => {
menuOpen = !menuOpen
if (!menuOpen) {
menu.style.display = 'none';
return;
}
let rect = widget.elem.getBoundingClientRect()
menu.style.display = 'flex'
menu.style.flexDirection = 'column'
let center = rect.x+(rect.width/2)
menu.style.left = center-(menu.clientWidth/2)+'px'
menu.style.top = rect.y+rect.height+'px'
})
document.body.appendChild(menu)
makeMenu(items: string[], activeItem: string, separator: boolean, callbackName: string, align: 'right'|'left') {
return new Menu(this.makeButton.bind(this), callbackName, items, activeItem, separator, align)
}
makeButton(defaultText: string, callbackName: string | null, separator: boolean, append=true, align='left') {
makeButton(defaultText: string, callbackName: string | null, separator: boolean, append=true, align='left', toggle=false) {
let button = document.createElement('button')
button.classList.add('topbar-button');
// button.style.color = window.pane.color
@ -137,7 +105,19 @@ export class TopBar {
}
if (callbackName) {
button.addEventListener('click', () => window.callbackFunction(`${widget.callbackName}_~_${button.innerText}`));
let handler;
if (toggle) {
let state = false;
handler = () => {
state = !state
window.callbackFunction(`${widget.callbackName}_~_${state}`)
button.style.backgroundColor = state ? 'var(--active-bg-color)' : '';
button.style.color = state ? 'var(--active-color)' : '';
}
} else {
handler = () => window.callbackFunction(`${widget.callbackName}_~_${button.innerText}`)
}
button.addEventListener('click', handler);
}
if (append) this.appendWidget(button, align, separator)
return widget

View File

@ -0,0 +1,37 @@
import { Coordinate, ISeriesPrimitiveAxisView, PriceFormatBuiltIn } from 'lightweight-charts';
import { HorizontalLine } from './horizontal-line';
export class HorizontalLineAxisView implements ISeriesPrimitiveAxisView {
_source: HorizontalLine;
_y: Coordinate | null = null;
_price: string | null = null;
constructor(source: HorizontalLine) {
this._source = source;
}
update() {
if (!this._source.series || !this._source._point) return;
this._y = this._source.series.priceToCoordinate(this._source._point.price);
const priceFormat = this._source.series.options().priceFormat as PriceFormatBuiltIn;
const precision = priceFormat.precision;
this._price = this._source._point.price.toFixed(precision).toString();
}
visible() {
return true;
}
tickVisible() {
return true;
}
coordinate() {
return this._y ?? 0;
}
text() {
return this._source._options.text || this._price || '';
}
textColor() {
return 'white';
}
backColor() {
return this._source._options.lineColor;
}
}

View File

@ -7,6 +7,7 @@ import { Drawing, InteractionState } from "../drawing/drawing";
import { DrawingOptions } from "../drawing/options";
import { HorizontalLinePaneView } from "./pane-view";
import { GlobalParams } from "../general/global-params";
import { HorizontalLineAxisView } from "./axis-view";
declare const window: GlobalParams;
@ -16,6 +17,7 @@ export class HorizontalLine extends Drawing {
_paneViews: HorizontalLinePaneView[];
_point: Point;
private _callbackName: string | null;
_priceAxisViews: HorizontalLineAxisView[];
protected _startDragPoint: Point | null = null;
@ -24,6 +26,7 @@ export class HorizontalLine extends Drawing {
this._point = point;
this._point.time = null; // time is null for horizontal lines
this._paneViews = [new HorizontalLinePaneView(this)];
this._priceAxisViews = [new HorizontalLineAxisView(this)];
this._callbackName = callbackName;
}
@ -37,6 +40,15 @@ export class HorizontalLine extends Drawing {
this.requestUpdate();
}
updateAllViews() {
this._paneViews.forEach((pw) => pw.update());
this._priceAxisViews.forEach((tw) => tw.update());
}
priceAxisViews() {
return this._priceAxisViews;
}
_moveToState(state: InteractionState) {
switch(state) {
case InteractionState.NONE:

View File

@ -0,0 +1,35 @@
import { Coordinate, ISeriesPrimitiveAxisView } from "lightweight-charts";
import { VerticalLine } from "./vertical-line";
export class VerticalLineTimeAxisView implements ISeriesPrimitiveAxisView {
_source: VerticalLine;
_x: Coordinate | null = null;
constructor(source: VerticalLine) {
this._source = source;
}
update() {
if (!this._source.chart|| !this._source._point) return;
const point = this._source._point;
const timeScale = this._source.chart.timeScale();
this._x = point.time ? timeScale.timeToCoordinate(point.time) : timeScale.logicalToCoordinate(point.logical);
}
visible() {
return !!this._source._options.text;
}
tickVisible() {
return true;
}
coordinate() {
return this._x ?? 0;
}
text() {
return this._source._options.text || '';
}
textColor() {
return "white";
}
backColor() {
return this._source._options.lineColor;
}
}

View File

@ -16,7 +16,7 @@ export class VerticalLinePaneView extends DrawingPaneView {
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.x = point.time ? timeScale.timeToCoordinate(point.time) : timeScale.logicalToCoordinate(point.logical)
this._point.y = series.priceToCoordinate(point.price);
}

View File

@ -7,6 +7,7 @@ import { Drawing, InteractionState } from "../drawing/drawing";
import { DrawingOptions } from "../drawing/options";
import { VerticalLinePaneView } from "./pane-view";
import { GlobalParams } from "../general/global-params";
import { VerticalLineTimeAxisView } from "./axis-view";
declare const window: GlobalParams;
@ -14,6 +15,7 @@ declare const window: GlobalParams;
export class VerticalLine extends Drawing {
_type = 'VerticalLine';
_paneViews: VerticalLinePaneView[];
_timeAxisViews: VerticalLineTimeAxisView[];
_point: Point;
private _callbackName: string | null;
@ -24,10 +26,27 @@ export class VerticalLine extends Drawing {
this._point = point;
this._paneViews = [new VerticalLinePaneView(this)];
this._callbackName = callbackName;
this._timeAxisViews = [new VerticalLineTimeAxisView(this)]
}
updateAllViews() {
this._paneViews.forEach(pw => pw.update());
this._timeAxisViews.forEach(tw => tw.update());
}
timeAxisViews() {
return this._timeAxisViews;
}
public updatePoints(...points: (Point | null)[]) {
for (const p of points) if (p) this._point = p;
for (const p of points) {
if (!p) continue;
if (!p.time && p.logical) {
p.time = this.series.dataByIndex(p.logical)?.time || null
}
this._point = p;
}
this.requestUpdate();
}