Загрузка данных
import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { CompareManager } from '@core/CompareManager';
import { DateFormat, IMoexChart, Locale, MoexChart, Timeframes } from '@lib';
import { IndicatorsIds } from '@lib/constants';
import { CompareMode, SymbolInfoInput } from '@lib/types';
// import { argTypes } from '../argTypes';
import { dataSourceProvider } from '../common';
import { activate } from '../worker';
import type { Meta, StoryObj } from '@storybook/react';
/**
* ## TradeRadar story
* describes an entry point of MoexChart for TR user
*/
type TRProps = Omit<IMoexChart, 'container'>;
activate();
const TREntry = (props: TRProps) => {
const [isCompareOpen, setIsCompareOpen] = useState(false);
const [moexChart, setMoexChart] = useState<MoexChart | undefined>();
const [snap, setSnap] = useState<any>();
const containerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const container = containerRef.current;
if (!container) {
return;
}
const chart = new MoexChart({
...props,
container,
chartCollectionPreset: {
...props.chartCollectionPreset,
openCompareModal: () => setIsCompareOpen(true),
},
});
setMoexChart(chart);
return () => {
chart.destroy();
};
}, [props]);
return (
<div
style={{
width: '100%',
height: '100dvh',
display: 'grid',
gridTemplateRows: 'auto auto minmax(0, 1fr)',
gap: 10,
padding: 20,
boxSizing: 'border-box',
}}
>
<h3
style={{
margin: 0,
}}
>
TradeRadar usage
</h3>
<div>
<button
type="button"
onClick={() => {
setSnap(moexChart?.getSnapshot());
}}
>
Сохранить стейт
</button>
<button
type="button"
onClick={() => {
if (snap) {
moexChart?.setSnapshot(snap);
}
}}
>
Применить стейт
</button>
</div>
<div ref={containerRef} />
{isCompareOpen &&
createPortal(
<Modal
onClose={() => setIsCompareOpen(false)}
compareManager={moexChart?.getCompareManager() ?? null}
/>,
document.body,
)}
</div>
);
};
const meta: Meta<TRProps> = {
title: 'TradeRadar',
component: TREntry,
// argTypes, // todo: пофиксить вместе с переписыванием доки
parameters: {
layout: 'fullscreen',
docs: {
description: {
component: `## TradeRadar story
describes an entry point of MoexChart for TR user`,
},
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
const args: TRProps = {
snapshot: {
charts: [
{
timeframe: Timeframes['10s'],
chartSeriesType: 'Candlestick',
symbolId: null,
panes: [
{
// empty panes deletes automatically
isMain: true, // Be careful. There is only one main pane can be present
id: 0,
indicators: [
{
indicatorType: IndicatorsIds.Volume, // if indicatorType is undefined, then its compareIndicator
},
{
indicatorType: IndicatorsIds.EMA,
},
],
drawings: [],
},
{
isMain: false,
id: 1,
indicators: [
{
indicatorType: IndicatorsIds.RSI,
},
],
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: 'Закр.' },
absoluteChange: {
visible: true,
label: 'Абсолютное изменение',
},
percentageChange: {
visible: true,
label: 'Относительное изменение',
},
volume: { visible: true, label: 'Объем' },
open: { visible: true, label: 'Откр.' },
high: { visible: true, label: 'Макс.' },
low: { visible: true, label: 'Мин.' },
},
supportedTimeframes: [
Timeframes['1s'],
Timeframes['5s'],
Timeframes['10s'],
Timeframes['1m'],
Timeframes['2m'],
Timeframes['30m'],
Timeframes['1h'],
Timeframes['2h'],
Timeframes['3h'],
Timeframes['4h'],
Timeframes['1d'],
Timeframes['1w'],
],
supportedChartSeriesTypes: ['Candlestick', 'Line', 'Bar'],
getDataSource: dataSourceProvider.generateCandles.bind(dataSourceProvider),
startRealtime: (getSymbols, getTimeframe, update) =>
dataSourceProvider.startRealtime(getSymbols, getTimeframe, update), // should return unsub
theme: 'tr',
ohlc: {
show: true,
precision: 2,
},
mode: 'dark',
locale: Locale.eng,
},
lwcInheritedChartOptions: {
timeVisible: true,
secondsVisible: false,
timeFormat: '24h',
dateFormat: DateFormat.DD_MM_YYYY_HH_mm_ss,
},
};
export const TradeRadar: Story = {
args,
parameters: {
controls: {
expanded: true, // отвечает за расширение колонок(+Description, +Default) в табе controls
},
},
};
const COMPARE_ITEMS: SymbolInfoInput[] = [
{ symbolId: 'TQBR:SBER', symbol: 'SBER', symbolName: 'Sberbank' },
{ symbolId: 'APAX' },
{ symbolId: 'SOL' },
];
const Modal = ({ onClose, compareManager }: { onClose: () => void; compareManager: CompareManager | null }) => {
const [isNewScaleDisabled, setIsNewScaleDisabled] = useState(false);
useEffect(() => {
if (!compareManager) {
setIsNewScaleDisabled(false);
return;
}
setIsNewScaleDisabled(compareManager.isNewScaleDisabled());
const subscription = compareManager.isNewScaleDisabledObservable().subscribe(setIsNewScaleDisabled);
return () => subscription.unsubscribe();
}, [compareManager]);
return (
<div
onClick={onClose}
style={{
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#0000004D',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 999,
cursor: 'pointer',
}}
>
<div
onClick={(e) => e.stopPropagation()}
style={{
display: 'grid',
gap: 16,
padding: 16,
backgroundColor: 'white',
}}
>
{COMPARE_ITEMS.map((symbolInfo) => (
<div
key={symbolInfo.symbolId}
style={{ display: 'flex', justifyContent: 'space-between', gap: 16 }}
>
<span>{symbolInfo.symbolName ?? symbolInfo.symbol ?? symbolInfo.symbolId}</span>
<div style={{ display: 'flex', gap: 8 }}>
<button
onClick={() => compareManager?.setSymbolMode('Line', symbolInfo, CompareMode.Absolute)}
style={{ backgroundColor: 'lightgray', padding: '2px 8px' }}
type="button"
>
Абсолютная шкала
</button>
<button
onClick={() => compareManager?.setSymbolMode('Line', symbolInfo, CompareMode.Percentage)}
style={{ backgroundColor: 'lightgray', padding: '2px 8px' }}
type="button"
>
%
</button>
<button
onClick={() => compareManager?.setSymbolMode('Line', symbolInfo, CompareMode.NewScale)}
style={{
backgroundColor: isNewScaleDisabled ? 'darkgray' : 'lightgray',
padding: '2px 8px',
cursor: isNewScaleDisabled ? 'not-allowed' : 'cursor',
}}
disabled={isNewScaleDisabled}
type="button"
>
Новая шкала
</button>
<button
onClick={() => compareManager?.setSymbolMode('Line', symbolInfo, CompareMode.NewPane)}
style={{ backgroundColor: 'lightgray', padding: '2px 8px' }}
type="button"
>
Новая панель
</button>
</div>
</div>
))}
</div>
</div>
);
};
import { DrawingsManagerSnapshot } from '@core/DrawingsManager';
import { ChartSeriesType, DateFormat, IndicatorsIds, Intervals, Timeframes } from '@lib';
import { Direction } from '@src/types/chart';
import { SymbolInfo, SymbolInfoInput } from '@src/types/symbol';
import { TimeFormat } from '@src/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; // todo: move to snap
chartSeriesType: ChartSeriesType; // todo: move to snap
}
export interface MoexChartSnapshotInput {
// settings: ChartSettingsSnapshot;
charts: ChartSnapshotInput[];
}
export interface MoexChartSnapshot {
// settings: ChartSettingsSnapshot;
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;
}
// todo: move DrawingsManagerSnapshot here
export interface DOMObjectSnapshot {
id: string;
name: string;
zIndex: number;
hidden: boolean;
paneId: number;
}
export interface SymbolInfo {
symbolId: string;
symbol: string;
symbolName: string;
}
export interface SymbolInfoInput {
symbolId: string;
symbol?: string;
symbolName?: string;
}