Загрузка данных


import { DrawingsManagerSnapshot } from '../core/DrawingsManager';
import { ChartSeriesType, DateFormat, IndicatorsIds, Intervals, Timeframes } from '..';
import { Direction } from '../types/chart';
import { SymbolInfo, SymbolInfoInput } from '../types/symbol';
import { TimeFormat } from '../types/timeScale';
import { SettingsValues } from './settings';
import type { PriceScaleMode } from 'lightweight-charts';
export type PriceScaleSide = Direction.Left | Direction.Right;
export interface ISerializable<T extends object> {
    getSnapshot: () => T;
}
export interface InitialSnapshot extends SymbolInfoInput {
    timeframe: Timeframes;
    chartSeriesType: ChartSeriesType;
}
export interface MoexChartSnapshotInput {
    charts: ChartSnapshotInput[];
}
export interface MoexChartSnapshot {
    charts: ChartSnapshot[];
}
interface ChartSnapshotBase {
    timeframe: Timeframes;
    chartSeriesType: ChartSeriesType;
    timeFormat?: TimeFormat;
    dateFormat?: DateFormat;
    interval?: Intervals | null;
    panes: PaneSnapshot[];
}
export interface ChartSnapshotInput extends ChartSnapshotBase, SymbolInfoInput {
}
export interface ChartSnapshot extends ChartSnapshotBase, SymbolInfo {
}
export interface PriceScaleSnapshot {
    side: PriceScaleSide;
    mode: PriceScaleMode;
}
export interface PaneSnapshot {
    isMain: boolean;
    id: number;
    indicators: (IndicatorSnapshot | CompareSnapshot)[];
    drawings: DrawingsManagerSnapshot;
    priceScales?: PriceScaleSnapshot[];
}
export interface CompareSnapshot extends Partial<DOMObjectSnapshot> {
    symbolInfo: SymbolInfoInput;
    seriesName: ChartSeriesType;
    scale: Direction;
}
export interface IndicatorSnapshot extends Partial<DOMObjectSnapshot> {
    indicatorType: IndicatorsIds;
    settings?: SettingsValues;
}
export interface DOMObjectSnapshot {
    id: string;
    name: string;
    zIndex: number;
    hidden: boolean;
    paneId: number;
}
export {};



import { DateFormat, IndicatorsIds, Locale, Timeframes } from 'moex-chart';

import type { ChartCollectionPreset, IMoexChart, MoexChartSnapshotInput } from 'moex-chart';

type ChartCollectionPresetConfig = Omit<ChartCollectionPreset, 'getDataSource' | 'startRealtime'>;
type ChartSnapshotConfig = Omit<MoexChartSnapshotInput['charts'][number], 'symbolId'>;
type MoexChartSnapshotConfig = Omit<MoexChartSnapshotInput, 'charts'> & {
  charts: ChartSnapshotConfig[];
};

type MoexChartConfig = Omit<IMoexChart, 'container' | 'chartCollectionPreset' | 'snapshot'> & {
  snapshot: MoexChartSnapshotConfig;
  chartCollectionPreset: ChartCollectionPresetConfig;
};

const MOEX_CHART_CONFIG: MoexChartConfig = {
  snapshot: {
    charts: [
      {
        chartSeriesType: 'Candlestick',
        timeframe: Timeframes['10s'],
        timeFormat: '24h',
        dateFormat: DateFormat.DD_MM_YYYY_HH_mm_ss,
        panes: [
          {
            isMain: true,
            id: 0,
            indicators: [
              {
                indicatorType: IndicatorsIds.Volume,
              },
            ],
            drawings: [],
          },
        ],
      },
    ],
  },

  chartCollectionPreset: {
    undoRedoEnabled: true,
    showMenuButton: true,
    showBottomPanel: true,
    showControlBar: true,
    showFullscreenButton: true,
    showSettingsButton: true,
    showCompareButton: true,
    tooltipConfig: {
      showTooltip: false,
      time: { visible: true, label: 'Время' },
      close: { visible: true, label: 'Закр.' },
      change: { visible: true, label: 'Изм.' },
      volume: { visible: true, label: 'Объем' },
      open: { visible: true, label: 'Откр.' },
      high: { visible: true, label: 'Макс.' },
      low: { visible: true, label: 'Мин.' },
    },

    supportedTimeframes: [
      Timeframes['1m'],
      Timeframes['5m'],
      Timeframes['10m'],
      Timeframes['15m'],
      Timeframes['30m'],
      Timeframes['45m'],
      Timeframes['1h'],
      Timeframes['2h'],
      Timeframes['3h'],
      Timeframes['4h'],
      Timeframes['1d'],
      Timeframes['1w'],
      Timeframes['1М'],
    ],
    supportedChartSeriesTypes: ['Candlestick', 'Line', 'Bar'],
    theme: 'tr',
    ohlc: {
      show: true,
      precision: 4,
    },
    mode: 'dark',
    locale: Locale.rus,
  },

  lwcInheritedChartOptions: {
    timeVisible: true,
    secondsVisible: false,
  },
};

