보조지표 합치기, 분리, 복사하기 기능 오류 수정
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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' };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user