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:
|
if tot <= 0:
|
||||||
return None
|
return None
|
||||||
return [
|
return [
|
||||||
{"name": "Protein", "value": round(pkcal / tot * 100), "color": "#059669", "grams": round(p, 1)},
|
{"name": "Protein", "value": round(pkcal / tot * 100), "color": "#4a8f72", "grams": round(p, 1)},
|
||||||
{"name": "KH", "value": round(ckcal / tot * 100), "color": "#EA580C", "grams": round(c, 1)},
|
{"name": "KH", "value": round(ckcal / tot * 100), "color": "#c17d45", "grams": round(c, 1)},
|
||||||
{"name": "Fett", "value": round(fkcal / tot * 100), "color": "#2563EB", "grams": round(f, 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 (%)",
|
"label": "Protein (%)",
|
||||||
"data": protein_pcts,
|
"data": protein_pcts,
|
||||||
"backgroundColor": "#1D9E75",
|
"backgroundColor": "#4a8f72",
|
||||||
"stack": "macro",
|
"stack": "macro",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Kohlenhydrate (%)",
|
"label": "Kohlenhydrate (%)",
|
||||||
"data": carbs_pcts,
|
"data": carbs_pcts,
|
||||||
"backgroundColor": "#F59E0B",
|
"backgroundColor": "#c17d45",
|
||||||
"stack": "macro",
|
"stack": "macro",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Fett (%)",
|
"label": "Fett (%)",
|
||||||
"data": fat_pcts,
|
"data": fat_pcts,
|
||||||
"backgroundColor": "#EF4444",
|
"backgroundColor": "#6e8eb8",
|
||||||
"stack": "macro",
|
"stack": "macro",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,35 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
|
||||||
min-width: 0;
|
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 {
|
.history-page__title {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
ComposedChart, ReferenceArea,
|
ComposedChart, ReferenceArea,
|
||||||
} from 'recharts'
|
} from 'recharts'
|
||||||
import { api } from '../utils/api'
|
import { api } from '../utils/api'
|
||||||
|
import { MACRO_CHART, NUTRITION_MACRO_CHART_BLOCK_PX } from '../utils/macroChartTheme'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
const fmtDate = d => dayjs(d).format('DD.MM')
|
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
|
Anteil der Kalorien aus jedem Makronährstoff pro Kalenderwoche (100 % gestapelt). Gut vergleichbar mit der
|
||||||
Donut-Übersicht links.
|
Donut-Übersicht links.
|
||||||
</div>
|
</div>
|
||||||
<ResponsiveContainer width="100%" height={280}>
|
<div className="nutrition-macro-pair__chart-wrap">
|
||||||
<BarChart data={chartData} margin={{ top: 8, right: 4, bottom: 4, left: -18 }} barCategoryGap="18%">
|
<ResponsiveContainer width="100%" height={NUTRITION_MACRO_CHART_BLOCK_PX}>
|
||||||
<CartesianGrid stroke="var(--border)" strokeDasharray="3 3" vertical={false} />
|
<BarChart data={chartData} margin={{ top: 8, right: 4, bottom: 4, left: -18 }} barCategoryGap="18%">
|
||||||
<XAxis
|
<CartesianGrid stroke="var(--border)" strokeDasharray="3 3" vertical={false} />
|
||||||
dataKey="week"
|
<XAxis
|
||||||
tick={{ fontSize: 9, fill: 'var(--text3)' }}
|
dataKey="week"
|
||||||
tickLine={false}
|
tick={{ fontSize: 9, fill: 'var(--text3)' }}
|
||||||
interval={Math.max(0, Math.floor(chartData.length / 8) - 1)}
|
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
|
<YAxis tick={{ fontSize: 9, fill: 'var(--text3)' }} tickLine={false} domain={[0, 100]} ticks={[0, 25, 50, 75, 100]} />
|
||||||
contentStyle={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 11 }}
|
<Tooltip
|
||||||
formatter={(v, name) => [`${v}%`, name]}
|
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]} />
|
<Legend wrapperStyle={{ fontSize: 10, paddingTop: 8 }} />
|
||||||
<Bar dataKey="carbs" stackId="a" fill="#EA580C" name="KH %" radius={[0, 0, 0, 0]} />
|
<Bar dataKey="protein" stackId="a" fill={MACRO_CHART.protein} name="Protein %" radius={[0, 0, 0, 0]} />
|
||||||
<Bar dataKey="fat" stackId="a" fill="#B91C1C" name="Fett %" radius={[6, 6, 0, 0]} />
|
<Bar dataKey="fat" stackId="a" fill={MACRO_CHART.fat} name="Fett %" radius={[0, 0, 0, 0]} />
|
||||||
</BarChart>
|
<Bar dataKey="carbs" stackId="a" fill={MACRO_CHART.carbs} name="KH %" radius={[6, 6, 0, 0]} />
|
||||||
</ResponsiveContainer>
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
<div style={{ marginTop: 10, fontSize: 10, color: 'var(--text3)', textAlign: 'center', lineHeight: 1.5 }}>
|
<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{' '}
|
Ø 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}%
|
{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 { photoMonthKey, photoSortKey, formatPhotoCaption } from '../utils/photoDisplay'
|
||||||
import { getBfCategory } from '../utils/calc'
|
import { getBfCategory } from '../utils/calc'
|
||||||
import { getStatusColor, getStatusBg } from '../utils/interpret'
|
import { getStatusColor, getStatusBg } from '../utils/interpret'
|
||||||
|
import { MACRO_CHART, macroFillByName, NUTRITION_MACRO_CHART_BLOCK_PX } from '../utils/macroChartTheme'
|
||||||
import Markdown from '../utils/Markdown'
|
import Markdown from '../utils/Markdown'
|
||||||
import TrainingTypeDistribution from '../components/TrainingTypeDistribution'
|
import TrainingTypeDistribution from '../components/TrainingTypeDistribution'
|
||||||
import NutritionCharts, { WeeklyMacroDistributionPanel } from '../components/NutritionCharts'
|
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)} />
|
<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} />
|
<YAxis tick={{ fontSize: 9, fill: 'var(--text3)' }} tickLine={false} />
|
||||||
{ptLow > 0 && (
|
{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]} />
|
<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="Protein" stackId="a" fill={MACRO_CHART.protein} name="Protein" />
|
||||||
<Bar dataKey="KH" stackId="a" fill="#FDBA74" name="KH" />
|
<Bar dataKey="Fett" stackId="a" fill={MACRO_CHART.fat} name="Fett" />
|
||||||
<Bar dataKey="Protein" stackId="a" fill="#059669" name="Protein" radius={[4, 4, 0, 0]} />
|
<Bar dataKey="KH" stackId="a" fill={MACRO_CHART.carbs} name="KH" radius={[5, 5, 0, 0]} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
<div style={{ display: 'flex', gap: 12, justifyContent: 'center', marginTop: 8, fontSize: 10, color: 'var(--text3)', flexWrap: 'wrap' }}>
|
<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: MACRO_CHART.protein, borderRadius: 2, verticalAlign: 'middle', marginRight: 4 }} />Protein (unten)</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: MACRO_CHART.fat, borderRadius: 2, verticalAlign: 'middle', marginRight: 4 }} />Fett (Mitte)</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.carbs, borderRadius: 2, verticalAlign: 'middle', marginRight: 4 }} />KH (oben)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -929,35 +930,52 @@ function NutritionSection({ profile, insights, onRequest, loadingSlug, filterAct
|
||||||
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text3)', marginBottom: 8 }}>
|
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text3)', marginBottom: 8 }}>
|
||||||
Ø Makro-Quote ({n} Tage)
|
Ø Makro-Quote ({n} Tage)
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
|
{pieData.length > 0 ? (
|
||||||
{pieData.length > 0 ? (
|
<div className="nutrition-macro-pair__donut-inner">
|
||||||
<>
|
<div className="nutrition-macro-pair__donut-chart">
|
||||||
<PieChart width={120} height={120}>
|
<ResponsiveContainer width="100%" height={NUTRITION_MACRO_CHART_BLOCK_PX}>
|
||||||
<Pie data={pieData} cx={58} cy={58} innerRadius={36} outerRadius={54} dataKey="value" startAngle={90} endAngle={-270}>
|
<PieChart>
|
||||||
{pieData.map((e, i) => <Cell key={i} fill={e.color} />)}
|
<Pie
|
||||||
</Pie>
|
data={pieData}
|
||||||
<Tooltip formatter={(v, name) => [`${v}%`, name]} />
|
cx="50%"
|
||||||
</PieChart>
|
cy="50%"
|
||||||
<div style={{ flex: 1, minWidth: 160 }}>
|
innerRadius="38%"
|
||||||
{pieData.map(p => (
|
outerRadius="58%"
|
||||||
<div key={p.name} style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
|
dataKey="value"
|
||||||
<div style={{ width: 10, height: 10, borderRadius: 2, background: p.color, flexShrink: 0 }} />
|
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={{ 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)' }}>
|
<div style={{ fontSize: 11, color: 'var(--text3)' }}>
|
||||||
{p.grams != null ? `${p.grams}g` : '—'}
|
{p.grams != null ? `${p.grams}g` : '—'}
|
||||||
</div>
|
</div>
|
||||||
</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 style={{ marginTop: 8, fontSize: 11, color: 'var(--text3)', borderTop: '1px solid var(--border)', paddingTop: 8 }}>
|
||||||
</div>
|
Ø {avgKcal} kcal/Tag · Anteil der Makro-Kalorien am Tagesumsatz
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<div style={{ fontSize: 12, color: 'var(--text3)' }}>Keine Makro-Mittelwerte im Zeitraum.</div>
|
) : (
|
||||||
)}
|
<div style={{ fontSize: 12, color: 'var(--text3)' }}>Keine Makro-Mittelwerte im Zeitraum.</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="card nutrition-macro-pair__weekly">
|
<div className="card nutrition-macro-pair__weekly">
|
||||||
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text3)', marginBottom: 4 }}>
|
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text3)', marginBottom: 4 }}>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
ResponsiveContainer, CartesianGrid, Legend, ReferenceLine, ScatterChart, Scatter
|
ResponsiveContainer, CartesianGrid, Legend, ReferenceLine, ScatterChart, Scatter
|
||||||
} from 'recharts'
|
} from 'recharts'
|
||||||
import { api as nutritionApi } from '../utils/api'
|
import { api as nutritionApi } from '../utils/api'
|
||||||
|
import { MACRO_CHART } from '../utils/macroChartTheme'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import isoWeek from 'dayjs/plugin/isoWeek'
|
import isoWeek from 'dayjs/plugin/isoWeek'
|
||||||
dayjs.extend(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}}
|
<Tooltip contentStyle={{background:'var(--surface)',border:'1px solid var(--border)',borderRadius:8,fontSize:11}}
|
||||||
formatter={(v,n)=>[`${Math.round(v)} g`, n]}/>
|
formatter={(v,n)=>[`${Math.round(v)} g`, n]}/>
|
||||||
<Legend wrapperStyle={{fontSize:11}}/>
|
<Legend wrapperStyle={{fontSize:11}}/>
|
||||||
<Bar dataKey="Protein" stackId="a" fill="#1D9E75"/>
|
<Bar dataKey="Protein" stackId="a" fill={MACRO_CHART.protein}/>
|
||||||
<Bar dataKey="Fett" stackId="a" fill="#378ADD"/>
|
<Bar dataKey="Fett" stackId="a" fill={MACRO_CHART.fat}/>
|
||||||
<Bar dataKey="Kohlenhydrate" stackId="a" fill="#D4537E" radius={[3,3,0,0]}/>
|
<Bar dataKey="Kohlenhydrate" stackId="a" fill={MACRO_CHART.carbs} radius={[3,3,0,0]}/>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</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