์๋์ ๋์ผํ๊ฒ ์ํ ์ฐจํธ๋ฅผ ๊ตฌํํ๊ณ ์ ํ๋ค.
Setting
npm install chartjs
npm install --save chart.js react-chartjs-2
chartjs ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ๋ค
๊ธฐ๋ณธ์ ์ธ DoughnutChart ์ปดํฌ๋ํธ ์์ฑ
์ผ๋จ ์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฐจํธ๋ฅผ ๊ทธ๋ ค๋ดค๋ค. ์ฐจํธ ์ปดํฌ๋ํธ๋ 'data' props๋ฅผ ํ์์ ์ผ๋ก ํฌํจํด์ผ ํ๋ค. ์ฐจํธ์ ํ์ํ ๋ฐ์ดํฐ๋ string ๋ฐฐ์ด 'labels'์ number ๋ฐฐ์ด 'data'์ด๋ค. ์๋ ์์๋ ['๊ฐ','๋','๋ค','๋ผ','๋ง'] ์ [1,2,3,4,5] ๋ก ๋ฐ์ดํฐ๋ฅผ ์ค์ ํ์๋ค.
DoughnutChart.tsx
import { Chart as ChartJS, ArcElement, Tooltip} from 'chart.js';
import { Doughnut } from 'react-chartjs-2';
export default function DoughnutChart() {
ChartJS.register(ArcElement, Tooltip);
const data = {
labels: ['๊ฐ','๋','๋ค','๋ผ','๋ง'],
datasets: [
{
label: '๋์ ์',
data: [1,2,3,4,5],
backgroundColor: ['#010101', '#868686', '#3A3A3A', '#C7C7C7', '#E9E9E9'],
borderColor: ['#010101', '#868686', '#3A3A3A', '#C7C7C7', '#E9E9E9'],
},
],
},
};
return(<Doughnut data={data} />)
}
๋ฐ์ดํฐ ๋ผ๋ฒจ์ ๋น์จ ์ฝ์
chartjs-plugin-datalabels ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํด์ผ ํ๋ค.
npm install chartjs-plugin-datalabels --save
formatter๋ฅผ ํตํด ๋ฐ์ดํฐ๋ผ๋ฒจ์ ๋ํ๋ผ ๊ฐ์ ๊ฐ๊ณตํ๋ค. ์๋ ์์๋ ๋น์จ์ด ๋ํ๋๋๋ก ํ์๋ค.
import { Chart as ChartJS, ArcElement, Tooltip} from 'chart.js';
import { Doughnut } from 'react-chartjs-2';
import ChartDataLabels from 'chartjs-plugin-datalabels'; // ์ถ๊ฐ
export default function DoughnutChart() {
ChartJS.register(ArcElement, Tooltip, ChartDataLabels); // ์ถ๊ฐ
const data = {
labels: ['๊ฐ','๋','๋ค','๋ผ','๋ง'],
datasets: [
{
label: '๋์ ์',
data: [1,2,3,4,5],
backgroundColor: ['#010101', '#868686', '#3A3A3A', '#C7C7C7', '#E9E9E9'],
borderColor: ['#010101', '#868686', '#3A3A3A', '#C7C7C7', '#E9E9E9'],
datalabels: { // ์ถ๊ฐ
color: 'white',
font: {
size: 15,
},
formatter: (value: number) => {
const percentage = `${Math.floor((value / 15) * 100)}%`;
return percentage;
},
},
},
],
};
return(<Doughnut data={data} />)
}
์ฐจํธ ์ค์์ ์ดํฉ ํ์
์ฐจํธ ์ปดํฌ๋ํธ์ 'plugins' props๋ฅผ ์์ฑํ์ฌ ์ฐจํธ ์ค์์ ํ
์คํธ๋ฅผ ๋ฐฐ์นํ๋ค. 'plugins' props์ ํ์
์ ๋ฐฐ์ด์ด๊ณ , textCenter ๊ฐ์ฒด ๋ณ์๋ฅผ ๋ฐ๋ก ์ ์ธํ์ฌ plugins ๋ฐฐ์ด์ ๋ฃ์ด์คฌ๋ค.
import { Chart as ChartJS, ArcElement, Tooltip} from 'chart.js';
import { Doughnut } from 'react-chartjs-2';
import ChartDataLabels from 'chartjs-plugin-datalabels';
export default function DoughnutChart() {
ChartJS.register(ArcElement, Tooltip, ChartDataLabels);
const data = {
labels: ['๊ฐ','๋','๋ค','๋ผ','๋ง'],
datasets: [
{
label: '๋์ ์',
data: [1,2,3,4,5],
backgroundColor: ['#010101', '#868686', '#3A3A3A', '#C7C7C7', '#E9E9E9'],
borderColor: ['#010101', '#868686', '#3A3A3A', '#C7C7C7', '#E9E9E9'],
datalabels: {
color: 'white',
font: {
size: 15,
},
formatter: (value: number) => {
const percentage = `${Math.floor((value / 15) * 100)}%`;
return percentage;
},
},
},
],
};
const textCenter = { // ์ถ๊ฐ
id: 'textCenter',
afterDatasetsDraw: (chart: ChartJS<'doughnut', number[], unknown>) => {
const ctx = chart.ctx;
const xCoor = chart.chartArea.left + (chart.chartArea.right - chart.chartArea.left) / 2;
const yCoor = chart.chartArea.top + (chart.chartArea.bottom - chart.chartArea.top) / 2;
let total = 0;
chart.data.datasets[0].data.forEach((item) => {
if (typeof item === 'number') {
total += item;
}
});
ctx.save();
ctx.font = 'bold 20px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`${total}๋ช
`, xCoor, yCoor);
},
};
return(<Doughnut data={data} plugins={[textCenter]}/>) // ์ถ๊ฐ
}
์ฐจํธ ๋ฐ์ดํฐ ๋ถ๋ชจ๋ก๋ถํฐ ๋ฐ์์ค๊ธฐ
๋ถ๋ชจ๋ก๋ถํฐ ์ฐจํธ ๋ฐ์ดํฐ์ ์ดํฉ์ ๋ฐ์์ฌ ์ ์๋๋ก ์์ฑํ์๋ค. ์ฐจํธ ๋ฐ์ดํฐ๋ labels์ data ํ๋กํผํฐ๋ฅผ ์ง๋ ๊ฐ์ฒด๋ค.
DoughnutChart.tsx
import { Chart as ChartJS, ArcElement, Tooltip} from 'chart.js';
import { Doughnut } from 'react-chartjs-2';
import ChartDataLabels from 'chartjs-plugin-datalabels';
export interface IChartData {
labels: string[];
data: number[];
}
export default function DoughnutChart({chartData, sum} : {chartData: IChartData, sum: number}) {
ChartJS.register(ArcElement, Tooltip, ChartDataLabels);
const data = {
labels: chartData.labels,
datasets: [
{
label: '๋์ ์',
data: chartData.data,
backgroundColor: ['#010101', '#868686', '#3A3A3A', '#C7C7C7', '#E9E9E9'],
borderColor: ['#010101', '#868686', '#3A3A3A', '#C7C7C7', '#E9E9E9'],
datalabels: {
color: 'white',
font: {
size: 15,
},
formatter: (value: number) => {
const percentage = `${Math.floor((value / sum ) * 100)}%`;
return percentage;
},
},
},
],
};
const textCenter = {
id: 'textCenter',
afterDatasetsDraw: (chart: ChartJS<'doughnut', number[], unknown>) => {
const ctx = chart.ctx;
const xCoor = chart.chartArea.left + (chart.chartArea.right - chart.chartArea.left) / 2;
const yCoor = chart.chartArea.top + (chart.chartArea.bottom - chart.chartArea.top) / 2;
let total = 0;
chart.data.datasets[0].data.forEach((item) => {
if (typeof item === 'number') {
total += item;
}
});
ctx.save();
ctx.font = 'bold 20px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`${total}๋ช
`, xCoor, yCoor);
},
};
return(<Doughnut data={data} plugins={[textCenter]}/>)
}
App.tsx
import "./App.css";
import DoughnutChart from "./DoughnutChart";
function App() {
const chartData = {
labels: ['๊ฐ','๋','๋ค','๋ผ','๋ง'],
data: [1,2,3,4,5]
}
const getSum = (data: number[]) => {
let sum = 0
data.forEach((item) => {
sum += item
})
return sum
}
return (
<DoughnutChart chartData={chartData} sum={getSum(chartData.data)}/>
);
}
export default App;
์ด์ ๊ฐ์ด ์ ์ ์ธ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฐจํธ๋ฅผ ๊ตฌํํ์๋ค.
๋์ ์ธ ๋ฐ์ดํฐ ๊ธฐ๋ฐ ์ฐจํธ ๊ตฌํ
๋ง์ฝ ๋ฒํผ์ ํด๋ฆญํ์์ ๋ ์ฐจํธ์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๋ค๋ฉด ๋ณ๊ฒฝ์ฌํญ์ด ์ฐจํธ์ ๋ฐ์๋๊ธฐ ์ํด์๋ ๋ฆฌ๋ ๋๋ง์ด ํ์ํ๋ค.
์์ ๋ง๋ DoughnutChart ์ปดํฌ๋ํธ๋ ๋ถ๋ชจ๋ก๋ถํฐ ๋ฐ์์จ data๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ฆฌ๋ ๋๋ง๋๋ฏ๋ก ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์๋ง๊ฒ data๋ฅผ ์ ๋ฌํ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
๊ทธ๋์ ๋ฒํผ์ ํด๋ฆญ์ ์ฐจํธ ์
๋ฐ์ดํธ๋ฅผ ์๋ฆฌ๊ธฐ ์ํด ์๋์ ๊ฐ์ด ์ ์ํ์๋ค
const [updatePost, setUpdatePost] = useState(false);
๊ทธ๋ฆฌ๊ณ useEffect Hook์ ํตํ์ฌ updatePost์ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋ ๋ค์ post๋ฅผ ๋ถ๋ฌ์ค๋๋ก ํ์ฌ ๋ณ๊ฒฝ์ฌํญ์ด ๋ฐ์๋๊ฒ ํ์๋ค.
useEffect(() => {
if (post !== undefined) {
getSum(post);
processData(post.statisticList);
}
}, [post, updatePost]);
๋๋ต์ ์ธ ์ฝ๋ (์๋ต ์กด์ฌ)
export default function PetitionDetail() {
const [updatePost, setUpdatePost] = useState(false);
const [chartData, setChartData] = useState({ labels: [''], data: [0] });
const { post: petition, postId } = useFetchPost<IPetition>({
api: API_PATH.POST.PETITION.ROOT,
update: updatePost,
});
const [sum, setSum] = useState(0);
// ์ดํฉ ๊ตฌํ๊ธฐ
const getSum = (post: IPetition) => {
setSum(0);
return post.statisticList.forEach((item) => setSum((prev) => prev + item.agreeCount)); // ๋จ๊ณผ๋ ์ด ํฌํ์
};
// ์ฒ์์ ์ฒญ์๊ธ ๋ถ๋ฌ์ฌ ๊ฒฝ์ฐ์ ์
๋ฐ์ดํธ ๊ฐ ๋ณ๊ฒฝ์ ์คํ
useEffect(() => {
if (petition !== undefined) {
getSum(petition);
processData(petition.statisticList);
}
}, [petition, updatePost]);
// ๋ฐ์ดํฐ ๊ฐ๊ณต
const processData = (data: IPetitionStatistic[]) => {
setChartData({ labels: [], data: [] }); // ์ฐจํธ ์ด๊ธฐํ
/* ๋จ๊ณผ๋ ํฌํ ๋ฐ์ดํฐ ๊ฐ๊ณต */
data.forEach((item) => {
setChartData((prev) => {
return {
labels: [...prev.labels, item.department],
data: [...prev.data, item.agreeCount],
};
});
});
};
// ๋์ ๋ฒํผ ํด๋ฆญ ๋ฐ์ดํฐ ์ ์ก
const handlePostAgree = async () => {
try {
await post(`${API_PATH.POST.PETITION.AGREE.ID(postId!)}`, null, {
authenticate: true,
});
setUpdatePost(true);
} catch (error) {
alert;
}
};
// ๋์ ๋ฒํผ ํด๋ฆญ ์ด๋ฒคํธ ํจ์
const handleAgreeButtonClick = () => {
if (petition?.agree) {
alert('์ด๋ฏธ ๋์ํ์
จ์ต๋๋ค');
} else {
handlePostAgree();
}
};
return (
<>
{petition !== undefined && (
<>
<PostBox>
<Text length={4}>์ด๋ค ๊ณผ์์ ๊ฐ์ฅ ๋์๋ฅผ ๋ง์ด ํ์๊น์?</Text>
<hr />
<DoughnutChart chartData={chartData} sum={sum} />
<PetitonChartList statisticList={petition.statisticList} sum={sum} />
</PostBox>
{/* ํ๋กํ
๋ฒํผ */}
<FloatingButton
event={() => {
handleAgreeButtonClick();
}}
>
{petition.agree ? (
<TbThumbUpFilled color='white' size={40} />
) : (
<TbThumbUp color='white' size={40} />
)}
</FloatingButton>
</>
)}
</>
);
}
์์ฑ !!