Загрузка данных
import { IPriceScaleApi, PriceScaleMode } from 'lightweight-charts';
import { PriceScaleControls } from '@components/PriceScaleControls';
import { ReactRenderer } from '@core/ReactRenderer';
import { Direction } from '@src/types';
import type { PriceScaleTarget } from './controller';
interface PriceScaleControlsControllerParams {
chartElement: HTMLElement;
getPaneElement: () => HTMLElement | null;
getPriceScale: (priceScaleId: Direction) => IPriceScaleApi;
getPriceScaleTarget: (priceScaleId: Direction) => PriceScaleTarget;
hasPriceScaleSeries: (priceScaleId: Direction) => boolean;
getSavedPriceScaleMode: (target: PriceScaleTarget) => PriceScaleMode;
togglePriceScaleMode: (target: PriceScaleTarget, mode: PriceScaleMode) => void;
}
export class PriceScaleControlsController {
private readonly chartElement: HTMLElement;
private readonly getPaneElement: () => HTMLElement | null;
private readonly getPriceScale: (priceScaleId: Direction) => IPriceScaleApi;
private readonly getPriceScaleTarget: (priceScaleId: Direction) => PriceScaleTarget;
private readonly hasPriceScaleSeries: (priceScaleId: Direction) => boolean;
private readonly getSavedPriceScaleMode: (target: PriceScaleTarget) => PriceScaleMode;
private readonly togglePriceScaleMode: (target: PriceScaleTarget, mode: PriceScaleMode) => void;
private readonly container: HTMLElement;
private readonly renderer: ReactRenderer;
private paneElement: HTMLElement | null = null;
private hoveredPriceScaleId: Direction | null = null;
constructor({
chartElement,
getPaneElement,
getPriceScale,
getPriceScaleTarget,
hasPriceScaleSeries,
getSavedPriceScaleMode,
togglePriceScaleMode,
}: PriceScaleControlsControllerParams) {
this.chartElement = chartElement;
this.getPaneElement = getPaneElement;
this.getPriceScale = getPriceScale;
this.getPriceScaleTarget = getPriceScaleTarget;
this.hasPriceScaleSeries = hasPriceScaleSeries;
this.getSavedPriceScaleMode = getSavedPriceScaleMode;
this.togglePriceScaleMode = togglePriceScaleMode;
this.container = document.createElement('div');
this.container.className = 'moex-price-scale-controls';
this.container.style.position = 'absolute';
this.container.style.zIndex = '10';
this.container.style.pointerEvents = 'none';
this.renderer = new ReactRenderer(this.container);
}
public mount(): void {
const paneElement = this.getPaneElement();
if (!paneElement) {
return;
}
if (
this.paneElement === paneElement &&
this.container.parentElement === this.chartElement
) {
this.update();
return;
}
this.unmount();
this.paneElement = paneElement;
this.chartElement.appendChild(this.container);
this.chartElement.addEventListener(
'pointermove',
this.handlePointerMove,
);
this.chartElement.addEventListener(
'pointerleave',
this.handlePointerLeave,
);
this.update();
}
public update(): void {
if (!this.hoveredPriceScaleId) {
this.hide();
return;
}
this.render(this.hoveredPriceScaleId);
}
public resetAutoScale(): void {
this.setAutoScale(Direction.Left);
this.setAutoScale(Direction.Right);
this.update();
}
public destroy(): void {
this.unmount();
this.renderer.destroy();
this.container.remove();
}
private handlePointerMove = (event: PointerEvent): void => {
if (!this.paneElement) {
return;
}
const chartRect = this.chartElement.getBoundingClientRect();
const paneRect = this.paneElement.getBoundingClientRect();
const isInsidePane =
event.clientY >= paneRect.top &&
event.clientY < paneRect.bottom;
const nextPriceScaleId = isInsidePane
? this.getHoveredPriceScaleId(
event.clientX - chartRect.left,
chartRect.width,
)
: null;
if (this.hoveredPriceScaleId === nextPriceScaleId) {
if (nextPriceScaleId) {
this.updatePosition(nextPriceScaleId);
}
return;
}
this.hoveredPriceScaleId = nextPriceScaleId;
this.update();
};
private handlePointerLeave = (): void => {
this.hoveredPriceScaleId = null;
this.update();
};
private render(priceScaleId: Direction): void {
if (!this.isPriceScaleAvailable(priceScaleId)) {
this.hide();
return;
}
this.updatePosition(priceScaleId);
const target = this.getPriceScaleTarget(priceScaleId);
const savedMode = this.getSavedPriceScaleMode(target);
this.renderer.renderComponent(
<PriceScaleControls
isAutoScale={this.isAutoScale(priceScaleId)}
isLogarithmic={savedMode === PriceScaleMode.Logarithmic}
onAutoScale={() => {
this.setAutoScale(priceScaleId);
this.update();
}}
onToggleLogarithmic={() => {
this.togglePriceScaleMode(
target,
PriceScaleMode.Logarithmic,
);
this.update();
}}
onRefresh={() => {
this.update();
}}
/>,
);
this.container.classList.add(
'moex-price-scale-controls_visible',
);
}
private hide(): void {
this.container.classList.remove(
'moex-price-scale-controls_visible',
);
}
private getHoveredPriceScaleId(
pointerX: number,
chartWidth: number,
): Direction | null {
const leftWidth = this.getPriceScaleWidth(Direction.Left);
const rightWidth = this.getPriceScaleWidth(Direction.Right);
if (
this.isPriceScaleAvailable(Direction.Left) &&
pointerX >= 0 &&
pointerX <= leftWidth
) {
return Direction.Left;
}
if (
this.isPriceScaleAvailable(Direction.Right) &&
pointerX >= chartWidth - rightWidth &&
pointerX <= chartWidth
) {
return Direction.Right;
}
return null;
}
private updatePosition(priceScaleId: Direction): void {
if (!this.paneElement) {
return;
}
const chartRect = this.chartElement.getBoundingClientRect();
const paneRect = this.paneElement.getBoundingClientRect();
const bottomOffset = Math.max(
0,
chartRect.bottom - paneRect.bottom,
);
this.container.style.width = `${this.getPriceScaleWidth(
priceScaleId,
)}px`;
this.container.style.bottom = `${bottomOffset}px`;
if (priceScaleId === Direction.Left) {
this.container.style.left = '0';
this.container.style.right = '';
return;
}
this.container.style.left = '';
this.container.style.right = '0';
}
private setAutoScale(priceScaleId: Direction): void {
this.getPriceScale(priceScaleId).setAutoScale(true);
}
private isAutoScale(priceScaleId: Direction): boolean {
return (
this.getPriceScale(priceScaleId).options().autoScale ?? true
);
}
private isPriceScaleAvailable(
priceScaleId: Direction,
): boolean {
const priceScale = this.getPriceScale(priceScaleId);
const options = priceScale.options();
return (
options.visible !== false &&
priceScale.width() > 0 &&
this.hasPriceScaleSeries(priceScaleId)
);
}
private getPriceScaleWidth(priceScaleId: Direction): number {
return this.getPriceScale(priceScaleId).width();
}
private unmount(): void {
this.chartElement.removeEventListener(
'pointermove',
this.handlePointerMove,
);
this.chartElement.removeEventListener(
'pointerleave',
this.handlePointerLeave,
);
this.paneElement = null;
this.hoveredPriceScaleId = null;
this.hide();
}
}
this.priceScaleControlsController =
new PriceScaleControlsController({
chartElement: chartContainer,
getPaneElement: () => this.lwcPane.getHTMLElement(),
getPriceScale: (priceScaleId) =>
this.getPriceScale(priceScaleId),
getPriceScaleTarget: (priceScaleId) =>
this.getPriceScaleTarget(priceScaleId),
hasPriceScaleSeries: (priceScaleId) =>
this.hasPriceScaleSeries(priceScaleId),
getSavedPriceScaleMode,
togglePriceScaleMode,
});