Skip to content

커스텀 시리즈 — 거래량 점

Series는 메서드 두 개다: valueExtent(y축에서 얼마를 차지하는가)와 draw(무엇을 그리는가). 이 호박색 점 시리즈는 코어가 모르는 표현이고, 종가에 거래량 크기의 원을 얹는다 — 라이브러리를 고치지 않고 만들었다. 거래량이 클수록 원이 크다.

소스

apps/examples/src/cases/custom-series.ts — CI가 타입체크하는 실물이다.

ts
import { PlotBuilder, browserDeps } from "@finchart/dom";
import type { DrawTarget, OHLC, Series } from "@finchart/core";
import { candleSeries, priceFormat, timeTicks } from "@finchart/core";
import { fixtureCandles } from "./fixture";
import { chartHost } from "./stage";

export const title = "커스텀 시리즈 — 거래량 점";
export const description =
  "Series는 메서드 두 개다: valueExtent(y축에서 얼마를 차지하는가)와 draw(무엇을 그리는가). 이 호박색 점 시리즈는 코어가 모르는 표현이고, 종가에 거래량 크기의 원을 얹는다 — 라이브러리를 고치지 않고 만들었다. 거래량이 클수록 원이 크다.";

/** fixtureCandles의 거래량 상한(대략) — 반지름 정규화에만 쓴다. */
const MAX_VOLUME = 500;
const DOT_COLOR = "rgba(217, 119, 6, 0.55)"; // amber-600 — 캔들의 녹/적과 안 겹치는 색

/**
 * README가 파는 계약("Series는 메서드 두 개다")의 실물이다 — 코어는 이
 * 시리즈의 존재를 전혀 모른다. `valueExtent`가 y축을 얼마나 차지할지
 * 말하고, `draw`가 그것을 명령으로 낸다.
 */
const volumeDots: Series<OHLC> = {
  valueExtent(data) {
    if (data.length === 0) return null;
    let min = Number.POSITIVE_INFINITY;
    let max = Number.NEGATIVE_INFINITY;
    for (const point of data) {
      if (point.close < min) min = point.close;
      if (point.close > max) max = point.close;
    }
    return { min, max };
  },
  draw(target: DrawTarget, { data, x, yScale }) {
    for (const point of data) {
      const radius = 3 + ((point.volume ?? 0) / MAX_VOLUME) * 13;
      target.drawShape({
        shape: "circle",
        cx: x.toPixel(point.x),
        cy: yScale.scale(point.close),
        r: radius,
        fill: DOT_COLOR,
      });
    }
  },
};

export function mount(container: HTMLElement): () => void {
  const host = chartHost(container, 420);
  const plot = PlotBuilder.create<OHLC>(browserDeps({ autoSize: true }))
    .setSize(container.clientWidth || 900, 420)
    .setAxis({
      x: { ticks: timeTicks({ timeZone: "UTC", locale: "ko" }) },
      y: { position: "right", format: priceFormat({ compact: true, locale: "ko" }) },
    })
    .build(host);

  // 봉 수를 줄인다 — 점이 봉 너비보다 커서, 촘촘하면 서로 덮는다.
  const data = fixtureCandles(80);
  plot.mainPane.addSeries({ series: candleSeries(), data, name: "가격" });
  plot.mainPane.addSeries({ series: volumeDots, data, name: "거래량(점)" });

  return Object.assign(
    () => {
      plot.destroy();
      host.remove();
    },
    { requestRender: () => plot.requestRender() },
  );
}