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


import { Button } from 'exchange-elements/v2';
import { Time } from 'lightweight-charts';
import React from 'react';

import { Observable } from 'rxjs';

import { TrashIcon } from '@components/Icon';

import { LegendModel, Ohlc, ohlcValuesToShowForMainSerie } from '@core/Legend';

import { OHLCConfig } from '../../types';
import { useObservable } from '../../utils';

import styles from './index.module.scss';

export interface LegendProps {
  ohlcConfig?: OHLCConfig;
  viewModel: Observable<LegendModel>;
}

export const LegendComponent: React.FC<LegendProps> = ({ ohlcConfig, viewModel }) => {
  const model = useObservable(viewModel);
  const showOhlc = ohlcConfig?.show;

  return (
    <section className={styles.legend}>
      {model?.map((indicator) => {
        const inds = Array.from(
          indicator.values as Map<keyof Ohlc, { value: number | string | Time; color: string; name: string }>,
        );
        return (
          <div
            key={`indicator-${indicator.name}`}
            className={styles.row}
          >
            <div className={styles.symbol}>{indicator.name}</div>
            {showOhlc &&
              inds?.map((x, index) => {
                const [key, serie] = x as [keyof Ohlc, { value: number | string | Time; color: string; name: string }];
                if (!indicator.isIndicator) {
                  if (!ohlcValuesToShowForMainSerie.includes(key)) {
                    return null;
                  }

                  return (
                    <div
                      key={serie.name === '' ? `${indicator.name}${index}` : serie.name}
                      className={styles.item}
                    >
                      {serie.name} <span style={{ color: serie.color }}>{serie.value as string}</span>
                    </div>
                  );
                }

                return (
                  <div
                    key={serie.name === '' ? `${indicator.name}${index}` : serie.name}
                    className={styles.item}
                  >
                    {serie.name} <span style={{ color: serie.color }}>{serie.value as string}</span>
                  </div>
                );
              })}
            {indicator.remove && (
              <Button
                size="sm"
                className={styles.button}
                onClick={() => indicator.remove?.()}
                label={<TrashIcon />}
              />
            )}
          </div>
        );
      })}
    </section>
  );
};








import { Divider } from 'exchange-elements/v2';
import { Time, UTCTimestamp } from 'lightweight-charts';
import { FC, useLayoutEffect, useRef, useState } from 'react';

import { Observable } from 'rxjs';

import { Ohlc } from '@core/Legend';

import { TooltipVM } from '@core/Tooltip';

import { getThemeStore } from '@src/theme';
import { OHLCConfig, TimeFormat, Timeframes, TooltipConfig } from '@src/types';
import { Defaults } from '@src/types/defaults';

import { calcTooltipPosition, DateFormat, formatDate, formatPrice, shouldShowTime, useObservable } from '@src/utils';

import styles from './index.module.scss';

export interface TooltipProps {
  formatObs: Observable<{ dateFormat: DateFormat; timeFormat: TimeFormat }>;
  timeframeObs: Observable<Timeframes>;
  tooltipConfig: TooltipConfig;
  viewModel: Observable<TooltipVM>;
  ohlcConfig: OHLCConfig;
}

const TOOLTIP_MARGIN = 15;

