aboutsummaryrefslogtreecommitdiff
path: root/src/lib/components/HealthChart.svelte
blob: f1f87ed623d0e8e95317b076a82c45f7907c8f56 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

<script lang="ts">
	import { AreaChart, Area, Points, Text } from 'layerchart';
	import type { HealthPoint } from '$lib/shared/routine-metrics';

	let {
		series,
		markers = [],
		ariaLabel,
	}: {
		series: HealthPoint[];
		markers?: number[];
		ariaLabel: string;
	} = $props();

	const dateFormatter = new Intl.DateTimeFormat('fr-FR', {
		day: 'numeric',
		month: 'short',
	});

	const data = $derived(
		series.map((p) => ({ date: new Date(p.at), health: p.health })),
	);
	const dots = $derived(
		markers.map((t) => ({ date: new Date(t), health: 100 })),
	);

	// Up to 5 x ticks spread across the series, skipping repeats (two samples
	// the same day would print the same label twice).
	const xTicks = $derived.by(() => {
		if (data.length < 2) return undefined;
		const step = Math.max(1, Math.floor((data.length - 1) / 4));
		const ticks: Date[] = [];
		for (let i = 0; i < data.length; i += step) {
			const last = i + step >= data.length;
			const date = last ? data[data.length - 1].date : data[i].date;
			const label = dateFormatter.format(date);
			if (
				ticks.length > 0 &&
				dateFormatter.format(ticks[ticks.length - 1]) === label
			)
				continue;
			ticks.push(date);
			if (last) break;
		}
		return ticks;
	});
</script>

{#if data.length >= 2}
	<div class="health-chart" role="img" aria-label={ariaLabel}>
		<AreaChart
			{data}
			x="date"
			y="health"
			yDomain={[0, 100]}
			series={[{ key: 'santé', value: 'health', color: 'var(--accent)' }]}
			axis
			grid={{ y: true, yTicks: [25, 50, 75, 100] }}
			rule={false}
			props={{
				xAxis: {
					ticks: xTicks,
					format: (d: Date) => dateFormatter.format(d),
					tickLabel: xTickLabel,
				},
				yAxis: { ticks: [25, 50, 75, 100] },
			}}
			marks={chartMarks}
		/>
	</div>
{:else}
	<p class="empty">Pas encore assez d'historique pour tracer la courbe.</p>
{/if}

{#snippet xTickLabel({
	props,
	index,
}: {
	props: Record<string, unknown>;
	index: number;
})}
	<Text
		{...props}
		textAnchor={index === 0
			? 'start'
			: index === (xTicks?.length ?? 0) - 1
				? 'end'
				: 'middle'}
	/>
{/snippet}

{#snippet chartMarks()}
	<Area seriesKey="santé" fillOpacity={0.2} line={{ strokeWidth: 2 }} />
	<Points
		data={dots}
		x="date"
		y="health"
		r={4}
		fill="var(--success)"
		stroke="var(--white)"
		strokeWidth={1.5}
	/>
{/snippet}

<style>
	.health-chart {
		width: 100%;
		height: 11rem;
	}
</style>