Files
CPLv4.0/components/main/kabelueberwachung/kue705FO/Charts/LoopMeasurementChart/LoopMeasurementChart.tsx
Ismail Ali 14ba25bc57 fix: Legendenbeschriftungen im LoopMeasurementChart benutzerfreundlich umbenannt
- Interne Datenkeys wie 'messwert' oder 'messwertMinimum' werden jetzt als 'Messwert', 'Minimum' usw. angezeigt
- Neue Mapping-Tabelle für sprechende Labels in der Legende eingeführt
2025-03-30 22:16:02 +02:00

245 lines
7.2 KiB
TypeScript

"use client"; // components/main/kabelueberwachung/kue705FO/Charts/LoopMeasurementChart/LoopMeasurementChart.tsx
import React, { useCallback, useEffect, useMemo } from "react";
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "../../../../../../redux/store";
import {
ComposedChart,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
Line,
Brush,
} from "recharts";
import { setBrushRange } from "../../../../../../redux/slices/brushSlice";
import CustomTooltip from "./CustomTooltip";
const LoopMeasurementChart = () => {
const dispatch = useDispatch();
const unit = useSelector(
(state: RootState) => state.kabelueberwachungChart.unit
);
const brushRange = useSelector((state: RootState) => state.brush);
const {
loopMeasurementCurveChartData,
selectedMode,
vonDatum,
bisDatum,
isFullScreen,
} = useSelector((state: RootState) => state.kabelueberwachungChart);
const formatierteDaten = useMemo(
() =>
loopMeasurementCurveChartData
.map((eintrag) => ({
zeit: new Date(eintrag.t).getTime(),
messwertMinimum: eintrag.i,
messwertMaximum: eintrag.a,
messwert: eintrag.m ?? null,
messwertDurchschnitt: ["DIA0", "DIA1", "DIA2"].includes(selectedMode)
? eintrag.g ?? null
: null,
}))
.reverse(),
[loopMeasurementCurveChartData, selectedMode]
);
useEffect(() => {
if (brushRange.endIndex === 0 && formatierteDaten.length) {
dispatch(
setBrushRange({
startIndex: 0,
endIndex: formatierteDaten.length - 1,
})
);
}
}, [formatierteDaten, brushRange.endIndex, dispatch]);
const handleBrushChange = useCallback(
({ startIndex, endIndex }: { startIndex?: number; endIndex?: number }) => {
if (startIndex === undefined || endIndex === undefined) return;
dispatch(
setBrushRange({
startIndex,
endIndex,
startDate: new Date(
formatierteDaten[startIndex]?.zeit || formatierteDaten[0].zeit
)
.toISOString()
.split("T")[0],
endDate: new Date(
formatierteDaten[endIndex]?.zeit ||
formatierteDaten[formatierteDaten.length - 1].zeit
)
.toISOString()
.split("T")[0],
})
);
},
[dispatch, formatierteDaten]
);
useEffect(() => {
if (formatierteDaten.length) {
const startIndex = formatierteDaten.findIndex(
(d) => new Date(d.zeit).toISOString().split("T")[0] === vonDatum
);
const endIndex = formatierteDaten.findIndex(
(d) => new Date(d.zeit).toISOString().split("T")[0] === bisDatum
);
if (startIndex !== -1 && endIndex !== -1) {
dispatch(
setBrushRange({
startIndex,
endIndex,
startDate: vonDatum,
endDate: bisDatum,
})
);
}
}
}, [vonDatum, bisDatum, formatierteDaten, dispatch]);
useEffect(() => {
if (formatierteDaten.length > 0) {
dispatch(
setBrushRange({
startIndex: 0,
endIndex: formatierteDaten.length - 1,
startDate: new Date(formatierteDaten[0].zeit)
.toISOString()
.split("T")[0],
endDate: new Date(formatierteDaten[formatierteDaten.length - 1].zeit)
.toISOString()
.split("T")[0],
})
);
}
}, [selectedMode, formatierteDaten, dispatch]);
const legendLabelMap: Record<string, string> = {
messwertMinimum: "Minimum",
messwert: "Messwert",
messwertMaximum: "Maximum",
messwertDurchschnitt: "Durchschnitt",
};
return (
<div style={{ width: "100%", height: isFullScreen ? "90%" : "400px" }}>
<ResponsiveContainer width="100%" height="100%">
<ComposedChart data={formatierteDaten} margin={{ right: 90, left: 20 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="zeit"
domain={["dataMin", "dataMax"]}
allowDataOverflow={true}
interval={Math.floor(formatierteDaten.length / 15)}
tickFormatter={(zeit) => {
const date = new Date(zeit);
return `${date.getDate()}.${date.getMonth() + 1}`;
}}
tick={(props) => {
const { x, y, payload } = props;
const date = new Date(payload.value);
return (
<text
x={x}
y={y}
dy={5}
textAnchor="end"
transform={`rotate(-25, ${x}, ${y})`}
>
{`${date.getDate()}.${date.getMonth() + 1}`}
</text>
);
}}
/>
<YAxis
label={{ value: unit, angle: -90, position: "insideLeft" }}
domain={["auto", "auto"]}
tickFormatter={(wert) => `${wert.toFixed(0)} `}
/>
<Tooltip content={<CustomTooltip unit={unit} />} />
<Legend
verticalAlign="top"
align="center"
content={({ payload }) => {
if (!payload) return null;
const orderedPayload = [...payload].sort((a, b) => {
const order = [
"messwertMinimum",
"messwert",
"messwertDurchschnitt",
"messwertMaximum",
];
return order.indexOf(a.value) - order.indexOf(b.value);
});
return (
<div
style={{
width: "100%",
display: "flex",
justifyContent: "center",
}}
>
{orderedPayload.map((entry, index) => (
<span
key={index}
style={{ margin: "0 10px", color: entry.color }}
>
{legendLabelMap[entry.value] ?? entry.value}
</span>
))}
</div>
);
}}
/>
<Line
type="monotone"
dataKey="messwertMinimum"
stroke="lightgrey"
dot={false}
/>
<Line
type="monotone"
dataKey="messwertMaximum"
stroke="lightgrey"
dot={false}
/>
{["DIA1", "DIA2"].includes(selectedMode) && (
<Line
type="monotone"
dataKey="messwertDurchschnitt"
stroke="#00AEEF"
dot
/>
)}
{selectedMode === "DIA0" && (
<Line type="monotone" dataKey="messwert" stroke="#00AEEF" dot />
)}
<Brush
dataKey="zeit"
height={30}
stroke="#8884d8"
onChange={handleBrushChange}
startIndex={brushRange.startIndex}
endIndex={brushRange.endIndex || formatierteDaten.length - 1}
tickFormatter={(zeit) => new Date(zeit).toLocaleDateString()}
/>
</ComposedChart>
</ResponsiveContainer>
</div>
);
};
export default LoopMeasurementChart;