own pane 지표 — MACD · ADX · OBV
가격과 자릿수가 다른 지표들 — 각자 pane을 만들어 얹고 dispose가 pane까지 걷는다. 오실레이터와 달리 축은 autoScale 그대로다. 버튼은 pane 최대화(P2~P4) 실증 — 균등이던 시절엔 가격 pane이 지표와 같은 크기로 눌렸다.
소스
apps/examples/src/cases/own-panes.ts — CI가 타입체크하는 실물이다.
ts
import { PlotBuilder, browserDeps } from "@finchart/dom";
import type { OHLC, Pane } from "@finchart/core";
import { candleSeries, paneMaximize, priceFormat, timeTicks } from "@finchart/core";
import { legend } from "@finchart/dom";
import { attachAdx, attachMacd, attachObv } from "@finchart/indicators";
import { fixtureCandles } from "./fixture";
import { chartHost } from "./stage";
export const title = "own pane 지표 — MACD · ADX · OBV";
export const description =
"가격과 자릿수가 다른 지표들 — 각자 pane을 만들어 얹고 dispose가 pane까지 걷는다. 오실레이터와 달리 축은 autoScale 그대로다. 버튼은 pane 최대화(P2~P4) 실증 — 균등이던 시절엔 가격 pane이 지표와 같은 크기로 눌렸다.";
/** 눌린 버튼의 배경 — 셸 CSS 없이도 케이스 혼자 보인다(senior-review-2026-08-13 D3). */
function paintPressed(el: HTMLButtonElement, pressed: boolean): void {
el.style.background = pressed ? "#3b82f6" : "";
el.style.color = pressed ? "#fff" : "";
}
export function mount(container: HTMLElement): () => void {
const toolbar = document.createElement("div");
toolbar.setAttribute("role", "group");
toolbar.setAttribute("aria-label", "pane 최대화");
toolbar.style.cssText = "display: flex; gap: 8px; margin-bottom: 8px";
container.append(toolbar);
const host = chartHost(container, 640);
const plot = PlotBuilder.create<OHLC>(browserDeps({ autoSize: true }))
.setSize(container.clientWidth || 900, 640)
.setAxis({
x: { ticks: timeTicks({ timeZone: "UTC", locale: "ko" }) },
y: { position: "right", format: priceFormat({ compact: true, locale: "ko" }) },
})
.build(host);
const price = plot.mainPane.addSeries({
series: candleSeries(),
data: fixtureCandles(),
name: "가격",
});
const macd = plot.use(attachMacd({ source: price }));
const adx = plot.use(attachAdx({ source: price }));
const obv = plot.use(attachObv({ source: price }));
plot.use(legend({}));
// 최대화 API — 코어 확장은 이것 하나다(PaneHost & PlotEventSource &
// InputHost만 요구). own pane 지표는 `.pane`으로 자기가 만든 Pane을
// 돌려준다(P4) — 그걸로 여기서 최대화할 대상을 고른다.
const max = plot.use(paneMaximize());
const panes: [string, Pane][] = [
["가격", plot.mainPane],
["MACD", macd.pane!],
["ADX", adx.pane!],
["OBV", obv.pane!],
];
const buttons = new Map<Pane, HTMLButtonElement>();
for (const [label, pane] of panes) {
const el = document.createElement("button");
el.textContent = `${label} 최대화`;
el.addEventListener("click", () => max.maximize(pane));
toolbar.appendChild(el);
buttons.set(pane, el);
}
const refresh = () => {
for (const [pane, el] of buttons) {
const pressed = max.maximizedPane === pane;
el.setAttribute("aria-pressed", String(pressed));
paintPressed(el, pressed);
}
};
refresh();
// 구분자 드래그로도 최대화가 풀릴 수 있다(senior-review-2026-08-12) —
// 버튼 클릭 밖에서 바뀌어도 표시가 따라오도록 상태 변화를 듣는다.
const unsubscribe = plot.on("stateChange", refresh);
return Object.assign(
() => {
unsubscribe();
plot.destroy();
toolbar.remove();
host.remove();
},
{ requestRender: () => plot.requestRender() },
);
}