Files
frontend/packages/common/business_components/src/ui/chart/LineChart.tsx
T

167 lines
5.2 KiB
TypeScript

"use client"
import type { CSSProperties } from "react";
import { CartesianGrid, Legend, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts";
import {
ChartContainer,
type ChartConfig
} from "./lib/chart";
import type { ContentType } from "recharts/types/component/DefaultLegendContent";
type LineChartProps = {
chartConfig: ChartConfig | undefined;
chartData: ChartDataType | undefined;
chartTitle: string;
chartClassName?: string;
graphSize?: number;
yFieldText?: string;
showLegend?: boolean;
}
/** 차트 기본 스타일 */
const customStyles = {
legendContainer: {
display: "flex",
justifyContent: "center",
gap: "20px",
fontSize: "9px",
fontWeight : "bold",
marginTop: "10px",
} as CSSProperties,
};
type LegendEntry = {
value: string;
color: string;
type?: string;
payload?: unknown;
};
type LegendProps = {
payload?: LegendEntry[];
};
// 커스텀 툴팁
const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: Array<{ name: string; value: number; color: string }>; label?: string }) => {
if (active && payload && payload.length) {
return (
<div className="w-[180px] rounded bg-white p-2.5 shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
<p className="text-[12px] font-bold m-3">{label}</p>
{payload.map((item, index) => (
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
{/* 색상 박스 추가 (범례 색상과 동일) */}
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value}`}</span>
</div>
))}
</div>
);
}
return null;
};
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
const CustomLegend = (props: LegendProps) => {
const { payload } = props;
if (!payload) return null;
return (
<div style={customStyles.legendContainer}>
{payload.map((entry: LegendEntry, index: number) => (
<div key={`legend-${index}`} className="flex items-center gap-2">
<div className="w-[14px] h-[14px] ml-10" style={{ backgroundColor: entry.color }}></div>
<span className="text-[12px]">{entry.value}</span>
</div>
))}
</div>
);
};
type DataType = {label: string; val: string};
type ChartDataType = {
[key: string]: DataType[];
};
export default function Linechart({
chartConfig,
chartData,
chartTitle,
chartClassName = 'w-full h-full',
graphSize = 40,
yFieldText,
showLegend = true
}: LineChartProps) {
if (!chartData || Object.keys(chartData).length === 0) {
return <div>데이터가 없습니다.</div>;
}
// 모든 데이터셋이 비어있는지 확인
const hasNoData = Object.values(chartData).every(dataset => !dataset || dataset.length === 0);
if (hasNoData) {
return <div>데이터가 없습니다.</div>;
}
// 데이터 병합 및 변환
const mergedData = Object.entries(chartData).reduce<Array<{[key: string]: string | number}>>((acc, [key, dataset]) => {
dataset.forEach((entry, index) => {
if (!acc[index]) {
acc[index] = { label: entry.label };
}
acc[index][key] = parseFloat(entry.val);
});
return acc;
}, []);
// 데이터의 최대값 계산
const getMaxValue = (data: ChartDataType): number => {
return Math.max(...Object.values(data).flatMap(dataset =>
dataset.map(entry => {
const val = parseFloat(entry.val);
return isNaN(val) ? 0 : val;
})
));
};
const lineMaxValue = getMaxValue(chartData);
return (
<div className={chartClassName}>
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
<div className="text-left text-[13px] font-bold mt-[13px] mb-[10px]">
{chartTitle}
</div>
) : null}
<div className="h-[calc(100%-42px)] flex justify-center">
<ChartContainer config={chartConfig ?? {}} className="h-full w-full">
<LineChart
data={mergedData}
height={graphSize * 7.5}
width={graphSize * 12.5}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid vertical={false} />
<XAxis dataKey="label" fontSize="8px" fontWeight="bold" />
<YAxis yAxisId="left" className="text-[12px] font-bold" domain={[0, Math.ceil(lineMaxValue * 1.1)]} tickCount={6} orientation="left" label={{ value: yFieldText, className:"text-[12px] font-bold", angle: -90, position: 'insideLeft' }} />
<Tooltip content={<CustomTooltip />} />
{showLegend && <Legend content={CustomLegend as ContentType}/>}
{Object.keys(chartData).map((key) => {
const config = chartConfig?.[key];
return (
<Line
key={key}
yAxisId="left"
dataKey={key}
type="linear"
strokeWidth={2}
dot={false}
name={String(config?.label || "X축")}
stroke={config?.color}
/>
);
})}
</LineChart>
</ChartContainer>
</div>
</div>
);
}