Spaces:
Running
Running
| 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; | |