110 lines
2.9 KiB
TypeScript
110 lines
2.9 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import React from 'react';
|
||
|
|
import {
|
||
|
|
PieChart,
|
||
|
|
Pie,
|
||
|
|
Cell,
|
||
|
|
Tooltip,
|
||
|
|
Legend,
|
||
|
|
ResponsiveContainer
|
||
|
|
} from 'recharts';
|
||
|
|
import { ChartConfig } from '../types';
|
||
|
|
|
||
|
|
interface DonutChartComponentProps {
|
||
|
|
data: any[];
|
||
|
|
config: ChartConfig;
|
||
|
|
width?: number;
|
||
|
|
height?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 도넛 차트 컴포넌트
|
||
|
|
* - Recharts PieChart (innerRadius 사용) 사용
|
||
|
|
* - 비율 표시에 적합 (중앙 공간 활용 가능)
|
||
|
|
*/
|
||
|
|
export function DonutChartComponent({ data, config, width = 250, height = 200 }: DonutChartComponentProps) {
|
||
|
|
const {
|
||
|
|
xAxis = 'x',
|
||
|
|
yAxis = 'y',
|
||
|
|
colors = ['#3B82F6', '#EF4444', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899'],
|
||
|
|
title,
|
||
|
|
showLegend = true
|
||
|
|
} = config;
|
||
|
|
|
||
|
|
// 파이 차트용 데이터 변환
|
||
|
|
const pieData = data.map(item => ({
|
||
|
|
name: String(item[xAxis] || ''),
|
||
|
|
value: typeof item[yAxis as string] === 'number' ? item[yAxis as string] : 0
|
||
|
|
}));
|
||
|
|
|
||
|
|
// 총합 계산
|
||
|
|
const total = pieData.reduce((sum, item) => sum + item.value, 0);
|
||
|
|
|
||
|
|
// 커스텀 라벨 (퍼센트 표시)
|
||
|
|
const renderLabel = (entry: any) => {
|
||
|
|
const percent = ((entry.value / total) * 100).toFixed(1);
|
||
|
|
return `${percent}%`;
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="w-full h-full p-2 flex flex-col">
|
||
|
|
{title && (
|
||
|
|
<div className="text-center text-sm font-semibold text-gray-700 mb-2">
|
||
|
|
{title}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<ResponsiveContainer width="100%" height="100%">
|
||
|
|
<PieChart>
|
||
|
|
<Pie
|
||
|
|
data={pieData}
|
||
|
|
cx="50%"
|
||
|
|
cy="50%"
|
||
|
|
labelLine={false}
|
||
|
|
label={renderLabel}
|
||
|
|
outerRadius={80}
|
||
|
|
innerRadius={50}
|
||
|
|
fill="#8884d8"
|
||
|
|
dataKey="value"
|
||
|
|
>
|
||
|
|
{pieData.map((entry, index) => (
|
||
|
|
<Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
|
||
|
|
))}
|
||
|
|
</Pie>
|
||
|
|
<Tooltip
|
||
|
|
contentStyle={{
|
||
|
|
backgroundColor: 'white',
|
||
|
|
border: '1px solid #ccc',
|
||
|
|
borderRadius: '4px',
|
||
|
|
fontSize: '12px'
|
||
|
|
}}
|
||
|
|
formatter={(value: any) => [
|
||
|
|
typeof value === 'number' ? value.toLocaleString() : value,
|
||
|
|
'값'
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
{showLegend && (
|
||
|
|
<Legend
|
||
|
|
wrapperStyle={{ fontSize: '12px' }}
|
||
|
|
layout="vertical"
|
||
|
|
align="right"
|
||
|
|
verticalAlign="middle"
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</PieChart>
|
||
|
|
</ResponsiveContainer>
|
||
|
|
|
||
|
|
{/* 중앙 총합 표시 */}
|
||
|
|
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 pointer-events-none">
|
||
|
|
<div className="text-center">
|
||
|
|
<div className="text-xs text-gray-500">Total</div>
|
||
|
|
<div className="text-sm font-bold text-gray-800">
|
||
|
|
{total.toLocaleString()}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|