
공식 홈페이지에서 소개하는 것과 같이 HighCharts는 다양한 유형의 차트를 지원하는 자바스크립트 라이브러리입니다. 크게 Core, Stock, Maps, DashBoard, Gantt 총 5가지의 카테고리가 있고
세부적으로 엄청나게 다양한 차트 데모를 가지고 있습니다.
각 데모는 코드를 제공하고, 공식 문서를 읽으면서 개발자가 쉽게 커스텀할 수 있습니다.
이번에 구현해 볼 차트는 Stock chart with GUI입니다.

위 차트는 이름부터 보시면 아시겠지만 주식이나 코인과 같은 캔들 데이터를 시각화하여 차트로 나타내는데에 특화되어있는 차트입니다. 저는 이 차트를 이용하여 실시간 코인 차트를 시각화 하는데에 사용했습니다. 그 경험을 바탕으로 어떻게 구현하는지 차근차근 설명하도록 하겠습니다.
https://www.highcharts.com/docs/getting-started/install-from-npm
// npm
npm install highcharts --save
npm install highcharts-react-official -- save
// yarn
yarn add highcharts
yarn add highcharts-react-official
먼저 터미널에서 위 명령어를 실행하여 프로젝트에 패키지를 설치해줍니다. highcharts-react-official은 리액트를 사용하는 프로젝트에서 차트를 컴포넌트 형태로 사용할 수 있게 해줍니다.
<HighchartsReact
highcharts={Highcharts}
constructorType={'stockChart'}
options={options}
/>
위와 같이 말이죠. HighCharts와 컨스트럭터 타입은 상수값으로 prop 형태로 전달해주면 되고 옵션은 초기 옵션을 설정해주고 상태로 관리하면서 컴포넌트 안에서 수정하고 싶은 부분만 바꿔주면 됩니다. 저는 이 라이브러리도 사용하여 구현했으니 여러분도 추천드립니다.
우선 전체 코드를 보여드리고, 세세하게 설명하도록 하겠습니다.
// Chart.jsx
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { globalColors } from '@/globalColors';
import { Box } from '@mui/material';
import axios from 'axios';
import Highcharts from 'highcharts/highstock';
import HighchartsReact from 'highcharts-react-official';
import indicators from 'highcharts/indicators/indicators';
indicators(Highcharts);
Highcharts.setOptions({
lang: {
rangeSelectorZoom: '기간', // 범위 설렉터 설명
},
time: {
useUTC: false, // UTC 시간 사용 여부
},
});
const initialOptions = {
chart: {
maxWidth: 900,
height: 400,
zooming: {
mouseWheel: {
enabled: true, // 마우스 휠줌 가능
sensitivity: 1.3, // 감도
},
},
},
accessibility: {
enabled: false,
},
credits: {
enabled: true, // 차트 우측 하단에 Highcharts.com 표시 여부
text: 'Mr Cryp',
},
navigator: {
enabled: true, // 구간을 선택할 수 있는 네비게이터 사용 여부
},
yAxis: [
{
labels: {
align: 'right', // 정렬
x: -4, // 차트 우측으로부터의 거리
// y축 천 단위 구분 기호 설정
formatter: function () {
return Highcharts.numberFormat(Number(this.value), 0, '', ',');
},
},
height: '80%', // y축 높이
lineWidth: 2, // y축 선 굵기
// 마우스 포인터 위치를 나타내는 크로스헤어
crosshair: {
snap: false,
},
},
{
labels: {
align: 'right',
x: -3,
},
top: '80%',
height: '20%', // 레이블의 높이
offset: 0,
lineWidth: 2, // 볼륨 선 굵기
},
],
plotOptions: {
candlestick: {
color: globalColors.color_neg['400'], // 음봉
upColor: globalColors.color_pos['400'], // 양봉
},
sma: {
linkedTo: 'upbit', // 이동평균선 연결
lineWidth: 0.8, // 이동평균선 굵기
zIndex: 1, // 이동평균선 z-index
marker: {
enabled: false, // 마커 표시 여부
},
enableMouseTracking: false, // 마우스 트래커 표시 여부
},
},
tooltip: {
shared: true, // 여러 series를 한 번에 설정하는 옵션
formatter: function () {
let tooltipText = `<b>${Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x)}</b><br/><br/>`; // x축 기준 시간
this.points.forEach(point => {
if (point.series.type === 'candlestick') {
const color =
point?.point?.close > point?.point?.open
? globalColors.color_pos
: globalColors.color_neg;
tooltipText += `
<span style="color:${color[400]}">●</span> <b>${point.series.name}</b><br/>
시가: ${Highcharts.numberFormat(point.point.open, 0, '.', ',')}<br/>
고가: ${Highcharts.numberFormat(point.point.high, 0, '.', ',')}<br/>
저가: ${Highcharts.numberFormat(point.point.low, 0, '.', ',')}<br/>
종가: ${Highcharts.numberFormat(point.point.close, 0, '.', ',')}<br/><br/>
`;
} else if (point.series.type === 'column') {
tooltipText += `<span style="color:${point.color}">●</span> <b>${point.series.name}</b><br/>${point.y}<br/>`;
}
});
return tooltipText;
},
style: {
fontSize: '0.75rem', // 툴팁의 폰트 크기
},
backgroundColor: globalColors.tooltip_bgColor,
borderRadius: 4,
borderWidth: 1,
shadow: false,
},
};
export default function Chart() {
const [options, setOptions] = useState(initialOptions);
const [candles, setCandles] = useState([]);
const code = useSelector(state => state.chart.code);
const fetchCandles = useCallback(
async type => {
let fetchedCandles;
try {
const response = await axios.get('/api/candles', {
params: {
type,
unit: type.replace('min', ''),
ticker: code,
count: 200,
},
});
fetchedCandles = response.data;
} catch (error) {
console.error('캔들 다운로드 중 에러 발생 :', error);
return;
}
setCandles(fetchedCandles);
},
[code],
);
useEffect(() => {
fetchCandles('1min'); // 기본값 1분봉
}, [fetchCandles]);
const rangeSelector = useMemo(
() => ({
allButtonsEnabled: true,
inputEnabled: false,
buttons: [
{
text: '1분봉',
events: {
click: () => fetchCandles('1min'),
},
},
{
text: '5분봉',
events: {
click: () => fetchCandles('5min'),
},
},
{
text: '일봉',
events: {
click: () => fetchCandles('days'),
},
},
{
text: '주봉',
events: {
click: () => fetchCandles('weeks'),
},
},
{
text: '월봉',
events: {
click: () => fetchCandles('months'),
},
},
],
}),
[fetchCandles],
);
useEffect(() => {
if (candles.length > 0) {
candles.sort((a, b) => a.timestamp - b.timestamp);
const ohlc = candles.map(candle => [
candle.timestamp,
candle.opening_price,
candle.high_price,
candle.low_price,
candle.trade_price,
]);
const minTimestamp = ohlc[0][0];
const maxTimestamp = ohlc[ohlc.length - 1][0];
const volume = candles.map(candle => ({
x: candle.timestamp,
y: candle.candle_acc_trade_volume,
color:
candle.opening_price <= candle.trade_price
? globalColors.hotpink['200']
: globalColors.skyblue['200'],
}));
setOptions(prevOptions => ({
...prevOptions,
// x 축
xAxis: {
min: minTimestamp,
max: maxTimestamp,
},
// 범위 셀렉터
rangeSelector,
// 시리즈
series: [
// 기간별 캔들스틱 차트
{
type: 'candlestick',
name: code,
id: 'upbit',
data: ohlc,
},
// 이동평균선 15
{
type: 'sma',
params: {
period: 15,
},
color: globalColors.sma_15,
},
// 이동평균선 50
{
type: 'sma',
params: {
period: 50,
},
color: globalColors.sma_50,
},
// 누적 거래량 막대 그래프
{
type: 'column',
name: '누적 거래량',
data: volume,
yAxis: 1,
},
],
}));
}
}, [candles, code, fetchCandles, rangeSelector]);
return (
<Box>
<HighchartsReact
highcharts={Highcharts}
constructorType={'stockChart'}
options={options}
/>
</Box>
);
}
https://api.highcharts.com/highstock/lang.thousandsSep
lang은 언어 객체입니다. lang은 Highcharts의 언어 관련 설정을 담당하며, 각 차트 인스턴스별로 설정할 수 있는 것이 아니라, 전역 기본 설정으로만 사용할 수 있습니다.
차트가 초기화되기 전에 설정하려면 Highcharts.setOptions을 사용합니다.
setOptions는 차트의 전역 기본 설정을 변경하기 위해 사용하는 메서드입니다.
설정한 옵션으로 나타난 결과입니다. 셀렉터 가장 좌측에 rangeSelectorZoom으로 설정한 '기간'이, 툴팁의 가격에는 천 단위 구분자로 ','이 적용된 모습입니다.

