크로스헤어 세로선 날짜표시

This commit is contained in:
Macbook
2026-05-29 21:37:41 +09:00
parent ab5c11fa14
commit 320675ea6b
3 changed files with 125 additions and 23 deletions
+51 -10
View File
@@ -1,10 +1,9 @@
/**
* 캔들 pane 하단 시간축 — 거래량·보조지표가 아래에 있을 때 캔들 영역 바로 아래에도 시간 표시.
*
* 드래그 패닝 중에는 라벨 좌표를 재계산하지 않고 translate3d 로 차트와 동일 픽셀만큼 이동한다.
* (setVisibleLogicalRange 직후 LWC 좌표 API가 한 프레임 늦게 갱신되는 문제 회피)
* 크로스헤어 세로선 위치에 해당 날짜·시간 배지를 함께 표시한다.
*/
import React, { useEffect, useCallback, useRef } from 'react';
import type { MouseEventParams, Time } from 'lightweight-charts';
import type { ChartManager } from '../utils/ChartManager';
import { subscribeChartTimeFormat } from '../utils/chartTimeFormat';
@@ -44,8 +43,36 @@ function applyOverlayLayout(
const CandlePaneTimeAxis: React.FC<CandlePaneTimeAxisProps> = ({ manager, containerEl }) => {
const axisRef = useRef<HTMLDivElement>(null);
const crosshairRef = useRef<HTMLDivElement>(null);
const labelPoolRef = useRef<HTMLSpanElement[]>([]);
const updateCrosshairLabel = useCallback((p: MouseEventParams<Time>) => {
const el = crosshairRef.current;
if (!el) return;
const band = manager.getCandlePaneTimeAxisBand();
if (!band || !p.time || p.point?.x == null) {
el.style.display = 'none';
return;
}
const x = p.point.x;
if (!Number.isFinite(x) || x < 0 || x > band.plotWidth) {
el.style.display = 'none';
return;
}
el.style.display = 'flex';
el.style.top = `${band.topY}px`;
el.style.height = `${band.height}px`;
el.style.left = `${x}px`;
el.textContent = manager.formatCrosshairAxisTime(p.time);
}, [manager]);
const hideCrosshairLabel = useCallback(() => {
if (crosshairRef.current) crosshairRef.current.style.display = 'none';
}, []);
const renderOverlay = useCallback(() => {
const root = axisRef.current;
if (!root || !containerEl.isConnected) return;
@@ -88,6 +115,10 @@ const CandlePaneTimeAxis: React.FC<CandlePaneTimeAxisProps> = ({ manager, contai
const unsubLayout = manager.subscribePaneLayout(renderOverlay);
const unsubRange = manager.subscribeViewport(renderOverlay);
const unsubFormat = subscribeChartTimeFormat(renderOverlay);
const unsubCrosshair = manager.subscribeCrosshair(updateCrosshairLabel);
const onLeave = () => hideCrosshairLabel();
containerEl.addEventListener('mouseleave', onLeave);
const timers = [0, 50, 150, 400].map(ms => window.setTimeout(renderOverlay, ms));
return () => {
@@ -95,17 +126,27 @@ const CandlePaneTimeAxis: React.FC<CandlePaneTimeAxisProps> = ({ manager, contai
unsubLayout();
unsubRange();
unsubFormat();
unsubCrosshair();
containerEl.removeEventListener('mouseleave', onLeave);
timers.forEach(clearTimeout);
};
}, [manager, containerEl, renderOverlay]);
}, [manager, containerEl, renderOverlay, updateCrosshairLabel, hideCrosshairLabel]);
return (
<div
ref={axisRef}
className="candle-pane-time-axis"
style={{ display: 'none' }}
aria-hidden
/>
<>
<div
ref={axisRef}
className="candle-pane-time-axis"
style={{ display: 'none' }}
aria-hidden
/>
<div
ref={crosshairRef}
className="candle-pane-time-axis__crosshair"
style={{ display: 'none' }}
aria-hidden
/>
</>
);
};