분석레포트 타임라인 표시
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 분석레포트 — 가로 봉 시간축 타임라인 (중앙 축 · 상단 매도 · 하단 매수)
|
||||
*/
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import {
|
||||
groupEventsByBarTime,
|
||||
type LiveTimelineEvent,
|
||||
} from '../../utils/liveTradeTimeline';
|
||||
import TimelineBarTimeBox from './TimelineBarTimeBox';
|
||||
import TimelineAxisEventCard from './TimelineAxisEventCard';
|
||||
import { useHorizontalWheelScroll } from '../../hooks/useHorizontalWheelScroll';
|
||||
import { useCenterAxisTimelineScroll } from '../../hooks/useCenterAxisTimelineScroll';
|
||||
import type { Timeframe } from '../../types';
|
||||
|
||||
const SEGMENT_COLORS = ['c0', 'c1', 'c2', 'c3', 'c4'] as const;
|
||||
|
||||
interface Props {
|
||||
events: LiveTimelineEvent[];
|
||||
timeframe: Timeframe;
|
||||
}
|
||||
|
||||
const TimelineAxisView: React.FC<Props> = ({ events, timeframe }) => {
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const trackRef = useRef<HTMLDivElement | null>(null);
|
||||
useHorizontalWheelScroll(scrollRef);
|
||||
|
||||
const slots = useMemo(
|
||||
() => groupEventsByBarTime(events, timeframe),
|
||||
[events, timeframe],
|
||||
);
|
||||
|
||||
useCenterAxisTimelineScroll(scrollRef, trackRef, slots.length > 0, [slots.length, events.length]);
|
||||
|
||||
if (slots.length === 0) {
|
||||
return <p className="arp-timeline-empty">봉 시간 기준으로 표시할 거래 이력이 없습니다.</p>;
|
||||
}
|
||||
|
||||
const trackMinWidth = Math.max(slots.length * 160, 320);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="arp-timeline-axis-scroll arp-timeline-h-scroll"
|
||||
aria-label="봉 시간축 거래 이력 타임라인"
|
||||
>
|
||||
<div className="arp-timeline-axis-scroll-inner">
|
||||
<div
|
||||
ref={trackRef}
|
||||
className="arp-timeline-axis-track"
|
||||
style={{ minWidth: trackMinWidth }}
|
||||
>
|
||||
<div className="arp-timeline-axis-zone arp-timeline-axis-zone--top">
|
||||
<span className="arp-timeline-axis-zone-label">매도</span>
|
||||
<div className="arp-timeline-axis-cols">
|
||||
{slots.map(slot => (
|
||||
<div key={`top-${slot.barTimeSec}`} className="arp-timeline-axis-col">
|
||||
{slot.sells.map(ev => (
|
||||
<div key={ev.id} className="arp-timeline-axis-col-stack">
|
||||
<TimelineAxisEventCard ev={ev} side="top" />
|
||||
<div
|
||||
className="arp-timeline-axis-connector arp-timeline-axis-connector--down arp-timeline-axis-connector--sell"
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="arp-timeline-axis-rail" aria-hidden>
|
||||
{slots.map((slot, i) => (
|
||||
<div
|
||||
key={slot.barTimeSec}
|
||||
className={[
|
||||
'arp-timeline-axis-segment',
|
||||
`arp-timeline-axis-segment--${SEGMENT_COLORS[i % SEGMENT_COLORS.length]}`,
|
||||
].join(' ')}
|
||||
>
|
||||
<TimelineBarTimeBox
|
||||
barTimeSec={slot.barTimeSec}
|
||||
timeframe={timeframe}
|
||||
variant="segment"
|
||||
showTimeframe={false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="arp-timeline-axis-zone arp-timeline-axis-zone--bottom">
|
||||
<div className="arp-timeline-axis-cols">
|
||||
{slots.map(slot => (
|
||||
<div key={`bottom-${slot.barTimeSec}`} className="arp-timeline-axis-col">
|
||||
{slot.buys.map(ev => (
|
||||
<div key={ev.id} className="arp-timeline-axis-col-stack">
|
||||
<div
|
||||
className="arp-timeline-axis-connector arp-timeline-axis-connector--up arp-timeline-axis-connector--buy"
|
||||
aria-hidden
|
||||
/>
|
||||
<TimelineAxisEventCard ev={ev} side="bottom" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<span className="arp-timeline-axis-zone-label">매수</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelineAxisView;
|
||||
Reference in New Issue
Block a user