Загрузка данных
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>
);
};