수정2
This commit is contained in:
@@ -240,6 +240,11 @@ function sortPlotDefsForSeriesAdd(type: string, plotDefs: PlotDef[]): PlotDef[]
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** OBV 신호선 — pane 내 독립 price scale (본선과 값이 가까워도 겹치지 않게) */
|
||||||
|
function obvSignalPriceScaleId(indicatorId: string): string {
|
||||||
|
return `obv-sig-${indicatorId}`;
|
||||||
|
}
|
||||||
|
|
||||||
export class ChartManager {
|
export class ChartManager {
|
||||||
private chart: IChartApi;
|
private chart: IChartApi;
|
||||||
private container: HTMLElement;
|
private container: HTMLElement;
|
||||||
@@ -753,9 +758,10 @@ export class ChartManager {
|
|||||||
config: IndicatorConfig,
|
config: IndicatorConfig,
|
||||||
def: ReturnType<typeof getIndicatorDef> | undefined,
|
def: ReturnType<typeof getIndicatorDef> | undefined,
|
||||||
): string[] {
|
): string[] {
|
||||||
const plotDefs = def?.plots ?? config.plots ?? [];
|
const enriched = enrichIndicatorConfig(config);
|
||||||
|
const plotDefs = enriched.plots ?? def?.plots ?? [];
|
||||||
return plotDefs
|
return plotDefs
|
||||||
.filter(p => p.type !== 'histogram' && config.plotVisibility?.[p.id] !== false)
|
.filter(p => p.type !== 'histogram' && enriched.plotVisibility?.[p.id] !== false)
|
||||||
.map(p => p.id);
|
.map(p => p.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,6 +779,37 @@ export class ChartManager {
|
|||||||
return expected.some(id => !existing.has(id));
|
return expected.some(id => !existing.has(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** OBV — 본선(하단)·신호선(상단) 독립 price scale margin */
|
||||||
|
private _configureObvDualLinePriceScales(
|
||||||
|
seriesList: ISeriesApi<SeriesType>[],
|
||||||
|
seriesMeta: IndicatorEntry['seriesMeta'],
|
||||||
|
): void {
|
||||||
|
for (let i = 0; i < seriesList.length; i++) {
|
||||||
|
const meta = seriesMeta[i];
|
||||||
|
if (!meta) continue;
|
||||||
|
try {
|
||||||
|
const ps = seriesList[i].priceScale();
|
||||||
|
if (meta.plotId === 'plot0') {
|
||||||
|
ps.applyOptions({ scaleMargins: { top: 0.52, bottom: 0.05 } });
|
||||||
|
} else if (meta.plotId === 'plot1') {
|
||||||
|
ps.applyOptions({
|
||||||
|
visible: false,
|
||||||
|
scaleMargins: { top: 0.05, bottom: 0.52 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch { /* ok */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dualLinePlotDefsForRender(
|
||||||
|
config: IndicatorConfig,
|
||||||
|
def: ReturnType<typeof getIndicatorDef>,
|
||||||
|
): PlotDef[] {
|
||||||
|
const enriched = enrichIndicatorConfig(config);
|
||||||
|
return sortPlotDefsForSeriesAdd(config.type, enriched.plots ?? def?.plots ?? [])
|
||||||
|
.filter(p => p.type !== 'histogram' && enriched.plotVisibility?.[p.id] !== false);
|
||||||
|
}
|
||||||
|
|
||||||
/** OBV·RSI 등 — plot0→plot1 순서로 시리즈를 한 번에 생성 (append 누락·순서 어긋남 방지) */
|
/** OBV·RSI 등 — plot0→plot1 순서로 시리즈를 한 번에 생성 (append 누락·순서 어긋남 방지) */
|
||||||
private _addDualLinePlotSeries(
|
private _addDualLinePlotSeries(
|
||||||
seriesList: ISeriesApi<SeriesType>[],
|
seriesList: ISeriesApi<SeriesType>[],
|
||||||
@@ -783,11 +820,7 @@ export class ChartManager {
|
|||||||
indicatorHidden: boolean,
|
indicatorHidden: boolean,
|
||||||
def: ReturnType<typeof getIndicatorDef>,
|
def: ReturnType<typeof getIndicatorDef>,
|
||||||
): void {
|
): void {
|
||||||
const regPlots = def?.plots ?? [];
|
const plotDefs = this._dualLinePlotDefsForRender(config, def);
|
||||||
const styleById = Object.fromEntries((config.plots ?? regPlots).map(p => [p.id, p]));
|
|
||||||
const plotDefs = sortDualLinePlotDefs(regPlots)
|
|
||||||
.filter(p => p.type !== 'histogram' && config.plotVisibility?.[p.id] !== false)
|
|
||||||
.map(p => ({ ...p, ...styleById[p.id], id: p.id, title: p.title }));
|
|
||||||
const indicatorPriceFormat = priceFormatForIndicatorPane(pane);
|
const indicatorPriceFormat = priceFormatForIndicatorPane(pane);
|
||||||
|
|
||||||
for (const plotDef of plotDefs) {
|
for (const plotDef of plotDefs) {
|
||||||
@@ -803,20 +836,33 @@ export class ChartManager {
|
|||||||
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 obvSignalScale = config.type === 'OBV' && plotDef.id === 'plot1'
|
||||||
|
? obvSignalPriceScaleId(config.id)
|
||||||
|
: undefined;
|
||||||
const series = this.chart.addSeries(LineSeries, {
|
const series = this.chart.addSeries(LineSeries, {
|
||||||
color: resolvePlotLineColor(plotDef.color, regPlot?.color ?? plotDef.color ?? '#2962FF'),
|
color: resolvePlotLineColor(plotDef.color, fallbackColor),
|
||||||
lineWidth: Math.max(1, plotDef.lineWidth ?? regPlot?.lineWidth ?? 1) as 1 | 2 | 3 | 4,
|
lineWidth: (config.type === 'OBV' && plotDef.id === 'plot0'
|
||||||
|
? Math.max(2, plotDef.lineWidth ?? regPlot?.lineWidth ?? 2)
|
||||||
|
: Math.max(1, plotDef.lineWidth ?? regPlot?.lineWidth ?? 1)) as 1 | 2 | 3 | 4,
|
||||||
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
|
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
|
||||||
priceLineVisible: false,
|
priceLineVisible: false,
|
||||||
lastValueVisible: showPriceLabel,
|
lastValueVisible: showPriceLabel,
|
||||||
title: showSeriesTitle ? this.seriesTitleForPlot(config, plotDef) : '',
|
title: showSeriesTitle ? this.seriesTitleForPlot(config, plotDef) : '',
|
||||||
visible: isPlotVisible,
|
visible: isPlotVisible,
|
||||||
...(indicatorPriceFormat ? { priceFormat: indicatorPriceFormat } : {}),
|
...(indicatorPriceFormat ? { priceFormat: indicatorPriceFormat } : {}),
|
||||||
|
...(obvSignalScale ? { priceScaleId: obvSignalScale } : {}),
|
||||||
}, pane);
|
}, pane);
|
||||||
series.setData(filtered.map(p => ({ time: p.time as Time, value: p.value })));
|
series.setData(filtered.map(p => ({ time: p.time as Time, value: p.value })));
|
||||||
seriesList.push(series);
|
seriesList.push(series);
|
||||||
seriesMeta.push({ plotId: plotDef.id, isHistogram: false, color: plotDef.color });
|
seriesMeta.push({ plotId: plotDef.id, isHistogram: false, color: plotDef.color });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.type === 'OBV' && seriesList.length > 0) {
|
||||||
|
this._configureObvDualLinePriceScales(seriesList, seriesMeta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _appendMissingPlotSeriesToLists(
|
private _appendMissingPlotSeriesToLists(
|
||||||
@@ -828,13 +874,12 @@ 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 = this._dualLinePlotDefsForRender(config, def);
|
||||||
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) 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;
|
||||||
@@ -849,20 +894,33 @@ export class ChartManager {
|
|||||||
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 obvSignalScale = config.type === 'OBV' && plotDef.id === 'plot1'
|
||||||
|
? obvSignalPriceScaleId(config.id)
|
||||||
|
: undefined;
|
||||||
const series = this.chart.addSeries(LineSeries, {
|
const series = this.chart.addSeries(LineSeries, {
|
||||||
color: resolvePlotLineColor(plotDef.color, regPlot?.color ?? plotDef.color ?? '#2962FF'),
|
color: resolvePlotLineColor(plotDef.color, fallbackColor),
|
||||||
lineWidth: Math.max(1, plotDef.lineWidth ?? regPlot?.lineWidth ?? 1) as 1 | 2 | 3 | 4,
|
lineWidth: (config.type === 'OBV' && plotDef.id === 'plot0'
|
||||||
|
? Math.max(2, plotDef.lineWidth ?? regPlot?.lineWidth ?? 2)
|
||||||
|
: Math.max(1, plotDef.lineWidth ?? regPlot?.lineWidth ?? 1)) as 1 | 2 | 3 | 4,
|
||||||
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
|
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
|
||||||
priceLineVisible: false,
|
priceLineVisible: false,
|
||||||
lastValueVisible: showPriceLabel,
|
lastValueVisible: showPriceLabel,
|
||||||
title: showSeriesTitle ? this.seriesTitleForPlot(config, plotDef) : '',
|
title: showSeriesTitle ? this.seriesTitleForPlot(config, plotDef) : '',
|
||||||
visible: isPlotVisible,
|
visible: isPlotVisible,
|
||||||
...(indicatorPriceFormat ? { priceFormat: indicatorPriceFormat } : {}),
|
...(indicatorPriceFormat ? { priceFormat: indicatorPriceFormat } : {}),
|
||||||
|
...(obvSignalScale ? { priceScaleId: obvSignalScale } : {}),
|
||||||
}, pane);
|
}, pane);
|
||||||
series.setData(filtered.map(p => ({ time: p.time as Time, value: p.value })));
|
series.setData(filtered.map(p => ({ time: p.time as Time, value: p.value })));
|
||||||
seriesList.push(series);
|
seriesList.push(series);
|
||||||
seriesMeta.push({ plotId: plotDef.id, isHistogram: false, color: plotDef.color });
|
seriesMeta.push({ plotId: plotDef.id, isHistogram: false, color: plotDef.color });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.type === 'OBV' && seriesList.length > 0) {
|
||||||
|
this._configureObvDualLinePriceScales(seriesList, seriesMeta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** plot0·plot1 시리즈 전부 제거 후 DB 설정 스타일로 재생성 */
|
/** plot0·plot1 시리즈 전부 제거 후 DB 설정 스타일로 재생성 */
|
||||||
@@ -3031,7 +3089,9 @@ 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);
|
||||||
opts['color'] = resolvePlotLineColor(plot.color, regPlot?.color ?? plot.color ?? '#aaa');
|
opts['color'] = resolvePlotLineColor(plot.color, regPlot?.color ?? plot.color ?? '#aaa');
|
||||||
opts['lineWidth'] = Math.max(1, plot.lineWidth ?? regPlot?.lineWidth ?? 1);
|
opts['lineWidth'] = config.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') {
|
||||||
@@ -3049,6 +3109,10 @@ export class ChartManager {
|
|||||||
series.applyOptions(opts as any);
|
series.applyOptions(opts as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (config.type === 'OBV' && entry.seriesList.length > 0) {
|
||||||
|
this._configureObvDualLinePriceScales(entry.seriesList, entry.seriesMeta);
|
||||||
|
}
|
||||||
|
|
||||||
// ── hlines(과열선/중앙선/침체선) 동기화 ─────────────────────────────────
|
// ── hlines(과열선/중앙선/침체선) 동기화 ─────────────────────────────────
|
||||||
if (entry.seriesList.length > 0) {
|
if (entry.seriesList.length > 0) {
|
||||||
const firstSeries = entry.seriesList[0];
|
const firstSeries = entry.seriesList[0];
|
||||||
|
|||||||
Reference in New Issue
Block a user