Files
goldenChart/frontend/src/utils/indicatorChartSync.ts
T

105 lines
3.0 KiB
TypeScript

import type { IndicatorConfig } from '../types';
import { layoutKey } from './indicatorPaneMerge';
export type IndicatorChartSyncMode =
| 'none'
| 'remove-only'
| 'add-only'
| 'params-changed'
| 'reorder-only'
| 'layout-changed'
| 'full';
export interface IndicatorChartChange {
mode: IndicatorChartSyncMode;
removedIds: string[];
added: IndicatorConfig[];
paramsChangedIds: string[];
}
export function singleIndParamKey(i: IndicatorConfig): string {
return `${JSON.stringify(i.params)}|${(i.plots ?? []).map(p => p.id).sort().join(',')}|${i.mergedWith ?? ''}`;
}
/** 차트에 로드된 지표 id 중 현재 설정에 없는(이전 탭 잔존) 항목이 있는지 */
export function chartHasStaleIndicators(
loadedIds: Set<string>,
indicators: IndicatorConfig[],
): boolean {
if (loadedIds.size === 0) return false;
const expected = new Set(
indicators.filter(i => i.hidden !== true).map(i => i.id),
);
for (const id of loadedIds) {
if (!expected.has(id)) return true;
}
return false;
}
/** 이전·현재 지표 목록 차이를 분류해 최소 갱신 경로를 선택 */
export function classifyIndicatorChartChange(
prev: IndicatorConfig[],
curr: IndicatorConfig[],
): IndicatorChartChange {
const prevMap = new Map(prev.map(i => [i.id, i]));
const currMap = new Map(curr.map(i => [i.id, i]));
const removedIds = prev.filter(i => !currMap.has(i.id)).map(i => i.id);
const added = curr.filter(i => !prevMap.has(i.id));
const paramsChangedIds: string[] = [];
for (const c of curr) {
const p = prevMap.get(c.id);
if (!p) continue;
if (singleIndParamKey(p) !== singleIndParamKey(c)) {
paramsChangedIds.push(c.id);
}
}
const empty: IndicatorChartChange = {
mode: 'none',
removedIds,
added,
paramsChangedIds,
};
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(',')) {
return { ...empty, mode: 'reorder-only' };
}
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' };
}
if (added.length > 0 && removedIds.length === 0 && paramsChangedIds.length === 0) {
return { ...empty, mode: 'add-only' };
}
if (
paramsChangedIds.length > 0
&& removedIds.length === 0
&& added.length === 0
) {
return { ...empty, mode: 'params-changed' };
}
return { ...empty, mode: 'full' };
}