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"
/>
} />
);
};