revert to three separate charts per metric

This commit is contained in:
Eddy de Vink 2026-06-27 23:53:59 +02:00
commit 55785fe9e3

View file

@ -11,11 +11,50 @@ const COLORS = {
muscleMass: '#2196F3', muscleMass: '#2196F3',
} }
function normalize(values: number[]): number[] { function SingleChart({
const min = Math.min(...values) title,
const max = Math.max(...values) data,
if (max === min) return values.map(() => 50) labels,
return values.map(v => ((v - min) / (max - min)) * 100) color,
unit,
}: {
title: string
data: number[]
labels: string[]
color: string
unit: string
}) {
return (
<View style={styles.chartBlock}>
<Text style={styles.chartTitle}>{title}</Text>
<LineChart
data={{ labels, datasets: [{ data, color: () => color, strokeWidth: 2 }] }}
width={Dimensions.get('window').width - 32}
height={180}
yAxisSuffix={unit}
chartConfig={{
backgroundColor: '#ffffff',
backgroundGradientFrom: '#ffffff',
backgroundGradientTo: '#ffffff',
decimalPlaces: 1,
color: (opacity = 1) => `rgba(${hexToRgb(color)}, ${opacity})`,
labelColor: () => '#666',
propsForDots: { r: '3' },
}}
bezier
style={styles.chart}
withInnerLines={false}
fromZero={false}
/>
</View>
)
}
function hexToRgb(hex: string): string {
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
const b = parseInt(hex.slice(5, 7), 16)
return `${r}, ${g}, ${b}`
} }
export default function WeightChart({ measurements }: Props) { export default function WeightChart({ measurements }: Props) {
@ -31,8 +70,6 @@ export default function WeightChart({ measurements }: Props) {
cutoff.setDate(cutoff.getDate() - 30) cutoff.setDate(cutoff.getDate() - 30)
const filtered = sorted.filter(m => new Date(m.measuredAt) >= cutoff) const filtered = sorted.filter(m => new Date(m.measuredAt) >= cutoff)
const dataPoints = filtered.length >= 2 ? filtered : sorted const dataPoints = filtered.length >= 2 ? filtered : sorted
const latest = dataPoints[dataPoints.length - 1]
const previous = dataPoints.length > 1 ? dataPoints[dataPoints.length - 2] : null
const labels = dataPoints.map(m => { const labels = dataPoints.map(m => {
const d = new Date(m.measuredAt) const d = new Date(m.measuredAt)
@ -42,96 +79,33 @@ export default function WeightChart({ measurements }: Props) {
const labelStep = labels.length > 8 ? Math.ceil(labels.length / 8) : 1 const labelStep = labels.length > 8 ? Math.ceil(labels.length / 8) : 1
const sparseLabels = labels.map((l, i) => (i % labelStep === 0 ? l : '')) const sparseLabels = labels.map((l, i) => (i % labelStep === 0 ? l : ''))
const weightData = dataPoints.map(m => m.weight)
const fatData = dataPoints.map(m => m.fatMass)
const muscleData = dataPoints.map(m => m.muscleMass)
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.title}>Voortgang (30 dagen)</Text> <Text style={styles.title}>Voortgang (30 dagen)</Text>
<LineChart
data={{ <SingleChart
labels: sparseLabels, title="Gewicht"
datasets: [ data={dataPoints.map(m => m.weight)}
{ data: normalize(weightData), color: () => COLORS.weight, strokeWidth: 2 }, labels={sparseLabels}
{ data: normalize(fatData), color: () => COLORS.fatMass, strokeWidth: 2 }, color={COLORS.weight}
{ data: normalize(muscleData), color: () => COLORS.muscleMass, strokeWidth: 2 }, unit=" kg"
],
}}
width={Dimensions.get('window').width - 32}
height={220}
chartConfig={{
backgroundColor: '#ffffff',
backgroundGradientFrom: '#ffffff',
backgroundGradientTo: '#ffffff',
decimalPlaces: 0,
color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
labelColor: () => 'transparent',
propsForDots: { r: '3' },
propsForLabels: { fontSize: 9 },
}}
bezier
style={styles.chart}
withInnerLines={false}
fromZero={false}
/> />
<View style={styles.legend}> <SingleChart
<MetricInfo title="Vetmassa"
color={COLORS.weight} data={dataPoints.map(m => m.fatMass)}
label="Gewicht" labels={sparseLabels}
value={latest.weight} color={COLORS.fatMass}
prev={previous?.weight} unit=" kg"
unit="kg" />
/>
<MetricInfo
color={COLORS.fatMass}
label="Vetmassa"
value={latest.fatMass}
prev={previous?.fatMass}
unit="kg"
/>
<MetricInfo
color={COLORS.muscleMass}
label="Spiermassa"
value={latest.muscleMass}
prev={previous?.muscleMass}
unit="kg"
/>
</View>
</View>
)
}
function MetricInfo({ <SingleChart
color, title="Spiermassa"
label, data={dataPoints.map(m => m.muscleMass)}
value, labels={sparseLabels}
prev, color={COLORS.muscleMass}
unit, unit=" kg"
}: { />
color: string
label: string
value: number
prev?: number
unit: string
}) {
const diff = prev !== undefined ? value - prev : 0
const sign = diff > 0 ? '+' : ''
return (
<View style={styles.metricItem}>
<View style={styles.metricRow}>
<View style={[styles.dot, { backgroundColor: color }]} />
<Text style={styles.metricLabel}>{label}</Text>
</View>
<Text style={styles.metricValue}>
{value.toFixed(1)} {unit}
{prev !== undefined && (
<Text style={styles.metricDiff}>
{' '}{sign}{diff.toFixed(1)}
</Text>
)}
</Text>
</View> </View>
) )
} }
@ -139,12 +113,7 @@ function MetricInfo({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { marginHorizontal: 16, marginBottom: 16 }, container: { marginHorizontal: 16, marginBottom: 16 },
title: { fontSize: 18, fontWeight: '700', marginBottom: 8 }, title: { fontSize: 18, fontWeight: '700', marginBottom: 8 },
chartBlock: { marginBottom: 12 },
chartTitle: { fontSize: 13, fontWeight: '600', color: '#555', marginBottom: 4, marginLeft: 4 },
chart: { borderRadius: 12 }, chart: { borderRadius: 12 },
legend: { flexDirection: 'row', justifyContent: 'space-around', marginTop: 8, gap: 8 },
metricItem: { alignItems: 'center', flex: 1 },
metricRow: { flexDirection: 'row', alignItems: 'center', gap: 4, marginBottom: 2 },
dot: { width: 8, height: 8, borderRadius: 4 },
metricLabel: { fontSize: 11, color: '#888' },
metricValue: { fontSize: 13, fontWeight: '600', color: '#333' },
metricDiff: { fontSize: 11, color: '#999' },
}) })