export { MOEX_CHART_CONFIG };


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]);

  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);

    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 = setInterval(() => {
      saveSnapshot();
    }, 1000);

    return () => {
      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 { DataSourceParams } from './DataSource';
import { CompareManager } from '../core/CompareManager';
import { ThemeKey, ThemeMode } from '../theme/types';
import { Locale } from '../translations';
import { Candle, ChartSeriesType, ChartTypeOptions, OHLCConfig, SymbolInfoInput, TooltipConfig } from '../types';
import { ISerializable, MoexChartSnapshot, MoexChartSnapshotInput } from '../types/snapshot';
import { Timeframes } from '../types/timeframes';
import { ChartSettings, ChartSettingsSource } from './ChartSettings';
import 'exchange-elements/dist/fonts/inter/font.css';
import 'exchange-elements/dist/style.css';
import 'exchange-elements/dist/tokens/moex.css';
import '../styles/global.scss';
export interface ChartCollectionPreset {
    undoRedoEnabled?: boolean;
    showMenuButton?: boolean;
    showBottomPanel?: boolean;
    showControlBar?: boolean;
    showFullscreenButton?: boolean;
    showSettingsButton?: boolean;
    showCompareButton?: boolean;
    showSymbolSearchButton?: boolean;
    /**
     * Дефолтная конфигурация тултипа - всегда показывается по умолчанию.
     * При добавлении/изменении полей в конфиге - они объединяются с дефолтными значениями.
     *
     * Полная кастомизация:
     * @example
     * ```typescript
     *    tooltipConfig: {
     *      time: { visible: true, label: 'Дата и время' },
     *      symbol: { visible: true, label: 'Инструмент' },
     *      close: { visible: true, label: 'Курс' },
     *      change: { visible: true, label: 'Изменение' },
     *      volume: { visible: true, label: 'Объем' },
     *      open: { visible: false },
     *      high: { visible: false },
     *      low: { visible: false }
     *    }
     *```
     */
    tooltipConfig?: TooltipConfig;
    size?: {
        width: number;
        height: number;
    } | false;
    supportedTimeframes: Timeframes[];
    supportedChartSeriesTypes: ChartSeriesType[];
    getDataSource: DataSourceParams['getData'];
    startRealtime: (getSymbols: () => string[], getTimeframe: () => Timeframes, update: (symbolId: string, candle: Candle) => void, periodMs?: number) => () => void;
    theme: ThemeKey;
    ohlc: OHLCConfig;
    locale: Locale;
    mode?: ThemeMode;
    openCompareModal?: () => void;
    openSymbolSearchModal?: () => void;
}
export interface IMoexChart {
    snapshot: MoexChartSnapshotInput;
    chartCollectionPreset: ChartCollectionPreset;
    container: HTMLElement;
    lwcInheritedChartOptions?: ChartTypeOptions;
}
export declare class MoexChart implements ISerializable<MoexChartSnapshot> {
    private chart;
    private resizeObserver?;
    private eventManager;
    private hotkeys;
    private rootContainer;
    private headerRenderer;
    private modalRenderer;
    private toolbarRenderer;
    private controlBarRenderer?;
    private footerRenderer?;
    private drawingToolbarRenderer;
    private timeScaleHoverController;
    private dataSource;
    private subscriptions;
    private fullscreen;
    private chartCollectionPresetSettings;
    constructor(config: IMoexChart);
    private setup;
    setSettings(settings: ChartSettingsSource): void;
    getSettings(): ChartSettings;
    getRealtimeApi(): {
        getTimeframe: () => Timeframes;
        getSymbols: () => string[];
        update: (symbolId: string, candle: Candle) => void;
    };
    getCompareManager(): CompareManager;
    setSnapshot(snapshot: MoexChartSnapshotInput): void;
    getSnapshot(): MoexChartSnapshot;
    setSymbol(symbolInfo: SymbolInfoInput): void;
    private renderAttachments;
    /**
     * Уничтожение графика и очистка ресурсов
     * @returns void
     */
    destroy(): void;
}