지표추가시 겹침문제

This commit is contained in:
Macbook
2026-05-30 01:06:55 +09:00
parent 09e44008cb
commit ba6695eb9c
2 changed files with 101 additions and 33 deletions
+41
View File
@@ -441,6 +441,11 @@ const TradingChart: React.FC<TradingChartProps> = ({
try {
await mgr.addIndicatorsBatch(missing);
afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged();
requestAnimationFrame(() => requestAnimationFrame(() => {
afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged();
}));
} finally {
indicatorSyncInFlightRef.current = false;
}
@@ -1178,10 +1183,38 @@ const TradingChart: React.FC<TradingChartProps> = ({
indicatorSyncInFlightRef.current = true;
mgr.removeIndicators(removedIds);
afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged();
restoreLogicalRange(mgr, savedLR);
syncIndicatorTrackingRefs(indicators);
indicatorSyncInFlightRef.current = false;
requestAnimationFrame(() => requestAnimationFrame(() => {
afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged();
}));
void repairMissingIndicators(mgr, indicators);
} else if (
addedConfigs.length > 0
&& removedIds.length > 0
&& paramsChangedConfigs.length === 0
) {
/** 지표 교체(제거+추가 동시) — CCI 제거 후 재추가 등 orphan pane·pane index 어긋남 방지 */
const savedLR = mgr.getVisibleLogicalRange();
indicatorSyncInFlightRef.current = true;
mgr.removeIndicators(removedIds);
void mgr.addIndicatorsBatch(addedConfigs).then(() => {
afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged();
restoreLogicalRange(mgr, savedLR);
syncIndicatorTrackingRefs(indicators);
indicatorSyncInFlightRef.current = false;
requestAnimationFrame(() => requestAnimationFrame(() => {
afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged();
[50, 150, 350].forEach(ms => {
setTimeout(() => mgr.notifyPaneLayoutChanged(), ms);
});
}));
});
} else if (
addedConfigs.length > 0
&& removedIds.length === 0
@@ -1191,9 +1224,17 @@ const TradingChart: React.FC<TradingChartProps> = ({
indicatorSyncInFlightRef.current = true;
void mgr.addIndicatorsBatch(addedConfigs).then(() => {
afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged();
restoreLogicalRange(mgr, savedLR);
syncIndicatorTrackingRefs(indicators);
indicatorSyncInFlightRef.current = false;
requestAnimationFrame(() => requestAnimationFrame(() => {
afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged();
[50, 150, 350].forEach(ms => {
setTimeout(() => mgr.notifyPaneLayoutChanged(), ms);
});
}));
});
} else if (
paramsChangedConfigs.length > 0
+60 -33
View File
@@ -652,6 +652,9 @@ export class ChartManager {
return;
}
this.indicators.set(config.id, { id: config.id, type: config.type, seriesList, seriesMeta: ichiMeta, paneIndex: pane, alertLines: [], hlineRefs: [], config, cloudPlugin });
const ichiPane = this._readSeriesPaneIndex(seriesList[0]);
const ichiEntry = this.indicators.get(config.id);
if (ichiEntry && ichiPane >= 2) ichiEntry.paneIndex = ichiPane;
this.applyIndicatorStyle(config);
return;
}
@@ -738,6 +741,8 @@ export class ChartManager {
id: config.id, type: config.type, seriesList, seriesMeta,
paneIndex: pane, alertLines: [], hlineRefs, fillPrimitive, config,
};
const actualPane = this._readSeriesPaneIndex(seriesList[0]);
if (actualPane >= 2) entry.paneIndex = actualPane;
if (this._indicatorLoadStale(dataGenAtStart) || this.indicators.has(config.id)) {
this._discardIndicatorSeries(seriesList, { fillPrimitive });
return;
@@ -751,25 +756,28 @@ export class ChartManager {
} catch (e) {
console.error(`[Indicator] ${config.type} error:`, e);
} finally {
if (
!options?.skipLayout
&& this._activeIndicatorPaneIndices().size > 0
) {
this.resetPaneHeights(this._lastLayoutAvailableHeight);
if (!options?.skipLayout) {
this._finalizeIndicatorPaneLayout();
}
}
}
/** 지표 추가·제거 후 pane index 동기화 + stretch 재배분 */
private _finalizeIndicatorPaneLayout(): void {
if (this._activeIndicatorPaneIndices().size === 0) return;
this._removeOrphanSubPanes();
this._resyncIndicatorPaneIndices();
this.resetPaneHeights(this._lastLayoutAvailableHeight);
this._notifyPaneLayout();
}
/** 여러 지표 추가 후 pane 레이아웃을 한 번만 갱신 */
async addIndicatorsBatch(configs: IndicatorConfig[]): Promise<void> {
for (const config of configs) {
await this.addIndicator(config, { skipLayout: true });
}
if (configs.length > 0 && this._activeIndicatorPaneIndices().size > 0) {
this._removeOrphanSubPanes();
this._resyncIndicatorPaneIndices();
this.resetPaneHeights(this._lastLayoutAvailableHeight);
this._notifyPaneLayout();
if (configs.length > 0) {
this._finalizeIndicatorPaneLayout();
}
}
@@ -831,11 +839,7 @@ export class ChartManager {
if (this._indicatorLoadStale(loadGen)) return;
// 시리즈 제거 후 남은 빈 sub-pane 제거 (상단 orphan pane → 레이블·그래프 어긋남 방지)
this._removeOrphanSubPanes();
// 빈 pane stretch 정리 + 캔들 pane 비율 복구
this.resetPaneHeights();
this._notifyPaneLayout();
this._finalizeIndicatorPaneLayout();
}
removeIndicator(id: string): void {
@@ -849,7 +853,9 @@ export class ChartManager {
if (this._detachIndicatorEntry(id)) any = true;
}
if (!any) return;
this._removeOrphanSubPanes();
this._trimTrailingEmptySubPanes();
this._resyncIndicatorPaneIndices();
this.resetPaneHeights(this._lastLayoutAvailableHeight);
this._notifyPaneLayout();
}
@@ -876,12 +882,7 @@ export class ChartManager {
}
if (this._indicatorLoadStale(loadGen)) return;
this._removeOrphanSubPanes();
this._resyncIndicatorPaneIndices();
if (this._activeIndicatorPaneIndices().size > 0) {
this.resetPaneHeights(this._lastLayoutAvailableHeight);
this._notifyPaneLayout();
}
this._finalizeIndicatorPaneLayout();
}
/**
@@ -1332,8 +1333,36 @@ export class ChartManager {
return [...historical, ...future];
}
private _readSeriesPaneIndex(series: ISeriesApi<SeriesType> | undefined): number {
if (!series) return -1;
try {
return series.getPane().paneIndex();
} catch {
return -1;
}
}
/** LWC 시리즈가 실제로 붙어 있는 sub-pane index (2+) */
private _collectUsedSubPaneIndices(): Set<number> {
const indices = new Set<number>();
for (const entry of this.indicators.values()) {
let found = false;
for (const series of entry.seriesList) {
const pi = this._readSeriesPaneIndex(series);
if (pi >= 2) {
indices.add(pi);
found = true;
}
}
if (!found && entry.paneIndex != null && entry.paneIndex >= 2) {
indices.add(entry.paneIndex);
}
}
return indices;
}
private _getNextSubPane(): number {
const used = new Set(Array.from(this.indicators.values()).map(e => e.paneIndex).filter(Boolean));
const used = this._collectUsedSubPaneIndices();
let pane = 2;
while (used.has(pane)) pane++;
return pane;
@@ -2091,7 +2120,7 @@ export class ChartManager {
let y = 0;
for (let i = 0; i < paneCount; i++) {
const h = Math.round((panes[i].getStretchFactor() / totalFactor) * chartAreaH);
result.push({ paneIndex: i, topY: y, height: h });
result.push({ paneIndex: panes[i].paneIndex(), topY: y, height: h });
y += h + (i < paneCount - 1 ? SEP_H : 0);
}
return result;
@@ -2673,12 +2702,7 @@ export class ChartManager {
/** 실제 보조지표가 붙어 있는 pane index 집합 (2+) */
private _activeIndicatorPaneIndices(): Set<number> {
const indices = new Set<number>();
for (const entry of this.indicators.values()) {
const p = entry.paneIndex;
if (p != null && p >= 2) indices.add(p);
}
return indices;
return this._collectUsedSubPaneIndices();
}
/** pane 2+ 빈 sub-pane 제거 — reloadIndicatorsOnly 전용 (모든 지표 시리즈 제거 후) */
@@ -2713,11 +2737,13 @@ export class ChartManager {
/** 활성 지표가 없는 맨 끝 sub-pane 만 제거 — 단일 지표 제거 시 나머지 pane 유지 */
private _trimTrailingEmptySubPanes(): void {
const active = this._activeIndicatorPaneIndices();
for (;;) {
const panes = this.chart.panes();
if (panes.length <= 2) return;
const last = panes.length - 1;
if (this._activeIndicatorPaneIndices().has(last)) return;
const paneIndex = panes[last].paneIndex();
if (active.has(paneIndex)) return;
try {
this.chart.removePane(last);
} catch {
@@ -3524,11 +3550,12 @@ export class ChartManager {
: ORPHAN_STRETCH;
for (let i = 0; i < panes.length; i++) {
if (i === 0) {
const paneIndex = panes[i].paneIndex();
if (paneIndex === 0) {
panes[i].setStretchFactor(mainWeight);
} else if (i === 1) {
} else if (paneIndex === 1) {
panes[i].setStretchFactor(volumeShown ? volWeight : ORPHAN_STRETCH);
} else if (activeIndPanes.has(i)) {
} else if (activeIndPanes.has(paneIndex)) {
panes[i].setStretchFactor(indWeight);
} else {
panes[i].setStretchFactor(ORPHAN_STRETCH);