보조지표 합치기, 분리, 복사하기 기능 오류 수정

This commit is contained in:
Macbook
2026-06-01 01:50:54 +09:00
parent f2208aab99
commit 227db1e607
4 changed files with 110 additions and 35 deletions
+28 -15
View File
@@ -1268,9 +1268,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
const currSK = styleKey(indicators);
const needsRecalc = prevPK !== currPK;
const isReorderOnly = needsRecalc
&& sortedParamKey(indicators) === sortedParamKey(prevList)
&& currSK === prevSK;
const chartChange = classifyIndicatorChartChange(prevList, indicators);
const prevMap = new Map(prevList.map(i => [i.id, i]));
const removedIds = prevList
@@ -1283,7 +1281,17 @@ const TradingChart: React.FC<TradingChartProps> = ({
return singleIndParamKey(p) !== singleIndParamKey(c);
});
if (!needsRecalc && prevSK !== currSK) {
if (
chartChange.mode === 'reorder-only'
|| chartChange.mode === 'layout-changed'
) {
/** 순서·병합·분리 — 설정에 맞게 sub-pane 전체 재배치 */
indicatorSyncInFlightRef.current = true;
reloadIndicatorsWithCover(mgr, indicators, () => {
syncIndicatorTrackingRefs(indicators);
indicatorSyncInFlightRef.current = false;
});
} else if (!needsRecalc && prevSK !== currSK) {
for (const ind of indicators) {
mgr.applyIndicatorStyle(ind);
}
@@ -1336,7 +1344,13 @@ const TradingChart: React.FC<TradingChartProps> = ({
) {
const savedLR = mgr.getVisibleLogicalRange();
indicatorSyncInFlightRef.current = true;
void mgr.addIndicatorsBatch(addedConfigs).then(() => {
/** 복사 등 목록 중간 삽입 — pane 순서를 맞추려면 전체 재로드 */
const insertIdx = addedConfigs.length === 1
? indicators.findIndex(i => i.id === addedConfigs[0].id)
: -1;
const reloadForPaneOrder = insertIdx >= 0 && insertIdx < indicators.length - 1;
const finishAdd = () => {
afterIndicatorPaneMutation(mgr);
mgr.notifyPaneLayoutChanged();
restoreLogicalRange(mgr, savedLR);
@@ -1349,7 +1363,13 @@ const TradingChart: React.FC<TradingChartProps> = ({
setTimeout(() => mgr.notifyPaneLayoutChanged(), ms);
});
}));
});
};
if (reloadForPaneOrder) {
reloadIndicatorsWithCover(mgr, indicators, finishAdd);
} else {
void mgr.addIndicatorsBatch(addedConfigs).then(finishAdd);
}
} else if (
paramsChangedConfigs.length > 0
&& removedIds.length === 0
@@ -1373,19 +1393,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
setPaneLegendPaused(false);
flushPendingIndicatorResync(mgr, syncGen);
});
} else if (isReorderOnly) {
} else if (chartChange.mode === 'full') {
reloadIndicatorsWithCover(mgr, indicators, () => {
syncIndicatorTrackingRefs(indicators);
});
} else {
const change = classifyIndicatorChartChange(prevList, indicators);
if (change.mode === 'full') {
reloadIndicatorsWithCover(mgr, indicators, () => {
syncIndicatorTrackingRefs(indicators);
});
} else {
syncIndicatorTrackingRefs(indicators);
}
syncIndicatorTrackingRefs(indicators);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
+68 -10
View File
@@ -34,7 +34,7 @@ import {
normalizeChartTimeFormat,
} from './chartTimeFormat';
import { DEFAULT_DISPLAY_TIMEZONE } from './timezone';
import { sortIndicatorsForPaneLoad } from './indicatorPaneMerge';
import { getPaneHostId, sortIndicatorsForPaneLoad } from './indicatorPaneMerge';
import { calculateIndicator, enrichIndicatorConfig, getIndicatorDef, getHLineLabel, type PlotData, type MarkerData } from './indicatorRegistry';
import { IchimokuCloudPlugin, type IchimokuCloudPoint } from './IchimokuCloudPlugin';
@@ -808,6 +808,8 @@ export class ChartManager {
private _finalizeIndicatorPaneLayout(): void {
this._removeOrphanSubPanes();
if (this._activeIndicatorPaneIndices().size > 0) {
this._resyncIndicatorPaneIndices();
this._splitCollidingIndicatorPanes();
this._resyncIndicatorPaneIndices();
this._applyAllSeriesPriceFormats();
} else {
@@ -929,6 +931,8 @@ export class ChartManager {
this._removeOrphanSubPanes();
this._trimTrailingEmptySubPanes();
this._resyncIndicatorPaneIndices();
this._splitCollidingIndicatorPanes();
this._resyncIndicatorPaneIndices();
this.resetPaneHeights(this._lastLayoutAvailableHeight);
this._notifyPaneLayout();
}
@@ -1420,21 +1424,71 @@ export class ChartManager {
private _collectUsedSubPaneIndices(): Set<number> {
const indices = new Set<number>();
for (const entry of this.indicators.values()) {
let found = false;
let pane = -1;
for (const series of entry.seriesList) {
const pi = this._readSeriesPaneIndex(series);
if (pi >= 2) {
indices.add(pi);
found = true;
}
if (pi >= 2) pane = Math.max(pane, pi);
}
if (!found && entry.paneIndex != null && entry.paneIndex >= 2) {
indices.add(entry.paneIndex);
if (pane < 2 && entry.paneIndex != null && entry.paneIndex >= 2) {
pane = entry.paneIndex;
}
if (pane >= 2) indices.add(pane);
}
return indices;
}
private _indicatorConfigsFromMap(): IndicatorConfig[] {
return Array.from(this.indicators.values()).map(e => e.config);
}
/**
* 병합(mergedWith)이 아닌 서로 다른 호스트가 같은 sub-pane에 붙어 있으면 분리.
* entry.paneIndex 와 실제 시리즈 pane 불일치·지표 제거 후 재추가 시 겹침 방지.
*/
private _splitCollidingIndicatorPanes(): void {
const configs = this._indicatorConfigsFromMap();
const hostsByPane = new Map<number, string[]>();
for (const [id, entry] of this.indicators) {
const hostId = getPaneHostId(id, configs);
if (id !== hostId) continue;
const def = getIndicatorDef(entry.type);
if (def?.overlay || def?.returnsMarkers || entry.seriesList.length === 0) continue;
let pi = -1;
for (const series of entry.seriesList) {
pi = Math.max(pi, this._readSeriesPaneIndex(series));
}
if (pi < 2 && entry.paneIndex != null && entry.paneIndex >= 2) pi = entry.paneIndex;
if (pi < 2) continue;
const list = hostsByPane.get(pi) ?? [];
if (!list.includes(hostId)) list.push(hostId);
hostsByPane.set(pi, list);
}
let changed = false;
for (const hostIds of hostsByPane.values()) {
if (hostIds.length <= 1) continue;
for (let i = 1; i < hostIds.length; i++) {
const hostId = hostIds[i];
const target = this._getNextSubPane();
for (const [mid, ment] of this.indicators) {
if (getPaneHostId(mid, configs) !== hostId) continue;
for (const series of ment.seriesList) {
try {
series.moveToPane(target);
} catch { /* ok */ }
}
ment.paneIndex = target;
}
changed = true;
}
}
if (changed) this._removeOrphanSubPanes();
}
private _getNextSubPane(): number {
const used = this._collectUsedSubPaneIndices();
let pane = 2;
@@ -1815,9 +1869,13 @@ export class ChartManager {
this._reapplyAllPatternMarkers();
this._removeExtraSubPanes();
for (const cfg of configs) {
const sorted = sortIndicatorsForPaneLoad(configs);
for (const cfg of sorted) {
if (this._indicatorLoadStale(loadGen)) break;
await this.addIndicator(cfg);
await this.addIndicator(cfg, { skipLayout: true });
}
if (!this._indicatorLoadStale(loadGen)) {
this._finalizeIndicatorPaneLayout();
}
}
+14
View File
@@ -1,4 +1,5 @@
import type { IndicatorConfig } from '../types';
import { layoutKey } from './indicatorPaneMerge';
export type IndicatorChartSyncMode =
| 'none'
@@ -6,6 +7,7 @@ export type IndicatorChartSyncMode =
| 'add-only'
| 'params-changed'
| 'reorder-only'
| 'layout-changed'
| 'full';
export interface IndicatorChartChange {
@@ -62,6 +64,9 @@ export function classifyIndicatorChartChange(
};
if (removedIds.length === 0 && added.length === 0 && paramsChangedIds.length === 0) {
if (layoutKey(prev) !== layoutKey(curr)) {
return { ...empty, mode: 'layout-changed' };
}
const sameSet = prev.length === curr.length
&& prev.every(p => currMap.has(p.id));
if (sameSet && prev.map(i => i.id).join(',') !== curr.map(i => i.id).join(',')) {
@@ -70,6 +75,15 @@ export function classifyIndicatorChartChange(
return empty;
}
/** 병합·분리 등 mergedWith 변경 — params 키에도 잡히지만 pane 재배치가 필요 */
if (
removedIds.length === 0
&& added.length === 0
&& layoutKey(prev) !== layoutKey(curr)
) {
return { ...empty, mode: 'layout-changed', paramsChangedIds };
}
if (removedIds.length > 0 && added.length === 0 && paramsChangedIds.length === 0) {
return { ...empty, mode: 'remove-only' };
}