실시간 차트 스크롤시 날짜 동기화 문제
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
/**
|
||||
* 캔들 pane 하단 시간축 — 거래량·보조지표가 아래에 있을 때 캔들 영역 바로 아래에도 시간 표시.
|
||||
*
|
||||
* 드래그 패닝 중에는 라벨 좌표를 재계산하지 않고 translate3d 로 차트와 동일 픽셀만큼 이동한다.
|
||||
* (setVisibleLogicalRange 직후 LWC 좌표 API가 한 프레임 늦게 갱신되는 문제 회피)
|
||||
*/
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback, useRef } from 'react';
|
||||
import type { ChartManager } from '../utils/ChartManager';
|
||||
import { subscribeChartTimeFormat } from '../utils/chartTimeFormat';
|
||||
|
||||
@@ -12,24 +15,81 @@ interface CandlePaneTimeAxisProps {
|
||||
containerEl: HTMLElement;
|
||||
}
|
||||
|
||||
const CandlePaneTimeAxis: React.FC<CandlePaneTimeAxisProps> = ({ manager, containerEl }) => {
|
||||
const [state, setState] = useState<ReturnType<ChartManager['getCandlePaneTimeAxisOverlay']>>(null);
|
||||
function applyOverlayLayout(
|
||||
root: HTMLDivElement,
|
||||
pool: HTMLSpanElement[],
|
||||
state: NonNullable<ReturnType<ChartManager['getCandlePaneTimeAxisOverlay']>>,
|
||||
): void {
|
||||
root.style.display = 'flex';
|
||||
root.style.top = `${state.topY}px`;
|
||||
root.style.height = `${state.height ?? AXIS_HEIGHT}px`;
|
||||
root.style.width = `${state.plotWidth}px`;
|
||||
|
||||
const sync = useCallback(() => {
|
||||
if (!containerEl.isConnected) return;
|
||||
setState(manager.getCandlePaneTimeAxisOverlay());
|
||||
state.labels.forEach((label, i) => {
|
||||
let span = pool[i];
|
||||
if (!span) {
|
||||
span = document.createElement('span');
|
||||
span.className = 'candle-pane-time-axis__label';
|
||||
root.appendChild(span);
|
||||
pool[i] = span;
|
||||
}
|
||||
span.style.display = '';
|
||||
span.style.left = `${label.x}px`;
|
||||
span.textContent = label.text;
|
||||
});
|
||||
for (let i = state.labels.length; i < pool.length; i++) {
|
||||
if (pool[i]) pool[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
const CandlePaneTimeAxis: React.FC<CandlePaneTimeAxisProps> = ({ manager, containerEl }) => {
|
||||
const axisRef = useRef<HTMLDivElement>(null);
|
||||
const labelPoolRef = useRef<HTMLSpanElement[]>([]);
|
||||
|
||||
const renderOverlay = useCallback(() => {
|
||||
const root = axisRef.current;
|
||||
if (!root || !containerEl.isConnected) return;
|
||||
|
||||
const panActive = manager.isCandleAxisPanActive();
|
||||
const panOffset = manager.getCandleAxisPanOffsetPx();
|
||||
|
||||
if (panActive) {
|
||||
const state = manager.getCandlePaneTimeAxisOverlay();
|
||||
if (!state || state.labels.length === 0) {
|
||||
root.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
if (panOffset === 0) {
|
||||
applyOverlayLayout(root, labelPoolRef.current, state);
|
||||
}
|
||||
root.style.transform = `translate3d(${panOffset}px, 0, 0)`;
|
||||
return;
|
||||
}
|
||||
|
||||
root.style.transform = '';
|
||||
|
||||
const state = manager.getCandlePaneTimeAxisOverlay();
|
||||
if (!state || state.labels.length === 0) {
|
||||
root.style.display = 'none';
|
||||
for (const span of labelPoolRef.current) {
|
||||
span.style.display = 'none';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
applyOverlayLayout(root, labelPoolRef.current, state);
|
||||
}, [manager, containerEl]);
|
||||
|
||||
useEffect(() => {
|
||||
sync();
|
||||
const ro = new ResizeObserver(sync);
|
||||
renderOverlay();
|
||||
const ro = new ResizeObserver(renderOverlay);
|
||||
ro.observe(containerEl);
|
||||
|
||||
const unsubLayout = manager.subscribePaneLayout(sync);
|
||||
const unsubRange = manager.subscribeViewport(sync);
|
||||
const unsubFormat = subscribeChartTimeFormat(sync);
|
||||
const unsubLayout = manager.subscribePaneLayout(renderOverlay);
|
||||
const unsubRange = manager.subscribeViewport(renderOverlay);
|
||||
const unsubFormat = subscribeChartTimeFormat(renderOverlay);
|
||||
|
||||
const timers = [0, 50, 150, 400].map(ms => window.setTimeout(sync, ms));
|
||||
const timers = [0, 50, 150, 400].map(ms => window.setTimeout(renderOverlay, ms));
|
||||
return () => {
|
||||
ro.disconnect();
|
||||
unsubLayout();
|
||||
@@ -37,30 +97,15 @@ const CandlePaneTimeAxis: React.FC<CandlePaneTimeAxisProps> = ({ manager, contai
|
||||
unsubFormat();
|
||||
timers.forEach(clearTimeout);
|
||||
};
|
||||
}, [manager, containerEl, sync]);
|
||||
|
||||
if (!state || state.labels.length === 0) return null;
|
||||
}, [manager, containerEl, renderOverlay]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={axisRef}
|
||||
className="candle-pane-time-axis"
|
||||
style={{
|
||||
top: state.topY,
|
||||
height: state.height ?? AXIS_HEIGHT,
|
||||
width: state.plotWidth,
|
||||
}}
|
||||
style={{ display: 'none' }}
|
||||
aria-hidden
|
||||
>
|
||||
{state.labels.map((label, idx) => (
|
||||
<span
|
||||
key={`${label.x}-${idx}`}
|
||||
className="candle-pane-time-axis__label"
|
||||
style={{ left: label.x }}
|
||||
>
|
||||
{label.text}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user