최종수정

This commit is contained in:
Macbook
2026-06-09 15:33:56 +09:00
parent 645c54f991
commit 4bd93cd037
2 changed files with 17 additions and 68 deletions
+13 -42
View File
@@ -225,17 +225,6 @@ function sortDualLinePlotDefs(plotDefs: PlotDef[]): PlotDef[] {
); );
} }
/** 이중 plot 지표 시리즈 추가 순서 — OBV는 plot1→plot0 순으로 본선이 위에 그려지게 */
function sortPlotDefsForSeriesAdd(type: string, plotDefs: PlotDef[]): PlotDef[] {
const sorted = sortDualLinePlotDefs(plotDefs);
return type === 'OBV' ? [...sorted].reverse() : sorted;
}
/** OBV 본선(plot0) — DB plotVisibility·투명색으로 숨김 방지 */
function isDualLinePlotForcedVisible(type: string, plotId: string): boolean {
return type === 'OBV' && plotId === 'plot0';
}
export class ChartManager { export class ChartManager {
private chart: IChartApi; private chart: IChartApi;
private container: HTMLElement; private container: HTMLElement;
@@ -772,13 +761,13 @@ export class ChartManager {
indicatorHidden: boolean, indicatorHidden: boolean,
def: ReturnType<typeof getIndicatorDef>, def: ReturnType<typeof getIndicatorDef>,
): void { ): void {
const plotDefs = sortPlotDefsForSeriesAdd(config.type, config.plots ?? def?.plots ?? []); const plotDefs = sortDualLinePlotDefs(config.plots ?? def?.plots ?? []);
const existingIds = new Set(seriesMeta.map(m => m.plotId)); const existingIds = new Set(seriesMeta.map(m => m.plotId));
const indicatorPriceFormat = priceFormatForIndicatorPane(pane); const indicatorPriceFormat = priceFormatForIndicatorPane(pane);
for (const plotDef of plotDefs) { for (const plotDef of plotDefs) {
if (existingIds.has(plotDef.id)) continue; if (existingIds.has(plotDef.id)) continue;
if (config.plotVisibility?.[plotDef.id] === false && !isDualLinePlotForcedVisible(config.type, plotDef.id)) continue; if (config.plotVisibility?.[plotDef.id] === false) continue;
if (plotDef.type === 'histogram') continue; if (plotDef.type === 'histogram') continue;
const plotData = result.plots[plotDef.id] as PlotData | undefined; const plotData = result.plots[plotDef.id] as PlotData | undefined;
@@ -787,21 +776,15 @@ export class ChartManager {
if (filtered.length === 0) continue; if (filtered.length === 0) continue;
const isPlotVisible = !indicatorHidden const isPlotVisible = !indicatorHidden
&& (isDualLinePlotForcedVisible(config.type, plotDef.id) && (this._isCustomOverlayApplied() && (config.type === 'SMA' || config.type === 'BollingerBands' || config.type === 'IchimokuCloud')
|| (this._isCustomOverlayApplied() && (config.type === 'SMA' || config.type === 'BollingerBands' || config.type === 'IchimokuCloud')
? isCustomPlotSelected(this._getAppliedCustomOverlaySelection(), config.type, plotDef.id) ? isCustomPlotSelected(this._getAppliedCustomOverlaySelection(), config.type, plotDef.id)
: config.plotVisibility?.[plotDef.id] !== false)); : config.plotVisibility?.[plotDef.id] !== false);
const showPriceLabel = this.shouldShowSeriesPriceLabel(config, true, pane); const showPriceLabel = this.shouldShowSeriesPriceLabel(config, true, pane);
const showSeriesTitle = pane < 2 && showPriceLabel; const showSeriesTitle = pane < 2 && showPriceLabel;
const regPlot = def?.plots?.find(p => p.id === plotDef.id); const regPlot = def?.plots?.find(p => p.id === plotDef.id);
const fallbackColor = config.type === 'OBV' && plotDef.id === 'plot0'
? (regPlot?.color ?? '#2196F3')
: (regPlot?.color ?? plotDef.color ?? '#2962FF');
const series = this.chart.addSeries(LineSeries, { const series = this.chart.addSeries(LineSeries, {
color: resolvePlotLineColor(plotDef.color, fallbackColor), color: resolvePlotLineColor(plotDef.color, regPlot?.color ?? plotDef.color ?? '#2962FF'),
lineWidth: (config.type === 'OBV' && plotDef.id === 'plot0' lineWidth: Math.max(1, plotDef.lineWidth ?? regPlot?.lineWidth ?? 1) as 1 | 2 | 3 | 4,
? Math.max(2, plotDef.lineWidth ?? regPlot?.lineWidth ?? 2)
: Math.max(1, plotDef.lineWidth ?? 1)) as 1 | 2 | 3 | 4,
lineStyle: plotSeriesLineStyle(plotDef.lineStyle), lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
priceLineVisible: false, priceLineVisible: false,
lastValueVisible: showPriceLabel, lastValueVisible: showPriceLabel,
@@ -899,7 +882,7 @@ export class ChartManager {
const plotDefsRaw = config.plots ?? def?.plots ?? []; const plotDefsRaw = config.plots ?? def?.plots ?? [];
const plotDefs = DUAL_LINE_INDICATOR_TYPES.has(config.type) const plotDefs = DUAL_LINE_INDICATOR_TYPES.has(config.type)
? sortPlotDefsForSeriesAdd(config.type, plotDefsRaw) ? sortDualLinePlotDefs(plotDefsRaw)
: plotDefsRaw; : plotDefsRaw;
for (const plotDef of plotDefs) { for (const plotDef of plotDefs) {
@@ -919,21 +902,15 @@ export class ChartManager {
seriesMeta.push({ plotId: plotDef.id, isHistogram: true, color: plotDef.color }); seriesMeta.push({ plotId: plotDef.id, isHistogram: true, color: plotDef.color });
} else { } else {
const isPlotVisible = !indicatorHidden const isPlotVisible = !indicatorHidden
&& (isDualLinePlotForcedVisible(config.type, plotDef.id) && (this._isCustomOverlayApplied() && (config.type === 'SMA' || config.type === 'BollingerBands' || config.type === 'IchimokuCloud')
|| (this._isCustomOverlayApplied() && (config.type === 'SMA' || config.type === 'BollingerBands' || config.type === 'IchimokuCloud')
? isCustomPlotSelected(this._getAppliedCustomOverlaySelection(), config.type, plotDef.id) ? isCustomPlotSelected(this._getAppliedCustomOverlaySelection(), config.type, plotDef.id)
: config.plotVisibility?.[plotDef.id] !== false)); : config.plotVisibility?.[plotDef.id] !== false);
const showPriceLabel = this.shouldShowSeriesPriceLabel(config, true, pane); const showPriceLabel = this.shouldShowSeriesPriceLabel(config, true, pane);
const showSeriesTitle = pane < 2 && showPriceLabel; const showSeriesTitle = pane < 2 && showPriceLabel;
const regPlot = def?.plots?.find(p => p.id === plotDef.id); const regPlot = def?.plots?.find(p => p.id === plotDef.id);
const fallbackColor = config.type === 'OBV' && plotDef.id === 'plot0'
? (regPlot?.color ?? '#2196F3')
: (regPlot?.color ?? plotDef.color ?? '#2962FF');
series = this.chart.addSeries(LineSeries, { series = this.chart.addSeries(LineSeries, {
color: resolvePlotLineColor(plotDef.color, fallbackColor), color: resolvePlotLineColor(plotDef.color, regPlot?.color ?? plotDef.color ?? '#2962FF'),
lineWidth: (config.type === 'OBV' && plotDef.id === 'plot0' lineWidth: Math.max(1, plotDef.lineWidth ?? regPlot?.lineWidth ?? 1) as 1 | 2 | 3 | 4,
? Math.max(2, plotDef.lineWidth ?? regPlot?.lineWidth ?? 2)
: Math.max(1, plotDef.lineWidth ?? 1)) as 1 | 2 | 3 | 4,
lineStyle: plotSeriesLineStyle(plotDef.lineStyle), lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
priceLineVisible: false, priceLineVisible: false,
lastValueVisible: showPriceLabel, lastValueVisible: showPriceLabel,
@@ -2695,7 +2672,6 @@ export class ChartManager {
/** custom 미적용 시 지표 설정 plotVisibility, 적용 시 custom 선택만 반영 */ /** custom 미적용 시 지표 설정 plotVisibility, 적용 시 custom 선택만 반영 */
private _resolveSeriesVisible(config: IndicatorConfig, plotId: string | undefined): boolean { private _resolveSeriesVisible(config: IndicatorConfig, plotId: string | undefined): boolean {
if (!plotId || !this._isIndicatorEffectivelyVisible(config)) return false; if (!plotId || !this._isIndicatorEffectivelyVisible(config)) return false;
if (isDualLinePlotForcedVisible(config.type, plotId)) return true;
if (!this._isCustomOverlayApplied()) { if (!this._isCustomOverlayApplied()) {
return config.plotVisibility?.[plotId] !== false; return config.plotVisibility?.[plotId] !== false;
} }
@@ -2926,13 +2902,8 @@ export class ChartManager {
} }
if (!entry.seriesMeta[i]?.isHistogram) { if (!entry.seriesMeta[i]?.isHistogram) {
const regPlot = def?.plots?.find(p => p.id === pid); const regPlot = def?.plots?.find(p => p.id === pid);
const fallbackColor = entry.type === 'OBV' && pid === 'plot0' opts['color'] = resolvePlotLineColor(plot.color, regPlot?.color ?? plot.color ?? '#aaa');
? (regPlot?.color ?? '#2196F3') opts['lineWidth'] = Math.max(1, plot.lineWidth ?? regPlot?.lineWidth ?? 1);
: (regPlot?.color ?? plot.color ?? '#aaa');
opts['color'] = resolvePlotLineColor(plot.color, fallbackColor);
opts['lineWidth'] = entry.type === 'OBV' && pid === 'plot0'
? Math.max(2, plot.lineWidth ?? regPlot?.lineWidth ?? 2)
: Math.max(1, plot.lineWidth ?? regPlot?.lineWidth ?? 1);
opts['lineStyle'] = plotSeriesLineStyle(plot.lineStyle); opts['lineStyle'] = plotSeriesLineStyle(plot.lineStyle);
let plotAllows = true; let plotAllows = true;
if (entry.type === 'IchimokuCloud') { if (entry.type === 'IchimokuCloud') {
+2 -24
View File
@@ -473,35 +473,13 @@ function normalizeSignalLineIndicatorPlots(
const nextPlots = merged.map(p => { const nextPlots = merged.map(p => {
const reg = registryPlots.find(r => r.id === p.id); const reg = registryPlots.find(r => r.id === p.id);
if (!reg) return p; if (!reg) return p;
const normalized = normalizeDualLinePlotDef(p, reg); return normalizeDualLinePlotDef(p, reg);
// OBV 본선 — DB 저장색(투명·신호선과 동일) 무시, registry 기본색·굵기 유지
if (p.id === 'plot0') {
return {
...normalized,
type: 'line' as const,
color: reg.color,
lineWidth: Math.max(2, normalized.lineWidth ?? reg.lineWidth ?? 2),
lineStyle: 'solid' as const,
};
}
// OBV 신호선 — 값이 본선과 거의 같더라도 점선으로 시각적 구분 보장
if (p.id === 'plot1') {
return {
...normalized,
lineStyle: 'dashed' as const,
};
}
return normalized;
}); });
// plot0(본선) → plot1(신호선) 순서 보장
nextPlots.sort((a, b) => { nextPlots.sort((a, b) => {
const order = ['plot0', 'plot1']; const order = ['plot0', 'plot1'];
return order.indexOf(a.id) - order.indexOf(b.id); return order.indexOf(a.id) - order.indexOf(b.id);
}); });
return { return { plots: nextPlots, plotVisibility: { ...plotVisibility } };
plots: nextPlots,
plotVisibility: { ...plotVisibility, plot0: true, plot1: true },
};
} }
const regSignal = registryPlots.find(p => p.id === 'plot1'); const regSignal = registryPlots.find(p => p.id === 'plot1');