지표추가시 겹침문제
This commit is contained in:
@@ -441,6 +441,11 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
try {
|
try {
|
||||||
await mgr.addIndicatorsBatch(missing);
|
await mgr.addIndicatorsBatch(missing);
|
||||||
afterIndicatorPaneMutation(mgr);
|
afterIndicatorPaneMutation(mgr);
|
||||||
|
mgr.notifyPaneLayoutChanged();
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||||
|
afterIndicatorPaneMutation(mgr);
|
||||||
|
mgr.notifyPaneLayoutChanged();
|
||||||
|
}));
|
||||||
} finally {
|
} finally {
|
||||||
indicatorSyncInFlightRef.current = false;
|
indicatorSyncInFlightRef.current = false;
|
||||||
}
|
}
|
||||||
@@ -1178,10 +1183,38 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
indicatorSyncInFlightRef.current = true;
|
indicatorSyncInFlightRef.current = true;
|
||||||
mgr.removeIndicators(removedIds);
|
mgr.removeIndicators(removedIds);
|
||||||
afterIndicatorPaneMutation(mgr);
|
afterIndicatorPaneMutation(mgr);
|
||||||
|
mgr.notifyPaneLayoutChanged();
|
||||||
restoreLogicalRange(mgr, savedLR);
|
restoreLogicalRange(mgr, savedLR);
|
||||||
syncIndicatorTrackingRefs(indicators);
|
syncIndicatorTrackingRefs(indicators);
|
||||||
indicatorSyncInFlightRef.current = false;
|
indicatorSyncInFlightRef.current = false;
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||||
|
afterIndicatorPaneMutation(mgr);
|
||||||
|
mgr.notifyPaneLayoutChanged();
|
||||||
|
}));
|
||||||
void repairMissingIndicators(mgr, indicators);
|
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 (
|
} else if (
|
||||||
addedConfigs.length > 0
|
addedConfigs.length > 0
|
||||||
&& removedIds.length === 0
|
&& removedIds.length === 0
|
||||||
@@ -1191,9 +1224,17 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
indicatorSyncInFlightRef.current = true;
|
indicatorSyncInFlightRef.current = true;
|
||||||
void mgr.addIndicatorsBatch(addedConfigs).then(() => {
|
void mgr.addIndicatorsBatch(addedConfigs).then(() => {
|
||||||
afterIndicatorPaneMutation(mgr);
|
afterIndicatorPaneMutation(mgr);
|
||||||
|
mgr.notifyPaneLayoutChanged();
|
||||||
restoreLogicalRange(mgr, savedLR);
|
restoreLogicalRange(mgr, savedLR);
|
||||||
syncIndicatorTrackingRefs(indicators);
|
syncIndicatorTrackingRefs(indicators);
|
||||||
indicatorSyncInFlightRef.current = false;
|
indicatorSyncInFlightRef.current = false;
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||||
|
afterIndicatorPaneMutation(mgr);
|
||||||
|
mgr.notifyPaneLayoutChanged();
|
||||||
|
[50, 150, 350].forEach(ms => {
|
||||||
|
setTimeout(() => mgr.notifyPaneLayoutChanged(), ms);
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
} else if (
|
} else if (
|
||||||
paramsChangedConfigs.length > 0
|
paramsChangedConfigs.length > 0
|
||||||
|
|||||||
@@ -652,6 +652,9 @@ export class ChartManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.indicators.set(config.id, { id: config.id, type: config.type, seriesList, seriesMeta: ichiMeta, paneIndex: pane, alertLines: [], hlineRefs: [], config, cloudPlugin });
|
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);
|
this.applyIndicatorStyle(config);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -738,6 +741,8 @@ export class ChartManager {
|
|||||||
id: config.id, type: config.type, seriesList, seriesMeta,
|
id: config.id, type: config.type, seriesList, seriesMeta,
|
||||||
paneIndex: pane, alertLines: [], hlineRefs, fillPrimitive, config,
|
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)) {
|
if (this._indicatorLoadStale(dataGenAtStart) || this.indicators.has(config.id)) {
|
||||||
this._discardIndicatorSeries(seriesList, { fillPrimitive });
|
this._discardIndicatorSeries(seriesList, { fillPrimitive });
|
||||||
return;
|
return;
|
||||||
@@ -751,13 +756,19 @@ export class ChartManager {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[Indicator] ${config.type} error:`, e);
|
console.error(`[Indicator] ${config.type} error:`, e);
|
||||||
} finally {
|
} finally {
|
||||||
if (
|
if (!options?.skipLayout) {
|
||||||
!options?.skipLayout
|
this._finalizeIndicatorPaneLayout();
|
||||||
&& this._activeIndicatorPaneIndices().size > 0
|
}
|
||||||
) {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 지표 추가·제거 후 pane index 동기화 + stretch 재배분 */
|
||||||
|
private _finalizeIndicatorPaneLayout(): void {
|
||||||
|
if (this._activeIndicatorPaneIndices().size === 0) return;
|
||||||
|
this._removeOrphanSubPanes();
|
||||||
|
this._resyncIndicatorPaneIndices();
|
||||||
this.resetPaneHeights(this._lastLayoutAvailableHeight);
|
this.resetPaneHeights(this._lastLayoutAvailableHeight);
|
||||||
}
|
this._notifyPaneLayout();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 여러 지표 추가 후 pane 레이아웃을 한 번만 갱신 */
|
/** 여러 지표 추가 후 pane 레이아웃을 한 번만 갱신 */
|
||||||
@@ -765,11 +776,8 @@ export class ChartManager {
|
|||||||
for (const config of configs) {
|
for (const config of configs) {
|
||||||
await this.addIndicator(config, { skipLayout: true });
|
await this.addIndicator(config, { skipLayout: true });
|
||||||
}
|
}
|
||||||
if (configs.length > 0 && this._activeIndicatorPaneIndices().size > 0) {
|
if (configs.length > 0) {
|
||||||
this._removeOrphanSubPanes();
|
this._finalizeIndicatorPaneLayout();
|
||||||
this._resyncIndicatorPaneIndices();
|
|
||||||
this.resetPaneHeights(this._lastLayoutAvailableHeight);
|
|
||||||
this._notifyPaneLayout();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,11 +839,7 @@ export class ChartManager {
|
|||||||
|
|
||||||
if (this._indicatorLoadStale(loadGen)) return;
|
if (this._indicatorLoadStale(loadGen)) return;
|
||||||
|
|
||||||
// 시리즈 제거 후 남은 빈 sub-pane 제거 (상단 orphan pane → 레이블·그래프 어긋남 방지)
|
this._finalizeIndicatorPaneLayout();
|
||||||
this._removeOrphanSubPanes();
|
|
||||||
// 빈 pane stretch 정리 + 캔들 pane 비율 복구
|
|
||||||
this.resetPaneHeights();
|
|
||||||
this._notifyPaneLayout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeIndicator(id: string): void {
|
removeIndicator(id: string): void {
|
||||||
@@ -849,7 +853,9 @@ export class ChartManager {
|
|||||||
if (this._detachIndicatorEntry(id)) any = true;
|
if (this._detachIndicatorEntry(id)) any = true;
|
||||||
}
|
}
|
||||||
if (!any) return;
|
if (!any) return;
|
||||||
|
this._removeOrphanSubPanes();
|
||||||
this._trimTrailingEmptySubPanes();
|
this._trimTrailingEmptySubPanes();
|
||||||
|
this._resyncIndicatorPaneIndices();
|
||||||
this.resetPaneHeights(this._lastLayoutAvailableHeight);
|
this.resetPaneHeights(this._lastLayoutAvailableHeight);
|
||||||
this._notifyPaneLayout();
|
this._notifyPaneLayout();
|
||||||
}
|
}
|
||||||
@@ -876,12 +882,7 @@ export class ChartManager {
|
|||||||
}
|
}
|
||||||
if (this._indicatorLoadStale(loadGen)) return;
|
if (this._indicatorLoadStale(loadGen)) return;
|
||||||
|
|
||||||
this._removeOrphanSubPanes();
|
this._finalizeIndicatorPaneLayout();
|
||||||
this._resyncIndicatorPaneIndices();
|
|
||||||
if (this._activeIndicatorPaneIndices().size > 0) {
|
|
||||||
this.resetPaneHeights(this._lastLayoutAvailableHeight);
|
|
||||||
this._notifyPaneLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1332,8 +1333,36 @@ export class ChartManager {
|
|||||||
return [...historical, ...future];
|
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 {
|
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;
|
let pane = 2;
|
||||||
while (used.has(pane)) pane++;
|
while (used.has(pane)) pane++;
|
||||||
return pane;
|
return pane;
|
||||||
@@ -2091,7 +2120,7 @@ export class ChartManager {
|
|||||||
let y = 0;
|
let y = 0;
|
||||||
for (let i = 0; i < paneCount; i++) {
|
for (let i = 0; i < paneCount; i++) {
|
||||||
const h = Math.round((panes[i].getStretchFactor() / totalFactor) * chartAreaH);
|
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);
|
y += h + (i < paneCount - 1 ? SEP_H : 0);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -2673,12 +2702,7 @@ export class ChartManager {
|
|||||||
|
|
||||||
/** 실제 보조지표가 붙어 있는 pane index 집합 (2+) */
|
/** 실제 보조지표가 붙어 있는 pane index 집합 (2+) */
|
||||||
private _activeIndicatorPaneIndices(): Set<number> {
|
private _activeIndicatorPaneIndices(): Set<number> {
|
||||||
const indices = new Set<number>();
|
return this._collectUsedSubPaneIndices();
|
||||||
for (const entry of this.indicators.values()) {
|
|
||||||
const p = entry.paneIndex;
|
|
||||||
if (p != null && p >= 2) indices.add(p);
|
|
||||||
}
|
|
||||||
return indices;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** pane 2+ 빈 sub-pane 제거 — reloadIndicatorsOnly 전용 (모든 지표 시리즈 제거 후) */
|
/** pane 2+ 빈 sub-pane 제거 — reloadIndicatorsOnly 전용 (모든 지표 시리즈 제거 후) */
|
||||||
@@ -2713,11 +2737,13 @@ export class ChartManager {
|
|||||||
|
|
||||||
/** 활성 지표가 없는 맨 끝 sub-pane 만 제거 — 단일 지표 제거 시 나머지 pane 유지 */
|
/** 활성 지표가 없는 맨 끝 sub-pane 만 제거 — 단일 지표 제거 시 나머지 pane 유지 */
|
||||||
private _trimTrailingEmptySubPanes(): void {
|
private _trimTrailingEmptySubPanes(): void {
|
||||||
|
const active = this._activeIndicatorPaneIndices();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const panes = this.chart.panes();
|
const panes = this.chart.panes();
|
||||||
if (panes.length <= 2) return;
|
if (panes.length <= 2) return;
|
||||||
const last = panes.length - 1;
|
const last = panes.length - 1;
|
||||||
if (this._activeIndicatorPaneIndices().has(last)) return;
|
const paneIndex = panes[last].paneIndex();
|
||||||
|
if (active.has(paneIndex)) return;
|
||||||
try {
|
try {
|
||||||
this.chart.removePane(last);
|
this.chart.removePane(last);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -3524,11 +3550,12 @@ export class ChartManager {
|
|||||||
: ORPHAN_STRETCH;
|
: ORPHAN_STRETCH;
|
||||||
|
|
||||||
for (let i = 0; i < panes.length; i++) {
|
for (let i = 0; i < panes.length; i++) {
|
||||||
if (i === 0) {
|
const paneIndex = panes[i].paneIndex();
|
||||||
|
if (paneIndex === 0) {
|
||||||
panes[i].setStretchFactor(mainWeight);
|
panes[i].setStretchFactor(mainWeight);
|
||||||
} else if (i === 1) {
|
} else if (paneIndex === 1) {
|
||||||
panes[i].setStretchFactor(volumeShown ? volWeight : ORPHAN_STRETCH);
|
panes[i].setStretchFactor(volumeShown ? volWeight : ORPHAN_STRETCH);
|
||||||
} else if (activeIndPanes.has(i)) {
|
} else if (activeIndPanes.has(paneIndex)) {
|
||||||
panes[i].setStretchFactor(indWeight);
|
panes[i].setStretchFactor(indWeight);
|
||||||
} else {
|
} else {
|
||||||
panes[i].setStretchFactor(ORPHAN_STRETCH);
|
panes[i].setStretchFactor(ORPHAN_STRETCH);
|
||||||
|
|||||||
Reference in New Issue
Block a user