-메인그래프 강우량-bod, 유입방출TP 완료, -공통 차트 커스텀 진행중

This commit is contained in:
YangJinSoo94
2025-05-16 17:38:23 +09:00
parent 7678c722c4
commit c2a2ca8baf
4 changed files with 182 additions and 123 deletions
@@ -42,7 +42,7 @@ const CustomTooltip = ({
}>;
label?: string;
}) => {
if (active && payload && payload.length) {ㅇ
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>
@@ -1,10 +1,6 @@
"use client"
import type { CSSProperties } from "react";
import { Bar, CartesianGrid, ComposedChart, Legend, Line, Tooltip, XAxis, YAxis } from "recharts";
import {
Card,
CardContent
} from "./lib/card";
import {
ChartContainer,
type ChartConfig
@@ -23,20 +19,29 @@ const customStyles = {
} as CSSProperties,
};
type LineBarChartProps = {
chartConfig: ChartConfig | undefined;
chartData: any[] | undefined;
chartTitle: string;
chartClassName?: string;
graphSize?: number;
yFieldText?: string;
showLegend?: boolean;
}
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;
}) => {
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)]">
@@ -55,11 +60,13 @@ const CustomTooltip = ({
};
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
const CustomLegend = (props: any) => {
const CustomLegend = (props: LegendProps) => {
const { payload } = props;
if (!payload) return null;
return (
<div style={customStyles.legendContainer}>
{payload.map((entry: any, index: number) => (
{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>
@@ -69,33 +76,66 @@ const CustomLegend = (props: any) => {
);
};
export default function LineBarchart(props: { chartConfig: ChartConfig; chartData: any[] | undefined; chartTitle:string }) {
export default function LineBarchart({
chartConfig,
chartData,
chartTitle,
chartClassName = 'w-full h-full',
graphSize = 40,
yFieldText,
showLegend = true
}: LineBarChartProps) {
if (!chartData || chartData.length === 0) {
return <div>데이터가 없습니다.</div>;
}
const barConfig = chartConfig?.barVal;
const lineConfig = chartConfig?.lineVal;
return (
<Card className="h-full">
<CardContent className="h-full">
{props.chartTitle && props.chartTitle !== "" && props.chartTitle !== "undefined" ? (
<div className={chartClassName}>
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
<div style={{ textAlign: "left", fontSize: "13px", fontWeight: "bold", marginTop:"13px", marginBottom: "10px" }}>
{props.chartTitle}
</div>): null}
<div className="h-[calc(100%-42px)] flex justify-center">
<ChartContainer config={props.chartConfig} className="h-full w-full" >
<ComposedChart data={props.chartData} className="h-full ">
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="label" fontSize="8px" fontWeight="bold"/>
{/* 왼쪽 Y축: lineVal (정상) */}
<YAxis yAxisId="left" orientation="left" />
{/* 오른쪽 Y축: barVal (위에서 아래로 반전) */}
<YAxis yAxisId="right" orientation="right" reversed />
<Tooltip content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />} />
<Legend content={CustomLegend}/>
{/* 바 차트 (오른쪽 Y축 사용, 위에서 아래로) */}
<Bar yAxisId="right" dataKey="barVal" fill="#8884d8" name="강수량(mm/일)"/>
{/* 라인 차트 (왼쪽 Y축 사용) */}
<Line yAxisId="left" type="monotone" dataKey="lineVal" stroke="orange" strokeWidth={2} dot={{r: 0}} name="BOD(mg/L)"/>
</ComposedChart>
</ChartContainer>
{chartTitle}
</div>
</CardContent>
</Card>
)
) : null}
<div className="h-[calc(100%-42px)] flex justify-center">
<ChartContainer config={chartConfig ?? {}} className="h-full w-full">
<ComposedChart
data={chartData}
height={graphSize * 7.5}
width={graphSize * 12.5}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="label" fontSize="8px" fontWeight="bold"/>
{/* 왼쪽 Y축: lineVal */}
<YAxis yAxisId="left" fontSize="8px" fontWeight="bold" orientation="left" label={{ value: yFieldText, angle: -90, position: 'insideLeft' }} />
{/* 오른쪽 Y축: barVal */}
<YAxis yAxisId="right" fontSize="8px" fontWeight="bold" orientation="right" reversed />
<Tooltip content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />} />
{showLegend && <Legend content={CustomLegend}/>}
{/* 바 차트 (오른쪽 Y축 사용) */}
<Bar
yAxisId="right"
dataKey="barVal"
fill={barConfig?.color || "black"}
name={String(barConfig?.label || "Y축")}
maxBarSize={80}
/>
{/* 라인 차트 (왼쪽 Y축 사용) */}
<Line
yAxisId="left"
type="monotone"
dataKey="lineVal"
stroke={lineConfig?.color || "black"}
strokeWidth={2}
dot={{r: 0}}
name={String(lineConfig?.label || "X축")}
/>
</ComposedChart>
</ChartContainer>
</div>
</div>
);
}
@@ -1,35 +1,24 @@
"use client"
import type { CSSProperties } from "react";
import { CartesianGrid, Legend, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts";
import {
Card,
CardContent
} from "./lib/card";
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 styles = {
tooltipDiv: {
borderRadius: "5px",
backgroundColor: "white",
padding: "10px",
boxShadow: "0px 2px 10px rgba(0,0,0,0.2)",
width: "180px", // 적절한 크기로 설정
} as React.CSSProperties,
tooltipLabel: {
fontWeight: "bold",
fontSize: "12px",
marginBottom: "5px",
borderBottom: "1px solid #ddd",
paddingBottom: "3px",
} as React.CSSProperties,
tooltipText: {
fontSize: "11px",
fontWeight:"bold",
} as React.CSSProperties,
const customStyles = {
legendContainer: {
display: "flex",
justifyContent: "center",
@@ -39,29 +28,29 @@ const styles = {
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;
}) => {
const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: Array<{ name: string; value: number; color: string }>; label?: string }) => {
if (active && payload && payload.length) {
return (
<div style={styles.tooltipDiv}>
<p style={styles.tooltipLabel}>{label}</p>
<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 style={styles.tooltipText}>{`${item.name}: ${item.value}`}</span>
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value}`}</span>
</div>
))}
</div>
@@ -71,60 +60,90 @@ const CustomTooltip = ({
};
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
const CustomLegend = (props: any) => {
const CustomLegend = (props: LegendProps) => {
const { payload } = props;
if (!payload) return null;
return (
<div style={styles.legendContainer}>
{payload.map((entry, index) => (
<div key={`legend-${index}`} style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<div style={{ width: "12px", height: "12px", backgroundColor: entry.color, borderRadius: "50%" }}></div>
<span>{entry.value}</span>
<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:number};
type DataType = {label: string; val: string};
type ChartDataType = {
chartData1: DataType[];
chartData2: DataType[];
[key: string]: DataType[];
};
export default function Linechart(props: { chartConfig: ChartConfig; chartData: ChartDataType; chartTitle:string}) {
const mergedData = Object.values(props.chartData).reduce((acc, dataset) => {
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 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 }; // label 추가
acc[index] = { label: entry.label };
}
const key = Object.keys(props.chartData).find(k => props.chartData[k] === dataset);
if (key) acc[index][key] = entry.val;
acc[index][key] = parseFloat(entry.val);
});
return acc;
}, [] as any[]);
}, []);
return (
<Card className="h-full">
<CardContent className="h-full">
{props.chartTitle && props.chartTitle !== "" && props.chartTitle !== "undefined" ? (
<div className={chartClassName}>
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
<div style={{ textAlign: "left", fontSize: "13px", fontWeight: "bold", marginTop:"13px", marginBottom: "10px" }}>
{props.chartTitle}
</div>): null}
<div className="h-[calc(100%-42px)] flex justify-center">
<ChartContainer config={props.chartConfig} className="h-full w-full">
<LineChart data={mergedData} className="h-full">
<CartesianGrid vertical={false} />
<XAxis dataKey="label" fontSize="8px" fontWeight="bold"/>
<YAxis yAxisId="left" orientation="left" />
<Tooltip content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />} />
<Legend content={CustomLegend}/>
{Object.keys(props.chartData).map((key, index) => (
<Line key={key} yAxisId="left" dataKey={key} type="linear" strokeWidth={2} dot={false} name={key} />
))}
</LineChart>
</ChartContainer>
{chartTitle}
</div>
</CardContent>
</Card>
)
) : 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" fontSize="8px" fontWeight="bold" orientation="left" label={{ value: yFieldText, 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>
);
}
@@ -37,7 +37,7 @@ function MainChart(props: MainChartProps) {
);
} else if( props.chartForm == CHART_FORM.LINE ){
return (
<LineChart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle} >
<LineChart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle} chartClassName={props.chartClassName} graphSize={props.graphSize} yFieldText={props.yFieldText} showLegend={props.showLegend}>
</LineChart>
);
} else if( props.chartForm == CHART_FORM.DN ){
@@ -47,7 +47,7 @@ function MainChart(props: MainChartProps) {
);
} else if (props.chartForm == CHART_FORM.LINEBAR){
return (
<LineBarchart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle}>
<LineBarchart chartConfig={props.chartConfig} chartData={props.chartData} chartTitle={props.chartTitle} chartClassName={props.chartClassName} graphSize={props.graphSize} yFieldText={props.yFieldText} showLegend={props.showLegend}>
</LineBarchart>
);
}