React로 관리자 페이지 개발을 하던 중 위 그림과 같은 차트를 메인에 띄우려고 한다.
Javascript에서 가장 많이 사용하는 Chart.js의 React Component용 react-chartjs-2가 있어서 사용해보았다. 공식문서
react-chartjs-2 | react-chartjs-2
React components for Chart.js
react-chartjs-2.js.org
react-chartjs-2 사용법
npm으로는 아래와 같은 명령어로 다운받았다.
npm install --save chart.js react-chartjs-2
공식문서에 있는 간단한 예시로 아래와 같은 Component를 만들었고
// ChartComponent.jsx
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from "chart.js";
import { Bar } from "react-chartjs-2";
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
export const options = {
responsive: true,
plugins: {
legend: {
position: "top",
},
title: {
display: true,
text: "Chart.js Bar Chart",
},
},
};
const labels = ["January", "February", "March", "April", "May", "June", "July"];
export const data = {
labels,
datasets: [
{
label: "Dataset 1",
data: [1, 6, 3, 2, 6, 1, 3],
backgroundColor: "rgba(255, 99, 132, 0.5)",
},
{
label: "Dataset 2",
data: [2, 6, 1, 7, 9, 3, 5],
backgroundColor: "rgba(53, 162, 235, 0.5)",
},
],
};
export function ChartComponent() {
return <Bar options={options} data={data} />;
}
안에 데이터가 선언되어 있어서 아래와 같이 호출했다.
<ChartComponent />
기본적인 사용법은 알았으니 이제 내가 원하는 차트를 그리려고 한다.
내가 원하는 차트를 그려보자
데이터 형식
먼저 그래프를 그리기 위해 사용할 data는 아래와 같은 형식이다.
const data = [
{ "time_hour": 5, "time_amount": 27710, "time_count": 5 },
{ "time_hour": 6, "time_amount": 27682, "time_count": 2 },
{ "time_hour": 7, "time_amount": 22764, "time_count": 4 },
{ "time_hour": 8, "time_amount": 46618, "time_count": 2 },
{ "time_hour": 9, "time_amount": 49682, "time_count": 2 },
{ "time_hour": 10, "time_amount": 30535, "time_count": 2 }
]
차트 형식
위 data로 내가 원하는 차트는 시간대별로 time_amount값은 막대그래프로, time_count값은 꺾은선 그래프로 보여주려고 한다.
- x축 : time_hour값
- 첫번째 y축 : time_amount 값
- 두번째 y축 : time_count 값
구현
<ChartComponent data={data} />
먼저 data값을 넘겨주고 ChartComponent에서 이 data를 받으려고 했다.
막대그래프는 bar형식이고 꺾은선 그래프는 line 형식이라 이 두개의 그래프를 datasets에 넣어주었고, yAxisID값을 각각 y1,y2로 두어 구분할 수 있게 하였다.
import { Bar } from "react-chartjs-2";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Legend,
PointElement,
LineElement,
} from "chart.js";
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Legend,
PointElement,
LineElement
);
const options = {
scales: {
y1: {
beginAtZero: true,
position: "left",
title: {
display: true,
text: "Bar차트",
},
},
y2: {
beginAtZero: true,
position: "right",
grid: {
drawOnChartArea: false,
},
title: {
display: true,
text: "Line차트",
},
},
},
plugins: {
legend: {
display: true,
},
},
};
const ChartComponent = ({ data }) => {
const chartData = {
labels: data.map((item) => `${item.time_hour}시`),
datasets: [
{
yAxisID: "y1",
type: "bar",
label: "Bar차트",
data: data.map((item) => item.time_amount),
backgroundColor: "rgba(255, 99, 132, 0.5)",
borderColor: "rgba(255, 99, 132, 1)",
borderWidth: 1,
},
{
yAxisID: "y2",
type: "line",
label: "Line차트",
data: data.map((item) => item.time_count),
backgroundColor: "rgba(54, 162, 235, 0.5)",
borderColor: "rgba(54, 162, 235, 1)",
borderWidth: 2,
fill: false,
tension: 0.1,
},
],
};
return <Bar data={chartData} options={options} />;
};
export default ChartComponent;
두개의 그래프를 띄웠으니 결과값을 한번 보자면
이렇게 나오게 되었다. 근데 우리가 원하는
이와 같은 그래프는 막대의 끝 값에 "Line차트"에 대한 값이 위치해야 하고, 그 자리에 값을 출력해야 한다.
하지만 일단 time_amount값과 time_count값은 연관성이 없어서 .. 따로 조절해야할 것 같았고, 일단 막대그레프에 따라 꺾은선 그래프를 보여주려고 했다.
datasets부분에서
datasets: [
{
yAxisID: "y1",
type: "bar",
label: "Bar차트",
data: data.map((item) => item.time_amount),
backgroundColor: "rgba(255, 99, 132, 0.5)",
borderColor: "rgba(255, 99, 132, 1)",
borderWidth: 1,
},
{
yAxisID: "y2",
type: "line",
label: "Line차트",
data: data.map((item) => item.time_amount), // item.time_count -> item.time_amount
backgroundColor: "rgba(54, 162, 235, 0.5)",
borderColor: "rgba(54, 162, 235, 1)",
borderWidth: 2,
fill: false,
tension: 0.1,
},
],
으로 바꾸어 데이터 값을 같게 해 주었고, 위에 띄우는 값만 time_count값으로 설정하면 된다고 생각했다.
react-chartjs-2에는 tooltip이 존재하지만 이건 마우스를 올려야 그 데이터에 대해 값이 나오는 것이라 항상 보이게 하고싶었다
이는 chartjs-plugin-datalabels로 해결할 수 있었다
chartjs-plugin-datalabels
npm install chartjs-plugin-datalabels --save
로 설치하고
import ChartDataLabels from 'chartjs-plugin-datalabels';
선언 해주고 아래와 같이 사용한다고 명시해야 한다.
return <Bar data={chartData} options={options} plugins={[ChartDataLabels]} />;
이후 기존 코드의 options의 plugins 내부에 datalabels를 작성했다.
plugins: {
legend: {
display: true,
},
datalabels: {
formatter: function (value) {
return value;
},
display: true,
color: "black",
anchor: "end",
align: "start",
},
},
이렇게 바꾸어 줬을 때 아래 그림처럼 datalabel이 2번씩 표시되어서
둘 중 하나의 그래프에서는 표시를 안하게 설정하였다.
plugins: {
legend: {
display: true,
},
datalabels: {
formatter: function (value) {
return value;
},
display: function (context) {
// yAxisID가 'y2'인 데이터셋의 레이블은 표시하지 않음
return context.dataset.yAxisID !== "y2";
},
color: "black",
anchor: "end",
align: "start",
},
},
근데 우리가 띄우려고 하는 건 기존 데이터의 value(time_amount)가 아닌 time_count 값 이기 때문에
value가 아닌 time_count값을 넣어줘야 한다.
편의를 위해 option을 따로 관리하였는데, 이렇게 되면 option에서 Bar에 넘겨주는 data에 접근할 수 없어 option 선언을 CharComponent안에 넣어주었고, 각 index에 따른 time_count값을 아래와 같이 선언하여 표시하였다.
plugins: {
legend: {
display: true,
},
datalabels: {
formatter: (value, context) => {
// 현재 데이터 포인트의 인덱스를 사용하여 time_count 값을 찾음
const timeCount = data[context.dataIndex].time_count;
return timeCount;
},
display: function (context) {
// yAxisID가 'y2'인 데이터셋의 레이블은 표시하지 않음
return context.dataset.yAxisID !== "y2";
},
color: "black",
anchor: "end",
align: "start",
},
},
원하는대로 그래프를 구현할 수 있었고 살짝만 formatting과 위치를 조절하여 anchor : "end" 로 바꾸어 주었는데 아래와 같이 그래프를 넘어서서 표시가 되면 잘리는 현상이 있었다.
따라서 그래프의 y축 범위를 살짝 늘렸고, 두 개의 그래프를 띄워 y축이 두 개가 나와 하나를 없애 주었다.
전체 소스코드
// ChartComponent.jsx
import { Bar } from "react-chartjs-2";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Legend,
PointElement,
LineElement,
} from "chart.js";
import ChartDataLabels from "chartjs-plugin-datalabels";
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Legend,
PointElement,
LineElement
);
const ChartComponent = ({ data }) => {
const maxAmount = Math.max(...data.map((item) => item.time_amount));
// 최대값에 1.3을 곱하여 y축의 max 설정
const maxYValue = Math.round((maxAmount * 1.3) / 1000) * 1000;
const chartData = {
labels: data.map((item) => `${item.time_hour}시`),
datasets: [
{
yAxisID: "y1",
type: "bar",
label: "Bar차트",
data: data.map((item) => item.time_amount),
backgroundColor: "rgba(255, 99, 132, 0.5)",
borderColor: "rgba(255, 99, 132, 1)",
borderWidth: 1,
},
{
yAxisID: "y2",
type: "line",
label: "Line차트",
data: data.map((item) => item.time_amount),
backgroundColor: "rgba(54, 162, 235, 0.5)",
borderColor: "rgba(54, 162, 235, 1)",
borderWidth: 2,
fill: false,
tension: 0.1,
},
],
};
const options = {
scales: {
y1: {
beginAtZero: true,
position: "left",
title: {
display: true,
text: "Bar차트",
},
max: maxYValue,
},
y2: {
display: false,
beginAtZero: true,
position: "right",
grid: {
drawOnChartArea: false,
},
title: {
display: true,
text: "Line차트",
},
max: maxYValue,
},
},
plugins: {
legend: {
display: true,
},
datalabels: {
formatter: (value, context) => {
// 현재 데이터 포인트의 인덱스를 사용하여 time_count 값을 찾음
const timeCount = data[context.dataIndex].time_count;
return timeCount + "건";
},
display: function (context) {
// yAxisID가 'y2'인 데이터셋의 레이블은 표시하지 않음
return context.dataset.yAxisID !== "y2";
},
color: "black",
anchor: "end",
align: "top",
},
},
};
return <Bar data={chartData} options={options} plugins={[ChartDataLabels]} />;
};
export default ChartComponent;
'Frontend > React' 카테고리의 다른 글
[React] react-responsive-carousel 내맘대로 커스텀해보자! (0) | 2024.04.27 |
---|---|
[React] 핸드폰 인증번호 타이머 기능을 구현해보자 (2) | 2024.02.10 |
vscode jsx파일 quick fix import안되는 오류 해결 (0) | 2024.02.05 |