247 lines
7.0 KiB
TypeScript
247 lines
7.0 KiB
TypeScript
"use client"
|
|
import type { CSSProperties } from "react";
|
|
import { CartesianGrid, Legend, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts";
|
|
import type { ContentType } from "recharts/types/component/DefaultLegendContent";
|
|
import {
|
|
ChartContainer,
|
|
type ChartConfig
|
|
} from "./lib/chart";
|
|
import { get } from "http";
|
|
|
|
type LineChartProps = {
|
|
chartConfig: ChartConfig | undefined;
|
|
chartData: ChartDataType | undefined;
|
|
chartTitle: string;
|
|
chartClassName?: string;
|
|
graphSize?: number;
|
|
yFieldText?: string;
|
|
showLegend?: boolean;
|
|
setXAxisYearTick?: boolean;
|
|
}
|
|
|
|
/** 차트 기본 스타일 */
|
|
/*
|
|
const customStyles = {
|
|
legendContainer: {
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
gap: "20px",
|
|
fontSize: "9px",
|
|
fontWeight : "bold",
|
|
marginTop: "10px",
|
|
} as CSSProperties,
|
|
};
|
|
*/
|
|
|
|
/** 차트 기본 스타일 */
|
|
const customStyles = {
|
|
legendContainer: {
|
|
display: "flex",
|
|
flexWrap: "wrap",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
gap: "12px",
|
|
fontSize: "13px",
|
|
fontWeight: "normal",
|
|
marginTop: "16px",
|
|
marginBottom: "8px",
|
|
minHeight: "32px",
|
|
} 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>
|
|
);
|
|
};*/
|
|
|
|
// 커스텀 범례 (도넛차트와 동일하게)
|
|
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"
|
|
style={{
|
|
minWidth: "90px",
|
|
margin: "2px 0",
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
width: "14px",
|
|
height: "14px",
|
|
backgroundColor: entry.color,
|
|
borderRadius: "3px",
|
|
marginRight: "8px",
|
|
minWidth: "14px",
|
|
minHeight: "14px",
|
|
}}
|
|
></div>
|
|
<span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>{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,
|
|
setXAxisYearTick = false,
|
|
}: 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 getTicks = (data : any []) => {
|
|
const monthSet = new Set<string>();
|
|
const rtnData : string[] = [];
|
|
for(const item of data){
|
|
const month = String(item.label).slice(0,7);
|
|
if(!monthSet.has(month)){
|
|
monthSet.add(month);
|
|
rtnData.push(item.label);
|
|
}
|
|
}
|
|
return rtnData;
|
|
}
|
|
|
|
let monthTicks : string[] = [];
|
|
if(setXAxisYearTick){
|
|
monthTicks = getTicks(mergedData);
|
|
}
|
|
|
|
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} />
|
|
|
|
{setXAxisYearTick ?
|
|
<XAxis
|
|
dataKey="label"
|
|
fontSize="8px"
|
|
fontWeight="bold"
|
|
ticks={monthTicks}
|
|
tickFormatter={(value) => value.slice(0,7)}
|
|
/> :
|
|
<XAxis
|
|
dataKey="label"
|
|
fontSize="8px"
|
|
fontWeight="bold"
|
|
/>
|
|
}
|
|
|
|
<YAxis
|
|
yAxisId="left"
|
|
className="text-[12px] font-bold"
|
|
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>
|
|
);
|
|
} |