import React, {useMemo} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import Svg, {Circle, Line, Path, Rect} from 'react-native-svg';
import {colors, spacing} from '../../../theme';
import type {MetricTrendPoint} from '../insights/metricInsights';
interface MetricTrendChartProps {
points: readonly MetricTrendPoint[];
color: string;
unit: string;
digits: number;
referenceRange: {minimum: number; maximum: number} | null;
}
const WIDTH = 320;
const HEIGHT = 104;
const PAD_X = 8;
const PAD_Y = 13;
const dateLabel = (value: string): string =>
new Date(value).toLocaleDateString('fr-FR', {
day: '2-digit',
month: 'short',
});
const createPath = (coordinates: readonly {x: number; y: number}[]): string => {
if (coordinates.length === 0) return '';
if (coordinates.length === 1) {
const only = coordinates[0]!;
return `M ${only.x} ${only.y}`;
}
return coordinates.reduce((path, point, index) => {
if (index === 0) return `M ${point.x} ${point.y}`;
const previous = coordinates[index - 1]!;
const controlX = (previous.x + point.x) / 2;
return `${path} C ${controlX} ${previous.y}, ${controlX} ${point.y}, ${point.x} ${point.y}`;
}, '');
};
export function MetricTrendChart({
points,
color,
unit,
digits,
referenceRange,
}: MetricTrendChartProps): React.JSX.Element {
const model = useMemo(() => {
if (points.length === 0) return null;
const allValues = [
...points.map(point => point.value),
...(referenceRange === null
? []
: [referenceRange.minimum, referenceRange.maximum]),
];
const rawMinimum = Math.min(...allValues);
const rawMaximum = Math.max(...allValues);
const padding = Math.max((rawMaximum - rawMinimum) * 0.18, Math.abs(rawMaximum) * 0.02, 0.5);
const minimum = rawMinimum - padding;
const maximum = rawMaximum + padding;
const scaleY = (value: number): number =>
PAD_Y + ((maximum - value) / Math.max(maximum - minimum, 0.0001)) * (HEIGHT - PAD_Y * 2);
const coordinates = points.map((point, index) => ({
x:
points.length === 1
? WIDTH / 2
: PAD_X + (index / (points.length - 1)) * (WIDTH - PAD_X * 2),
y: scaleY(point.value),
}));
return {
coordinates,
path: createPath(coordinates),
rangeY:
referenceRange === null
? null
: {
top: scaleY(referenceRange.maximum),
bottom: scaleY(referenceRange.minimum),
},
};
}, [points, referenceRange]);
if (model === null || points.length < 2) {
return (
Tendance en construction
Encore {Math.max(0, 2 - points.length)} mesure comparable pour tracer une évolution.
);
}
const first = points[0]!;
const last = points[points.length - 1]!;
const firstCoordinate = model.coordinates[0]!;
const lastCoordinate = model.coordinates[model.coordinates.length - 1]!;
const valueLabel = `${last.value.toFixed(digits)} ${unit}`;
return (
{dateLabel(first.measuredAt)}
{valueLabel}
{dateLabel(last.measuredAt)}
{points.length} mesures réelles{referenceRange === null ? '' : ' · zone pâle : repère personnel'}
);
}
const styles = StyleSheet.create({
empty: {
minHeight: 96,
justifyContent: 'center',
paddingVertical: spacing.md,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: colors.border,
},
emptyTitle: {color: colors.text, fontSize: 13, fontWeight: '700'},
emptyText: {color: colors.muted, fontSize: 11, lineHeight: 16, marginTop: 3},
labels: {
marginTop: -2,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
date: {color: colors.muted, fontSize: 10},
lastValue: {fontSize: 11, fontWeight: '800', fontVariant: ['tabular-nums']},
caption: {color: colors.muted, fontSize: 9, marginTop: spacing.xs},
});