Загрузка данных
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 36bac5f..84d127a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# next
+- Добавлена поддержка хоткеев на элементы рисования (alt+t, alt+v, alt+h, alt+f, del, esc)
+
+# 0.1.10
+
- Добавление логарифмической шкалы
# 0.1.9
@@ -9,6 +13,8 @@
# 0.1.8
- Фикс перевода в uppercase всех символов
+- Фикс отображения лейбла на прайс-шкале
+- Удаление пейнов теперь доступно в произвольном порядке
# 0.1.7
diff --git a/src/components/Toolbar/index.tsx b/src/components/Toolbar/index.tsx
index 6f0e5bb..fded0c0 100644
--- a/src/components/Toolbar/index.tsx
+++ b/src/components/Toolbar/index.tsx
@@ -1,10 +1,13 @@
import classNames from 'classnames';
+
import { Button, Divider, Tooltip } from 'exchange-elements/v2';
-import { Dispatch, SetStateAction, useRef, useState } from 'react';
+import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
import { Observable } from 'rxjs';
+import { Hotkeys, Keys } from '@core/Hotkeys';
+
import { MenuList } from '@src/components/Menu';
import SplitDropdown from '@src/components/SplitDropdown';
@@ -39,11 +42,12 @@ import styles from './index.module.scss';
interface ToolbarProps {
toggleDOM: () => void;
- addDrawing: (name: DrawingsNames) => void;
+ addDrawing: (name: DrawingsNames) => Promise<void>;
setEndlessDrawingsMode: (value: boolean) => void;
isEndlessDrawingsMode$: Observable<boolean>;
activateCrosshair: () => void;
activeTool$: Observable<ActiveDrawingTool>;
+ hotkeys: Hotkeys;
}
const implemented = {
@@ -66,6 +70,7 @@ export default function Toolbar({
isEndlessDrawingsMode$,
activateCrosshair,
activeTool$,
+ hotkeys,
}: ToolbarProps) {
const [selectedLineType, setSelectedLineType] = useState<DrawingsNames>(DrawingsNames.trendLine);
const [selectedMeasurementTool, setSelectedMeasurementTool] = useState(DrawingsNames.fixedRangeProfile);
@@ -78,9 +83,9 @@ export default function Toolbar({
const toolbarRef = useRef<HTMLDivElement | null>(null);
- const createDrawingHandler = (setter: Dispatch<SetStateAction<DrawingsNames>>) => (value: DrawingsNames) => {
+ const createDrawingHandler = (setter: Dispatch<SetStateAction<DrawingsNames>>) => async (value: DrawingsNames) => {
setter(value);
- addDrawing(value);
+ await addDrawing(value);
};
const findOptionByValue = <T extends { value: string }>(options: T[], selectedValue: T['value']): T | undefined =>
@@ -98,6 +103,39 @@ export default function Toolbar({
const getTooltipClassName = () => classNames(styles.tooltipHint, TOOLTIP_CLASSNAME);
+ useEffect(() => {
+ hotkeys.register({
+ keys: [Keys.alt, Keys.t],
+ callback: async () => {
+ await createDrawingHandler(setSelectedLineType)(DrawingsNames.trendLine);
+ },
+ });
+ hotkeys.register({
+ keys: [Keys.alt, Keys.h],
+ callback: async () => {
+ await createDrawingHandler(setSelectedLineType)(DrawingsNames.horizontalLine);
+ },
+ });
+ hotkeys.register({
+ keys: [Keys.alt, Keys.v],
+ callback: async () => {
+ await createDrawingHandler(setSelectedLineType)(DrawingsNames.verticalLine);
+ },
+ });
+ hotkeys.register({
+ keys: [Keys.alt, Keys.f],
+ callback: async () => {
+ await createDrawingHandler(setSelectedGannAndFibonacci)(DrawingsNames.fibonacciRetracement);
+ },
+ });
+ hotkeys.register({
+ keys: [Keys.shift],
+ longPress: true,
+ callback: async () => {
+ await addDrawing(DrawingsNames.ruler);
+ },
+ });
+ }, []);
return (
<div
ref={toolbarRef}
diff --git a/src/constants/drawing.ts b/src/constants/drawing.ts
index 34607e5..741bce8 100644
--- a/src/constants/drawing.ts
+++ b/src/constants/drawing.ts
@@ -196,9 +196,10 @@ export const drawingsMap: Record<DrawingsNames, DrawingConfig> = {
},
[DrawingsNames.ruler]: {
singleInstance: true,
- construct: ({ chart, series, eventManager, removeSelf }) => {
+ construct: ({ chart, series, eventManager, container, removeSelf }) => {
return new Ruler(chart, series, {
formatObservable: eventManager.getChartOptionsModel(),
+ container,
resetTriggers: [eventManager.getTimeframeObs(), eventManager.getInterval()],
removeSelf,
});
diff --git a/src/core/Chart.ts b/src/core/Chart.ts
index 5b8fb3f..5382a98 100644
--- a/src/core/Chart.ts
+++ b/src/core/Chart.ts
@@ -22,6 +22,7 @@ import { DataSource } from '@core/DataSource';
import { DOMModel } from '@core/DOMModel';
import { DrawingsManager } from '@core/DrawingsManager';
import { EventManager } from '@core/EventManager';
+import { Hotkeys } from '@core/Hotkeys';
import { IndicatorManager } from '@core/IndicatorManager';
import { ModalRenderer } from '@core/ModalRenderer';
import { PaneManager } from '@core/PaneManager';
@@ -69,6 +70,7 @@ interface ChartParams {
ohlcConfig: OHLCConfig;
tooltipConfig: TooltipConfig;
panes: PaneSnapshot[];
+ hotkeys: Hotkeys;
};
lwcChartConfig: ChartConfig;
}
@@ -171,6 +173,7 @@ export class Chart implements ISerializable<ChartSnapshot> {
chartContainer: this.container,
tooltipConfig,
modalRenderer,
+ hotkeys: params.hotkeys,
});
this.mainSeries = this.paneManager.getMainPane().getMainSerie();
diff --git a/src/core/Drawings.ts b/src/core/Drawings.ts
index 3bf77b9..bbceca5 100644
--- a/src/core/Drawings.ts
+++ b/src/core/Drawings.ts
@@ -2,6 +2,8 @@ import { IChartApi, ISeriesApi, SeriesType } from 'lightweight-charts';
import { DOMObject, DOMObjectParams } from '@core/DOMObject';
import { ISeriesDrawing } from '@core/Drawings/common';
+import { DrawingSnapshotItem } from '@core/DrawingsManager';
+import { Hotkeys, Keys } from '@core/Hotkeys';
import { DrawingsNames } from '@src/constants';
import { SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
import { SettingsTab, SettingsValues } from '@src/types/settings';
@@ -14,12 +16,20 @@ interface DrawingParams extends DOMObjectParams {
mainSeries: SeriesStrategies;
onDelete: (id: string) => void;
construct: (chart: IChartApi, series: ISeriesApi<SeriesType>) => ISeriesDrawing;
+ hotkeys: Hotkeys;
+ setCopyPasteBuffer: (copiedObject: DrawingSnapshotItem) => void;
+ resetActiveTool: () => void;
}
export class Drawing extends DOMObject implements IDrawing {
private lwcDrawing: ISeriesDrawing;
private mainSeries: SeriesStrategies;
private drawingName: DrawingsNames;
+ private hotkeys: Hotkeys;
+
+ private escapeUnregisterHash: string | null = null;
+ private deleteUnregisterHash: string | null = null;
+ private copyUnregisterHash: string | null = null;
constructor({
lwcChart,
@@ -33,13 +43,66 @@ export class Drawing extends DOMObject implements IDrawing {
moveDown,
construct,
paneId,
+ hotkeys,
+ setCopyPasteBuffer,
+ resetActiveTool,
}: DrawingParams) {
super({ id, name, zIndex, onDelete, moveUp, moveDown, paneId });
-
+ this.hotkeys = hotkeys;
this.lwcDrawing = construct(lwcChart, mainSeries);
this.onDelete = onDelete;
+ this.escapeUnregisterHash = hotkeys.register({
+ keys: [Keys.escape],
+ callback: () => {
+ this.delete();
+ resetActiveTool();
+ },
+ });
+
+ this.deleteUnregisterHash = this.hotkeys.register({
+ keys: [Keys.delete],
+ callback: () => {
+ this.delete();
+ },
+ });
+
+ this.lwcDrawing.subscribeIsSelected((isSelected) => {
+ if (isSelected) {
+ this.deleteUnregisterHash = hotkeys.register({
+ keys: [Keys.delete],
+ callback: () => {
+ this.delete();
+ },
+ });
+ this.copyUnregisterHash = hotkeys.register({
+ keys: [Keys.control, Keys.c],
+ callback: () => {
+ if (!this.isCreationPending()) {
+ const copiedObject = {
+ id: this.id,
+ drawingName: this.getDrawingName(),
+ state: this.getState(),
+ };
+ setCopyPasteBuffer(copiedObject);
+ }
+ },
+ });
+ } else {
+ hotkeys.unregister({
+ keys: [Keys.delete],
+ hash: this.deleteUnregisterHash,
+ });
+ }
+ });
this.mainSeries = mainSeries;
this.drawingName = drawingName;
+
+ this.afterCreation(() => {
+ hotkeys.unregister({
+ keys: [Keys.escape],
+ hash: this.escapeUnregisterHash,
+ });
+ });
}
public delete() {
@@ -74,6 +137,10 @@ export class Drawing extends DOMObject implements IDrawing {
return this.lwcDrawing.isCreationPending();
}
+ public async waitForCreation(): Promise<void> {
+ return this.lwcDrawing.waitTillReady();
+ }
+
public shouldShowInObjectTree(): boolean {
return this.lwcDrawing.shouldShowInObjectTree();
}
@@ -103,7 +170,24 @@ export class Drawing extends DOMObject implements IDrawing {
}
public destroy() {
+ this.hotkeys.unregister({
+ keys: [Keys.delete],
+ hash: this.deleteUnregisterHash,
+ });
+ this.hotkeys.unregister({
+ keys: [Keys.escape],
+ hash: this.escapeUnregisterHash,
+ });
+ this.hotkeys.unregister({
+ keys: [Keys.control, Keys.c],
+ hash: this.copyUnregisterHash,
+ });
this.mainSeries.detachPrimitive(this.lwcDrawing);
this.lwcDrawing.destroy();
}
+
+ private async afterCreation(cb: () => void) {
+ await this.lwcDrawing.waitTillReady();
+ cb();
+ }
}
diff --git a/src/core/Drawings/axisLine/axisLine.ts b/src/core/Drawings/axisLine/axisLine.ts
index 0a793df..74faefd 100644
--- a/src/core/Drawings/axisLine/axisLine.ts
+++ b/src/core/Drawings/axisLine/axisLine.ts
@@ -1,26 +1,16 @@
-import {
- AutoscaleInfo,
- CrosshairMode,
- IChartApi,
- IPrimitivePaneView,
- Logical,
- PrimitiveHoveredItem,
- SeriesAttachedParameter,
- SeriesOptionsMap,
- Time,
- UTCTimestamp,
-} from 'lightweight-charts';
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, Time, UTCTimestamp } from 'lightweight-charts';
+import { Observable } from 'rxjs';
import { CustomPriceAxisView, CustomTimeAxisView } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
- getPointerPoint as getPointerPointFromEvent,
getPriceFromYCoordinate,
getTimeFromXCoordinate,
getXCoordinateFromTime,
getYCoordinateFromPrice,
isNearPoint,
} from '@core/Drawings/helpers';
+import { AxisSegment } from '@core/Drawings/types';
import { updateViews } from '@core/Drawings/utils';
import { getThemeStore } from '@src/theme';
@@ -40,7 +30,7 @@ import {
import type { ISeriesDrawing } from '@core/Drawings/common';
import type { AxisLabel, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
export type AxisLineDirection = 'vertical' | 'horizontal';
@@ -73,22 +63,15 @@ export interface AxisLineRenderData extends AxisLineStyle, AxisLineTextStyle {
const HANDLE_HIT_TOLERANCE = 8;
const LINE_HIT_TOLERANCE = 6;
-export class AxisLine implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private container: HTMLElement;
+export class AxisLine extends SeriesDrawingBase<AxisLineSettings> implements ISeriesDrawing {
private removeSelf?: () => void;
private openSettings?: () => void;
- private settings: AxisLineSettings = createDefaultSettings();
+ protected settings: AxisLineSettings = createDefaultSettings();
- private requestUpdate: (() => void) | null = null;
- private subscriptions = new Subscription();
private isBound = false;
- private hidden = false;
- private isActive = false;
- private mode: AxisLineMode = 'idle';
+ protected mode: AxisLineMode = 'idle';
private direction: AxisLineDirection;
private time: Time | null = null;
@@ -110,10 +93,8 @@ export class AxisLine implements ISeriesDrawing {
series: SeriesApi,
{ direction, container, formatObservable, removeSelf, openSettings }: AxisLineParams,
) {
- this.chart = chart;
- this.series = series;
+ super({ chart, series, container });
this.direction = direction;
- this.container = container;
this.removeSelf = removeSelf;
this.openSettings = openSettings;
@@ -141,53 +122,14 @@ export class AxisLine implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.showCrosshair();
- this.render();
- }
-
- public destroy(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.showCrosshair();
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
public getState(): AxisLineState {
return {
hidden: this.hidden,
- isActive: this.isActive,
+ isActive: this.isActive.value,
mode: this.mode,
time: this.time,
price: this.price,
@@ -195,6 +137,14 @@ export class AxisLine implements ISeriesDrawing {
};
}
+ public timeAxisPaneViews(): readonly IPrimitivePaneView[] {
+ return [];
+ }
+
+ public priceAxisPaneViews(): readonly IPrimitivePaneView[] {
+ return [];
+ }
+
public setState(state: unknown): void {
const nextState = state as Partial<AxisLineState>;
@@ -203,7 +153,7 @@ export class AxisLine implements ISeriesDrawing {
}
if ('isActive' in nextState && typeof nextState.isActive === 'boolean') {
- this.isActive = nextState.isActive;
+ this.isActive.next(nextState.isActive);
}
if ('mode' in nextState && nextState.mode) {
@@ -228,34 +178,10 @@ export class AxisLine implements ISeriesDrawing {
this.render();
}
- public getSettings(): SettingsValues {
- return { ...this.settings };
- }
-
public getSettingsTabs(): SettingsTab[] {
return getAxisLineSettingsTabs(this.settings);
}
- public updateSettings(settings: SettingsValues): void {
- this.settings = {
- ...this.settings,
- ...settings,
- };
-
- this.render();
- }
-
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([this.paneView, this.timeAxisView, this.priceAxisView]);
}
@@ -272,10 +198,6 @@ export class AxisLine implements ISeriesDrawing {
return this.direction === 'horizontal' ? [this.priceAxisView] : [];
}
- public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): AxisLineRenderData | null {
if (this.hidden) {
return null;
@@ -293,7 +215,7 @@ export class AxisLine implements ISeriesDrawing {
direction: this.direction,
coordinate,
handle: this.direction === 'vertical' ? { x: coordinate, y: height / 2 } : { x: width / 2, y: coordinate },
- showHandle: this.isActive,
+ showHandle: this.isActive.value,
...this.settings,
};
}
@@ -310,7 +232,7 @@ export class AxisLine implements ISeriesDrawing {
return null;
}
- if (this.isActive && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE)) {
+ if (this.isActive.value && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE)) {
return {
cursorStyle: this.getCursorStyle(),
externalId: 'axis-line',
@@ -330,7 +252,7 @@ export class AxisLine implements ISeriesDrawing {
}
public getTimeAxisLabel(kind: string): AxisLabel | null {
- if (kind !== 'main' || this.direction !== 'vertical' || !this.isActive || this.time === null) {
+ if (kind !== 'main' || this.direction !== 'vertical' || !this.isActive.value || this.time === null) {
return null;
}
@@ -356,7 +278,7 @@ export class AxisLine implements ISeriesDrawing {
}
public getPriceAxisLabel(kind: string): AxisLabel | null {
- if (kind !== 'main' || this.direction !== 'horizontal' || !this.isActive || this.price === null) {
+ if (kind !== 'main' || this.direction !== 'horizontal' || !this.isActive.value || this.price === null) {
return null;
}
@@ -376,7 +298,19 @@ export class AxisLine implements ISeriesDrawing {
};
}
- private bindEvents(): void {
+ public getTimeAxisSegments(): AxisSegment[] {
+ return [];
+ }
+
+ public getPriceAxisSegments(): AxisSegment[] {
+ return [];
+ }
+
+ protected getGeometry(): void {
+ console.log('stub');
+ }
+
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -390,7 +324,7 @@ export class AxisLine implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -421,7 +355,7 @@ export class AxisLine implements ISeriesDrawing {
y: event.clientY - rect.top,
};
- const isNearHandle = this.isActive && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE);
+ const isNearHandle = this.isActive.value && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE);
const isNearLine = this.isPointNearLine(point, data.coordinate);
if (!isNearHandle && !isNearLine) {
@@ -446,8 +380,10 @@ export class AxisLine implements ISeriesDrawing {
event.stopPropagation();
this.updateLine(point);
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'ready';
+ this.resolveReady?.();
+
this.render();
return;
}
@@ -462,10 +398,10 @@ export class AxisLine implements ISeriesDrawing {
return;
}
- const isNearHandle = this.isActive && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE);
+ const isNearHandle = this.isActive.value && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE);
const isNearLine = this.isPointNearLine(point, data.coordinate);
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!isNearLine) {
return;
}
@@ -473,13 +409,13 @@ export class AxisLine implements ISeriesDrawing {
event.preventDefault();
event.stopPropagation();
- this.isActive = true;
+ this.isActive.next(true);
this.render();
return;
}
if (!isNearHandle && !isNearLine) {
- this.isActive = false;
+ this.isActive.next(false);
this.render();
return;
}
@@ -511,6 +447,8 @@ export class AxisLine implements ISeriesDrawing {
}
this.mode = 'ready';
+ this.resolveReady?.();
+
this.dragPointerId = null;
this.showCrosshair();
this.render();
@@ -552,29 +490,4 @@ export class AxisLine implements ISeriesDrawing {
private getCursorStyle(): PrimitiveHoveredItem['cursorStyle'] {
return this.direction === 'vertical' ? 'ew-resize' : 'ns-resize';
}
-
- private hideCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Hidden,
- },
- });
- }
-
- private showCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Normal,
- },
- });
- }
-
- private getEventPoint(event: PointerEvent): Point {
- return getPointerPointFromEvent(this.container, event);
- }
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
diff --git a/src/core/Drawings/common.ts b/src/core/Drawings/common.ts
index 2b3bed2..37094b0 100644
--- a/src/core/Drawings/common.ts
+++ b/src/core/Drawings/common.ts
@@ -1,31 +1,34 @@
-import { Coordinate, ISeriesApi, ISeriesPrimitive, SeriesType, Time } from 'lightweight-charts';
+import {
+ AutoscaleInfo,
+ CrosshairMode,
+ IChartApi,
+ IPrimitivePaneView,
+ ISeriesApi,
+ ISeriesPrimitive,
+ ISeriesPrimitiveAxisView,
+ Logical,
+ PrimitiveHoveredItem,
+ SeriesAttachedParameter,
+ SeriesOptionsMap,
+ SeriesType,
+ Time,
+} from 'lightweight-charts';
-import { SettingsTab, SettingsValues } from '@src/types';
+import { BehaviorSubject, distinctUntilChanged, Observable, Subscription } from 'rxjs';
-export interface BitmapPositionLength {
- /** coordinate for use with a bitmap rendering scope */
- position: number;
- /** length for use with a bitmap rendering scope */
- length: number;
-}
+import { getPointerPoint as getPointerPointFromEvent } from '@core/Drawings/helpers';
+import { AxisLabel, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
-export interface ViewPoint {
- x: Coordinate | null;
- y: Coordinate | null;
-}
+import { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
-export interface Point {
- time: Time;
- price: number;
-}
-
-// todo: make base abstract class and extend all drawings by this
export interface ISeriesDrawing extends ISeriesPrimitive<Time> {
show(): void;
hide(): void;
rebind(series: ISeriesApi<SeriesType>): void;
destroy(): void;
isCreationPending(): boolean;
+ waitTillReady(): Promise<void>;
+
shouldShowInObjectTree(): boolean;
getState(): unknown;
@@ -34,14 +37,165 @@ export interface ISeriesDrawing extends ISeriesPrimitive<Time> {
getSettings(): SettingsValues;
updateSettings(settings: SettingsValues): void;
getSettingsTabs(): SettingsTab[];
+
+ subscribeIsSelected(cb: (isSelected: boolean) => void): void;
+}
+
+interface SeriesDrawingBaseParams {
+ container: HTMLElement;
+ chart: IChartApi;
+ series: SeriesApi;
}
-export function positionsBox(position1Media: number, position2Media: number, pixelRatio: number): BitmapPositionLength {
- const scaledPosition1 = Math.round(pixelRatio * position1Media);
- const scaledPosition2 = Math.round(pixelRatio * position2Media);
+export abstract class SeriesDrawingBase<TSettings extends SettingsValues = SettingsValues> implements ISeriesDrawing {
+ protected hidden = false;
+ protected chart: IChartApi;
+ protected series: SeriesApi;
+ protected subscriptions = new Subscription();
+ protected abstract mode: unknown;
+ protected abstract settings: TSettings;
+ protected readonly container: HTMLElement;
+ protected isActive: BehaviorSubject<boolean> = new BehaviorSubject(false);
+
+ protected readyPromise: null | Promise<void> = null;
+ protected resolveReady: null | (() => void) = null;
+
+ protected requestUpdate: (() => void) | null = null;
+
+ constructor({ chart, series, container }: SeriesDrawingBaseParams) {
+ this.chart = chart;
+ this.series = series;
+ this.container = container;
+ }
+
+ public subscribeIsSelected(cb: (isSelected: boolean) => void) {
+ return this.isActive.pipe(distinctUntilChanged()).subscribe(cb);
+ }
+
+ public show(): void {
+ this.hidden = false;
+ this.render();
+ }
+
+ public hide(): void {
+ this.hidden = true;
+ this.showCrosshair();
+ this.render();
+ }
+
+ public rebind(series: SeriesApi): void {
+ if (this.series === series) {
+ return;
+ }
+
+ this.showCrosshair();
+ this.unbindEvents();
+ this.series.detachPrimitive(this as unknown as ISeriesPrimitive<Time>);
+
+ this.series = series;
+ this.requestUpdate = null;
+
+ this.series.attachPrimitive(this as unknown as ISeriesPrimitive<Time>);
+ this.render();
+ }
+
+ public destroy(): void {
+ this.showCrosshair();
+ this.unbindEvents();
+ this.subscriptions.unsubscribe();
+ this.series.detachPrimitive(this as unknown as ISeriesPrimitive<Time>);
+ this.requestUpdate = null;
+ }
+
+ public waitTillReady(): Promise<void> {
+ if (this.mode === 'ready') {
+ return Promise.resolve();
+ }
+ if (!this.readyPromise) {
+ this.readyPromise = new Promise((resolve) => {
+ this.resolveReady = resolve as () => void;
+ });
+ }
+
+ return this.readyPromise;
+ }
+
+ public shouldShowInObjectTree(): boolean {
+ return this.mode !== 'idle';
+ }
+
+ public getSettings(): SettingsValues {
+ return { ...this.settings };
+ }
+
+ public updateSettings(settings: SettingsValues): void {
+ this.settings = {
+ ...this.settings,
+ ...settings,
+ };
+
+ this.render();
+ }
+
+ public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
+ this.requestUpdate = param.requestUpdate;
+ this.bindEvents();
+ }
+
+ public detached(): void {
+ this.showCrosshair();
+ this.unbindEvents();
+ this.requestUpdate = null;
+ }
+
+ public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
+ return null;
+ }
+
+ protected render(): void {
+ this.updateAllViews();
+ this.requestUpdate?.();
+ }
+
+ protected hideCrosshair(): void {
+ this.chart.applyOptions({
+ crosshair: {
+ mode: CrosshairMode.Hidden,
+ },
+ });
+ }
+
+ protected showCrosshair(): void {
+ this.chart.applyOptions({
+ crosshair: {
+ mode: CrosshairMode.Normal,
+ },
+ });
+ }
+
+ protected getEventPoint(event: PointerEvent): Point {
+ return getPointerPointFromEvent(this.container, event);
+ }
+
+ public abstract getRenderData(): any; // todo: make proper type // todo: добавить в интерфейс\точно ли паблик
+ public abstract hitTest(x: number, y: number): PrimitiveHoveredItem | null; // todo: добавить в интерфейс\точно ли паблик
+ public abstract getTimeAxisSegments(): AxisSegment[]; // todo: добавить в интерфейс\точно ли паблик
+ public abstract getPriceAxisSegments(): AxisSegment[]; // todo: добавить в интерфейс\точно ли паблик
+ public abstract getTimeAxisLabel(kind: string): AxisLabel | null; // todo: добавить в интерфейс\точно ли паблик
+ public abstract getPriceAxisLabel(kind: string): AxisLabel | null; // todo: добавить в интерфейс\точно ли паблик
+ protected abstract bindEvents(): void;
+ public abstract updateAllViews(): void;
+ protected abstract unbindEvents(): void;
+ protected abstract getGeometry(): any; // todo: make proper type // todo: добавить в интерфейс\точно ли паблик
+
+ public abstract getSettingsTabs(): SettingsTab[];
+ public abstract getState(): unknown;
+ public abstract isCreationPending(): boolean;
- return {
- position: Math.min(scaledPosition1, scaledPosition2),
- length: Math.abs(scaledPosition1 - scaledPosition2) + 1,
- };
+ public abstract paneViews(): readonly IPrimitivePaneView[];
+ public abstract priceAxisPaneViews(): readonly IPrimitivePaneView[];
+ public abstract priceAxisViews(): readonly ISeriesPrimitiveAxisView[];
+ public abstract setState(state: unknown): void;
+ public abstract timeAxisPaneViews(): readonly IPrimitivePaneView[];
+ public abstract timeAxisViews(): readonly ISeriesPrimitiveAxisView[];
}
diff --git a/src/core/Drawings/diapson/diapson.ts b/src/core/Drawings/diapson/diapson.ts
index bfbef38..b6f80a2 100644
--- a/src/core/Drawings/diapson/diapson.ts
+++ b/src/core/Drawings/diapson/diapson.ts
@@ -1,4 +1,4 @@
-import { Observable, Subscription } from 'rxjs';
+import { Observable } from 'rxjs';
import {
CustomPriceAxisPaneView,
@@ -6,12 +6,12 @@ import {
CustomTimeAxisPaneView,
CustomTimeAxisView,
} from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
clamp,
clampPointToContainer as clampPointToContainerInElement,
getAnchorFromPoint,
getContainerSize as getElementContainerSize,
- getPointerPoint as getPointerPointFromEvent,
getXCoordinateFromTime,
getYCoordinateFromPrice,
isNearPoint,
@@ -37,18 +37,8 @@ import {
import type { ISeriesDrawing } from '@core/Drawings/common';
import type { Anchor, AxisLabel, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
-import type {
- AutoscaleInfo,
- IChartApi,
- IPrimitivePaneView,
- Logical,
- PrimitiveHoveredItem,
- SeriesAttachedParameter,
- SeriesOptionsMap,
- Time,
- UTCTimestamp,
-} from 'lightweight-charts';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
+import type { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, Time, UTCTimestamp } from 'lightweight-charts';
export type DiapsonRangeMode = 'date' | 'price';
@@ -114,22 +104,15 @@ const BODY_HIT_TOLERANCE = 6;
const MIN_RECTANGLE_WIDTH = 6;
const MIN_RECTANGLE_HEIGHT = 6;
-export class Diapson implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private container: HTMLElement;
+export class Diapson extends SeriesDrawingBase<DiapsonSettings> implements ISeriesDrawing {
private removeSelf?: () => void;
private openSettings?: () => void;
- private settings: DiapsonSettings = createDefaultSettings();
+ protected settings: DiapsonSettings = createDefaultSettings();
- private requestUpdate: (() => void) | null = null;
private isBound = false;
- private subscriptions = new Subscription();
- private hidden = false;
- private isActive = false;
- private mode: DiapsonMode = 'idle';
+ protected mode: DiapsonMode = 'idle';
private rangeMode: DiapsonRangeMode;
private startTime: Time | null = null;
@@ -160,11 +143,10 @@ export class Diapson implements ISeriesDrawing {
private readonly bottomPriceAxisView: CustomPriceAxisView;
constructor(chart: IChartApi, series: SeriesApi, params: DiapsonParams) {
- const { container, rangeMode, formatObservable, removeSelf, openSettings, stepSize = 1, stepLabel = '' } = params;
+ super({ chart, series, container: params.container });
+
+ const { rangeMode, formatObservable, removeSelf, openSettings, stepSize = 1, stepLabel = '' } = params;
- this.chart = chart;
- this.series = series;
- this.container = container;
this.rangeMode = rangeMode;
this.removeSelf = removeSelf;
this.openSettings = openSettings;
@@ -213,46 +195,10 @@ export class Diapson implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.render();
- }
-
- public destroy(): void {
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle' || this.mode === 'drawing';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
public setRangeMode(nextMode: DiapsonRangeMode): void {
if (this.rangeMode === nextMode) {
return;
@@ -265,7 +211,7 @@ export class Diapson implements ISeriesDrawing {
public getState(): DiapsonState {
return {
hidden: this.hidden,
- isActive: this.isActive,
+ isActive: this.isActive.value,
mode: this.mode,
rangeMode: this.rangeMode,
startTime: this.startTime,
@@ -284,7 +230,9 @@ export class Diapson implements ISeriesDrawing {
const nextState = state as Partial<DiapsonState>;
this.hidden = typeof nextState.hidden === 'boolean' ? nextState.hidden : this.hidden;
- this.isActive = typeof nextState.isActive === 'boolean' ? nextState.isActive : this.isActive;
+ if (typeof nextState.isActive === 'boolean') {
+ this.isActive.next(nextState.isActive);
+ }
this.mode = nextState.mode ?? this.mode;
this.rangeMode = nextState.rangeMode ?? this.rangeMode;
this.startTime = 'startTime' in nextState ? (nextState.startTime ?? null) : this.startTime;
@@ -302,33 +250,10 @@ export class Diapson implements ISeriesDrawing {
this.render();
}
- public getSettings(): SettingsValues {
- return { ...this.settings };
- }
-
public getSettingsTabs(): SettingsTab[] {
return getDiapsonSettingsTabs(this.settings);
}
- public updateSettings(settings: SettingsValues): void {
- this.settings = {
- ...this.settings,
- ...settings,
- };
-
- this.render();
- }
-
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([
this.paneView,
@@ -361,10 +286,6 @@ export class Diapson implements ISeriesDrawing {
return [this.topPriceAxisView, this.bottomPriceAxisView];
}
- public autoscaleInfo(_startTimePoint: Logical, _endTimePoint: Logical): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): DiapsonRenderData | null {
if (this.hidden) {
return null;
@@ -380,14 +301,14 @@ export class Diapson implements ISeriesDrawing {
...geometry,
rangeMode: this.rangeMode,
showFill: true,
- showHandles: this.isActive,
+ showHandles: this.isActive.value,
labelLines: this.getLabelLines(),
...this.settings,
};
}
public getTimeAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -409,7 +330,7 @@ export class Diapson implements ISeriesDrawing {
}
public getPriceAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -431,7 +352,7 @@ export class Diapson implements ISeriesDrawing {
}
public getTimeAxisLabel(kind: string): AxisLabel | null {
- if (!this.isActive || (kind !== 'left' && kind !== 'right')) {
+ if (!this.isActive.value || (kind !== 'left' && kind !== 'right')) {
return null;
}
@@ -454,7 +375,7 @@ export class Diapson implements ISeriesDrawing {
}
public getPriceAxisLabel(kind: string): AxisLabel | null {
- if (!this.isActive || (kind !== 'top' && kind !== 'bottom')) {
+ if (!this.isActive.value || (kind !== 'top' && kind !== 'bottom')) {
return null;
}
@@ -483,7 +404,7 @@ export class Diapson implements ISeriesDrawing {
const point = { x, y };
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!this.containsPoint(point)) {
return null;
}
@@ -516,7 +437,7 @@ export class Diapson implements ISeriesDrawing {
};
}
- private bindEvents(): void {
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -530,7 +451,7 @@ export class Diapson implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -589,7 +510,7 @@ export class Diapson implements ISeriesDrawing {
return;
}
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!this.containsPoint(point)) {
return;
}
@@ -597,7 +518,7 @@ export class Diapson implements ISeriesDrawing {
event.preventDefault();
event.stopPropagation();
- this.isActive = true;
+ this.isActive.next(true);
this.render();
return;
}
@@ -605,7 +526,7 @@ export class Diapson implements ISeriesDrawing {
const dragTarget = this.getDragTarget(point);
if (!dragTarget) {
- this.isActive = false;
+ this.isActive.next(false);
this.render();
return;
}
@@ -660,7 +581,7 @@ export class Diapson implements ISeriesDrawing {
this.startPrice = anchor.price;
this.endPrice = anchor.price;
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'drawing';
this.render();
}
@@ -697,6 +618,8 @@ export class Diapson implements ISeriesDrawing {
}
this.mode = 'ready';
+ this.resolveReady?.();
+
this.render();
}
@@ -712,6 +635,8 @@ export class Diapson implements ISeriesDrawing {
private finishDragging(): void {
this.mode = 'ready';
+ this.resolveReady?.();
+
this.clearInteractionState();
this.render();
}
@@ -725,7 +650,7 @@ export class Diapson implements ISeriesDrawing {
private resetToIdle(): void {
this.hidden = false;
- this.isActive = false;
+ this.isActive.next(false);
this.mode = 'idle';
this.startTime = null;
this.endTime = null;
@@ -813,7 +738,7 @@ export class Diapson implements ISeriesDrawing {
return getAnchorFromPoint(this.chart, this.series, point);
}
- private getGeometry(): DiapsonGeometry | null {
+ protected getGeometry(): DiapsonGeometry | null {
if (this.startTime === null || this.endTime === null || this.startPrice === null || this.endPrice === null) {
return null;
}
@@ -1212,13 +1137,4 @@ export class Diapson implements ISeriesDrawing {
private clampPointToContainer(point: Point): Point {
return clampPointToContainerInElement(point, this.container);
}
-
- private getEventPoint(event: PointerEvent): Point {
- return getPointerPointFromEvent(this.container, event);
- }
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
diff --git a/src/core/Drawings/fibonacciRetracement/fibonacciRetracement.ts b/src/core/Drawings/fibonacciRetracement/fibonacciRetracement.ts
index e78ba00..3b2223f 100644
--- a/src/core/Drawings/fibonacciRetracement/fibonacciRetracement.ts
+++ b/src/core/Drawings/fibonacciRetracement/fibonacciRetracement.ts
@@ -1,4 +1,4 @@
-import { Observable, Subscription } from 'rxjs';
+import { Observable } from 'rxjs';
import {
CustomPriceAxisPaneView,
@@ -6,12 +6,12 @@ import {
CustomTimeAxisPaneView,
CustomTimeAxisView,
} from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
clamp,
clampPointToContainer as clampPointToContainerInElement,
getAnchorFromPoint,
getContainerSize as getElementContainerSize,
- getPointerPoint as getPointerPointFromEvent,
getPriceDelta as getPriceDeltaFromCoordinates,
getXCoordinateFromTime,
getYCoordinateFromPrice,
@@ -41,16 +41,7 @@ import {
import type { ISeriesDrawing } from '@core/Drawings/common';
import type { AxisLabel, AxisSegment, Bounds, Point, SeriesApi } from '@core/Drawings/types';
import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
-import type {
- AutoscaleInfo,
- IChartApi,
- IPrimitivePaneView,
- PrimitiveHoveredItem,
- SeriesAttachedParameter,
- SeriesOptionsMap,
- Time,
- UTCTimestamp,
-} from 'lightweight-charts';
+import type { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, Time, UTCTimestamp } from 'lightweight-charts';
type FibonacciRetracementMode = 'idle' | 'drawing' | 'ready' | 'dragging';
type FibonacciRetracementHandle = 'body' | 'start' | 'end' | null;
@@ -120,22 +111,15 @@ const LINE_HIT_TOLERANCE = 6;
const MIN_DISTANCE = 6;
const PERCENT_DIVIDER = 100;
-export class FibonacciRetracement implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private container: HTMLElement;
+export class FibonacciRetracement extends SeriesDrawingBase<FibonacciRetracementSettings> implements ISeriesDrawing {
private removeSelf?: () => void;
private openSettings?: () => void;
- private settings: FibonacciRetracementSettings = createDefaultSettings();
+ protected settings: FibonacciRetracementSettings = createDefaultSettings();
- private requestUpdate: (() => void) | null = null;
private isBound = false;
- private subscriptions = new Subscription();
- private hidden = false;
- private isActive = false;
- private mode: FibonacciRetracementMode = 'idle';
+ protected mode: FibonacciRetracementMode = 'idle';
private startTime: Time | null = null;
private endTime: Time | null = null;
@@ -167,9 +151,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
series: SeriesApi,
{ container, formatObservable, removeSelf, openSettings }: FibonacciRetracementParams,
) {
- this.chart = chart;
- this.series = series;
- this.container = container;
+ super({ chart, series, container });
this.removeSelf = removeSelf;
this.openSettings = openSettings;
@@ -215,50 +197,14 @@ export class FibonacciRetracement implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.render();
- }
-
- public destroy(): void {
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle' || this.mode === 'drawing';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
public getState(): FibonacciRetracementState {
return {
hidden: this.hidden,
- isActive: this.isActive,
+ isActive: this.isActive.value,
mode: this.mode,
startTime: this.startTime,
endTime: this.endTime,
@@ -272,7 +218,9 @@ export class FibonacciRetracement implements ISeriesDrawing {
const next = state as Partial<FibonacciRetracementState>;
this.hidden = next.hidden ?? this.hidden;
- this.isActive = next.isActive ?? this.isActive;
+ if (next.isActive !== undefined) {
+ this.isActive.next(next.isActive);
+ }
this.mode = next.mode ?? this.mode;
this.startTime = next.startTime ?? this.startTime;
@@ -300,16 +248,6 @@ export class FibonacciRetracement implements ISeriesDrawing {
this.render();
}
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([
this.paneView,
@@ -342,10 +280,6 @@ export class FibonacciRetracement implements ISeriesDrawing {
return [this.topPriceAxisView, this.bottomPriceAxisView];
}
- public autoscaleInfo(): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): FibonacciRetracementRenderData | null {
const geometry = this.hidden ? null : this.getGeometry();
@@ -355,7 +289,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
return {
...geometry,
- showHandles: this.isActive,
+ showHandles: this.isActive.value,
showBackground: this.settings.showBackground,
backgroundOpacity: this.settings.backgroundOpacity / PERCENT_DIVIDER,
@@ -371,7 +305,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
}
public getTimeAxisSegments(): AxisSegment[] {
- const bounds = this.isActive ? this.getTimeBounds() : null;
+ const bounds = this.isActive.value ? this.getTimeBounds() : null;
if (!bounds) {
return [];
@@ -389,7 +323,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
}
public getPriceAxisSegments(): AxisSegment[] {
- const bounds = this.isActive ? this.getPriceBounds() : null;
+ const bounds = this.isActive.value ? this.getPriceBounds() : null;
if (!bounds) {
return [];
@@ -407,7 +341,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
}
public getTimeAxisLabel(kind: string): AxisLabel | null {
- const coordinate = this.isActive ? this.getTimeCoordinate(kind as TimeLabelKind) : null;
+ const coordinate = this.isActive.value ? this.getTimeCoordinate(kind as TimeLabelKind) : null;
const text = this.getTimeText(kind as TimeLabelKind);
if (coordinate === null || !text) {
@@ -425,7 +359,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
}
public getPriceAxisLabel(kind: string): AxisLabel | null {
- const coordinate = this.isActive ? this.getPriceCoordinate(kind as PriceLabelKind) : null;
+ const coordinate = this.isActive.value ? this.getPriceCoordinate(kind as PriceLabelKind) : null;
const text = this.getPriceText(kind as PriceLabelKind);
if (coordinate === null || !text) {
@@ -449,11 +383,11 @@ export class FibonacciRetracement implements ISeriesDrawing {
const point = { x, y };
- if (!this.isActive && !this.containsPoint(point)) {
+ if (!this.isActive.value && !this.containsPoint(point)) {
return null;
}
- const handleTarget = this.isActive ? this.getHandleTarget(point) : null;
+ const handleTarget = this.isActive.value ? this.getHandleTarget(point) : null;
if (handleTarget) {
return {
@@ -468,13 +402,13 @@ export class FibonacciRetracement implements ISeriesDrawing {
}
return {
- cursorStyle: this.isActive ? 'grab' : 'pointer',
+ cursorStyle: this.isActive.value ? 'grab' : 'pointer',
externalId: 'fibonacci-retracement-position',
zOrder: 'top',
};
}
- private bindEvents(): void {
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -488,7 +422,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -543,8 +477,8 @@ export class FibonacciRetracement implements ISeriesDrawing {
return;
}
- if (!this.isActive) {
- this.isActive = this.containsPoint(point);
+ if (!this.isActive.value) {
+ this.isActive.next(this.containsPoint(point));
this.render();
return;
}
@@ -552,7 +486,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
const dragTarget = this.getDragTarget(point);
if (!dragTarget) {
- this.isActive = false;
+ this.isActive.next(false);
this.render();
return;
}
@@ -592,6 +526,8 @@ export class FibonacciRetracement implements ISeriesDrawing {
}
this.mode = 'ready';
+ this.resolveReady?.();
+
this.clearInteractionState();
this.render();
};
@@ -608,7 +544,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
this.startPrice = anchor.price;
this.endPrice = anchor.price;
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'drawing';
this.render();
@@ -645,6 +581,8 @@ export class FibonacciRetracement implements ISeriesDrawing {
}
this.mode = 'ready';
+ this.resolveReady?.();
+
this.render();
}
@@ -670,7 +608,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
private resetToIdle(): void {
this.hidden = false;
- this.isActive = false;
+ this.isActive.next(false);
this.mode = 'idle';
this.startTime = null;
@@ -756,7 +694,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
return getAnchorFromPoint(this.chart, this.series, point);
}
- private getGeometry(): FibonacciRetracementGeometry | null {
+ protected getGeometry(): FibonacciRetracementGeometry | null {
if (this.startTime === null || this.endTime === null || this.startPrice === null || this.endPrice === null) {
return null;
}
@@ -1001,10 +939,6 @@ export class FibonacciRetracement implements ISeriesDrawing {
return clampPointToContainerInElement(point, this.container);
}
- private getEventPoint(event: PointerEvent): Point {
- return getPointerPointFromEvent(this.container, event);
- }
-
private getMousePoint(event: MouseEvent): Point {
const rect = this.container.getBoundingClientRect();
@@ -1013,9 +947,4 @@ export class FibonacciRetracement implements ISeriesDrawing {
y: event.clientY - rect.top,
});
}
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
diff --git a/src/core/Drawings/ray/ray.ts b/src/core/Drawings/ray/ray.ts
index 9076149..6427435 100644
--- a/src/core/Drawings/ray/ray.ts
+++ b/src/core/Drawings/ray/ray.ts
@@ -1,16 +1,5 @@
-import {
- AutoscaleInfo,
- CrosshairMode,
- IChartApi,
- IPrimitivePaneView,
- Logical,
- PrimitiveHoveredItem,
- SeriesAttachedParameter,
- SeriesOptionsMap,
- Time,
- UTCTimestamp,
-} from 'lightweight-charts';
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, UTCTimestamp } from 'lightweight-charts';
+import { Observable } from 'rxjs';
import {
CustomPriceAxisPaneView,
@@ -18,9 +7,9 @@ import {
CustomTimeAxisPaneView,
CustomTimeAxisView,
} from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
getAnchorFromPoint,
- getPointerPoint as getPointerPointFromEvent,
getPriceDelta as getPriceDeltaFromCoordinates,
getXCoordinateFromTime,
getYCoordinateFromPrice,
@@ -40,7 +29,7 @@ import { createDefaultSettings, getRaySettingTabs, RaySettings, RayStyle, RayTex
import type { ISeriesDrawing } from '@core/Drawings/common';
import type { Anchor, AxisLabel, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
type RayMode = 'idle' | 'drawing' | 'ready' | 'dragging-start' | 'dragging-direction' | 'dragging-body';
type TimeLabelKind = 'start' | 'direction';
@@ -80,22 +69,15 @@ const POINT_HIT_TOLERANCE = 8;
const LINE_HIT_TOLERANCE = 6;
const MIN_LINE_SIZE = 4;
-export class Ray implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private container: HTMLElement;
+export class Ray extends SeriesDrawingBase<RaySettings> implements ISeriesDrawing {
private removeSelf?: () => void;
private openSettings?: () => void;
- private settings: RaySettings = createDefaultSettings();
+ protected settings: RaySettings = createDefaultSettings();
- private requestUpdate: (() => void) | null = null;
- private subscriptions = new Subscription();
private isBound = false;
- private hidden = false;
- private isActive = false;
- private mode: RayMode = 'idle';
+ protected mode: RayMode = 'idle';
private startAnchor: Anchor | null = null;
private directionAnchor: Anchor | null = null;
@@ -123,9 +105,7 @@ export class Ray implements ISeriesDrawing {
series: SeriesApi,
{ container, formatObservable, removeSelf, openSettings }: RayParams,
) {
- this.chart = chart;
- this.series = series;
- this.container = container;
+ super({ chart, series, container });
this.removeSelf = removeSelf;
this.openSettings = openSettings;
@@ -171,53 +151,14 @@ export class Ray implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.showCrosshair();
- this.render();
- }
-
- public destroy(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.showCrosshair();
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle' || this.mode === 'drawing';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
public getState(): RayState {
return {
hidden: this.hidden,
- isActive: this.isActive,
+ isActive: this.isActive.value,
mode: this.mode,
startAnchor: this.startAnchor,
directionAnchor: this.directionAnchor,
@@ -233,7 +174,7 @@ export class Ray implements ISeriesDrawing {
}
if ('isActive' in nextState && typeof nextState.isActive === 'boolean') {
- this.isActive = nextState.isActive;
+ this.isActive.next(nextState.isActive);
}
if ('mode' in nextState && nextState.mode) {
@@ -258,34 +199,10 @@ export class Ray implements ISeriesDrawing {
this.render();
}
- public getSettings(): SettingsValues {
- return { ...this.settings };
- }
-
public getSettingsTabs(): SettingsTab[] {
return getRaySettingTabs(this.settings);
}
- public updateSettings(settings: SettingsValues): void {
- this.settings = {
- ...this.settings,
- ...settings,
- };
-
- this.render();
- }
-
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([
this.paneView,
@@ -318,10 +235,6 @@ export class Ray implements ISeriesDrawing {
return [this.startPriceAxisView, this.directionPriceAxisView];
}
- public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): RayRenderData | null {
if (this.hidden) {
return null;
@@ -335,7 +248,7 @@ export class Ray implements ISeriesDrawing {
return {
...geometry,
- showHandles: this.isActive,
+ showHandles: this.isActive.value,
...this.settings,
};
}
@@ -367,7 +280,7 @@ export class Ray implements ISeriesDrawing {
}
public getTimeAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -389,7 +302,7 @@ export class Ray implements ISeriesDrawing {
}
public getPriceAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -411,7 +324,7 @@ export class Ray implements ISeriesDrawing {
}
public getTimeAxisLabel(kind: string): AxisLabel | null {
- if (!this.isActive || (kind !== 'start' && kind !== 'direction')) {
+ if (!this.isActive.value || (kind !== 'start' && kind !== 'direction')) {
return null;
}
@@ -433,7 +346,7 @@ export class Ray implements ISeriesDrawing {
}
public getPriceAxisLabel(kind: string): AxisLabel | null {
- if (!this.isActive || (kind !== 'start' && kind !== 'direction')) {
+ if (!this.isActive.value || (kind !== 'start' && kind !== 'direction')) {
return null;
}
@@ -454,7 +367,7 @@ export class Ray implements ISeriesDrawing {
};
}
- private bindEvents(): void {
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -468,7 +381,7 @@ export class Ray implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -533,7 +446,7 @@ export class Ray implements ISeriesDrawing {
const pointTarget = this.getPointTarget(point);
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!pointTarget && !this.isPointNearRay(point)) {
return;
}
@@ -541,7 +454,7 @@ export class Ray implements ISeriesDrawing {
event.preventDefault();
event.stopPropagation();
- this.isActive = true;
+ this.isActive.next(true);
this.render();
return;
}
@@ -570,7 +483,7 @@ export class Ray implements ISeriesDrawing {
return;
}
- this.isActive = false;
+ this.isActive.next(false);
this.render();
};
@@ -623,7 +536,7 @@ export class Ray implements ISeriesDrawing {
this.startAnchor = anchor;
this.directionAnchor = anchor;
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'drawing';
this.render();
@@ -658,6 +571,7 @@ export class Ray implements ISeriesDrawing {
}
this.mode = 'ready';
+ this.resolveReady?.();
this.render();
}
@@ -675,6 +589,8 @@ export class Ray implements ISeriesDrawing {
private finishDragging(): void {
this.mode = 'ready';
+ this.resolveReady?.();
+
this.dragPointerId = null;
this.dragStartPoint = null;
this.dragStateSnapshot = null;
@@ -731,7 +647,7 @@ export class Ray implements ISeriesDrawing {
return getAnchorFromPoint(this.chart, this.series, point);
}
- private getGeometry(): RayGeometry | null {
+ protected getGeometry(): RayGeometry | null {
if (!this.startAnchor || !this.directionAnchor) {
return null;
}
@@ -913,29 +829,4 @@ export class Ray implements ISeriesDrawing {
return formatPrice(anchor.price) ?? '';
}
-
- private hideCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Hidden,
- },
- });
- }
-
- private showCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Normal,
- },
- });
- }
-
- private getEventPoint(event: PointerEvent): Point {
- return getPointerPointFromEvent(this.container, event);
- }
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
diff --git a/src/core/Drawings/rectangle/rectangle.ts b/src/core/Drawings/rectangle/rectangle.ts
index 3d19a2b..bba8fe1 100644
--- a/src/core/Drawings/rectangle/rectangle.ts
+++ b/src/core/Drawings/rectangle/rectangle.ts
@@ -1,4 +1,4 @@
-import { Observable, Subscription } from 'rxjs';
+import { Observable } from 'rxjs';
import {
CustomPriceAxisPaneView,
@@ -6,6 +6,7 @@ import {
CustomTimeAxisPaneView,
CustomTimeAxisView,
} from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
clamp,
clampPointToContainer as clampPointToContainerInElement,
@@ -96,22 +97,15 @@ const HANDLE_HIT_TOLERANCE = 8;
const BODY_HIT_TOLERANCE = 6;
const MIN_RECTANGLE_SIZE = 6;
-export class Rectangle implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private container: HTMLElement;
+export class Rectangle extends SeriesDrawingBase<RectangleSettings> implements ISeriesDrawing {
private removeSelf?: () => void;
private openSettings?: () => void;
- private settings: RectangleSettings = createDefaultSettings();
+ protected settings: RectangleSettings = createDefaultSettings();
- private requestUpdate: (() => void) | null = null;
private isBound = false;
- private subscriptions = new Subscription();
- private hidden = false;
- private isActive = false;
- private mode: RectangleMode = 'idle';
+ protected mode: RectangleMode = 'idle';
private startTime: Time | null = null;
private endTime: Time | null = null;
@@ -143,9 +137,7 @@ export class Rectangle implements ISeriesDrawing {
series: SeriesApi,
{ container, formatObservable, removeSelf, openSettings }: RectangleParams,
) {
- this.chart = chart;
- this.series = series;
- this.container = container;
+ super({ chart, series, container });
this.removeSelf = removeSelf;
this.openSettings = openSettings;
@@ -191,50 +183,14 @@ export class Rectangle implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.render();
- }
-
- public destroy(): void {
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle' || this.mode === 'drawing';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
public getState(): RectangleState {
return {
hidden: this.hidden,
- isActive: this.isActive,
+ isActive: this.isActive.value,
mode: this.mode,
startTime: this.startTime,
endTime: this.endTime,
@@ -252,7 +208,7 @@ export class Rectangle implements ISeriesDrawing {
}
if ('isActive' in nextState && typeof nextState.isActive === 'boolean') {
- this.isActive = nextState.isActive;
+ this.isActive.next(nextState.isActive);
}
if ('mode' in nextState && nextState.mode) {
@@ -285,33 +241,10 @@ export class Rectangle implements ISeriesDrawing {
this.render();
}
- public getSettings(): SettingsValues {
- return { ...this.settings };
- }
-
public getSettingsTabs(): SettingsTab[] {
return getRectangleSettingsTabs(this.settings);
}
- public updateSettings(settings: SettingsValues): void {
- this.settings = {
- ...this.settings,
- ...settings,
- };
-
- this.render();
- }
-
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([
this.paneView,
@@ -344,10 +277,6 @@ export class Rectangle implements ISeriesDrawing {
return [this.topPriceAxisView, this.bottomPriceAxisView];
}
- public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): RectangleRenderData | null {
if (this.hidden) {
return null;
@@ -362,13 +291,13 @@ export class Rectangle implements ISeriesDrawing {
return {
...geometry,
showFill: true,
- showHandles: this.isActive,
+ showHandles: this.isActive.value,
...this.settings,
};
}
public getTimeAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -390,7 +319,7 @@ export class Rectangle implements ISeriesDrawing {
}
public getPriceAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -412,7 +341,7 @@ export class Rectangle implements ISeriesDrawing {
}
public getTimeAxisLabel(kind: string): AxisLabel | null {
- if (!this.isActive || (kind !== 'left' && kind !== 'right')) {
+ if (!this.isActive.value || (kind !== 'left' && kind !== 'right')) {
return null;
}
@@ -435,7 +364,7 @@ export class Rectangle implements ISeriesDrawing {
}
public getPriceAxisLabel(kind: string): AxisLabel | null {
- if (!this.isActive || (kind !== 'top' && kind !== 'bottom')) {
+ if (!this.isActive.value || (kind !== 'top' && kind !== 'bottom')) {
return null;
}
@@ -464,7 +393,7 @@ export class Rectangle implements ISeriesDrawing {
const point = { x, y };
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!this.containsPoint(point)) {
return null;
}
@@ -497,7 +426,7 @@ export class Rectangle implements ISeriesDrawing {
};
}
- private bindEvents(): void {
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -511,7 +440,7 @@ export class Rectangle implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -574,7 +503,7 @@ export class Rectangle implements ISeriesDrawing {
return;
}
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!this.containsPoint(point)) {
return;
}
@@ -582,7 +511,7 @@ export class Rectangle implements ISeriesDrawing {
event.preventDefault();
event.stopPropagation();
- this.isActive = true;
+ this.isActive.next(true);
this.render();
return;
}
@@ -590,7 +519,7 @@ export class Rectangle implements ISeriesDrawing {
const dragTarget = this.getDragTarget(point);
if (!dragTarget) {
- this.isActive = false;
+ this.isActive.next(false);
this.render();
return;
}
@@ -645,7 +574,7 @@ export class Rectangle implements ISeriesDrawing {
this.startPrice = anchor.price;
this.endPrice = anchor.price;
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'drawing';
this.render();
@@ -679,6 +608,7 @@ export class Rectangle implements ISeriesDrawing {
}
this.mode = 'ready';
+ this.resolveReady?.();
this.render();
}
@@ -697,6 +627,7 @@ export class Rectangle implements ISeriesDrawing {
private finishDragging(): void {
this.mode = 'ready';
+ this.resolveReady?.();
this.clearInteractionState();
this.render();
@@ -712,7 +643,7 @@ export class Rectangle implements ISeriesDrawing {
private resetToIdle(): void {
this.hidden = false;
- this.isActive = false;
+ this.isActive.next(false);
this.mode = 'idle';
this.startTime = null;
@@ -856,7 +787,7 @@ export class Rectangle implements ISeriesDrawing {
return getAnchorFromPoint(this.chart, this.series, point);
}
- private getGeometry(): RectangleGeometry | null {
+ protected getGeometry(): RectangleGeometry | null {
if (this.startTime === null || this.endTime === null || this.startPrice === null || this.endPrice === null) {
return null;
}
@@ -1077,13 +1008,4 @@ export class Rectangle implements ISeriesDrawing {
private clampPointToContainer(point: Point): Point {
return clampPointToContainerInElement(point, this.container);
}
-
- private getEventPoint(event: PointerEvent): Point {
- return getPointerPointFromEvent(this.container, event);
- }
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
diff --git a/src/core/Drawings/ruler/ruler.ts b/src/core/Drawings/ruler/ruler.ts
index 5850c13..29e1524 100644
--- a/src/core/Drawings/ruler/ruler.ts
+++ b/src/core/Drawings/ruler/ruler.ts
@@ -1,4 +1,5 @@
-import { Observable, skip, Subscription } from 'rxjs';
+import { PrimitiveHoveredItem } from 'lightweight-charts';
+import { Observable, skip } from 'rxjs';
import {
CustomPriceAxisPaneView,
@@ -6,6 +7,7 @@ import {
CustomTimeAxisPaneView,
CustomTimeAxisView,
} from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import { getPriceFromYCoordinate, getXCoordinateFromTime, getYCoordinateFromPrice } from '@core/Drawings/helpers';
import { updateViews } from '@core/Drawings/utils';
@@ -48,6 +50,7 @@ interface RulerState {
}
interface RulerParams {
+ container: HTMLElement;
formatObservable?: Observable<ChartOptionsModel>;
resetTriggers?: Observable<unknown>[];
removeSelf?: () => void;
@@ -65,14 +68,9 @@ export interface RulerRenderData {
verticalArrowSide: Direction.Top | Direction.Bottom | null;
}
-export class Ruler implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
-
- private requestUpdate: (() => void) | null = null;
+export class Ruler extends SeriesDrawingBase implements ISeriesDrawing {
private removeSelf?: () => void;
-
- private subscriptions = new Subscription();
+ protected settings: SettingsValues = {};
private displayFormat: ChartOptionsModel = {
dateFormat: Defaults.dateFormat,
@@ -80,8 +78,7 @@ export class Ruler implements ISeriesDrawing {
showTime: Defaults.showTime,
};
- private hidden = false;
- private mode: RulerMode = 'idle';
+ protected mode: RulerMode = 'idle';
private startAnchor: Anchor | null = null;
private endAnchor: Anchor | null = null;
@@ -99,9 +96,12 @@ export class Ruler implements ISeriesDrawing {
private readonly startPriceAxisView: CustomPriceAxisView;
private readonly endPriceAxisView: CustomPriceAxisView;
- constructor(chart: IChartApi, series: SeriesApi, { resetTriggers = [], formatObservable, removeSelf }: RulerParams) {
- this.chart = chart;
- this.series = series;
+ constructor(
+ chart: IChartApi,
+ series: SeriesApi,
+ { resetTriggers = [], formatObservable, removeSelf, container }: RulerParams,
+ ) {
+ super({ chart, series, container });
this.removeSelf = removeSelf;
this.clickHandler = (params) => this.handleClick(params);
@@ -146,7 +146,7 @@ export class Ruler implements ISeriesDrawing {
);
}
- resetTriggers.forEach((trigger) => {
+ (resetTriggers ?? []).forEach((trigger) => {
this.subscriptions.add(
trigger.pipe(skip(1)).subscribe(() => {
this.removeSelf?.();
@@ -157,57 +157,14 @@ export class Ruler implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.render();
- }
-
- public destroy(): void {
- this.setCrosshairVisible(true);
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle' || this.mode === 'placingEnd';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
- public getSettings(): SettingsValues {
- return {};
- }
-
public getSettingsTabs(): SettingsTab[] {
return [];
}
- public updateSettings(_settings: SettingsValues): void {}
-
public getState(): RulerState {
return {
hidden: this.hidden,
@@ -228,17 +185,6 @@ export class Ruler implements ISeriesDrawing {
this.render();
}
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.setCrosshairVisible(true);
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([
this.paneView,
@@ -511,6 +457,10 @@ export class Ruler implements ISeriesDrawing {
};
}
+ public hitTest(x: number, y: number): PrimitiveHoveredItem | null {
+ return null;
+ }
+
private findIndexByTime(time: Time): number {
const data = this.series.data() ?? [];
@@ -523,22 +473,7 @@ export class Ruler implements ISeriesDrawing {
});
}
- private setCrosshairVisible(visible: boolean): void {
- this.chart.applyOptions({
- crosshair: {
- vertLine: {
- visible,
- labelVisible: visible,
- },
- horzLine: {
- visible,
- labelVisible: visible,
- },
- },
- });
- }
-
- private bindEvents(): void {
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -548,7 +483,11 @@ export class Ruler implements ISeriesDrawing {
this.chart.subscribeCrosshairMove(this.moveHandler);
}
- private unbindEvents(): void {
+ protected getGeometry(): void {
+ console.log('stub');
+ }
+
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -558,11 +497,6 @@ export class Ruler implements ISeriesDrawing {
this.chart.unsubscribeCrosshairMove(this.moveHandler);
}
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
-
private handleClick(params: MouseEventParams<Time>): void {
if (this.hidden || !params.point) {
return;
@@ -584,7 +518,7 @@ export class Ruler implements ISeriesDrawing {
this.endAnchor = anchor;
this.mode = 'placingEnd';
- this.setCrosshairVisible(false);
+ this.hideCrosshair();
this.render();
return;
}
@@ -592,8 +526,8 @@ export class Ruler implements ISeriesDrawing {
if (this.mode === 'placingEnd') {
this.endAnchor = anchor;
this.mode = 'ready';
-
- this.setCrosshairVisible(true);
+ this.resolveReady?.();
+ this.showCrosshair();
this.render();
}
}
diff --git a/src/core/Drawings/sliderPosition/sliderPosition.ts b/src/core/Drawings/sliderPosition/sliderPosition.ts
index fd444f1..4315382 100644
--- a/src/core/Drawings/sliderPosition/sliderPosition.ts
+++ b/src/core/Drawings/sliderPosition/sliderPosition.ts
@@ -1,4 +1,4 @@
-import { Observable, skip, Subscription } from 'rxjs';
+import { Observable, skip } from 'rxjs';
import {
CustomPriceAxisPaneView,
@@ -6,6 +6,7 @@ import {
CustomTimeAxisPaneView,
CustomTimeAxisView,
} from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
getPointerPoint as getPointerPointFromEvent,
getPriceDelta as getPriceDeltaFromCoordinates,
@@ -38,17 +39,13 @@ import {
import type { ISeriesDrawing } from '@core/Drawings/common';
import type { AxisLabel, AxisSegment, Bounds, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
import type {
- AutoscaleInfo,
IChartApi,
IPrimitivePaneView,
- Logical,
MouseEventHandler,
MouseEventParams,
PrimitiveHoveredItem,
- SeriesAttachedParameter,
- SeriesOptionsMap,
Time,
UTCTimestamp,
} from 'lightweight-charts';
@@ -114,17 +111,11 @@ const HIT_TOLERANCE = 8;
const INITIAL_WIDTH_PX = 160;
const MIN_DISTANCE = 0.00000001;
-export class SliderPosition implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private container: HTMLElement;
+export class SliderPosition extends SeriesDrawingBase<SliderPositionSettings> implements ISeriesDrawing {
private removeSelf?: () => void;
private openSettings?: () => void;
- private settings: SliderPositionSettings = createDefaultSettings();
-
- private requestUpdate: (() => void) | null = null;
- private subscriptions = new Subscription();
+ protected settings: SliderPositionSettings = createDefaultSettings();
private displayFormat: ChartOptionsModel = {
dateFormat: Defaults.dateFormat,
@@ -132,9 +123,7 @@ export class SliderPosition implements ISeriesDrawing {
showTime: Defaults.showTime,
};
- private hidden = false;
- private active = true;
- private mode: SliderMode = 'idle';
+ protected mode: SliderMode = 'idle';
private side: SliderSide;
private startTime: Time | null = null;
@@ -171,10 +160,8 @@ export class SliderPosition implements ISeriesDrawing {
series: SeriesApi,
{ side, container, formatObservable, resetTriggers = [], removeSelf, openSettings }: SliderPositionParams,
) {
- this.chart = chart;
- this.series = series;
+ super({ chart, series, container });
this.side = side;
- this.container = container;
this.removeSelf = removeSelf;
this.openSettings = openSettings;
@@ -235,49 +222,14 @@ export class SliderPosition implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.render();
- }
-
- public destroy(): void {
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- this.setCrosshairVisible(true);
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.unbindEvents();
- this.series.detachPrimitive(this);
- this.series = series;
- this.requestUpdate = null;
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
public getState(): SliderPositionState {
return {
hidden: this.hidden,
- active: this.active,
+ active: this.isActive.value,
mode: this.mode,
startTime: this.startTime,
endTime: this.endTime,
@@ -295,7 +247,9 @@ export class SliderPosition implements ISeriesDrawing {
const next = state as Partial<SliderPositionState>;
this.hidden = next.hidden ?? this.hidden;
- this.active = next.active ?? this.active;
+ if (typeof next.active === 'boolean') {
+ this.isActive.next(next.active);
+ }
this.mode = next.mode ?? this.mode;
this.startTime = next.startTime ?? this.startTime;
@@ -334,34 +288,10 @@ export class SliderPosition implements ISeriesDrawing {
this.render();
}
- public getSettings(): SettingsValues {
- return { ...this.settings };
- }
-
public getSettingsTabs(): SettingsTab[] {
return getSliderPositionSettingsTabs(this.settings);
}
- public updateSettings(settings: SettingsValues): void {
- this.settings = {
- ...this.settings,
- ...settings,
- };
-
- this.render();
- }
-
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.unbindEvents();
- this.requestUpdate = null;
- this.setCrosshairVisible(true);
- }
-
public updateAllViews(): void {
updateViews([
this.paneView,
@@ -395,10 +325,6 @@ export class SliderPosition implements ISeriesDrawing {
return [this.targetPriceAxisView, this.entryPriceAxisView, this.stopPriceAxisView];
}
- public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): SliderRenderData | null {
if (this.hidden) {
return null;
@@ -430,14 +356,14 @@ export class SliderPosition implements ISeriesDrawing {
stopText: this.getStopText(geometry),
centerBoxColor: pnl >= 0 ? colors.chartCandleUp : colors.chartCandleDown,
showFill: true,
- showHandles: this.active,
- showLabels: this.active,
+ showHandles: this.isActive.value,
+ showLabels: this.isActive.value,
...this.settings,
};
}
public getTimeAxisSegments(): AxisSegment[] {
- if (!this.active) {
+ if (!this.isActive.value) {
return [];
}
@@ -459,7 +385,7 @@ export class SliderPosition implements ISeriesDrawing {
}
public getPriceAxisSegments(): AxisSegment[] {
- if (!this.active) {
+ if (!this.isActive.value) {
return [];
}
@@ -486,7 +412,7 @@ export class SliderPosition implements ISeriesDrawing {
}
public getTimeAxisLabel(kind: string): AxisLabel | null {
- if (!this.active || (kind !== 'start' && kind !== 'end')) {
+ if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
return null;
}
@@ -622,7 +548,7 @@ export class SliderPosition implements ISeriesDrawing {
public hitTest(x: number, y: number): PrimitiveHoveredItem | null {
const point = { x, y };
- if (!this.active) {
+ if (!this.isActive.value) {
if (!this.containsPoint(point)) {
return null;
}
@@ -657,7 +583,7 @@ export class SliderPosition implements ISeriesDrawing {
};
}
- private bindEvents(): void {
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -673,7 +599,7 @@ export class SliderPosition implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -689,23 +615,8 @@ export class SliderPosition implements ISeriesDrawing {
window.removeEventListener('pointercancel', this.handlePointerUp);
}
- private setCrosshairVisible(visible: boolean): void {
- this.chart.applyOptions({
- crosshair: {
- vertLine: {
- visible,
- labelVisible: visible,
- },
- horzLine: {
- visible,
- labelVisible: visible,
- },
- },
- });
- }
-
private handleDoubleClick = (event: MouseEvent): void => {
- if (this.hidden || this.mode !== 'ready' || !this.active) {
+ if (this.hidden || this.mode !== 'ready' || !this.isActive.value) {
return;
}
@@ -752,19 +663,20 @@ export class SliderPosition implements ISeriesDrawing {
distance,
);
- this.active = true;
+ this.isActive.next(true);
this.mode = 'ready';
+ this.resolveReady?.();
this.render();
return;
}
- this.active = this.containsPoint({ x: params.point.x, y: params.point.y });
+ this.isActive.next(this.containsPoint({ x: params.point.x, y: params.point.y }));
this.render();
}
private handlePointerDown = (event: PointerEvent): void => {
- if (this.hidden || this.mode !== 'ready' || !this.active) {
+ if (this.hidden || this.mode !== 'ready' || !this.isActive.value) {
return;
}
@@ -811,6 +723,7 @@ export class SliderPosition implements ISeriesDrawing {
this.dragStateSnapshot = null;
this.didDrag = false;
this.mode = 'ready';
+ this.resolveReady?.();
this.render();
};
@@ -1102,7 +1015,7 @@ export class SliderPosition implements ISeriesDrawing {
return lastPrice;
}
- private getGeometry(): SliderGeometry | null {
+ protected getGeometry(): SliderGeometry | null {
if (this.startTime === null || this.endTime === null) {
return null;
}
@@ -1242,9 +1155,4 @@ export class SliderPosition implements ISeriesDrawing {
private getLocalPoint(event: PointerEvent): Point {
return getPointerPointFromEvent(this.container, event);
}
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
diff --git a/src/core/Drawings/text/text.ts b/src/core/Drawings/text/text.ts
index 4c9d7fc..3711206 100644
--- a/src/core/Drawings/text/text.ts
+++ b/src/core/Drawings/text/text.ts
@@ -1,31 +1,22 @@
-import {
- AutoscaleInfo,
- CrosshairMode,
- IChartApi,
- IPrimitivePaneView,
- PrimitiveHoveredItem,
- SeriesAttachedParameter,
- SeriesOptionsMap,
- Time,
- UTCTimestamp,
-} from 'lightweight-charts';
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, UTCTimestamp } from 'lightweight-charts';
+import { Observable } from 'rxjs';
import { CustomPriceAxisView, CustomTimeAxisView } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
clamp,
clampPointToContainer as clampPointToContainerInElement,
getAnchorFromPoint,
getContainerSize as getElementContainerSize,
- getPointerPoint as getPointerPointFromEvent,
getXCoordinateFromTime,
getYCoordinateFromPrice,
isPointInBounds,
} from '@core/Drawings/helpers';
+import { AxisSegment } from '@core/Drawings/types';
import { updateViews } from '@core/Drawings/utils';
import { getThemeStore } from '@src/theme';
-import { SettingsTab, SettingsValues } from '@src/types';
+import { SettingsTab } from '@src/types';
import { Defaults } from '@src/types/defaults';
@@ -79,21 +70,13 @@ const UI = {
let measureCanvas: HTMLCanvasElement | null = null;
-export class Text implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private readonly container: HTMLElement;
+export class Text extends SeriesDrawingBase<TextSettings> implements ISeriesDrawing {
private readonly removeSelf?: () => void;
private readonly openSettings?: () => void;
- private requestUpdate: (() => void) | null = null;
private isBound = false;
- private hidden = false;
- private isActive = false;
- private mode: TextMode = 'idle';
-
- private subscriptions = new Subscription();
+ protected mode: TextMode = 'idle';
private displayFormat: ChartOptionsModel = {
dateFormat: Defaults.dateFormat,
@@ -102,7 +85,7 @@ export class Text implements ISeriesDrawing {
};
private point: Anchor | null = null;
- private settings: TextSettings = createDefaultSettings();
+ protected settings: TextSettings = createDefaultSettings();
private dragPointerId: number | null = null;
private dragStartPoint: Point | null = null;
@@ -113,11 +96,9 @@ export class Text implements ISeriesDrawing {
private readonly priceAxisView: CustomPriceAxisView;
constructor(chart: IChartApi, series: SeriesApi, params: TextParams) {
- const { container, formatObservable, removeSelf, openSettings } = params;
+ super({ chart, series, container: params.container });
+ const { formatObservable, removeSelf, openSettings } = params;
- this.chart = chart;
- this.series = series;
- this.container = container;
this.removeSelf = removeSelf;
this.openSettings = openSettings;
@@ -145,70 +126,18 @@ export class Text implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.showCrosshair();
- this.render();
- }
-
- public destroy(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.showCrosshair();
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
- public getSettings(): TextSettings {
- return { ...this.settings };
- }
-
public getSettingsTabs(): SettingsTab[] {
return getTextSettingsTabs(this.settings);
}
- public updateSettings(settings: SettingsValues): void {
- this.settings = {
- ...this.settings,
- ...settings,
- };
-
- this.render();
- }
-
public getState(): TextState {
return {
hidden: this.hidden,
- isActive: this.isActive,
+ isActive: this.isActive.value,
mode: this.mode,
point: this.point,
settings: { ...this.settings },
@@ -223,7 +152,9 @@ export class Text implements ISeriesDrawing {
const nextState = state as Partial<TextState>;
this.hidden = typeof nextState.hidden === 'boolean' ? nextState.hidden : this.hidden;
- this.isActive = typeof nextState.isActive === 'boolean' ? nextState.isActive : this.isActive;
+ if (typeof nextState.isActive === 'boolean') {
+ this.isActive.next(nextState.isActive);
+ }
this.mode = nextState.mode ?? this.mode;
this.point = nextState.point ?? this.point;
@@ -237,17 +168,6 @@ export class Text implements ISeriesDrawing {
this.render();
}
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([this.paneView, this.timeAxisView, this.priceAxisView]);
}
@@ -272,10 +192,6 @@ export class Text implements ISeriesDrawing {
return [this.priceAxisView];
}
- public autoscaleInfo(): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): TextRenderData | null {
if (this.hidden) {
return null;
@@ -290,12 +206,12 @@ export class Text implements ISeriesDrawing {
return {
...geometry,
...this.settings,
- showHandles: this.isActive,
+ showHandles: this.isActive.value,
};
}
public getTimeAxisLabel(kind: string): AxisLabel | null {
- if (kind !== 'main' || !this.isActive || !this.point || typeof this.point.time !== 'number') {
+ if (kind !== 'main' || !this.isActive.value || !this.point || typeof this.point.time !== 'number') {
return null;
}
@@ -321,7 +237,7 @@ export class Text implements ISeriesDrawing {
}
public getPriceAxisLabel(kind: string): AxisLabel | null {
- if (kind !== 'main' || !this.isActive || !this.point) {
+ if (kind !== 'main' || !this.isActive.value || !this.point) {
return null;
}
@@ -357,7 +273,15 @@ export class Text implements ISeriesDrawing {
};
}
- private bindEvents(): void {
+ public getPriceAxisSegments(): AxisSegment[] {
+ return [];
+ }
+
+ public getTimeAxisSegments(): AxisSegment[] {
+ return [];
+ }
+
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -371,7 +295,7 @@ export class Text implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -406,7 +330,7 @@ export class Text implements ISeriesDrawing {
const containsPoint = this.containsPoint(point);
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!containsPoint) {
return;
}
@@ -414,7 +338,7 @@ export class Text implements ISeriesDrawing {
event.preventDefault();
event.stopPropagation();
- this.isActive = true;
+ this.isActive.next(true);
this.render();
return;
}
@@ -427,12 +351,12 @@ export class Text implements ISeriesDrawing {
return;
}
- this.isActive = false;
+ this.isActive.next(false);
this.render();
};
private handleDoubleClick = (event: MouseEvent): void => {
- if (this.hidden || this.mode !== 'ready' || !this.isActive) {
+ if (this.hidden || this.mode !== 'ready' || !this.isActive.value) {
return;
}
@@ -477,8 +401,10 @@ export class Text implements ISeriesDrawing {
}
this.point = anchor;
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'ready';
+ this.resolveReady?.();
+
this.render();
}
@@ -494,6 +420,8 @@ export class Text implements ISeriesDrawing {
private finishDragging(): void {
this.mode = 'ready';
+ this.resolveReady?.();
+
this.dragPointerId = null;
this.dragStartPoint = null;
this.dragGeometrySnapshot = null;
@@ -531,7 +459,7 @@ export class Text implements ISeriesDrawing {
return getAnchorFromPoint(this.chart, this.series, point);
}
- private getGeometry(): TextGeometry | null {
+ protected getGeometry(): TextGeometry | null {
if (!this.point) {
return null;
}
@@ -587,22 +515,6 @@ export class Text implements ISeriesDrawing {
return isPointInBounds(point, geometry, 2);
}
- private hideCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Hidden,
- },
- });
- }
-
- private showCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Normal,
- },
- });
- }
-
private getContainerSize(): { width: number; height: number } {
return getElementContainerSize(this.container);
}
@@ -610,15 +522,6 @@ export class Text implements ISeriesDrawing {
private clampPointToContainer(point: Point): Point {
return clampPointToContainerInElement(point, this.container);
}
-
- private getEventPoint(event: PointerEvent): Point {
- return getPointerPointFromEvent(this.container, event);
- }
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
function getTextLines(text: string): string[] {
diff --git a/src/core/Drawings/traectory/traectory.ts b/src/core/Drawings/traectory/traectory.ts
index 7d9b61a..968f22a 100644
--- a/src/core/Drawings/traectory/traectory.ts
+++ b/src/core/Drawings/traectory/traectory.ts
@@ -1,26 +1,18 @@
-import {
- AutoscaleInfo,
- CrosshairMode,
- IChartApi,
- IPrimitivePaneView,
- PrimitiveHoveredItem,
- SeriesAttachedParameter,
- SeriesOptionsMap,
- Time,
-} from 'lightweight-charts';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem } from 'lightweight-charts';
import { Observable } from 'rxjs';
import { CustomPriceAxisPaneView, CustomTimeAxisPaneView } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
clamp,
clampPointToContainer as clampPointToContainerInElement,
getAnchorFromPoint,
getContainerSize as getElementContainerSize,
- getPointerPoint as getPointerPointFromEvent,
getXCoordinateFromTime,
getYCoordinateFromPrice,
isNearPoint,
} from '@core/Drawings/helpers';
+import { AxisLabel } from '@core/Drawings/types';
import { updateViews } from '@core/Drawings/utils';
import { getThemeStore } from '@src/theme';
@@ -31,7 +23,7 @@ import { createDefaultSettings, getTraectorySettingsTabs, TraectorySettings, Tra
import type { ISeriesDrawing } from '@core/Drawings/common';
import type { Anchor, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
type TraectoryMode = 'idle' | 'drawing' | 'ready' | 'dragging-point' | 'dragging-body';
@@ -68,21 +60,15 @@ const POINT_HIT_TOLERANCE = 8;
const SEGMENT_HIT_TOLERANCE = 6;
const MIN_POINTS_COUNT = 2;
-export class Traectory implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private container: HTMLElement;
+export class Traectory extends SeriesDrawingBase<TraectorySettings> implements ISeriesDrawing {
private removeSelf?: () => void;
private openSettings?: () => void;
- private settings: TraectorySettings = createDefaultSettings();
+ protected settings: TraectorySettings = createDefaultSettings();
- private requestUpdate: (() => void) | null = null;
private isBound = false;
- private hidden = false;
- private isActive = false;
- private mode: TraectoryMode = 'idle';
+ protected mode: TraectoryMode = 'idle';
private points: Anchor[] = [];
private previewAnchor: Anchor | null = null;
@@ -97,11 +83,9 @@ export class Traectory implements ISeriesDrawing {
private readonly priceAxisPaneView: CustomPriceAxisPaneView;
constructor(chart: IChartApi, series: SeriesApi, params: TraectoryParams) {
- const { container, removeSelf, openSettings } = params;
+ super({ chart, series, container: params.container });
+ const { removeSelf, openSettings } = params;
- this.chart = chart;
- this.series = series;
- this.container = container;
this.removeSelf = removeSelf;
this.openSettings = openSettings;
@@ -118,52 +102,14 @@ export class Traectory implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.showCrosshair();
- this.render();
- }
-
- public destroy(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.showCrosshair();
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle' || this.mode === 'drawing';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
public getState(): TraectoryState {
return {
hidden: this.hidden,
- isActive: this.isActive,
+ isActive: this.isActive.value,
mode: this.mode,
points: this.points,
settings: { ...this.settings },
@@ -178,7 +124,9 @@ export class Traectory implements ISeriesDrawing {
const nextState = state as Partial<TraectoryState>;
this.hidden = typeof nextState.hidden === 'boolean' ? nextState.hidden : this.hidden;
- this.isActive = typeof nextState.isActive === 'boolean' ? nextState.isActive : this.isActive;
+ if (typeof nextState.isActive === 'boolean') {
+ this.isActive.next(nextState.isActive);
+ }
this.mode = nextState.mode ?? this.mode;
this.points = Array.isArray(nextState.points) ? nextState.points : this.points;
@@ -193,34 +141,10 @@ export class Traectory implements ISeriesDrawing {
this.render();
}
- public getSettings(): SettingsValues {
- return { ...this.settings };
- }
-
public getSettingsTabs(): SettingsTab[] {
return getTraectorySettingsTabs(this.settings);
}
- public updateSettings(settings: SettingsValues): void {
- this.settings = {
- ...this.settings,
- ...settings,
- };
-
- this.render();
- }
-
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([this.paneView, this.timeAxisPaneView, this.priceAxisPaneView]);
}
@@ -245,10 +169,6 @@ export class Traectory implements ISeriesDrawing {
return [];
}
- public autoscaleInfo(): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): TraectoryRenderData | null {
if (this.hidden) {
return null;
@@ -263,14 +183,14 @@ export class Traectory implements ISeriesDrawing {
return {
...geometry,
previewPoint: this.getPreviewPoint(),
- showHandles: this.mode === 'drawing' || this.isActive,
+ showHandles: this.mode === 'drawing' || this.isActive.value,
showArrow: this.mode !== 'drawing' && geometry.points.length > 1,
...this.settings,
};
}
public getTimeAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -292,7 +212,7 @@ export class Traectory implements ISeriesDrawing {
}
public getPriceAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -340,7 +260,15 @@ export class Traectory implements ISeriesDrawing {
};
}
- private bindEvents(): void {
+ public getPriceAxisLabel(kind: string): AxisLabel | null {
+ return null;
+ }
+
+ public getTimeAxisLabel(kind: string): AxisLabel | null {
+ return null;
+ }
+
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -355,7 +283,7 @@ export class Traectory implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -386,7 +314,7 @@ export class Traectory implements ISeriesDrawing {
return;
}
- if (this.mode !== 'ready' || !this.isActive) {
+ if (this.mode !== 'ready' || !this.isActive.value) {
return;
}
@@ -447,7 +375,7 @@ export class Traectory implements ISeriesDrawing {
const pointIndex = this.getPointIndexAt(point);
const isInsideTraectory = pointIndex !== null || this.isPointNearTraectory(point);
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!isInsideTraectory) {
return;
}
@@ -455,7 +383,7 @@ export class Traectory implements ISeriesDrawing {
event.preventDefault();
event.stopPropagation();
- this.isActive = true;
+ this.isActive.next(true);
this.render();
return;
}
@@ -469,7 +397,7 @@ export class Traectory implements ISeriesDrawing {
}
if (!this.isPointNearTraectory(point)) {
- this.isActive = false;
+ this.isActive.next(false);
this.render();
return;
}
@@ -525,7 +453,7 @@ export class Traectory implements ISeriesDrawing {
this.points = [anchor];
this.previewAnchor = anchor;
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'drawing';
this.render();
@@ -574,8 +502,9 @@ export class Traectory implements ISeriesDrawing {
}
this.previewAnchor = null;
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'ready';
+ this.resolveReady?.();
this.render();
}
@@ -606,6 +535,7 @@ export class Traectory implements ISeriesDrawing {
private finishDragging(): void {
this.mode = 'ready';
+ this.resolveReady?.();
this.dragPointerId = null;
this.dragStartPoint = null;
@@ -617,7 +547,7 @@ export class Traectory implements ISeriesDrawing {
}
private resetToIdle(): void {
- this.isActive = false;
+ this.isActive.next(false);
this.mode = 'idle';
this.points = [];
@@ -693,7 +623,7 @@ export class Traectory implements ISeriesDrawing {
return getAnchorFromPoint(this.chart, this.series, point);
}
- private getGeometry(): TraectoryGeometry | null {
+ protected getGeometry(): TraectoryGeometry | null {
if (!this.points.length) {
return null;
}
@@ -805,22 +735,6 @@ export class Traectory implements ISeriesDrawing {
return Math.hypot(point.x - projectionX, point.y - projectionY);
}
- private hideCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Hidden,
- },
- });
- }
-
- private showCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Normal,
- },
- });
- }
-
private getContainerSize(): { width: number; height: number } {
return getElementContainerSize(this.container);
}
@@ -828,13 +742,4 @@ export class Traectory implements ISeriesDrawing {
private clampPointToContainer(point: Point): Point {
return clampPointToContainerInElement(point, this.container);
}
-
- private getEventPoint(event: PointerEvent): Point {
- return getPointerPointFromEvent(this.container, event);
- }
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
diff --git a/src/core/Drawings/trendLine/trendLine.ts b/src/core/Drawings/trendLine/trendLine.ts
index 508ade0..514e804 100644
--- a/src/core/Drawings/trendLine/trendLine.ts
+++ b/src/core/Drawings/trendLine/trendLine.ts
@@ -1,16 +1,5 @@
-import {
- AutoscaleInfo,
- CrosshairMode,
- IChartApi,
- IPrimitivePaneView,
- Logical,
- PrimitiveHoveredItem,
- SeriesAttachedParameter,
- SeriesOptionsMap,
- Time,
- UTCTimestamp,
-} from 'lightweight-charts';
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, UTCTimestamp } from 'lightweight-charts';
+import { Observable } from 'rxjs';
import {
CustomPriceAxisPaneView,
@@ -18,9 +7,9 @@ import {
CustomTimeAxisPaneView,
CustomTimeAxisView,
} from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
getAnchorFromPoint,
- getPointerPoint as getPointerPointFromEvent,
getPriceDelta as getPriceDeltaFromCoordinates,
getXCoordinateFromTime,
getYCoordinateFromPrice,
@@ -46,7 +35,7 @@ import {
import type { ISeriesDrawing } from '@core/Drawings/common';
import type { Anchor, AxisLabel, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
type TrendLineMode = 'idle' | 'drawing' | 'ready' | 'dragging-start' | 'dragging-end' | 'dragging-body';
type TimeLabelKind = 'start' | 'end';
@@ -84,22 +73,15 @@ export interface TrendLineRenderData extends TrendLineGeometry, TrendLineStyle,
const LINE_HIT_TOLERANCE = 6;
const MIN_LINE_SIZE = 4;
-export class TrendLine implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private container: HTMLElement;
+export class TrendLine extends SeriesDrawingBase<TrendLineSettings> implements ISeriesDrawing {
private removeSelf?: () => void;
private openSettings?: () => void;
- private settings: TrendLineSettings = createDefaultSettings();
+ protected settings: TrendLineSettings = createDefaultSettings();
- private requestUpdate: (() => void) | null = null;
- private subscriptions = new Subscription();
private isBound = false;
- private hidden = false;
- private isActive = false;
- private mode: TrendLineMode = 'idle';
+ protected mode: TrendLineMode = 'idle';
private startAnchor: Anchor | null = null;
private endAnchor: Anchor | null = null;
@@ -127,9 +109,7 @@ export class TrendLine implements ISeriesDrawing {
series: SeriesApi,
{ container, formatObservable, removeSelf, openSettings }: TrendLineParams,
) {
- this.chart = chart;
- this.series = series;
- this.container = container;
+ super({ chart, series, container });
this.removeSelf = removeSelf;
this.openSettings = openSettings;
@@ -175,53 +155,14 @@ export class TrendLine implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.showCrosshair();
- this.render();
- }
-
- public destroy(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.showCrosshair();
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.render();
- }
-
public isCreationPending(): boolean {
return this.mode === 'idle' || this.mode === 'drawing';
}
- public shouldShowInObjectTree(): boolean {
- return this.mode !== 'idle';
- }
-
public getState(): TrendLineState {
return {
hidden: this.hidden,
- isActive: this.isActive,
+ isActive: this.isActive.value,
mode: this.mode,
startAnchor: this.startAnchor,
endAnchor: this.endAnchor,
@@ -237,7 +178,7 @@ export class TrendLine implements ISeriesDrawing {
}
if ('isActive' in nextState && typeof nextState.isActive === 'boolean') {
- this.isActive = nextState.isActive;
+ this.isActive.next(nextState.isActive);
}
if ('mode' in nextState && nextState.mode) {
@@ -262,34 +203,10 @@ export class TrendLine implements ISeriesDrawing {
this.render();
}
- public getSettings(): SettingsValues {
- return { ...this.settings };
- }
-
- public updateSettings(settings: SettingsValues): void {
- this.settings = {
- ...this.settings,
- ...settings,
- };
-
- this.render();
- }
-
public getSettingsTabs(): SettingsTab[] {
return getTrendLineSettingsTabs(this.settings);
}
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([
this.paneView,
@@ -322,10 +239,6 @@ export class TrendLine implements ISeriesDrawing {
return [this.startPriceAxisView, this.endPriceAxisView];
}
- public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): TrendLineRenderData | null {
if (this.hidden) {
return null;
@@ -339,7 +252,7 @@ export class TrendLine implements ISeriesDrawing {
return {
...geometry,
- showHandles: this.isActive,
+ showHandles: this.isActive.value,
...this.settings,
};
}
@@ -371,7 +284,7 @@ export class TrendLine implements ISeriesDrawing {
}
public getTimeAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -393,7 +306,7 @@ export class TrendLine implements ISeriesDrawing {
}
public getPriceAxisSegments(): AxisSegment[] {
- if (!this.isActive) {
+ if (!this.isActive.value) {
return [];
}
@@ -415,7 +328,7 @@ export class TrendLine implements ISeriesDrawing {
}
public getTimeAxisLabel(kind: string): AxisLabel | null {
- if (!this.isActive || (kind !== 'start' && kind !== 'end')) {
+ if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
return null;
}
@@ -437,7 +350,7 @@ export class TrendLine implements ISeriesDrawing {
}
public getPriceAxisLabel(kind: string): AxisLabel | null {
- if (!this.isActive || (kind !== 'start' && kind !== 'end')) {
+ if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
return null;
}
@@ -458,7 +371,7 @@ export class TrendLine implements ISeriesDrawing {
};
}
- private bindEvents(): void {
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -472,7 +385,7 @@ export class TrendLine implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -537,7 +450,7 @@ export class TrendLine implements ISeriesDrawing {
const pointTarget = this.getPointTarget(point);
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!pointTarget && !this.isPointNearLine(point)) {
return;
}
@@ -545,7 +458,7 @@ export class TrendLine implements ISeriesDrawing {
event.preventDefault();
event.stopPropagation();
- this.isActive = true;
+ this.isActive.next(true);
this.render();
return;
}
@@ -574,7 +487,7 @@ export class TrendLine implements ISeriesDrawing {
return;
}
- this.isActive = false;
+ this.isActive.next(false);
this.render();
};
@@ -627,7 +540,7 @@ export class TrendLine implements ISeriesDrawing {
this.startAnchor = anchor;
this.endAnchor = anchor;
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'drawing';
this.render();
@@ -662,6 +575,7 @@ export class TrendLine implements ISeriesDrawing {
}
this.mode = 'ready';
+ this.resolveReady?.();
this.render();
}
@@ -679,6 +593,7 @@ export class TrendLine implements ISeriesDrawing {
private finishDragging(): void {
this.mode = 'ready';
+ this.resolveReady?.();
this.dragPointerId = null;
this.dragStartPoint = null;
@@ -736,7 +651,7 @@ export class TrendLine implements ISeriesDrawing {
return getAnchorFromPoint(this.chart, this.series, point);
}
- private getGeometry(): TrendLineGeometry | null {
+ protected getGeometry(): TrendLineGeometry | null {
if (!this.startAnchor || !this.endAnchor) {
return null;
}
@@ -865,29 +780,4 @@ export class TrendLine implements ISeriesDrawing {
return formatPrice(anchor.price) ?? '';
}
-
- private hideCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Hidden,
- },
- });
- }
-
- private showCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Normal,
- },
- });
- }
-
- private getEventPoint(event: PointerEvent): Point {
- return getPointerPointFromEvent(this.container, event);
- }
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
diff --git a/src/core/Drawings/volumeProfile/volumeProfile.ts b/src/core/Drawings/volumeProfile/volumeProfile.ts
index 34b9008..3b4ce51 100644
--- a/src/core/Drawings/volumeProfile/volumeProfile.ts
+++ b/src/core/Drawings/volumeProfile/volumeProfile.ts
@@ -1,17 +1,6 @@
-import {
- AutoscaleInfo,
- CrosshairMode,
- IChartApi,
- IPrimitivePaneView,
- Logical,
- PrimitiveHoveredItem,
- SeriesAttachedParameter,
- SeriesOptionsMap,
- Time,
- UTCTimestamp,
-} from 'lightweight-charts';
-
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, Time, UTCTimestamp } from 'lightweight-charts';
+
+import { Observable } from 'rxjs';
import {
CustomPriceAxisPaneView,
@@ -19,12 +8,12 @@ import {
CustomTimeAxisPaneView,
CustomTimeAxisView,
} from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
import {
clamp,
clampPointToContainer as clampPointToContainerInElement,
getAnchorFromPoint,
getContainerSize as getElementContainerSize,
- getPointerPoint as getPointerPointFromEvent,
getXCoordinateFromTime,
getYCoordinateFromPrice,
isNearPoint,
@@ -33,7 +22,7 @@ import {
import { updateViews } from '@core/Drawings/utils';
import { getThemeStore } from '@src/theme';
-import { SettingsTab, SettingsValues } from '@src/types';
+import { SettingsTab } from '@src/types';
import { Defaults } from '@src/types/defaults';
import { formatPrice } from '@src/utils';
import { formatDate } from '@src/utils/formatter';
@@ -127,23 +116,15 @@ const BODY_HIT_TOLERANCE = 4;
const MIN_PROFILE_SIZE = 8;
const MAX_VISIBLE_RANGE_START_RATIO = 0.95;
-export class VolumeProfile implements ISeriesDrawing {
- private chart: IChartApi;
- private series: SeriesApi;
- private container: HTMLElement;
+export class VolumeProfile extends SeriesDrawingBase<VolumeProfileSettings> implements ISeriesDrawing {
private removeSelf?: () => void;
private openSettings?: () => void;
private readonly profileKind: VolumeProfileKind;
- private settings: VolumeProfileSettings = createDefaultSettings();
+ protected settings: VolumeProfileSettings = createDefaultSettings();
- private requestUpdate: (() => void) | null = null;
private isBound = false;
- private subscriptions = new Subscription();
-
- private hidden = false;
- private isActive = false;
- private mode: VolumeProfileMode = 'idle';
+ protected mode: VolumeProfileMode = 'idle';
private startAnchor: Anchor | null = null;
private endAnchor: Anchor | null = null;
@@ -178,16 +159,14 @@ export class VolumeProfile implements ISeriesDrawing {
series: SeriesApi,
{ container, profileKind = 'fixedRange', formatObservable, removeSelf, openSettings }: VolumeProfileParams,
) {
- this.chart = chart;
- this.series = series;
- this.container = container;
+ super({ chart, series, container });
this.profileKind = profileKind;
this.removeSelf = removeSelf;
this.openSettings = openSettings;
if (this.profileKind === 'visibleRange') {
this.mode = 'ready';
- this.isActive = true;
+ this.isActive.next(true);
}
this.paneView = new VolumeProfilePaneView(this);
@@ -237,45 +216,11 @@ export class VolumeProfile implements ISeriesDrawing {
this.series.attachPrimitive(this);
}
- public show(): void {
- this.hidden = false;
- this.calculateProfile();
- this.render();
- }
-
- public hide(): void {
- this.hidden = true;
- this.showCrosshair();
- this.render();
- }
-
public destroy(): void {
if (this.profileKind === 'visibleRange') {
this.chart.timeScale().unsubscribeVisibleLogicalRangeChange(this.handleVisibleLogicalRangeChange);
}
-
- this.showCrosshair();
- this.unbindEvents();
- this.subscriptions.unsubscribe();
- this.series.detachPrimitive(this);
- this.requestUpdate = null;
- }
-
- public rebind(series: SeriesApi): void {
- if (this.series === series) {
- return;
- }
-
- this.showCrosshair();
- this.unbindEvents();
- this.series.detachPrimitive(this);
-
- this.series = series;
- this.requestUpdate = null;
-
- this.series.attachPrimitive(this);
- this.calculateProfile();
- this.render();
+ super.destroy();
}
public isCreationPending(): boolean {
@@ -291,13 +236,13 @@ export class VolumeProfile implements ISeriesDrawing {
return true;
}
- return this.mode !== 'idle';
+ return super.shouldShowInObjectTree();
}
public getState(): VolumeProfileState {
return {
hidden: this.hidden,
- isActive: this.isActive,
+ isActive: this.isActive.value,
mode: this.mode,
startAnchor: this.startAnchor,
endAnchor: this.endAnchor,
@@ -314,7 +259,9 @@ export class VolumeProfile implements ISeriesDrawing {
const nextState = state as Partial<VolumeProfileState>;
this.hidden = typeof nextState.hidden === 'boolean' ? nextState.hidden : this.hidden;
- this.isActive = typeof nextState.isActive === 'boolean' ? nextState.isActive : this.isActive;
+ if (typeof nextState.isActive === 'boolean') {
+ this.isActive.next(nextState.isActive);
+ }
if (this.profileKind === 'fixedRange') {
this.mode = nextState.mode ?? this.mode;
@@ -324,6 +271,7 @@ export class VolumeProfile implements ISeriesDrawing {
if (this.profileKind === 'visibleRange') {
this.mode = 'ready';
+ this.resolveReady?.();
if (typeof nextState.visibleRangeStartRatio === 'number') {
this.visibleRangeStartRatio = clamp(nextState.visibleRangeStartRatio, 0, MAX_VISIBLE_RANGE_START_RATIO);
@@ -341,34 +289,10 @@ export class VolumeProfile implements ISeriesDrawing {
this.render();
}
- public getSettings(): SettingsValues {
- return { ...this.settings };
- }
-
public getSettingsTabs(): SettingsTab[] {
return getVolumeProfileSettingsTabs(this.settings);
}
- public updateSettings(settings: SettingsValues): void {
- this.settings = {
- ...this.settings,
- ...settings,
- };
-
- this.render();
- }
-
- public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
- this.requestUpdate = param.requestUpdate;
- this.bindEvents();
- }
-
- public detached(): void {
- this.showCrosshair();
- this.unbindEvents();
- this.requestUpdate = null;
- }
-
public updateAllViews(): void {
updateViews([
this.paneView,
@@ -417,10 +341,6 @@ export class VolumeProfile implements ISeriesDrawing {
return [this.startPriceAxisView, this.endPriceAxisView];
}
- public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
- return null;
- }
-
public getRenderData(): VolumeProfileRenderData | null {
if (this.hidden) {
return null;
@@ -439,13 +359,13 @@ export class VolumeProfile implements ISeriesDrawing {
profileKind: this.profileKind,
rows,
pocY,
- showHandles: this.isActive || this.mode === 'drawing',
+ showHandles: this.isActive.value || this.mode === 'drawing',
...this.settings,
};
}
public getTimeAxisSegments(): AxisSegment[] {
- if (this.profileKind === 'visibleRange' || !this.isActive) {
+ if (this.profileKind === 'visibleRange' || !this.isActive.value) {
return [];
}
@@ -467,7 +387,7 @@ export class VolumeProfile implements ISeriesDrawing {
}
public getPriceAxisSegments(): AxisSegment[] {
- if (this.profileKind === 'visibleRange' || !this.isActive) {
+ if (this.profileKind === 'visibleRange' || !this.isActive.value) {
return [];
}
@@ -493,7 +413,7 @@ export class VolumeProfile implements ISeriesDrawing {
return null;
}
- if (!this.isActive || (kind !== 'start' && kind !== 'end')) {
+ if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
return null;
}
@@ -529,7 +449,7 @@ export class VolumeProfile implements ISeriesDrawing {
return null;
}
- if (!this.isActive || (kind !== 'start' && kind !== 'end')) {
+ if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
return null;
}
@@ -575,7 +495,7 @@ export class VolumeProfile implements ISeriesDrawing {
}
return {
- cursorStyle: this.isActive ? 'grab' : 'pointer',
+ cursorStyle: this.isActive.value ? 'grab' : 'pointer',
externalId: 'volume-profile',
zOrder: 'top',
};
@@ -590,7 +510,7 @@ export class VolumeProfile implements ISeriesDrawing {
this.render();
};
- private bindEvents(): void {
+ protected bindEvents(): void {
if (this.isBound) {
return;
}
@@ -604,7 +524,7 @@ export class VolumeProfile implements ISeriesDrawing {
window.addEventListener('pointercancel', this.handlePointerUp);
}
- private unbindEvents(): void {
+ protected unbindEvents(): void {
if (!this.isBound) {
return;
}
@@ -619,7 +539,7 @@ export class VolumeProfile implements ISeriesDrawing {
}
private handleDoubleClick = (event: MouseEvent): void => {
- if (this.hidden || this.mode !== 'ready' || !this.isActive) {
+ if (this.hidden || this.mode !== 'ready' || !this.isActive.value) {
return;
}
@@ -654,7 +574,7 @@ export class VolumeProfile implements ISeriesDrawing {
const dragTarget = this.getVisibleRangeDragTarget(point);
if (!dragTarget) {
- this.isActive = false;
+ this.isActive.next(false);
this.render();
return;
}
@@ -662,7 +582,7 @@ export class VolumeProfile implements ISeriesDrawing {
event.preventDefault();
event.stopPropagation();
- this.isActive = true;
+ this.isActive.next(true);
if (dragTarget !== 'poc') {
this.render();
@@ -701,7 +621,7 @@ export class VolumeProfile implements ISeriesDrawing {
const dragTarget = this.getFixedRangeDragTarget(point);
- if (!this.isActive) {
+ if (!this.isActive.value) {
if (!dragTarget) {
return;
}
@@ -709,13 +629,13 @@ export class VolumeProfile implements ISeriesDrawing {
event.preventDefault();
event.stopPropagation();
- this.isActive = true;
+ this.isActive.next(true);
this.render();
return;
}
if (!dragTarget) {
- this.isActive = false;
+ this.isActive.next(false);
this.render();
return;
}
@@ -788,7 +708,7 @@ export class VolumeProfile implements ISeriesDrawing {
this.startAnchor = anchor;
this.endAnchor = anchor;
- this.isActive = true;
+ this.isActive.next(true);
this.mode = 'drawing';
this.calculateProfile();
@@ -825,6 +745,8 @@ export class VolumeProfile implements ISeriesDrawing {
}
this.mode = 'ready';
+ this.resolveReady?.();
+
this.showCrosshair();
this.render();
}
@@ -855,7 +777,7 @@ export class VolumeProfile implements ISeriesDrawing {
private resetToIdle(): void {
this.hidden = false;
- this.isActive = false;
+ this.isActive.next(false);
this.mode = 'idle';
this.startAnchor = null;
@@ -1259,7 +1181,7 @@ export class VolumeProfile implements ISeriesDrawing {
return typeof value === 'number' && Number.isFinite(value) ? value : null;
}
- private getGeometry(): VolumeProfileGeometry | null {
+ protected getGeometry(): VolumeProfileGeometry | null {
if (this.profileKind === 'visibleRange') {
return this.getVisibleRangeGeometry();
}
@@ -1401,22 +1323,6 @@ export class VolumeProfile implements ISeriesDrawing {
return getAnchorFromPoint(this.chart, this.series, point);
}
- private hideCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Hidden,
- },
- });
- }
-
- private showCrosshair(): void {
- this.chart.applyOptions({
- crosshair: {
- mode: CrosshairMode.Normal,
- },
- });
- }
-
private getContainerSize(): { width: number; height: number } {
return getElementContainerSize(this.container);
}
@@ -1424,13 +1330,4 @@ export class VolumeProfile implements ISeriesDrawing {
private clampPointToContainer(point: Point): Point {
return clampPointToContainerInElement(point, this.container);
}
-
- private getEventPoint(event: PointerEvent): Point {
- return getPointerPointFromEvent(this.container, event);
- }
-
- private render(): void {
- this.updateAllViews();
- this.requestUpdate?.();
- }
}
diff --git a/src/core/DrawingsManager.tsx b/src/core/DrawingsManager.tsx
index b7a85de..a879e99 100644
--- a/src/core/DrawingsManager.tsx
+++ b/src/core/DrawingsManager.tsx
@@ -4,6 +4,7 @@ import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { EventManager } from '@core';
import { DOMModel } from '@core/DOMModel';
import { Drawing } from '@core/Drawings';
+import { Hotkeys, Keys } from '@core/Hotkeys';
import { EntitySettingsModal } from '@src/components/EntitySettingsModal';
import { drawingLabelById, drawingsMap, DrawingsNames } from '@src/constants';
@@ -19,6 +20,7 @@ interface DrawingsManagerParams {
container: HTMLElement;
modalRenderer: ModalRenderer;
paneId: number;
+ hotkeys: Hotkeys;
}
export interface DrawingSnapshotItem {
@@ -51,13 +53,27 @@ export class DrawingsManager {
private pendingSnapshot: DrawingsManagerSnapshot | null = null;
private paneId: number;
- constructor({ eventManager, mainSeries$, lwcChart, DOM, container, modalRenderer, paneId }: DrawingsManagerParams) {
+ private hotkeys: Hotkeys;
+ private copyPasteBuffer: DrawingSnapshotItem | null = null;
+ private escapeUnregisterHash: string | null = null;
+
+ constructor({
+ eventManager,
+ mainSeries$,
+ lwcChart,
+ DOM,
+ container,
+ modalRenderer,
+ paneId,
+ hotkeys,
+ }: DrawingsManagerParams) {
this.DOM = DOM;
this.eventManager = eventManager;
this.paneId = paneId;
this.lwcChart = lwcChart;
this.container = container;
this.modalRenderer = modalRenderer;
+ this.hotkeys = hotkeys;
this.subscriptions.add(
mainSeries$.subscribe((series) => {
@@ -79,6 +95,30 @@ export class DrawingsManager {
window.addEventListener('pointerup', this.handlePointerUp);
this.container.addEventListener('click', this.handleClick);
this.container.addEventListener('pointerdown', this.handlePointerDown);
+ // todo: implement ctrl+v
+ // hotkeys.register({
+ // keys: [Keys.control, Keys.v],
+ // callback: () => {
+ // const bufferWithAppliedPosition = {
+ // ...this.copyPasteBuffer,
+ // state: {
+ // ...this.copyPasteBuffer?.state,
+ // startAnchor: {
+ // price: 73.36210252637723,
+ // time: 1783679170
+ // }
+ // }
+ // }
+ //
+ // this.setSnapshot([
+ // ...this.getSnapshot(),
+ // bufferWithAppliedPosition
+ // ])
+ // // hotkeys.unregister({ // todo: unregister all else ctrl+c's
+ // // keys: [Keys.control, Keys.c]
+ // // })
+ // }
+ // })
}
private handlePointerDown = (): void => {
@@ -184,7 +224,7 @@ export class DrawingsManager {
this.DOM.refreshEntities();
}
- public addDrawingForce = (name: DrawingsNames): void => {
+ public addDrawingForce = (name: DrawingsNames): Promise<void> => {
this.removePendingDrawings(false);
if (drawingsMap[name].singleInstance) {
@@ -192,8 +232,10 @@ export class DrawingsManager {
}
this.activeTool$.next(name);
- this.createDrawing(name);
+ const drawing = this.createDrawing(name);
this.DOM.refreshEntities();
+
+ return drawing.waitForCreation();
};
private createDrawing(name: DrawingsNames, options: CreateDrawingOptions = {}): Drawing {
@@ -235,6 +277,13 @@ export class DrawingsManager {
moveUp,
construct,
paneId: this.paneId,
+ hotkeys: this.hotkeys,
+ setCopyPasteBuffer: (copyPasteBuffer) => {
+ this.copyPasteBuffer = copyPasteBuffer;
+ },
+ resetActiveTool: () => {
+ this.activeTool$.next('crosshair');
+ },
});
const entity = this.DOM.setEntity<Drawing>(drawingFactory);
@@ -291,6 +340,19 @@ export class DrawingsManager {
}
public setEndlessDrawingMode = (value: boolean): void => {
+ if (value) {
+ this.escapeUnregisterHash = this.hotkeys.register({
+ keys: [Keys.escape],
+ callback: () => {
+ this.setEndlessDrawingMode(false);
+ },
+ });
+ } else {
+ this.hotkeys.unregister({
+ keys: [Keys.escape],
+ hash: this.escapeUnregisterHash,
+ });
+ }
this.endlessMode$.next(value);
};
@@ -348,6 +410,10 @@ export class DrawingsManager {
}
public destroy(): void {
+ this.hotkeys.unregister({
+ keys: [Keys.escape],
+ hash: this.escapeUnregisterHash,
+ });
window.removeEventListener('pointerup', this.handlePointerUp);
this.container.removeEventListener('click', this.handleClick);
this.container.removeEventListener('pointerdown', this.handlePointerDown);
diff --git a/src/core/Hotkeys.ts b/src/core/Hotkeys.ts
new file mode 100644
index 0000000..4b02df7
--- /dev/null
+++ b/src/core/Hotkeys.ts
@@ -0,0 +1,174 @@
+export enum Keys { // todo: add macOS buttons support
+ 'escape' = 'escape',
+ 'delete' = 'delete',
+ 'tab' = 'tab',
+ 'shift' = 'shiftleft',
+ 'control' = 'controlleft',
+ 'alt' = 'altleft',
+ 't' = 'keyt',
+ 'h' = 'keyh',
+ 'v' = 'keyv',
+ 'f' = 'keyf',
+ 'z' = 'keyz',
+ 'c' = 'keyc',
+ 'mousedown' = 'mousedown',
+}
+
+export interface IHotkeys {
+ register: ({ keys, callback }: { keys: Keys[]; callback: () => void }) => void;
+}
+
+export interface HotkeysParams {
+ listenTarget: HTMLElement;
+}
+
+type Tree = {
+ children: Map<Keys, Tree>;
+ callbacks: Map<string, () => void> | null;
+ longPress?: boolean;
+};
+
+// todo: возможно можно сделать синглтоном и использовать импортируя экземпляр класса,
+// там где это нужно вместо props drilling, как сейчас
+export class Hotkeys implements IHotkeys {
+ private tree: Tree = { children: new Map(), callbacks: null, longPress: false };
+
+ private buffer: Keys[] = [];
+ private prevKey: null | Keys = null;
+
+ constructor(_: HotkeysParams) {
+ document.addEventListener('keydown', this.handleKeyDown.bind(this));
+ document.addEventListener('keyup', this.handleKeyUp.bind(this));
+ }
+
+ public unregister({ keys, hash }: { keys: Keys[]; hash?: string | null }) {
+ if (!hash) {
+ return;
+ }
+ let node = this.tree;
+
+ keys.forEach((key: Keys) => {
+ const child = node.children.get(key);
+ if (child) {
+ node = child;
+ } else {
+ throw new Error('[Hotkeys]: ошибка при отписке от хоткея');
+ }
+ });
+
+ if (!hash) {
+ node.callbacks = null;
+ }
+
+ node.callbacks?.delete(hash);
+
+ if (node.callbacks?.size === 0) {
+ node.callbacks = null;
+ }
+ }
+
+ public register({
+ keys,
+ callback,
+ longPress = false,
+ }: {
+ keys: Keys[];
+ callback: () => void;
+ longPress?: boolean;
+ }): string | null {
+ if (keys.length === 0) {
+ console.error('[Hotkeys] попытка задать пустой хоткей');
+ return null;
+ }
+
+ let node = this.tree; // todo: remove any from type
+
+ keys.forEach((key: Keys) => {
+ if (!node.children.has(key)) {
+ node.children.set(key, { children: new Map(), callbacks: null, longPress });
+ }
+
+ node = node.children.get(key) as Tree;
+ });
+
+ if (node.callbacks === null || node.callbacks === undefined) {
+ node.callbacks = new Map();
+ }
+
+ const hash = `hotkeyCallback-${crypto.randomUUID()}`;
+ node.callbacks?.set(hash, callback);
+ return hash;
+ }
+
+ private findNode(keys: Keys[]) {
+ let node = this.tree;
+
+ keys.forEach((key) => {
+ const k = normalize(key);
+ const child = node.children.get(k);
+ if (child) {
+ node = child;
+ } else {
+ return null;
+ }
+ });
+
+ return node;
+ }
+
+ private handleKeyUp = async () => {
+ const node = this.findNode(this.buffer);
+
+ if (node.callbacks && node.longPress) {
+ await this.handleKeyDown.bind(this)({ code: 'Escape' } as KeyboardEvent);
+ }
+ this.buffer = [];
+ this.prevKey = null;
+ };
+
+ private handleKeyDown = async (event: KeyboardEvent) => {
+ const key = normalize(event.code);
+
+ if (key === this.prevKey) return;
+
+ this.prevKey = key;
+
+ if (key === Keys.escape) {
+ this.buffer = [];
+ }
+
+ this.buffer.push(key);
+
+ const node = this.findNode(this.buffer);
+
+ if (!node) {
+ console.log('[Hotkeys] нет такой комбинации');
+ this.buffer = [];
+ this.prevKey = null;
+ return;
+ }
+
+ if (node.callbacks && node.children.size <= 0) {
+ event.preventDefault?.();
+ const callbacks = [];
+
+ for (const [_, cb] of node.callbacks) {
+ callbacks.push(cb);
+ }
+
+ const reversedCallbacks = [...callbacks].reverse();
+
+ for (const cb of reversedCallbacks) {
+ // eslint-disable-next-line no-await-in-loop
+ await cb();
+ }
+
+ this.buffer = [];
+ this.prevKey = null;
+ }
+ };
+}
+
+function normalize(key: string): Keys {
+ return key.toLowerCase() as Keys;
+}
diff --git a/src/core/MoexChart.tsx b/src/core/MoexChart.tsx
index 2869e77..26aaa4e 100644
--- a/src/core/MoexChart.tsx
+++ b/src/core/MoexChart.tsx
@@ -5,6 +5,7 @@ import { Footer } from '@components/Footer';
import { Header } from '@components/Header';
import { DataSource, DataSourceParams } from '@core/DataSource';
+import { Hotkeys } from '@core/Hotkeys';
import { ModalRenderer } from '@core/ModalRenderer';
import { SettingsModal } from '@src/components/SettingsModal';
@@ -101,6 +102,7 @@ export class MoexChart implements ISerializable<MoexChartSnapshot> {
private chart!: Chart;
private resizeObserver?: ResizeObserver;
private eventManager!: EventManager;
+ private hotkeys!: Hotkeys;
private rootContainer!: HTMLElement;
private headerRenderer!: UIRenderer;
@@ -169,6 +171,8 @@ export class MoexChart implements ISerializable<MoexChartSnapshot> {
showMenuButton: config.chartCollectionPreset.showMenuButton,
});
+ this.hotkeys = new Hotkeys({ listenTarget: this.rootContainer });
+
this.modalRenderer = new ModalRenderer(modalContainer);
this.chart = new Chart({
@@ -179,6 +183,7 @@ export class MoexChart implements ISerializable<MoexChartSnapshot> {
ohlcConfig: config.chartCollectionPreset.ohlc, // todo: omptimize
tooltipConfig: config.chartCollectionPreset.tooltipConfig ?? {},
panes: config.snapshot.charts[0].panes,
+ hotkeys: this.hotkeys,
},
lwcChartConfig: {
container: chartAreaContainer,
@@ -322,14 +327,12 @@ export class MoexChart implements ISerializable<MoexChartSnapshot> {
this.toolbarRenderer.renderComponent(
<Toolbar
toggleDOM={this.chart.getDom().toggleDOM}
- addDrawing={(name) => {
- // todo: deal with new panes logic
- this.chart.getDrawingsManager().addDrawingForce(name);
- }}
+ addDrawing={this.chart.getDrawingsManager().addDrawingForce}
setEndlessDrawingsMode={this.chart.getDrawingsManager().setEndlessDrawingMode}
isEndlessDrawingsMode$={this.chart.getDrawingsManager().isEndlessDrawingsMode()}
activateCrosshair={() => this.chart.getDrawingsManager().activateCrosshair()}
activeTool$={this.chart.getDrawingsManager().getActiveTool()}
+ hotkeys={this.hotkeys}
/>,
);
}
diff --git a/src/core/Pane.tsx b/src/core/Pane.tsx
index 84fe454..2b228aa 100644
--- a/src/core/Pane.tsx
+++ b/src/core/Pane.tsx
@@ -10,6 +10,7 @@ import { DataSource } from '@core/DataSource';
import { DOMModel } from '@core/DOMModel';
import { DrawingsManager, DrawingsManagerSnapshot } from '@core/DrawingsManager';
import { EventManager } from '@core/EventManager';
+import { Hotkeys } from '@core/Hotkeys';
import { Indicator } from '@core/Indicator';
import { Legend } from '@core/Legend';
import { PriceScale, PriceScaleControls } from '@core/PriceScale';
@@ -50,6 +51,7 @@ export interface PaneParams {
onPriceScaleStateChange: () => void;
leftPriceScaleVisible: boolean;
rightPriceScaleVisible: boolean;
+ hotkeys: Hotkeys;
}
// todo: Pane, ему должна принадлежать mainSerie, а также IndicatorManager и drawingsManager, mouseEvents. Также перекинуть соответствующие/необходимые свойства из чарта, и из чарта удалить
@@ -102,6 +104,7 @@ export class Pane implements ISerializable<PaneSnapshot> {
onPriceScaleStateChange,
leftPriceScaleVisible,
rightPriceScaleVisible,
+ hotkeys,
}: PaneParams) {
this.onDelete = onDelete;
this.onPriceScaleStateChange = onPriceScaleStateChange;
@@ -166,6 +169,7 @@ export class Pane implements ISerializable<PaneSnapshot> {
container: chartContainer,
modalRenderer: this.modalRenderer,
paneId: this.id,
+ hotkeys,
});
this.subscriptions.add(
diff --git a/src/modules/README.md b/src/modules/README.md
deleted file mode 100644
index 0097b46..0000000
--- a/src/modules/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Modules
-
-Модули функциональности библиотеки.
-
-## Назначение
-
-Этот модуль содержит специализированную функциональность библиотеки. Каждый модуль имеет четко определенную ответственность и может быть использован независимо.
diff --git a/src/modules/indicators/IndicatorFactory.ts b/src/modules/indicators/IndicatorFactory.ts
deleted file mode 100644
index 0adc96e..0000000
--- a/src/modules/indicators/IndicatorFactory.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// import { EventManager } from '@src/core';
-//
-// import { IndicatorConfig } from '../../types';
-//
-// import { Indicator } from './base/indicator';
-// import { SMA } from './implementations/SMA';
-// import { VolumeIndicator } from './implementations/Volume';
-//
-// export type IndicatorFactory = (config: IndicatorConfig, eventManager: EventManager) => Indicator;
-//
-// export interface IndicatorRegistration {
-// type: IndicatorConfig['type'];
-// create: IndicatorFactory;
-// }
-//
-// const registry = new Map<IndicatorConfig['type'], IndicatorFactory>([
-// ['SMA', (config) => new SMA(config)],
-// ['VOL', (config: IndicatorConfig, eventManager: EventManager) => new VolumeIndicator(config, eventManager)],
-// ]);
-//
-// export function registerIndicator({ type, create }: IndicatorRegistration): void {
-// registry.set(type, create);
-// }
-//
-// export function createIndicator(config: IndicatorConfig, eventManager: EventManager): Indicator {
-// const { type } = config;
-//
-// switch (type) {
-// case 'SMA':
-// return new SMA(config);
-// case 'VOL':
-// return new VolumeIndicator(config, eventManager); // todo: тут не должно быть ни eventManger, ни таймфрейма
-// default:
-// throw new Error(`Unknown indicator type: ${type}`);
-// }
-// }
diff --git a/src/modules/indicators/implementations/SMA.ts b/src/modules/indicators/implementations/SMA.ts
deleted file mode 100644
index b6c9222..0000000
--- a/src/modules/indicators/implementations/SMA.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-// import { Time } from 'lightweight-charts';
-//
-// import { Candle } from '../../../types';
-//
-// import { IndicatorConfig, IndicatorData } from '../../../types/indicator';
-// import { Indicator } from '../base/indicator';
-//
-// // Простая скользящая средняя (Simple Moving Average)
-// export class SMA extends Indicator {
-// constructor(config: IndicatorConfig) {
-// super(config);
-// }
-//
-// /**
-// * Расчет простой скользящей средней
-// */
-// calculate(candles: Candle[]): IndicatorData[] {
-// const period = this.config.period || 20;
-// const result: IndicatorData[] = [];
-//
-// if (candles.length < period) {
-// return result;
-// }
-//
-// for (let i = period - 1; i < candles.length; i + 1) {
-// let sum = 0;
-//
-// for (let j = i - period + 1; j <= i; j += 1) {
-// sum += candles[j].close;
-// }
-//
-// const average = sum / period;
-//
-// result.push({
-// time: candles[i].time as Time,
-// value: parseFloat(average.toFixed(4)),
-// });
-// }
-//
-// this.data = result;
-// return result;
-// }
-//
-// /**
-// * Добавить одну новую свечу и пересчитать последнее значение SMA
-// */
-// addCandle(candle: Candle, allCandles: Candle[]): void {
-// const period = this.config.period || 20;
-//
-// if (allCandles.length < period) {
-// return;
-// }
-//
-// // Берем последние 'period' свечей включая новую
-// const slice = allCandles.slice(-period);
-// const sum = slice.reduce((acc, c: Candle) => acc + c.close, 0);
-// const average = sum / period;
-//
-// const newPoint: IndicatorData = {
-// time: candle.time as Time,
-// value: parseFloat(average.toFixed(4)),
-// };
-//
-// this.data.push(newPoint);
-//
-// // Ограничиваем количество хранимых точек (например, последние 1000)
-// if (this.data.length > 1000) {
-// this.data = this.data.slice(-1000);
-// }
-// }
-//
-// updateConfig(config: Partial<IndicatorConfig>): void {
-// this.config = { ...this.config, ...config };
-// // Пересчитать данные при изменении конфигурации
-// this.data = [];
-// }
-//
-// /**
-// * Получить текущие данные индикатора
-// */
-// getData(): IndicatorData[] {
-// return this.data;
-// }
-//
-// /**
-// * Получить конфигурацию индикатора
-// */
-// getConfig(): IndicatorConfig {
-// return this.config;
-// }
-//
-// /**
-// * Очистить данные индикатора
-// */
-// clear(): void {
-// this.data = [];
-// }
-// }
diff --git a/src/modules/indicators/implementations/Volume.ts b/src/modules/indicators/implementations/Volume.ts
deleted file mode 100644
index 3ac1656..0000000
--- a/src/modules/indicators/implementations/Volume.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-// import dayjs from 'dayjs';
-// import { HistogramSeriesPartialOptions, SeriesType } from 'lightweight-charts';
-//
-// import { Subscription } from 'rxjs';
-//
-// import { EventManager } from '@src/core';
-// import { getThemeStore } from '@src/theme/store';
-// import { Candle, IndicatorConfig, IndicatorData } from '@src/types';
-// import { Timeframes } from '@src/types/timeframes';
-// import { parseTimeframe } from '@src/utils';
-//
-// import { Indicator } from '../base/indicator';
-//
-// export class VolumeIndicator extends Indicator {
-// private currentTimeframe!: Timeframes;
-// private timeframeSubscription: Subscription;
-//
-// // todo: тут не должно быть ни eventManger, ни таймфрейма
-// constructor(config: IndicatorConfig, eventManager: EventManager) {
-// super({ ...config, pane: 'new' });
-// this.timeframeSubscription = eventManager.subscribeTimeframe((newTimeframe) => {
-// this.setTimeframe(newTimeframe);
-// });
-// }
-//
-// public getSeriesType(): SeriesType {
-// return 'Histogram';
-// }
-//
-// /**
-// * Переопределяем метод, чтобы задать специальные опции для гистограммы Volume.
-// */
-// public getSeriesOptions(): HistogramSeriesPartialOptions {
-// const baseOptions = {};
-//
-// return {
-// ...baseOptions,
-// priceFormat: {
-// type: 'volume',
-// },
-// priceScaleId: 'new',
-// autoscaleInfoProvider: () => null,
-// };
-// }
-//
-// /**
-// * Обновить таймфрейм для агрегации данных
-// */
-// public setTimeframe(timeframe: Timeframes): void {
-// this.currentTimeframe = timeframe;
-// }
-//
-// /**
-// * Логика расчета данных для Volume с агрегацией по таймфрейму.
-// */
-// public calculate(candles: Candle[]): IndicatorData[] {
-// if (candles.length === 0) {
-// this.data = [];
-// return this.data;
-// }
-//
-// const { colors } = getThemeStore();
-//
-// const { dayjsUnit } = parseTimeframe(this.currentTimeframe);
-//
-// const timeframeGroups = new Map();
-//
-// candles.forEach((candle) => {
-// const timeframeStart = dayjs.unix(candle.time).startOf(dayjsUnit).unix();
-// const existing = timeframeGroups.get(timeframeStart);
-//
-// if (existing) {
-// existing.value += candle.volume ?? 0;
-//
-// existing.color = candle.close >= candle.open ? colors.chartCandleWickUp : colors.chartCandleWickDown;
-// } else {
-// // Новый таймфрейм
-// timeframeGroups.set(timeframeStart, {
-// time: timeframeStart,
-// value: candle.volume || 0,
-// color: candle.close >= candle.open ? colors.chartCandleWickUp : colors.chartCandleWickDown,
-// });
-// }
-// });
-//
-// this.data = Array.from(timeframeGroups.values()).sort((a, b) => a.time - b.time);
-//
-// return this.data;
-// }
-//
-// /**
-// * Добавить новую свечу с правильной агрегацией по таймфрейму
-// */
-// public addCandle(candle: Candle, allCandles: Candle[]): void {
-// // Пересчитываем все данные с учетом нового таймфрейма
-// this.calculate(allCandles);
-// }
-//
-// public destroy() {
-// this.timeframeSubscription.unsubscribe();
-// }
-// }
diff --git a/src/modules/indicators/index.ts b/src/modules/indicators/index.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/modules/series-strategies/SeriesManager.ts b/src/modules/series-strategies/SeriesManager.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/types/settings.ts b/src/types/settings.ts
index c887b77..5904562 100644
--- a/src/types/settings.ts
+++ b/src/types/settings.ts
@@ -1,4 +1,4 @@
-export type SettingValue = string | number | boolean;
+export type SettingValue = string | number | boolean | any;
export type SettingsValues = Record<string, SettingValue>;
interface BaseSettingField {