lewtun's picture
lewtun HF Staff
Align pain chart points vertically by switching to categorical LineChart
81c5264
import {
BarChart, Bar, LineChart, Line, Legend,
XAxis, YAxis, Tooltip, CartesianGrid, ResponsiveContainer, Label,
} from 'recharts';
import './Charts.css';
const PAIN_DURING_COLOR = '#ef4444';
const PAIN_AFTER_COLOR = '#3b82f6';
const PAIN_CHARTS = [
{
location: 'left_knee',
title: 'Left Knee Pain',
duringKey: 'left_knee_during',
afterKey: 'left_knee_after',
},
{
location: 'right_knee',
title: 'Right Knee Pain',
duringKey: 'right_knee_during',
afterKey: 'right_knee_after',
},
];
function Charts({ data, painData }) {
if (!data || data.length === 0) {
return (
<div className="charts card">
<p className="empty-message">Log some runs to see your progress over time.</p>
</div>
);
}
// Shorten week labels for x-axis: "2026-W10" → "W10"
const chartData = data.map((d) => ({
...d,
label: d.week.replace(/^\d{4}-/, ''),
}));
// Determine which locations have data
const activePainCharts = painData?.locations
? PAIN_CHARTS.filter((chart) => {
const loc = painData.locations[chart.location];
return loc && loc.points.length > 0;
})
: [];
return (
<div className="charts">
<div className="chart-container card">
<h2>Weekly Distance</h2>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="label" />
<YAxis>
<Label value="Km" angle={-90} position="insideLeft" style={{ textAnchor: 'middle', fill: '#6b7280' }} />
</YAxis>
<Tooltip />
<Bar dataKey="distance" fill="var(--color-primary)" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
<div className="chart-container card">
<h2>Training Load Over Time</h2>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="label" />
<YAxis>
<Label value="Load" angle={-90} position="insideLeft" style={{ textAnchor: 'middle', fill: '#6b7280' }} />
</YAxis>
<Tooltip />
<Line
type="monotone"
dataKey="training_load"
stroke="var(--color-secondary)"
strokeWidth={2}
dot={{ r: 4 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
{activePainCharts.map((chart) => {
const loc = painData.locations[chart.location];
return (
<div key={chart.location} className="chart-container card">
<h2>{chart.title}</h2>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={loc.points}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="label" padding={{ left: 20, right: 20 }} />
<YAxis domain={[0, 10]}>
<Label value="Pain" angle={-90} position="insideLeft" style={{ textAnchor: 'middle', fill: '#6b7280' }} />
</YAxis>
<Tooltip
content={({ payload }) => {
if (!payload || payload.length === 0) return null;
const pt = payload[0].payload;
const items = payload.filter((p) => p.value != null);
return (
<div className="pain-tooltip">
<p>{pt.date}</p>
{items.map((p) => (
<p key={p.name} style={{ color: p.color }}>{p.name}: {p.value}</p>
))}
</div>
);
}}
/>
<Legend wrapperStyle={{ paddingLeft: '65px' }} />
<Line
name="During"
dataKey="during"
stroke={PAIN_DURING_COLOR}
strokeWidth={1.5}
dot={{ fill: PAIN_DURING_COLOR, r: 4 }}
connectNulls
/>
<Line
name="After"
dataKey="after"
stroke={PAIN_AFTER_COLOR}
strokeWidth={1.5}
strokeDasharray="5 3"
dot={{ fill: PAIN_AFTER_COLOR, r: 4 }}
connectNulls
/>
</LineChart>
</ResponsiveContainer>
</div>
);
})}
</div>
);
}
export default Charts;