implement toggleable buttons; menu items can now be changed; add horizontal line and vertical line labels
This commit is contained in:
@ -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
59
src/general/menu.ts
Normal 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]+' ↓';
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
37
src/horizontal-line/axis-view.ts
Normal file
37
src/horizontal-line/axis-view.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
|
||||
35
src/vertical-line/axis-view.ts
Normal file
35
src/vertical-line/axis-view.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user