indicators는 Highcharts 모듈의 일부로, 이를 사용하면 차트에 기술적 분석 지표를 표시할 수 있게 됩니다. 이동 평균선(SMA)과 같은 인디케이터를 처리할 수 있게 되어 plotOptions 및 series 속성에서 sma 타입의 시리즈를 추가할 수 있게 합니다.

{
type: 'column',
name: '누적 거래량',
data: volume,
yAxis: 1,
}
컴포넌트 안에서 useState로 관리되고 있는 Highcharts의 options의 초기값 역할인 객체입니다. 여기서 설정해준 옵션들은 후에 컴포넌트 내부에서 useEffect 스코프 안에서 실행되는 setOptions로 덮어써지지 않는다면 계속 차트의 옵션으로 유지됩니다.
chart
차트 자체의 속성을 설정합니다. 이 옵션은 제가 사용한 속성을 위주로만 설명하겠습니다.
accessibility
Highcharts에서 제공하는 웹 접근성 개선에 관한 속성을 설정합니다.

보시는바와 같이 키보드로만 차트를 조작할 수 있게 도와주는 keyboardNavigation이나, 시각장애인을 위한 screenReaderSection, 색약인 사람들을 위해서 색상 극대비를 적용시켜주는 highContrastMode와 Theme 옵션을 갖고 있습니다.
저는 따로 이 설정들을 해주지 않았습니다. 혹시나 이러한 웹 접근성도 관심이 있으시거나 필요하다면 공식 문서를 읽으시면서 옵션을 설정해주시면 될 것 같습니다.
credits
차트가 표시되는 영역 우측 하단에 크레딧을 표시할 것인지 여부와 연결할 링크, 텍스트 등을 설정할 수 있습니다.

