06. [React] Rechart로 주식차트 그리기

zero zoo·2024년 5월 6일
0


지난 01. ~ 05. 포스트에서의 환경세팅 및 백엔드 데이터 수집을 통해 차트를 그릴 수 있는 준비가 끝났습니다.
이번엔 본격적으로 차트를 그려보도록 하겠습니다.

Rechart

차트 표현을 위해 Rechart 라는 라이브러리를 사용하였습니다.

Rechart는 React에서 사용할 수 있는 다양한 그래프 및 차트를 표현할 수 있는 라이브러리입니다.
Rechart BarChart 공식문서

Rechart에서 주식차트처럼 y방향으로의 범위 데이터를 그리는 BarChart를 그리기 위해서는, 아래와 같은 코드를 사용합니다.

<BarChart width={730} height={250} data={rangeData} >
  <XAxis dataKey="day" />
  <YAxis />
  <Tooltip />
  <Bar dataKey="temperature" fill="#8884d8" />
</BarChart>

그 중 rangeData 의 형식은 아래와 같습니다.

const data = [
  {
    "day": "05-01",
    "temperature": [-1,10]
  },
  {
    "day": "05-02",
    "temperature": [2,15]
  },
  {
    "day": "05-03",
    "temperature": [3,12]
  },
  {
    "day": "05-04",
    "temperature": [4,12]
  },
  {
    "day": "05-05",
    "temperature": [12,16]
  },
  {
    "day": "05-06",
    "temperature": [5,16]
  },
  {
    "day": "05-07",
    "temperature": [3,12]
  },
  {
    "day": "05-08",
    "temperature": [0,8]
  },
  {
    "day": "05-09",
    "temperature": [-3,5]
  }
]

위 예시는 x 좌표가 될 day 라는 key와, y방향의 value가 될 temperature 라는 key로 구성된 구조체로 이루어진 array 입니다.

이전 과정에서 주식차트를 그리기 위해서 아래와 같은 형식으로 백엔드에서 데이터를 가공하였습니다.

parseData =
[
	...
    {
        "name" : "하이브보통주",
        "date" : "20240304",
        "openClose" : [192700,199200],
        "highLow"	: [192300,202000]
    },
	...
]

이 중, date는 x축의 data가 될 것이고, openClose(시가와 종가)는 차트 데이터가 될 것입니다. (highLow는 다음 단계에서 사용합니다.)

1. BarChart에 넣으면 주식차트처럼 보일까?

<BarChart width={1000} height={500} data={parseData} margin={{top: 20, right: 20, bottom: 20, left: 20}} >
  	<XAxis dataKey="date" />
    <YAxis />
    <CartesianGrid strokeDasharray="2 2" />
    <Tooltip />

    <Bar dataKey="openClose" fill="#8884d8" >
    </Bar>
</BarChart>

일단 알맞게 데이터를 BarChart 컴포넌트에 넣어줍니다.

결과

실제 차트와 유사한 것은 맞지만, 아직 갈 길은 멀어 보입니다,,,

2. 주가 상승/하락에 따라 색이 필요해!

<BarChart  width={1000} height={500} data={parseData} margin={{top: 20, right: 20, bottom: 20, left: 20}} >
	<XAxis dataKey="date" />
    <YAxis />
    <CartesianGrid strokeDasharray="2 2" />
    <Tooltip />
    
    <Bar dataKey="openClose" fill="#8884d8">
 	{
    	parseData && parseData.map((entry, index) => (
        	<Cell key={`cell-${index}`}
				fill={Number(entry.openClose[0]) > Number(entry.openClose[1]) ? '#0048ff' : '#c20404' }
			/>
		))
	}
	</Bar>
</BarChart>

구조체 안의 시가(openClose[0]) 와 종가(openClose[1]) 를 비교하여, 그 결과에 따라 색을 fill 옵션으로 지정해줍니다.
이는 ReChart에서 제공하는 Cell 컴포넌트를 Bar 컴포넌트 내부에 넣어주어 설정할 수 있습니다.

parseData && parseData.map( ~~ )
위와 같이 표현하지 않으면, parseData가 아직 불러와지지 않아 구조체가 비어 있을 경우, 웹에서 아래와 같은 오류가 날 수 있습니다.
AxiosError: Request failed with status code 500

결과

