동료 간 익명의 피드백을 주고 받을 수 있는 웹사이트를 개발하면서, 사용자가 피드백 결과를 한 눈에 파악할 수 있도록 시각화하기로 하였다. 관련 라이브러리를 찾아 보던 중, Chart.js를 알게 되었으며 이를 이용하여 도넛 차트를 구현하였다.
Chart.js는 "Area Chart", "Bar Chart", "Bubble Chart" 등 다양한 차트 타입을 제공하고 있다. 도넛 차트를 구현하던 중 여러 이유로 다른 차트로 변경하게 되더라도 굳이 새로운 라이브러리를 설치할 필요없이 Chart.js의 또다른 타입을 사용할 수 있다. 또한, Chart.js의 기본 UI가 더없이 깔끔하고, Dataset Properties를 통해 스타일 속성을 자유롭게 변경할 수 있다는 게 마음에 들었다.
동료가 사용자에게 피드백을 남길 때, 15개의 태그 중 5개의 태그를 선택하는 질문이 있다. 사용자가 수많은 피드백을 받았을 때, 가장 많은 선택을 받은 5개의 태그명과 차지하는 비율을 시각화하기 위해 도넛차트를 구현하였다.
const top5ChartData = {
labels: data.slice(0, 5).map((item) => item.tag),
datasets: [
{
data: data.slice(0, 5).map((item) => item.percentage),
backgroundColor: [
"#D5FBE5",
"#F9C7C7",
"#F6EED4",
"#E2E9FF",
"#EDD0F5",
],
borderColor: ["#D5FBE5", "#F9C7C7", "#F6EED4", "#E2E9FF", "#EDD0F5"],
},
],
};
먼저 차트에 넣을 데이터 값을 labels와 datasets 속성을 통해 설정한다.
const options = {
plugins: {
legend: {
display: true,
labels: {
fontSize: 10,
boxWidth: 10,
boxHeight: 10,
color: "black",
font: {
family: "Pretendard",
},
},
},
tooltip: {
enabled: false,
},
datalabels: {
color: "black",
font: {
family: "Pretendard",
},
formatter: function (value: number, context: any) {
const dataLabel = top5ChartData.labels[context.dataIndex];
return `${dataLabel}\n${value}%`;
},
},
},
};

Chart.js는 다양한 plugins을 제공하고 있기 때문에 각 속성값들을 변경하여 원하는 형태로 차트를 구현할 수 있다. 위 이미지는 plugins을 통해 구현한 도넛 차트다.
<Doughnut data={top5ChartData} options={options}></Doughnut>
마지막으로 return문에 위 코드를 넣으면 차트 구현이 성공적으로 이루어진다!
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
import { Doughnut } from "react-chartjs-2";
import ChartDataLabels from "chartjs-plugin-datalabels";
ChartJS.register(ArcElement, Tooltip, Legend, ChartDataLabels);
const SkillChart = ({
data,
}: {
data: { tag: string; percentage: number }[];
}) => {
const top5ChartData = {
labels: data.slice(0, 5).map((item) => item.tag),
datasets: [
{
data: data.slice(0, 5).map((item) => item.percentage),
backgroundColor: [
"#D5FBE5",
"#F9C7C7",
"#F6EED4",
"#E2E9FF",
"#EDD0F5",
],
borderColor: ["#D5FBE5", "#F9C7C7", "#F6EED4", "#E2E9FF", "#EDD0F5"],
},
],
};
const options = {
plugins: {
legend: {
display: true,
labels: {
fontSize: 10,
boxWidth: 10,
boxHeight: 10,
color: "black",
font: {
family: "Pretendard",
},
},
},
tooltip: {
enabled: false,
},
datalabels: {
color: "black",
font: {
family: "Pretendard",
},
formatter: function (value: number, context: any) {
const dataLabel = top5ChartData.labels[context.dataIndex];
return `${dataLabel}\n${value}%`;
},
},
},
};
const generateSentence = () => {
const [first, second, third] = top5ChartData.labels.slice(0, 3);
const sentence = `당신은 ${first}, ${second}, ${third}있는 사람이네요`;
return sentence;
};
return (
<div className="flex flex-col justify-center w-full gap-4">
<p className="font-pre text-[16px] font-bold">{generateSentence()}</p>
<Doughnut data={top5ChartData} options={options}></Doughnut>{" "}
</div>
);
};
function Chart() {
return (
<SkillChart data={workData} />
);
}