import { WeightEntry } from "@/components/Weight/models/WeightEntry"; import { calculateEMA, EMADataPoint } from "@/components/Weight/widgets/WeightChart/ema"; import { dateToLocale } from "@/core/lib/date"; import { Paper, Stack, Typography, useTheme } from "@mui/material"; import { useTranslation } from "react-i18next"; import { CartesianGrid, Legend, Line, LineChart, ReferenceLine, Tooltip, useXAxisScale, useYAxisScale, XAxis, YAxis } from 'recharts'; const NR_OF_WEIGHTS_CHART_DOT = 30; export interface WeightChartProps { weights: WeightEntry[], height?: number, } export interface TooltipProps { active?: boolean, // eslint-disable-next-line @typescript-eslint/no-explicit-any payload?: any, label?: string, } const CustomTooltip = ({ active, payload, label }: TooltipProps) => { const [t] = useTranslation(); const theme = useTheme(); if (active && payload && payload.length) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const actualWeight = payload.find((p: any) => p.dataKey === 'weight'); // eslint-disable-next-line @typescript-eslint/no-explicit-any const trendWeight = payload.find((p: any) => p.dataKey === 'ema'); const variance = actualWeight && trendWeight ? actualWeight.value - trendWeight.value : 0; return (

{dateToLocale(new Date(label!))}

{actualWeight &&

{t('weight')}: {actualWeight.value.toFixed(1)}

} {trendWeight &&

{t('trend')}: {trendWeight.value.toFixed(1)}

} {actualWeight && trendWeight && (

0 ? theme.palette.error.main : theme.palette.success.main }}> {t('variance')}: {variance > 0 ? '+' : ''}{variance.toFixed(1)}

)}
); } return null; }; const VarianceLines = ({ emaData }: { emaData: EMADataPoint[] }) => { const xScale = useXAxisScale(); const yScale = useYAxisScale(); const theme = useTheme(); if (!xScale || !yScale || emaData.length > NR_OF_WEIGHTS_CHART_DOT) { return null; } return ( {emaData.map(point => { const x = xScale(point.date) as number; return ( point.ema ? theme.palette.error.main : theme.palette.success.main} strokeWidth={1} strokeDasharray="2,2" opacity={0.5} /> ); })} ); }; export const WeightChart = ({ weights, height = 300 }: WeightChartProps) => { const theme = useTheme(); const [t] = useTranslation(); const sortedWeights = [...weights].sort((a, b) => a.date.getTime() - b.date.getTime()); const weightData = sortedWeights.map(weight => ({ date: weight.date.getTime(), weight: weight.weight, })); const emaData = calculateEMA(weightData, 10); const meanWeight = weightData.length > 0 ? weightData.reduce((sum, w) => sum + w.weight, 0) / weightData.length : 0; const currentTrend = emaData.length > 0 ? emaData[emaData.length - 1].ema : 0; const allWeights = emaData.flatMap(d => [d.weight, d.ema]); const minWeight = allWeights.length > 0 ? Math.min(...allWeights) : 0; const maxWeight = allWeights.length > 0 ? Math.max(...allWeights) : 0; const padding = (maxWeight - minWeight) * 0.1; const yAxisDomain: [number, number] = [minWeight - padding, maxWeight + padding]; return (
{weightData.length > 0 && ( {t('mean')}: {meanWeight.toFixed(1)} {t('currentTrend')}: {currentTrend.toFixed(1)} )} dateToLocale(new Date(timeStr))} /> Math.round(value).toString()} /> NR_OF_WEIGHTS_CHART_DOT ? false : { fill: theme.palette.secondary.main, stroke: theme.palette.secondary.dark, strokeWidth: 1, r: 4 }} activeDot={{ fill: theme.palette.secondary.main, stroke: theme.palette.secondary.dark, strokeWidth: 2, r: 6 }} name={t('weight')} legendType="circle" /> } />
); };