기본적인 틀은 완료되었는데, 중요한 고가와 저가 막대가 없으니 허전합니다.

3. 고가와 저가를 어떻게 저 막대 그래프에 겹쳐 놓을까,,

<BarChart barGap={-440/dataNum} width={1000} height={500} data={parseData} margin={{top: 20, right: 20, bottom: 20, left: 20}} >
	<XAxis dataKey="date" />
    <YAxis />
    <CartesianGrid strokeDasharray="2 2" />
    <Tooltip />
    <Bar dataKey="openClose" fill="#8884d8" barSize={800/dataNum}>
    {
    	parseData && parseData.map((entry, index) => (
        	<Cell key={`cell-${index}`}
            	fill={Number(entry.openClose[0]) > Number(entry.openClose[1]) ? '#0048ff' : '#c20404' }
            />
        ))
    }
    </Bar>
    <Bar dataKey="highLow" fill="#8884d8" barSize={80/dataNum}>
    {
    	parseData && parseData.map((entry, index) => (
        	<Cell key={`cell-${index}`}
            	fill={Number(entry.openClose[0]) > Number(entry.openClose[1]) ? '#0048ff' : '#c20404' }
            />
        ))
    }
    </Bar>
</BarChart>

두개의 BarChart를 겹치는 방식을 사용하였습니다.
이번에는 highLow(고가와 저가) 를 dataKey로 사용하는 차트를 그렸습니다.

그리고 두 개의 Bar 모두 barSize를 data의 개수를 나타내는 dataNum에 반비례하게 설정하여 차트 조회 기간 변경에 따른 bar 폭 변경에 대응할 수 있도록 수정하였습니다.

시가/종가의 봉 그래프와 고가/저가의 꼬리 그래프의 폭을 10배 차이를 주는 방식으로 꼬리를 표현하였습니다.

또한, 꼬리의 위치는 BarChart 컴포넌트의 옵션인 barGap으로 계산하여 표현했습니다.

결과

y축 범위의 수정은 필요해보입니다.

4. 그래프 y축 범위 조정이 필요하다!

// 변경 전
<YAxis/>
  
// 변경 후
<YAxis type="number" domain={[minPrice-(maxPrice-minPrice)*0.1, maxPrice+(maxPrice-minPrice)*0.1]}/>
  
// 참고
maxPrice = Math.max.apply(null,parseData.map((e)=> e?parseInt(e.highLow[0]):0));
minPrice = Math.min.apply(null,parseData.map((e)=> e?parseInt(e.highLow[1]):0));
dataNum = parseData.length;

YAxis의 domain 옵션을 사용하고, 위아래 10프로 여유를 두며 domain을 설정하여 차트를 완성합니다.

결과

차트 디자인은 종료인 듯 합니다.


추가적으로 진행한 작업

<div>
    {"차트 기간 :    "}
    {DAYLIST.map((k) => (
        <button className = {k}
                style={{marginRight: '10px', marginBottom: '10px', width: '60px',
                    background : 'white'}}
                onClick={changeDay}>
            {k}
        </button>
    ))}
</div>

<div className="select" style={{width:"1000px"}}>
    <Select
        placeholder="종목을 선택하세요"
        options={apiListData?JSON.parse(apiListData) : ItemJsonList} // 종목 정보가 로드가 안되어 있을 땐 임시 파일 사용
        onChange={handleItemId}
    />
</div>
  1. 차트 기간을 선택할 수 있는 버튼
  2. 조회 종목을 검색 및 선택할 수 있는 Select box

    Select

    react-select 라는 컴포넌트를 사용하였습니다.
    import Select from "react-select";
    데이터는 역시 공공데이터포탈 API 를 사용했습니다.
    금융위원회_KRX상장종목정보
    이 또한 공공데이터포탈 API 호출 딜레이가 걱정이 되어 local에 2024년 버전의 주식종목정보 json 데이터(ItemJsonList)를 따로 두어 에러에 대비하였습니다.


미해결 과제


시가와 종가 같아 range bar 그래프를 그릴 수 없는 캔들은 어찌 표현할 방도가 없었습니다.
쉽게 표현할 수 있는 방법을 떠올리지 못하여 그냥 두게 되었는데, 이게 계속 눈에 밟히고 아쉬웠습니다.

profile
한방향으로 지그재그

0개의 댓글