보조지표 툴바추가

This commit is contained in:
Macbook
2026-05-29 23:51:13 +09:00
parent 095b0d7db7
commit 6332d198b1
11 changed files with 846 additions and 436 deletions
+123
View File
@@ -0,0 +1,123 @@
/**
* PaneLegend / ChartRightToolbar 공통 — pane 항목·좌표 계산
*/
import type { IndicatorConfig } from '../types';
import { ChartManager } from '../utils/ChartManager';
import { getIndicatorDef, getIndicatorChartTitle } from '../utils/indicatorRegistry';
import { getGlobalParamKeys, getPlotParamKeys } from '../utils/indicatorSettingsLayout';
import { formatBbLegendLabel } from '../utils/bollingerConfig';
export interface PaneItem {
id: string;
paneIndex: number;
label: string;
plotColors: string[];
hidden?: boolean;
layoutTopY?: number;
layoutHeight?: number;
}
export const MIN_SUB_PANE_HEIGHT = 12;
function makeLabel(config: IndicatorConfig): string {
if (config.type === 'BollingerBands') {
return formatBbLegendLabel(config.params ?? {});
}
const name = getIndicatorChartTitle(config.type);
const def = getIndicatorDef(config.type);
const params = (config.params ?? def?.defaultParams ?? {}) as Record<string, number | string | boolean>;
const plots = config.plots ?? def?.plots ?? [];
const orderedKeys: string[] = [];
for (let i = 0; i < plots.length; i++) {
for (const k of getPlotParamKeys(config.type, plots[i].id, i, plots, params)) {
if (!orderedKeys.includes(k)) orderedKeys.push(k);
}
}
for (const k of getGlobalParamKeys(config.type, params, plots)) {
if (!orderedKeys.includes(k) && typeof params[k] === 'number') orderedKeys.push(k);
}
const nums = orderedKeys
.map(k => params[k])
.filter((v): v is number => typeof v === 'number')
.slice(0, 3);
return nums.length ? `${name} ${nums.join(', ')}` : name;
}
function applyPaneLayoutToItem(
item: PaneItem,
layout: { topY: number; height: number },
overwrite = false,
): void {
if (!overwrite && item.layoutTopY != null) return;
item.layoutTopY = layout.topY;
item.layoutHeight = layout.height;
}
export function buildPaneItems(
manager: ChartManager,
indicators: IndicatorConfig[],
): PaneItem[] {
const wantedIds = new Set(
indicators
.filter(ind => {
const def = getIndicatorDef(ind.type);
return def && !def.overlay && !def.returnsMarkers;
})
.map(ind => ind.id),
);
const configById = new Map(indicators.map(ind => [ind.id, ind]));
const items: PaneItem[] = [];
const seenIds = new Set<string>();
for (const info of manager.getIndicatorPaneInfo()) {
if (!wantedIds.has(info.id) || seenIds.has(info.id)) continue;
seenIds.add(info.id);
const ind = configById.get(info.id) ?? info.config;
const def = getIndicatorDef(ind.type);
if (!def || def.overlay || def.returnsMarkers) continue;
let chartPane = info.paneIndex;
if (ind.mergedWith) {
const host = manager.getIndicatorPaneIndex(ind.mergedWith);
if (host >= 2) chartPane = host;
}
items.push({
id: info.id,
paneIndex: chartPane,
label: makeLabel(ind),
plotColors: info.plotColors.map(c => c.slice(0, 7)),
hidden: ind.hidden === true,
});
}
if (items.length === 0) return [];
items.sort((a, b) => a.paneIndex - b.paneIndex);
for (const item of items) {
const lay = manager.getIndicatorPaneScreenLayout(item.id);
if (lay) applyPaneLayoutToItem(item, lay, true);
}
items.sort((a, b) => {
const ay = a.layoutTopY ?? 1e9;
const by = b.layoutTopY ?? 1e9;
if (ay !== by) return ay - by;
return a.paneIndex - b.paneIndex;
});
return items.filter(
item => item.layoutTopY != null && (item.layoutHeight ?? 0) > MIN_SUB_PANE_HEIGHT,
);
}
export function resolvePaneItemLayout(
manager: ChartManager,
item: PaneItem,
): { topY: number; height: number } | null {
return manager.getIndicatorPaneScreenLayout(item.id);
}