export const ChartTooltip: FC<TooltipProps> = ({
  formatObs,
  timeframeObs,
  ohlcConfig,
  tooltipConfig,
  viewModel: vm,
}) => {
  const viewModel = useObservable(vm);
  const format = useObservable<{ dateFormat: DateFormat; timeFormat: TimeFormat }>(formatObs);
  const timeframe = useObservable<Timeframes>(timeframeObs);

  const refTooltip = useRef<HTMLDivElement | null>(null);
  const [tooltipSize, setTooltipSize] = useState({ width: 0, height: 0 });

  const { colors } = getThemeStore();

  const { visible, position, viewport, vm: vmLegend } = viewModel ?? {};
  const showTime = timeframe ? shouldShowTime(timeframe) : true;

  useLayoutEffect(() => {
    const el = refTooltip.current;
    if (!el || !visible) return;
    const width = el?.offsetWidth;
    const height = el?.offsetHeight;

    setTooltipSize({ width, height });
  }, [visible]);

  if (!visible || !position || !viewport) {
    return null;
  }
  const coords = calcTooltipPosition({
    position,
    viewport,
    tooltipSize,
    margin: TOOLTIP_MARGIN,
  });
  const style = { backgroundColor: colors.chartTooltipBackground, left: `${coords.left}px`, top: `${coords.top}px` };

  const precision = ohlcConfig?.precision;

  // const priceColor = getPriceColor(ohlc ?? null);

  const renderRow = (label?: string, value?: number | string) => (
    <div
      className={styles.row}
      key={label}
    >
      <span className={styles.label}>{label}</span>
      <span className={styles.value}>{value}</span>
    </div>
  );

  return (
    <div
      className={styles.tooltip}
      style={style}
      ref={refTooltip}
    >
      {vmLegend?.map((indicator) => {
        if (indicator.isIndicator) {
          const entries = Array.from(
            indicator.values as Map<keyof Ohlc, { value: number | string | Time; color: string; name: string }>,
          );

          return (
            <>
              {entries.map((x) => {
                const [key, entry] = x as [keyof Ohlc, { value: number | string | Time; color: string; name: string }];

                return renderRow(key as string, entry.value as string);
              })}
            </>
          );
        }

        const entries: Partial<Record<keyof Ohlc, { value: number | string | Time; color: string; name: string }>> =
          Object.fromEntries(indicator.values as any);

        return (
          <>
            {tooltipConfig.time?.visible &&
              entries.time?.value &&
              renderRow(
                tooltipConfig.time.label,
                formatDate(
                  entries.time.value as UTCTimestamp,
                  format?.dateFormat ?? Defaults.dateFormat,
                  format?.timeFormat ?? Defaults.timeFormat,
                  showTime,
                ),
              )}

            {((tooltipConfig.close?.visible && !!entries.close?.value) ||
              (tooltipConfig.open?.visible && !!entries.open?.value) ||
              (tooltipConfig.value?.visible && !!entries.value?.value)) && (
              <Divider
                direction="horizontal"
                pt={{ divider: { className: styles.divider } }}
              />
            )}

            {tooltipConfig.close?.visible &&
              entries.close?.value &&
              renderRow(tooltipConfig.close.label, formatPrice(entries.close?.value as string, precision))}

            {tooltipConfig.value?.visible &&
              entries.value?.value &&
              renderRow(tooltipConfig.value.label, formatPrice(entries.value.value as string, precision))}

            {tooltipConfig.open?.visible &&
              entries.open?.value &&
              renderRow(tooltipConfig.open.label, formatPrice(entries.open.value as string, precision))}

            {((tooltipConfig.high?.visible && !!entries.high?.value) ||
              (tooltipConfig.low?.visible && !!entries.low?.value) ||
              (tooltipConfig.change?.visible && !!entries.absoluteChange?.value) ||
              (tooltipConfig.change?.visible && !!entries.percentageChange?.value)) && (
              <Divider
                direction="horizontal"
                pt={{ divider: { className: styles.divider } }}
              />
            )}

            {tooltipConfig.high?.visible &&
              entries.high?.value &&
              renderRow(tooltipConfig.high.label, formatPrice(entries.high.value as string, precision))}

            {tooltipConfig.low?.visible &&
              entries.low?.value &&
              renderRow(tooltipConfig.low.label, formatPrice(entries.low.value as string, precision))}

            {tooltipConfig.change?.visible &&
              entries.absoluteChange?.value &&
              renderRow('Абсолютное изменение', entries.absoluteChange.value as string)}

            {tooltipConfig.change?.visible &&
              entries.percentageChange?.value &&
              renderRow('Относительное изменение', entries.percentageChange.value as string)}
          </>
        );
      })}
    </div>
  );
};