refactor: update nutrition chart colors and enhance layout
- Changed color codes for macro nutrients in the nutrition interpretation and metrics files to improve visual consistency. - Added new CSS styles for uniform chart height and layout adjustments in the frontend components, enhancing the overall user experience. - Refactored the NutritionCharts component to utilize the new macro chart theme for better maintainability and readability.
This commit is contained in:
parent
b96b1931db
commit
31fbf33031
|
|
@ -174,7 +174,7 @@ def build_macro_donut_from_averages(navg: Dict[str, Any]) -> Optional[List[Dict[
|
|||
if tot <= 0:
|
||||
return None
|
||||
return [
|
||||
{"name": "Protein", "value": round(pkcal / tot * 100), "color": "#059669", "grams": round(p, 1)},
|
||||
{"name": "KH", "value": round(ckcal / tot * 100), "color": "#EA580C", "grams": round(c, 1)},
|
||||
{"name": "Fett", "value": round(fkcal / tot * 100), "color": "#2563EB", "grams": round(f, 1)},
|
||||
{"name": "Protein", "value": round(pkcal / tot * 100), "color": "#4a8f72", "grams": round(p, 1)},
|
||||
{"name": "KH", "value": round(ckcal / tot * 100), "color": "#c17d45", "grams": round(c, 1)},
|
||||
{"name": "Fett", "value": round(fkcal / tot * 100), "color": "#6e8eb8", "grams": round(f, 1)},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -668,19 +668,19 @@ def get_weekly_macro_distribution_chart_data(profile_id: str, weeks: int) -> Dic
|
|||
{
|
||||
"label": "Protein (%)",
|
||||
"data": protein_pcts,
|
||||
"backgroundColor": "#1D9E75",
|
||||
"backgroundColor": "#4a8f72",
|
||||
"stack": "macro",
|
||||
},
|
||||
{
|
||||
"label": "Kohlenhydrate (%)",
|
||||
"data": carbs_pcts,
|
||||
"backgroundColor": "#F59E0B",
|
||||
"backgroundColor": "#c17d45",
|
||||
"stack": "macro",
|
||||
},
|
||||
{
|
||||
"label": "Fett (%)",
|
||||
"data": fat_pcts,
|
||||
"backgroundColor": "#EF4444",
|
||||
"backgroundColor": "#6e8eb8",
|
||||
"stack": "macro",
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -368,6 +368,35 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Einheitliche Chart-Höhe (Donut-Bereich ≈ E3-Balken) */
|
||||
.nutrition-macro-pair__chart-wrap {
|
||||
width: 100%;
|
||||
min-height: 260px;
|
||||
}
|
||||
|
||||
.nutrition-macro-pair__donut-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nutrition-macro-pair__donut-chart {
|
||||
width: 100%;
|
||||
min-height: 260px;
|
||||
}
|
||||
|
||||
.nutrition-macro-pair__legend {
|
||||
width: 100%;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.nutrition-macro-pair .card.nutrition-macro-pair__donut,
|
||||
.nutrition-macro-pair .card.nutrition-macro-pair__weekly {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.history-page__title {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
ComposedChart, ReferenceArea,
|
||||
} from 'recharts'
|
||||
import { api } from '../utils/api'
|
||||
import { MACRO_CHART, NUTRITION_MACRO_CHART_BLOCK_PX } from '../utils/macroChartTheme'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const fmtDate = d => dayjs(d).format('DD.MM')
|
||||
|
|
@ -172,26 +173,28 @@ export function WeeklyMacroDistributionPanel({ macroWeeklyData, loading, error }
|
|||
Anteil der Kalorien aus jedem Makronährstoff pro Kalenderwoche (100 % gestapelt). Gut vergleichbar mit der
|
||||
Donut-Übersicht links.
|
||||
</div>
|
||||
<ResponsiveContainer width="100%" height={280}>
|
||||
<BarChart data={chartData} margin={{ top: 8, right: 4, bottom: 4, left: -18 }} barCategoryGap="18%">
|
||||
<CartesianGrid stroke="var(--border)" strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="week"
|
||||
tick={{ fontSize: 9, fill: 'var(--text3)' }}
|
||||
tickLine={false}
|
||||
interval={Math.max(0, Math.floor(chartData.length / 8) - 1)}
|
||||
/>
|
||||
<YAxis tick={{ fontSize: 9, fill: 'var(--text3)' }} tickLine={false} domain={[0, 100]} ticks={[0, 25, 50, 75, 100]} />
|
||||
<Tooltip
|
||||
contentStyle={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 11 }}
|
||||
formatter={(v, name) => [`${v}%`, name]}
|
||||
/>
|
||||
<Legend wrapperStyle={{ fontSize: 10, paddingTop: 8 }} />
|
||||
<Bar dataKey="protein" stackId="a" fill="#059669" name="Protein %" radius={[0, 0, 0, 0]} />
|
||||
<Bar dataKey="carbs" stackId="a" fill="#EA580C" name="KH %" radius={[0, 0, 0, 0]} />
|
||||
<Bar dataKey="fat" stackId="a" fill="#B91C1C" name="Fett %" radius={[6, 6, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
<div className="nutrition-macro-pair__chart-wrap">
|
||||
<ResponsiveContainer width="100%" height={NUTRITION_MACRO_CHART_BLOCK_PX}>
|
||||
<BarChart data={chartData} margin={{ top: 8, right: 4, bottom: 4, left: -18 }} barCategoryGap="18%">
|
||||
<CartesianGrid stroke="var(--border)" strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="week"
|
||||
tick={{ fontSize: 9, fill: 'var(--text3)' }}
|
||||
tickLine={false}
|
||||
interval={Math.max(0, Math.floor(chartData.length / 8) - 1)}
|
||||
/>
|
||||
<YAxis tick={{ fontSize: 9, fill: 'var(--text3)' }} tickLine={false} domain={[0, 100]} ticks={[0, 25, 50, 75, 100]} />
|
||||
<Tooltip
|
||||
contentStyle={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 11 }}
|
||||
formatter={(v, name) => [`${v}%`, name]}
|
||||
/>
|
||||
<Legend wrapperStyle={{ fontSize: 10, paddingTop: 8 }} />
|
||||
<Bar dataKey="protein" stackId="a" fill={MACRO_CHART.protein} name="Protein %" radius={[0, 0, 0, 0]} />
|
||||
<Bar dataKey="fat" stackId="a" fill={MACRO_CHART.fat} name="Fett %" radius={[0, 0, 0, 0]} />
|
||||
<Bar dataKey="carbs" stackId="a" fill={MACRO_CHART.carbs} name="KH %" radius={[6, 6, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<div style={{ marginTop: 10, fontSize: 10, color: 'var(--text3)', textAlign: 'center', lineHeight: 1.5 }}>
|
||||
Ø Verteilung: P {meta.avg_protein_pct}% · KH {meta.avg_carbs_pct}% · F {meta.avg_fat_pct}% · Variabilität (CV): P{' '}
|
||||
{meta.protein_cv}% · KH {meta.carbs_cv}% · F {meta.fat_cv}%
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { api } from '../utils/api'
|
|||
import { photoMonthKey, photoSortKey, formatPhotoCaption } from '../utils/photoDisplay'
|
||||
import { getBfCategory } from '../utils/calc'
|
||||
import { getStatusColor, getStatusBg } from '../utils/interpret'
|
||||
import { MACRO_CHART, macroFillByName, NUTRITION_MACRO_CHART_BLOCK_PX } from '../utils/macroChartTheme'
|
||||
import Markdown from '../utils/Markdown'
|
||||
import TrainingTypeDistribution from '../components/TrainingTypeDistribution'
|
||||
import NutritionCharts, { WeeklyMacroDistributionPanel } from '../components/NutritionCharts'
|
||||
|
|
@ -909,18 +910,18 @@ function NutritionSection({ profile, insights, onRequest, loadingSlug, filterAct
|
|||
<XAxis dataKey="date" tick={{ fontSize: 9, fill: 'var(--text3)' }} tickLine={false} interval={Math.max(0, Math.floor(cdMacro.length / 6) - 1)} />
|
||||
<YAxis tick={{ fontSize: 9, fill: 'var(--text3)' }} tickLine={false} />
|
||||
{ptLow > 0 && (
|
||||
<ReferenceLine y={ptLow} stroke="#059669" strokeDasharray="5 4" strokeWidth={2} label={{ value: `${ptLow}g P`, fontSize: 9, fill: '#059669', position: 'insideTopRight' }} />
|
||||
<ReferenceLine y={ptLow} stroke={MACRO_CHART.protein} strokeDasharray="5 4" strokeWidth={2} label={{ value: `${ptLow}g P`, fontSize: 9, fill: MACRO_CHART.protein, position: 'insideTopRight' }} />
|
||||
)}
|
||||
<Tooltip contentStyle={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 11 }} formatter={(v, name) => [`${v}g`, name]} />
|
||||
<Bar dataKey="Fett" stackId="a" fill="#93C5FD" name="Fett" />
|
||||
<Bar dataKey="KH" stackId="a" fill="#FDBA74" name="KH" />
|
||||
<Bar dataKey="Protein" stackId="a" fill="#059669" name="Protein" radius={[4, 4, 0, 0]} />
|
||||
<Bar dataKey="Protein" stackId="a" fill={MACRO_CHART.protein} name="Protein" />
|
||||
<Bar dataKey="Fett" stackId="a" fill={MACRO_CHART.fat} name="Fett" />
|
||||
<Bar dataKey="KH" stackId="a" fill={MACRO_CHART.carbs} name="KH" radius={[5, 5, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
<div style={{ display: 'flex', gap: 12, justifyContent: 'center', marginTop: 8, fontSize: 10, color: 'var(--text3)', flexWrap: 'wrap' }}>
|
||||
<span><span style={{ display: 'inline-block', width: 10, height: 10, background: '#059669', borderRadius: 2, verticalAlign: 'middle', marginRight: 4 }} />Protein (oben)</span>
|
||||
<span><span style={{ display: 'inline-block', width: 10, height: 10, background: '#FDBA74', borderRadius: 2, verticalAlign: 'middle', marginRight: 4 }} />KH</span>
|
||||
<span><span style={{ display: 'inline-block', width: 10, height: 10, background: '#93C5FD', borderRadius: 2, verticalAlign: 'middle', marginRight: 4 }} />Fett</span>
|
||||
<span><span style={{ display: 'inline-block', width: 10, height: 10, background: MACRO_CHART.protein, borderRadius: 2, verticalAlign: 'middle', marginRight: 4 }} />Protein (unten)</span>
|
||||
<span><span style={{ display: 'inline-block', width: 10, height: 10, background: MACRO_CHART.fat, borderRadius: 2, verticalAlign: 'middle', marginRight: 4 }} />Fett (Mitte)</span>
|
||||
<span><span style={{ display: 'inline-block', width: 10, height: 10, background: MACRO_CHART.carbs, borderRadius: 2, verticalAlign: 'middle', marginRight: 4 }} />KH (oben)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -929,35 +930,52 @@ function NutritionSection({ profile, insights, onRequest, loadingSlug, filterAct
|
|||
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text3)', marginBottom: 8 }}>
|
||||
Ø Makro-Quote ({n} Tage)
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
|
||||
{pieData.length > 0 ? (
|
||||
<>
|
||||
<PieChart width={120} height={120}>
|
||||
<Pie data={pieData} cx={58} cy={58} innerRadius={36} outerRadius={54} dataKey="value" startAngle={90} endAngle={-270}>
|
||||
{pieData.map((e, i) => <Cell key={i} fill={e.color} />)}
|
||||
</Pie>
|
||||
<Tooltip formatter={(v, name) => [`${v}%`, name]} />
|
||||
</PieChart>
|
||||
<div style={{ flex: 1, minWidth: 160 }}>
|
||||
{pieData.map(p => (
|
||||
<div key={p.name} style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
|
||||
<div style={{ width: 10, height: 10, borderRadius: 2, background: p.color, flexShrink: 0 }} />
|
||||
{pieData.length > 0 ? (
|
||||
<div className="nutrition-macro-pair__donut-inner">
|
||||
<div className="nutrition-macro-pair__donut-chart">
|
||||
<ResponsiveContainer width="100%" height={NUTRITION_MACRO_CHART_BLOCK_PX}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={pieData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius="38%"
|
||||
outerRadius="58%"
|
||||
dataKey="value"
|
||||
startAngle={90}
|
||||
endAngle={-270}
|
||||
paddingAngle={1}
|
||||
>
|
||||
{pieData.map((e, i) => (
|
||||
<Cell key={i} fill={macroFillByName(e.name)} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip formatter={(v, name) => [`${v}%`, name]} />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<div className="nutrition-macro-pair__legend">
|
||||
{pieData.map(p => {
|
||||
const fill = macroFillByName(p.name)
|
||||
return (
|
||||
<div key={p.name} style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
|
||||
<div style={{ width: 10, height: 10, borderRadius: 2, background: fill, flexShrink: 0 }} />
|
||||
<div style={{ flex: 1, fontSize: 13 }}>{p.name}</div>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: p.color }}>{p.value}%</div>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: fill }}>{p.value}%</div>
|
||||
<div style={{ fontSize: 11, color: 'var(--text3)' }}>
|
||||
{p.grams != null ? `${p.grams}g` : '—'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div style={{ marginTop: 8, fontSize: 11, color: 'var(--text3)', borderTop: '1px solid var(--border)', paddingTop: 8 }}>
|
||||
Ø {avgKcal} kcal/Tag · Anteil der Makro-Kalorien am Tagesumsatz
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div style={{ marginTop: 8, fontSize: 11, color: 'var(--text3)', borderTop: '1px solid var(--border)', paddingTop: 8 }}>
|
||||
Ø {avgKcal} kcal/Tag · Anteil der Makro-Kalorien am Tagesumsatz
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ fontSize: 12, color: 'var(--text3)' }}>Keine Makro-Mittelwerte im Zeitraum.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ fontSize: 12, color: 'var(--text3)' }}>Keine Makro-Mittelwerte im Zeitraum.</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="card nutrition-macro-pair__weekly">
|
||||
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text3)', marginBottom: 4 }}>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
ResponsiveContainer, CartesianGrid, Legend, ReferenceLine, ScatterChart, Scatter
|
||||
} from 'recharts'
|
||||
import { api as nutritionApi } from '../utils/api'
|
||||
import { MACRO_CHART } from '../utils/macroChartTheme'
|
||||
import dayjs from 'dayjs'
|
||||
import isoWeek from 'dayjs/plugin/isoWeek'
|
||||
dayjs.extend(isoWeek)
|
||||
|
|
@ -709,9 +710,9 @@ function WeeklyMacros({ weekly }) {
|
|||
<Tooltip contentStyle={{background:'var(--surface)',border:'1px solid var(--border)',borderRadius:8,fontSize:11}}
|
||||
formatter={(v,n)=>[`${Math.round(v)} g`, n]}/>
|
||||
<Legend wrapperStyle={{fontSize:11}}/>
|
||||
<Bar dataKey="Protein" stackId="a" fill="#1D9E75"/>
|
||||
<Bar dataKey="Fett" stackId="a" fill="#378ADD"/>
|
||||
<Bar dataKey="Kohlenhydrate" stackId="a" fill="#D4537E" radius={[3,3,0,0]}/>
|
||||
<Bar dataKey="Protein" stackId="a" fill={MACRO_CHART.protein}/>
|
||||
<Bar dataKey="Fett" stackId="a" fill={MACRO_CHART.fat}/>
|
||||
<Bar dataKey="Kohlenhydrate" stackId="a" fill={MACRO_CHART.carbs} radius={[3,3,0,0]}/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
|
|
|
|||
21
frontend/src/utils/macroChartTheme.js
Normal file
21
frontend/src/utils/macroChartTheme.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Einheitliche Makro-Farben für Verlauf (Balken, Donut, E3).
|
||||
* Reihenfolge gestapelter Balken (Recharts, unten zuerst): Protein → Fett → Kohlenhydrate.
|
||||
*/
|
||||
export const MACRO_CHART = {
|
||||
protein: '#4a8f72',
|
||||
fat: '#6e8eb8',
|
||||
carbs: '#c17d45',
|
||||
}
|
||||
|
||||
/** Einheitliche Höhe Donut-Bereich / E3-Balken (Verlauf) */
|
||||
export const NUTRITION_MACRO_CHART_BLOCK_PX = 260
|
||||
|
||||
/** Farbe nach Segment-Name (Protein / KH / Fett / englische Keys). */
|
||||
export function macroFillByName(name) {
|
||||
const n = String(name || '').toLowerCase()
|
||||
if (n.includes('protein') || n === 'p') return MACRO_CHART.protein
|
||||
if (n.includes('fett') || n.includes('fat')) return MACRO_CHART.fat
|
||||
if (n.includes('kh') || n.includes('kohlenhydrat') || n.includes('carb')) return MACRO_CHART.carbs
|
||||
return MACRO_CHART.carbs
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user