Skip to content

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() },
  );
}