기본은 'Highcharts.com'이라는 텍스트가 표시됩니다. 여러분이 프로젝트에서 차트를 구현한다면 여기다 프로젝트 이름을 넣어도 좋을 것 같습니다.
navigator
차트 하단에 차트로 표현되는 시간대의 구간을 조정할 수 있는 네비게이터를 설정합니다.

Stack Overflow에서 가져온 이미지입니다. 빨간색 동그라미가 쳐진 부분이 네비게이터입니다.

여기서도 height이나 margin, outlineColor와 같은 레이아웃의 스타일 설정도 가능하고 x축과 y축에 대한 속성도 있으니 필요에 따라 추가하여 옵션을 설정하시면 될 것 같습니다.
yAxis
차트에서 핵심인 y축 데이터에 관한 설정입니다. yAxis는 배열 형태로 다수의 시리즈에 대해서 옵션을 설정할 수 있습니다. 시리즈란 한국어로 시계열을 말합니다. 일정 시간 간격으로 배치된 데이터들의 수열을 의미합니다.
현재 제가 구현한 코드에서는 캔들 데이터를 받아와서 만들어놓은
총 2개의 시리즈가 존재합니다.
// 기간별 캔들스틱 차트
{
type: 'candlestick',
name: code,
id: 'upbit',
data: ohlc,
}
// 누적 거래량 막대 그래프
{
type: 'column',
name: '누적 거래량',
data: volume,
yAxis: 1,
}
다수의 시리즈를 yAxis에서 설정할 때는 시리즈 간 순서를 설정해야 합니다. 저는 누적 거래량 시리즈에 yAxis 값을 1로 설정해줬는데 yAxis 옵션 배열에서 인덱스 1번째의 객체로 설정하겠다는 것을 의미합니다. 그럼 객체 안에 정의된 옵션들을 설명하겠습니다.
plotOptions
차트의 특정 시리즈 타입에 대한 전역 옵션을 설정하는 데 사용됩니다.
tooltip
차트 위에 마우스 포인터를 올렸을 때 x축과 y축을 기준으로 해당 시간의 시리즈 정보들을 표시해주는 툴팁 박스에 대한 옵션을 설정합니다.
초기 옵션인 initialOptions를 설정하는 과정은 끝났습니다. 그럼 useEffect에서 시리즈에 사용할 데이터를 어떻게 저장하고 새로운 options를 설정하는 과정을 설명한 뒤 마치도록 하겠습니다.
useEffect(() => {
if (candles.length > 0) {
candles.sort((a, b) => a.timestamp - b.timestamp);
const ohlc = candles.map(candle => [
candle.timestamp,
candle.opening_price,
candle.high_price,
candle.low_price,
candle.trade_price,
]);
const minTimestamp = ohlc[0][0];
const maxTimestamp = ohlc[ohlc.length - 1][0];
const volume = candles.map(candle => ({
x: candle.timestamp,
y: candle.candle_acc_trade_volume,
color:
candle.opening_price <= candle.trade_price
? globalColors.skyblue['200']
: globalColors.hotpink['200'],
}));
setOptions(prevOptions => ({
...prevOptions,
// x 축
xAxis: {
min: minTimestamp,
max: maxTimestamp,
},
// 범위 셀렉터
rangeSelector,
// 시리즈
series: [
// 기간별 캔들스틱 차트
{
type: 'candlestick',
name: code,
id: 'upbit',
data: ohlc,
},
// 이동평균선 15
{
type: 'sma',
params: {
period: 15,
},
color: globalColors.sma_15,
},
// 이동평균선 50
{
type: 'sma',
params: {
period: 50,
},
color: globalColors.sma_50,
},
// 누적 거래량 막대 그래프
{
type: 'column',
name: '누적 거래량',
data: volume,
yAxis: 1,
},
],
}));
}
}, [candles, code, fetchCandles, rangeSelector]);
ohlc
ohlc는 Open, High, Low, Close = 시가, 고가, 저가, 종가를 의미합니다.
캔들스틱 차트는 하나의 캔들 데이터를 툴팁으로 표시하기 위해서 ohlc 배열이 필요합니다.
배열에는 타임스탬프가 포함되어도 포함되지 않아도 상관없지만 저는 포함시켜서 툴팁에 표시되게 만들었습니다.
candles라는 데이터는 현재 제 프로젝트에서는 업비트의 REST API를 사용하여 받아오는 중인데
여러분은 자신이 사용하는 오픈 API로 데이터를 받아와서 candles에 저장하시면 되겠습니다.
minTimestamp, maxTimestmap
차트에서 타임스탬프의 시작점과 종료지점을 설정하기 위해 필요한 속성입니다. 설정은 간단합니다. ohlc 배열에서 첫 번째 값의 타임스탬프와 가장 마지막 값의 타임스탬프를 각각 min, max로 해주면되겠죠. 이건 아래에 setOptions로 x축에 설정하는 min, max에 그대로 넣어주면 됩니다.
volume
누적 거래량을 의미합니다. 타입은 객체입니다.
setOptions
초기 옵션에서 추가해 줄 옵션이나 덮어쓸 옵션을 넣어줍니다.

구현 중 헤매는 부분은 구글링과 GPT의 도움을 받아 해결했었는데, 공식 문서만 잘 읽어도 원하는 옵션을 설정하여 차트를 구현하는데에는 무리가 없을 것 같네요. Highcharts는 사용하기 쉬워서 차트로 시각화가 필요할 때 좋은 라이브러리라고 생각합니다.