Skip to content

60초 튜토리얼

아직 npm에 발행 전이다. 지금은 저장소 루트 README의 pnpm pack 절차를 쓴다.

bash
# 아래 예제가 @finchart/indicators까지 쓴다 — 셋을 같이 넣는다.
pnpm add @finchart/core @finchart/dom @finchart/indicators

@finchart/core@finchart/dom한 버전으로 같이 나간다 — 그리는 법 (시리즈·스케일·상태)은 코어에, 브라우저에 붙이는 배선 (PlotBuilder·browserDeps)은 @finchart/dom에 산다.

ts
/**
 * 60초 예제의 실물 — getting-started.md가 이 파일을 그대로 임베드하고,
 * `pnpm --filter charts-docs type-check`가 컴파일을 지킨다. 예제가
 * 낡으면 CI가 잡는다 (cold-consumer 2026-08-15 마찰 #1의 재발 방지).
 * 루트 README의 "60초" 절과 같은 코드다 — 그쪽은 GitHub 렌더라 임베드가
 * 안 되므로, 바꿀 때 둘을 같이 바꾼다.
 */
import { candleSeries } from "@finchart/core";
import { PlotBuilder, browserDeps } from "@finchart/dom";

const plot = PlotBuilder.create(browserDeps(), candleSeries())
  .addDataPoints([
    { x: 0, open: 100, high: 108, low: 98, close: 106 },
    { x: 1, open: 106, high: 112, low: 104, close: 109 },
    { x: 2, open: 109, high: 111, low: 101, close: 103 },
  ])
  .setSize(800, 400)
  .build(document.getElementById("chart")!);

export { plot };

끝이다. 드래그하면 움직이고 휠로 확대된다.

데이터는 x 오름차순이어야 한다. 아니면 DataError가 난다.

다음 걸음 — 거래량 pane · 이동평균 · 실시간

위 예제는 시리즈를 빌더에 넘긴다. 실서비스는 보통 한 걸음 더 간다:

ts
/**
 * **60초 다음 걸음의 실물** — 캔들 + 거래량 pane + 이동평균 + 실시간.
 * getting-started.md가 이 파일을 그대로 임베드하고,
 * `pnpm --filter charts-docs type-check`가 컴파일을 지킨다.
 *
 * 36차 콜드 컨슈머 #6·#7이 이 파일을 만들게 했다: React는
 * `packages/react/README.md`에 완결 예제가 있는데 바닐라는 없어서,
 * 소비자가 60초 예제 + `plot-contract.md` 단편 + `d.ts`의 JSDoc을
 * 스스로 종합해야 했다. 그 종합이 여기 있다.
 */
import { candleSeries, histogramSeries, priceFormat, timeTicks } from "@finchart/core";
import type { OHLC } from "@finchart/core";
import { PlotBuilder, browserDeps } from "@finchart/dom";
import { attachMovingAverage } from "@finchart/indicators";

declare const bars: OHLC[]; // 네가 들고 오는 것 — x 오름차순

const plot = PlotBuilder.create<OHLC>(browserDeps({ autoSize: true }))
  .setSize(900, 480)
  .setAxis({
    x: { ticks: timeTicks({ locale: "ko" }) },
    y: { position: "right", format: priceFormat({ compact: true, locale: "ko" }) },
  })
  .build(document.getElementById("chart")!);

/**
 * **시리즈는 빌더가 아니라 pane에 얹는다.**
 *
 * 빌더에 시리즈를 넘겨도(`PlotBuilder.create(deps, candleSeries())`) 되지만
 * 그러면 **손잡이를 못 받는다** — 그리고 그 손잡이가 실시간 갱신의 문이자
 * 지표의 입력이다. 손잡이가 필요하면 빌더는 무대만 세우고, 시리즈는 이렇게 얹는다.
 */
const price = plot.mainPane.addSeries({
  series: candleSeries(),
  data: bars,
  name: "가격",
});

// 이동평균은 가격 pane 위에 겹친다 — 손잡이가 그대로 입력이 된다.
plot.mainPane.use(attachMovingAverage({ source: price, period: 20 }));

// 거래량은 자기 pane. 높이는 flex가 정한다 — 픽셀 산술을 하지 않는다.
const volumePane = plot.addPane({ flex: 0.25, minHeight: 48 });
const volume = volumePane.addSeries({
  series: histogramSeries(),
  data: bars.map((bar) => ({ x: bar.x, y: bar.volume ?? 0 })),
  name: "거래량",
});

/**
 * 실시간 — **틱과 새 봉이 같은 함수다.**
 *
 * `updateLast`는 마지막 점과 x가 같으면 교체하고 크면 붙인다. 그래서
 * "진행 중인 봉이 갱신됐나 / 새 봉이 열렸나"를 소비자가 판별하지 않는다.
 * 도메인도 안 건드리므로 사용자가 과거를 보고 있는 중이라면 화면이
 * 튀지 않는다. 지표는 손잡이를 물고 있어서 저절로 따라온다.
 */
export function onTick(bar: OHLC): void {
  price.updateLast(bar);
  volume.updateLast({ x: bar.x, y: bar.volume ?? 0 });
}

export { plot };

바뀐 것 하나가 나머지를 끌고 온다 — 시리즈를 빌더가 아니라 pane.addSeries()로 얹는다. 그래야 손잡이를 받고, 그 손잡이가 실시간 갱신의 문(updateLast)이자 지표의 입력(source)이다. 둘이 같은 물건이라, 시리즈를 얹는 순간 나머지가 다 열린다.

지표를 더 얹거나 계산을 여러 그림이 나눠 쓰는 법은 @finchart/indicators, React로 같은 차트를 짓는 법은 @finchart/react의 60초 예제에 있다.

크기가 궁금하면

이 예제는 ≈16 KB brotli로 번들된다. 안 쓰는 확장·시리즈·틱 전략은 import하지 않는 한 0 B고(라인 하나만 headless로 쓰면 748 B), 프리셋이 싣는 것을 조각 단위로 고르고 싶으면 명시 배선으로 내려간다 — 같은 차트가 45 KB min으로 선다. pointer: false 같은 옵션은 동작을 끄는 문이다. 바이트의 문은 import다.