feat(chart): 메인 차트 하단에 거래량 막대 overlay 추가
- 캔들 priceScale 을 위 78%(top=0.05, bottom=0.22)로 좁힘
- 거래량 Histogram 시리즈를 overlay priceScale("volume")로 같은 차트에 부착,
scaleMargins top=0.82 → 차트 영역 아래 18% 만 사용
- 상승봉=빨강(0x40 알파), 하락봉=파랑 — 한국 거래소 관행
- 가격축 라벨/마지막값/priceLine 숨김 → 캔들 시야 방해 최소화
- 시계열은 메인 timeScale 그대로 따라가서 별도 sync 불필요
cleanup 에서 volumeRef.current=null 도 같이 정리. intraday 포함 항상 ON
(거래량은 일/분봉 공통으로 의미 있음).
This commit is contained in:
@@ -161,6 +161,7 @@ export function StockChart({ chart, prediction }: Props) {
|
|||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const chartRef = useRef<IChartApi | null>(null);
|
const chartRef = useRef<IChartApi | null>(null);
|
||||||
const candleRef = useRef<ISeriesApi<"Candlestick"> | null>(null);
|
const candleRef = useRef<ISeriesApi<"Candlestick"> | null>(null);
|
||||||
|
const volumeRef = useRef<ISeriesApi<"Histogram"> | null>(null);
|
||||||
const predRef = useRef<ISeriesApi<"Candlestick"> | null>(null);
|
const predRef = useRef<ISeriesApi<"Candlestick"> | null>(null);
|
||||||
const smaRefs = useRef<Map<SmaWindow, ISeriesApi<"Line">>>(new Map());
|
const smaRefs = useRef<Map<SmaWindow, ISeriesApi<"Line">>>(new Map());
|
||||||
// RSI 보조차트 — 별도 chart 인스턴스로 메인 아래에 붙여 timeScale 동기화.
|
// RSI 보조차트 — 별도 chart 인스턴스로 메인 아래에 붙여 timeScale 동기화.
|
||||||
@@ -213,13 +214,30 @@ export function StockChart({ chart, prediction }: Props) {
|
|||||||
wickUpColor: COLOR_UP,
|
wickUpColor: COLOR_UP,
|
||||||
wickDownColor: COLOR_DOWN,
|
wickDownColor: COLOR_DOWN,
|
||||||
});
|
});
|
||||||
|
// 캔들 priceScale 을 위쪽 78% 로 좁혀서 거래량 막대를 아래쪽에 둘 공간 확보.
|
||||||
|
c.priceScale("right").applyOptions({
|
||||||
|
scaleMargins: { top: 0.05, bottom: 0.22 },
|
||||||
|
});
|
||||||
|
// 거래량 막대 — overlay priceScale("volume") 로 같은 chart 안에 띄움.
|
||||||
|
// bottom 0/top 0.82 → 차트 영역의 아래 18% 만 사용. 가격축 라벨은 숨김.
|
||||||
|
const volume = c.addHistogramSeries({
|
||||||
|
priceFormat: { type: "volume" },
|
||||||
|
priceScaleId: "volume",
|
||||||
|
priceLineVisible: false,
|
||||||
|
lastValueVisible: false,
|
||||||
|
});
|
||||||
|
c.priceScale("volume").applyOptions({
|
||||||
|
scaleMargins: { top: 0.82, bottom: 0 },
|
||||||
|
});
|
||||||
chartRef.current = c;
|
chartRef.current = c;
|
||||||
candleRef.current = candle;
|
candleRef.current = candle;
|
||||||
|
volumeRef.current = volume;
|
||||||
const smaMap = smaRefs.current;
|
const smaMap = smaRefs.current;
|
||||||
return () => {
|
return () => {
|
||||||
c.remove();
|
c.remove();
|
||||||
chartRef.current = null;
|
chartRef.current = null;
|
||||||
candleRef.current = null;
|
candleRef.current = null;
|
||||||
|
volumeRef.current = null;
|
||||||
predRef.current = null;
|
predRef.current = null;
|
||||||
smaMap.clear();
|
smaMap.clear();
|
||||||
};
|
};
|
||||||
@@ -241,6 +259,23 @@ export function StockChart({ chart, prediction }: Props) {
|
|||||||
chartRef.current?.timeScale().fitContent();
|
chartRef.current?.timeScale().fitContent();
|
||||||
}, [chart, isIntraday]);
|
}, [chart, isIntraday]);
|
||||||
|
|
||||||
|
// 거래량 막대 — 종가 ≥ 시가면 한국 관행으로 빨강(상승), 아니면 파랑(하락).
|
||||||
|
// null volume 은 그 시점 캔들 자체가 빠진 케이스(상장폐지/거래정지) 라 건너뛴다.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!volumeRef.current) return;
|
||||||
|
const data: HistogramData[] = chart.ohlcv
|
||||||
|
.filter((p) => p.volume != null && p.volume > 0)
|
||||||
|
.map((p) => {
|
||||||
|
const up = p.close != null && p.open != null ? p.close >= p.open : true;
|
||||||
|
return {
|
||||||
|
time: isoToUtcTs(p.date),
|
||||||
|
value: p.volume as number,
|
||||||
|
color: up ? "#ef444466" : "#3b82f666",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
volumeRef.current.setData(data);
|
||||||
|
}, [chart, isIntraday]);
|
||||||
|
|
||||||
// SMA 시리즈 — chart.ohlcv 종가 기준. 토글 변화 또는 데이터 변화 시 갱신.
|
// SMA 시리즈 — chart.ohlcv 종가 기준. 토글 변화 또는 데이터 변화 시 갱신.
|
||||||
// 비활성 윈도우는 제거하고, 활성은 재계산. 마지막에 fitContent 는 호출 안 함 — 사용자 줌 유지.
|
// 비활성 윈도우는 제거하고, 활성은 재계산. 마지막에 fitContent 는 호출 안 함 — 사용자 줌 유지.
|
||||||
const closes = useMemo(() => chart.ohlcv.map((p) => p.close), [chart]);
|
const closes = useMemo(() => chart.ohlcv.map((p) => p.close), [chart]);
|
||||||
|
|||||||
Reference in New Issue
Block a user