Загрузка данных
import { PriceScaleMode } from 'lightweight-charts';
import { PriceScaleControls as PriceScaleControlsView } from '@components/PriceScaleControls';
import { ReactRenderer } from '@core/ReactRenderer';
import { Direction } from '@src/types';
import { PriceScale } from './PriceScale';
interface PriceScaleControlsParams {
leftPriceScale: PriceScale;
rightPriceScale: PriceScale;
onPriceScaleChange: () => void;
}
export class PriceScaleControls {
private readonly leftPriceScale: PriceScale;
private readonly rightPriceScale: PriceScale;
private readonly onPriceScaleChange: () => void;
private readonly container: HTMLElement;
private readonly renderer: ReactRenderer;
private paneElement: HTMLElement | null = null;
private hoveredPriceScale: PriceScale | null = null;
constructor({
leftPriceScale,
rightPriceScale,
onPriceScaleChange,
}: PriceScaleControlsParams) {
this.leftPriceScale = leftPriceScale;
this.rightPriceScale = rightPriceScale;
this.onPriceScaleChange = onPriceScaleChange;
this.container = document.createElement('div');
this.container.className = 'moex-price-scale-controls';
this.container.style.position = 'absolute';
this.container.style.inset = '0';
this.container.style.display = 'flex';
this.container.style.alignItems = 'center';
this.container.style.justifyContent = 'center';
this.container.style.zIndex = '10';
this.container.style.pointerEvents = 'none';
this.renderer = new ReactRenderer(this.container);
}
public mount(paneElement: HTMLElement): void {
if (this.paneElement === paneElement) {
this.refresh();
return;
}
this.unmount();
this.paneElement = paneElement;
this.paneElement.addEventListener('pointermove', this.handlePointerMove);
this.paneElement.addEventListener('pointerleave', this.handlePointerLeave);
this.refresh();
}
public refresh(): void {
if (!this.hoveredPriceScale) {
this.hide();
return;
}
this.render(this.hoveredPriceScale);
}
public destroy(): void {
this.unmount();
this.renderer.destroy();
}
private handlePointerMove = (event: PointerEvent): void => {
const nextPriceScale = this.getHoveredPriceScale(event.clientX);
if (this.hoveredPriceScale === nextPriceScale) {
return;
}
this.hoveredPriceScale = nextPriceScale;
this.refresh();
};
private handlePointerLeave = (): void => {
this.hoveredPriceScale = null;
this.refresh();
};
private render(priceScale: PriceScale): void {
const priceScaleElement = this.getPriceScaleElement(priceScale);
if (!priceScaleElement || !this.canShowControls(priceScale, priceScaleElement)) {
this.hide();
return;
}
priceScaleElement.style.position = 'relative';
if (this.container.parentElement !== priceScaleElement) {
priceScaleElement.appendChild(this.container);
}
this.renderer.renderComponent(
<PriceScaleControlsView
isAutoScale={priceScale.isAutoScaleEnabled()}
isLogarithmic={priceScale.getMode() === PriceScaleMode.Logarithmic}
onToggleAutoScale={() => {
priceScale.toggleAutoScale();
this.refresh();
this.onPriceScaleChange();
}}
onToggleLogarithmic={() => {
priceScale.toggleLogarithmic();
this.refresh();
this.onPriceScaleChange();
}}
/>,
);
this.container.classList.add('moex-price-scale-controls_visible');
}
private hide(): void {
this.container.classList.remove('moex-price-scale-controls_visible');
}
private getHoveredPriceScale(clientX: number): PriceScale | null {
if (this.isPointerInsidePriceScale(this.leftPriceScale, clientX)) {
return this.leftPriceScale;
}
if (this.isPointerInsidePriceScale(this.rightPriceScale, clientX)) {
return this.rightPriceScale;
}
return null;
}
private isPointerInsidePriceScale(priceScale: PriceScale, clientX: number): boolean {
const priceScaleElement = this.getPriceScaleElement(priceScale);
if (!priceScaleElement || !this.canShowControls(priceScale, priceScaleElement)) {
return false;
}
const rect = priceScaleElement.getBoundingClientRect();
return clientX >= rect.left && clientX <= rect.right;
}
private canShowControls(priceScale: PriceScale, priceScaleElement: HTMLElement): boolean {
return (
priceScale.isVisible() &&
priceScale.hasVisibleSeriesData() &&
priceScaleElement.getBoundingClientRect().width > 0
);
}
private getPriceScaleElement(priceScale: PriceScale): HTMLTableCellElement | null {
if (!this.paneElement) {
return null;
}
const cells = this.paneElement.querySelectorAll<HTMLTableCellElement>(':scope > td');
if (cells.length < 3) {
return null;
}
return priceScale.side === Direction.Left ? cells.item(0) : cells.item(cells.length - 1);
}
private unmount(): void {
if (this.paneElement) {
this.paneElement.removeEventListener('pointermove', this.handlePointerMove);
this.paneElement.removeEventListener('pointerleave', this.handlePointerLeave);
}
this.paneElement = null;
this.hoveredPriceScale = null;
this.container.remove();
this.hide();
}
}
private syncPaneContainers(): void {
const lwcPaneElement = this.lwcPane.getHTMLElement();
if (!lwcPaneElement) {
this.schedulePaneContainerSync();
return;
}
const cells = lwcPaneElement.querySelectorAll<HTMLTableCellElement>(':scope > td');
const chartCell = cells.item(1);
if (!chartCell) {
this.schedulePaneContainerSync();
return;
}
chartCell.style.position = 'relative';
chartCell.appendChild(this.legendContainer);
chartCell.appendChild(this.paneOverlayContainer);
this.priceScaleControls.mount(lwcPaneElement);
}