Загрузка данных
import { Intervals, MoexChart, Timeframes } from 'moex-chart';
import { useCallback, useEffect, useRef, useState } from 'react';
import {
useChangeProperties,
useSelectProperties,
} from '@modules/widgetProperties';
import { WidgetProperties } from '@widgets/Chart/properties/types';
import { ChartIndicativeData } from '@widgets/Chart/types';
import { MOEX_CHART_CONFIG } from '../constants';
import { DataSourceProvider } from '../dataSourceProvide';
import type {
__CompareManager__,
IMoexChart,
SymbolInfoInput,
} from 'moex-chart';
interface TUseMoexChartProps {
symbolInfo: SymbolInfoInput;
indicativeData?: ChartIndicativeData;
}
export const useMoexChart = ({
symbolInfo,
indicativeData,
}: TUseMoexChartProps) => {
const moexChartState = useSelectProperties(
(widgetProperties: Partial<WidgetProperties>) =>
widgetProperties.moexChartState,
);
const { updateProperties } =
useChangeProperties<WidgetProperties>();
const [isCompareOpen, setIsCompareOpen] = useState(false);
const [isSymbolSearchOpen, setIsSymbolSearchOpen] =
useState(false);
const containerRef = useRef<HTMLDivElement | null>(null);
const chartRef = useRef<MoexChart | null>(null);
const compareManagerRef =
useRef<__CompareManager__ | null>(null);
const currentSymbolInfoRef =
useRef<SymbolInfoInput>(symbolInfo);
const timeframeRef = useRef<Timeframes | undefined>(
moexChartState?.timeframe,
);
const savedDataRef = useRef<string | undefined>(
moexChartState?.savedData,
);
const initialIntervalRef = useRef<Intervals | undefined>(
moexChartState?.initialInterval,
);
const updateTimeframeRef = useRef<
((timeframe: Timeframes) => void) | null
>(null);
useEffect(() => {
const currentSymbolInfo = currentSymbolInfoRef.current;
if (
currentSymbolInfo.symbolId === symbolInfo.symbolId &&
currentSymbolInfo.symbol === symbolInfo.symbol &&
currentSymbolInfo.symbolName === symbolInfo.symbolName
) {
return;
}
currentSymbolInfoRef.current = symbolInfo;
chartRef.current?.setSymbol(symbolInfo);
}, [
symbolInfo.symbol,
symbolInfo.symbolId,
symbolInfo.symbolName,
]);
useEffect(() => {
timeframeRef.current = moexChartState?.timeframe;
savedDataRef.current = moexChartState?.savedData;
}, [
moexChartState?.savedData,
moexChartState?.timeframe,
]);
updateTimeframeRef.current = (
timeframe: Timeframes,
): void => {
if (timeframeRef.current === timeframe) {
return;
}
timeframeRef.current = timeframe;
updateProperties((state) => {
state.moexChartState = {
...state.moexChartState,
timeframe,
};
});
};
const saveSnapshot = useCallback((): void => {
const snapshot = chartRef.current?.getSnapshot();
if (!snapshot) {
return;
}
const savedData = JSON.stringify({
...snapshot,
charts: snapshot.charts.map((chart) => ({
...chart,
panes: chart.panes.map((pane) => ({
...pane,
indicators: pane.indicators.map((indicator) => {
const compareSeries =
indicator.config?.series[0];
const compareSymbolInfo =
indicator.config?.symbolInfo;
return {
...indicator,
dataSource: undefined,
config:
indicator.indicatorType === undefined &&
compareSymbolInfo &&
indicator.config?.label &&
compareSeries
? {
symbolInfo: compareSymbolInfo,
label: indicator.config.label,
newPane: indicator.config.newPane,
series: [
{
name: compareSeries.name,
seriesOptions: {
priceScaleId:
compareSeries.seriesOptions
?.priceScaleId,
},
},
],
}
: undefined,
};
}),
})),
})),
});
if (savedDataRef.current === savedData) {
return;
}
savedDataRef.current = savedData;
updateProperties((state) => {
state.moexChartState = {
...state.moexChartState,
timeframe:
timeframeRef.current ?? Timeframes['1m'],
savedData,
};
});
}, [updateProperties]);
const getSnapshotWithCurrentSettings = (
snapshot:
| typeof MOEX_CHART_CONFIG.snapshot
| IMoexChart['snapshot'],
timeframe: Timeframes,
initialInterval?: Intervals,
): IMoexChart['snapshot'] => ({
...snapshot,
charts: snapshot.charts.map((chartSnapshot) => ({
...chartSnapshot,
...currentSymbolInfoRef.current,
timeframe,
...(initialInterval
? { interval: initialInterval }
: {}),
})),
});
const applySnapshot = (): void => {
if (!savedDataRef.current || !chartRef.current) {
return;
}
const savedSnapshot = JSON.parse(
savedDataRef.current,
) as IMoexChart['snapshot'];
const timeframe =
timeframeRef.current ?? Timeframes['1m'];
chartRef.current.setSnapshot(
getSnapshotWithCurrentSettings(
savedSnapshot,
timeframe,
),
);
compareManagerRef.current =
chartRef.current.getCompareManager();
};
useEffect(() => {
const container = containerRef.current;
if (!container) {
return undefined;
}
const timeframe =
timeframeRef.current ?? Timeframes['1m'];
const initialInterval =
initialIntervalRef.current;
const savedSnapshot = savedDataRef.current
? (JSON.parse(
savedDataRef.current,
) as IMoexChart['snapshot'])
: MOEX_CHART_CONFIG.snapshot;
const dataProvider = new DataSourceProvider();
const chart = new MoexChart({
...MOEX_CHART_CONFIG,
container,
snapshot: getSnapshotWithCurrentSettings(
savedSnapshot,
timeframe,
initialInterval,
),
chartCollectionPreset: {
...MOEX_CHART_CONFIG.chartCollectionPreset,
openCompareModal: () =>
setIsCompareOpen(true),
openSymbolSearchModal: () =>
setIsSymbolSearchOpen(true),
getDataSource: dataProvider.getDataSource(
indicativeData,
(nextTimeframe) => {
updateTimeframeRef.current?.(
nextTimeframe,
);
},
),
startRealtime: (
getSymbols,
getTimeframe,
update,
) =>
dataProvider.startRealtime({
getSymbols,
getTimeframe,
update,
indicativeData,
}),
},
});
chartRef.current = chart;
compareManagerRef.current =
chart.getCompareManager();
if (initialInterval) {
initialIntervalRef.current = undefined;
updateProperties((state) => {
state.moexChartState = {
...state.moexChartState,
initialInterval: undefined,
};
});
}
const intervalId = window.setInterval(() => {
saveSnapshot();
}, 1000);
return () => {
window.clearInterval(intervalId);
saveSnapshot();
chartRef.current = null;
compareManagerRef.current = null;
chart.destroy();
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- график пересоздаётся только при изменении источника индикативных данных
}, [indicativeData?.key]);
return {
containerRef,
isCompareOpen,
isSymbolSearchOpen,
compareManagerRef,
setIsCompareOpen,
setIsSymbolSearchOpen,
saveSnapshot,
applySnapshot,
hasSavedSnapshot: Boolean(
moexChartState?.savedData,
),
};
};
import type { ChartIndicativeData } from '../types';
import type { Contract } from '@modules/contracts/types';
import type { WidgetProperties as BaseWidgetProperties } from '@modules/widgetProperties/types';
import type { Intervals, Timeframes } from 'moex-chart';
export interface WidgetProperties
extends BaseWidgetProperties {
chartState: {
savedInstrument: Contract['issKey'] | null;
savedSymbol?: Contract['symbol'];
savedSymbolName?: Contract['displayName'];
interval: string;
savedData?: string;
};
indicativeData?: ChartIndicativeData;
moexChartState?: {
initialInterval?: Intervals;
timeframe?: Timeframes;
savedData?: string;
};
}