115 lines
4.1 KiB
TypeScript
115 lines
4.1 KiB
TypeScript
/**
|
|
* 분석레포트 — 가로 봉 시간축 타임라인 (중앙 축 · 상단 매도 · 하단 매수)
|
|
*/
|
|
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;
|