실시간 차트 보조지표 탭 문제 수정

This commit is contained in:
Macbook
2026-05-28 01:26:53 +09:00
parent 4f6694b206
commit 98dfb3613c
19 changed files with 801 additions and 182 deletions
+65 -16
View File
@@ -50,7 +50,7 @@ import ChartContextMenu from './ChartContextMenu';
import CandlePaneControls from './CandlePaneControls';
import { getKoreanName } from '../utils/marketNameCache';
import { DEFAULT_DISPLAY_TIMEZONE } from '../utils/timezone';
import { classifyIndicatorChartChange, singleIndParamKey } from '../utils/indicatorChartSync';
import { chartHasStaleIndicators, classifyIndicatorChartChange, singleIndParamKey } from '../utils/indicatorChartSync';
import type { ChartPaneSeparatorOptions } from '../types/chartPaneSeparator';
import PaneSeparatorOverlay from './PaneSeparatorOverlay';
@@ -429,6 +429,8 @@ const TradingChart: React.FC<TradingChartProps> = ({
return;
}
afterIndicatorPaneMutation(mgr);
// resetPaneHeights 직후 레이블 좌표 재동기화 (탭 교체 후 어긋남 방지)
mgr.notifyPaneLayoutChanged();
requestAnimationFrame(() => requestAnimationFrame(() => {
if (reloadGen !== indicatorReloadGenRef.current) {
fadeOutIndicatorReloadCover(cover);
@@ -437,6 +439,14 @@ const TradingChart: React.FC<TradingChartProps> = ({
restoreLogicalRange(mgr, savedLR);
fadeOutIndicatorReloadCover(cover);
indicatorSyncInFlightRef.current = false;
mgr.notifyPaneLayoutChanged();
[50, 150, 350, 700, 1200].forEach(ms => {
setTimeout(() => {
if (reloadGen === indicatorReloadGenRef.current) {
mgr.notifyPaneLayoutChanged();
}
}, ms);
});
onComplete?.();
}));
});
@@ -475,7 +485,17 @@ const TradingChart: React.FC<TradingChartProps> = ({
const expected = countExpectedVisibleIndicators(inds);
if (expected === 0) return;
if (m.getLoadedIndicatorCount() >= expected) return;
const loadedIds = m.getLoadedIndicatorIds();
if (chartHasStaleIndicators(loadedIds, inds)) {
reloadIndicatorsWithCover(m, inds, () => {
prevBarsKey.current = barsKey(barData, market);
syncIndicatorTrackingRefs(inds);
applyPaneLayout(m);
});
return;
}
if (loadedIds.size >= expected) return;
prevBarsKey.current = '';
const ct = prevChartType.current;
@@ -484,7 +504,10 @@ const TradingChart: React.FC<TradingChartProps> = ({
if (m.hasMainSeries()) {
void repairMissingIndicators(m, inds).then(() => {
if (m.getLoadedIndicatorCount() >= expected) {
if (
m.getLoadedIndicatorCount() >= expected
&& !chartHasStaleIndicators(m.getLoadedIndicatorIds(), inds)
) {
prevBarsKey.current = barsKey(barData, market);
syncIndicatorTrackingRefs(inds);
applyPaneLayout(m);
@@ -496,7 +519,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
}
reloadAllRef.current?.(m, barData, ct, th, ls, inds);
}, 120);
}, [market, barsMarket, applyPaneLayout, repairMissingIndicators, syncIndicatorTrackingRefs]);
}, [market, barsMarket, applyPaneLayout, repairMissingIndicators, reloadIndicatorsWithCover, syncIndicatorTrackingRefs]);
/** setData 직후 캔들·볼륨 pane 레이아웃 확정 (지표 로드 중단 시에도 빈 화면 방지) */
const commitCandleLayout = useCallback((mgr: ChartManager) => {
@@ -1037,12 +1060,21 @@ const TradingChart: React.FC<TradingChartProps> = ({
if (barsChanged || ctChanged) {
void reloadAll(mgr, bars, chartType, theme, logScale, indicators);
} else if (indicatorsIncomplete) {
// 누락 지표만 추가 (전체 제거·재추가로 화면이 비는 문제 방지)
void repairMissingIndicators(mgr, indicators).then(() => {
syncIndicatorTrackingRefs(indicators);
prevBarsKey.current = barsKey(bars, market);
});
} else if (indicatorsIncomplete && !indChanged) {
// 지표 목록은 같지만 일부만 로드된 경우 — 누락분만 추가.
// 차트에 이전 탭 id가 남아 있으면(설정 화면에서 탭 교체 등) 전체 재로드.
const loadedIds = mgr.getLoadedIndicatorIds();
if (chartHasStaleIndicators(loadedIds, indicators)) {
reloadIndicatorsWithCover(mgr, indicators, () => {
syncIndicatorTrackingRefs(indicators);
prevBarsKey.current = barsKey(bars, market);
});
} else {
void repairMissingIndicators(mgr, indicators).then(() => {
syncIndicatorTrackingRefs(indicators);
prevBarsKey.current = barsKey(bars, market);
});
}
} else if (lsChanged) {
prevLogScale.current = logScale;
mgr.setLogScale(logScale);
@@ -1168,7 +1200,11 @@ const TradingChart: React.FC<TradingChartProps> = ({
const expected = countExpectedVisibleIndicators(indicators);
if (expected === 0) return;
if (mgr.getLoadedIndicatorCount() >= expected) return;
const loadedIds = mgr.getLoadedIndicatorIds();
if (
loadedIds.size >= expected
&& !chartHasStaleIndicators(loadedIds, indicators)
) return;
const timers = [120, 400, 900].map(delay =>
window.setTimeout(() => {
@@ -1176,10 +1212,23 @@ const TradingChart: React.FC<TradingChartProps> = ({
const m = managerRef.current;
if (!m || !m.hasMainSeries()) return;
if (!canApplyChartBars(bars, barsMarket, market)) return;
const exp = countExpectedVisibleIndicators(indicatorsRef.current);
if (exp === 0 || m.getLoadedIndicatorCount() >= exp) return;
void repairMissingIndicators(m, indicatorsRef.current).then(() => {
if (m.getLoadedIndicatorCount() >= exp) return;
const inds = indicatorsRef.current;
const exp = countExpectedVisibleIndicators(inds);
if (exp === 0) return;
const ids = m.getLoadedIndicatorIds();
if (chartHasStaleIndicators(ids, inds)) {
reloadIndicatorsWithCover(m, inds, () => {
syncIndicatorTrackingRefs(inds);
prevBarsKey.current = barsKey(bars, market);
});
return;
}
if (ids.size >= exp) return;
void repairMissingIndicators(m, inds).then(() => {
if (
m.getLoadedIndicatorCount() >= exp
&& !chartHasStaleIndicators(m.getLoadedIndicatorIds(), inds)
) return;
prevBarsKey.current = '';
queueIndicatorRecovery();
});
@@ -1187,7 +1236,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
);
return () => { timers.forEach(clearTimeout); };
}, [chartMgr, bars, barsMarket, market, chartType, theme, logScale, indicators, chartVisible, queueIndicatorRecovery, repairMissingIndicators]);
}, [chartMgr, bars, barsMarket, market, chartType, theme, logScale, indicators, chartVisible, queueIndicatorRecovery, repairMissingIndicators, reloadIndicatorsWithCover, syncIndicatorTrackingRefs]);
// cursor 모드에서 드로잉 위에 있으면 pointer 커서로 변경
const handleWrapperMouseMove = useCallback((e: React.PointerEvent) => {