revert to three separate charts per metric
This commit is contained in:
parent
4dd2ddaa58
commit
55785fe9e3
1 changed files with 67 additions and 98 deletions
|
|
@ -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={{
|
|
||||||
labels: sparseLabels,
|
|
||||||
datasets: [
|
|
||||||
{ data: normalize(weightData), color: () => COLORS.weight, strokeWidth: 2 },
|
|
||||||
{ data: normalize(fatData), color: () => COLORS.fatMass, strokeWidth: 2 },
|
|
||||||
{ data: normalize(muscleData), color: () => COLORS.muscleMass, strokeWidth: 2 },
|
|
||||||
],
|
|
||||||
}}
|
|
||||||
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="Gewicht"
|
||||||
|
data={dataPoints.map(m => m.weight)}
|
||||||
|
labels={sparseLabels}
|
||||||
color={COLORS.weight}
|
color={COLORS.weight}
|
||||||
label="Gewicht"
|
unit=" kg"
|
||||||
value={latest.weight}
|
|
||||||
prev={previous?.weight}
|
|
||||||
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="Vetmassa"
|
||||||
label,
|
data={dataPoints.map(m => m.fatMass)}
|
||||||
value,
|
labels={sparseLabels}
|
||||||
prev,
|
color={COLORS.fatMass}
|
||||||
unit,
|
unit=" kg"
|
||||||
}: {
|
/>
|
||||||
color: string
|
|
||||||
label: string
|
<SingleChart
|
||||||
value: number
|
title="Spiermassa"
|
||||||
prev?: number
|
data={dataPoints.map(m => m.muscleMass)}
|
||||||
unit: string
|
labels={sparseLabels}
|
||||||
}) {
|
color={COLORS.muscleMass}
|
||||||
const diff = prev !== undefined ? value - prev : 0
|
unit=" kg"
|
||||||
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' },
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue