| name | health-charts |
| description | Chart.js 4 configuration patterns for health data visualization including time series with target range bands, alert threshold lines, dual-axis blood pressure charts, rolling averages, and correlation bar charts. Use when building or debugging health data charts in any health tracking application. |
health-charts
Chart.js 4 patterns for health and vital sign data visualization.
Global chart defaults
import {
Chart,
LineController,
LineElement,
PointElement,
LinearScale,
TimeScale,
Tooltip,
Legend,
} from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation';
import 'chartjs-adapter-date-fns';
Chart.register(
LineController, LineElement, PointElement,
LinearScale, TimeScale,
Tooltip, Legend,
annotationPlugin
);
export const baseChartDefaults = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: (ctx: { parsed: { y: number }; dataset: { label?: string } }) =>
`${ctx.dataset.label ?? ''}: ${ctx.parsed.y}`,
},
},
},
scales: {
x: {
type: 'time' as const,
time: { unit: 'day' as const },
grid: { color: 'var(--border)' },
},
y: {
grid: { color: 'var(--border)' },
},
},
};
Target range band annotation
import type { AnnotationOptions } from 'chartjs-plugin-annotation';
export function targetRangeAnnotation(
min: number,
max: number,
label = 'Target range'
): AnnotationOptions {
return {
type: 'box',
yMin: min,
yMax: max,
backgroundColor: 'rgba(8,145,178,0.08)',
borderColor: 'rgba(8,145,178,0.3)',
borderWidth: 1,
label: {
display: true,
content: label,
position: { x: 'start', y: 'start' },
font: { size: 10 },
color: 'rgba(8,145,178,0.6)',
},
};
}
export function alertLineAnnotation(
value: number,
type: 'high' | 'low'
): AnnotationOptions {
return {
type: 'line',
yMin: value,
: value,
: ,
: ,
: [, ],
: {
: ,
: === ? : ,
: ,
: { : },
: ,
},
};
}
Single vital time series config
import type { ChartConfiguration } from 'chart.js';
interface Reading {
recorded_at: string;
value1: number;
}
interface VitalType {
label: string;
target_min: number | null;
target_max: number | null;
alert_low: number | null;
alert_high: number | null;
}
export function buildVitalChartConfig(
readings: Reading[],
vitalType: VitalType
): ChartConfiguration {
const annotations: Record<string, AnnotationOptions> = {};
if (vitalType.target_min !== null && vitalType.target_max !== null) {
annotations.targetRange = targetRangeAnnotation(
vitalType.target_min,
vitalType.target_max
);
}
if (vitalType.alert_high !== ) {
annotations. = (vitalType., );
}
(vitalType. !== ) {
annotations. = (vitalType., );
}
{
: ,
: {
: [{
: vitalType.,
: readings.( ({ : r., : r. })),
: ,
: ,
: ,
: ,
: ,
}],
},
: {
...baseChartDefaults,
: {
...baseChartDefaults.,
: { annotations },
},
},
};
}
Blood pressure dual-line config
interface BpReading {
recorded_at: string;
value1: number;
value2: number;
}
export function buildBpChartConfig(readings: BpReading[]): ChartConfiguration {
return {
type: 'line',
data: {
datasets: [
{
label: 'Systolic',
data: readings.map((r) => ({ x: r.recorded_at, y: r.value1 })),
borderColor: '#0891b2',
borderWidth: 2,
pointRadius: 3,
tension: 0.2,
},
{
label: 'Diastolic',
data: readings.map((r) => ({ x: r.recorded_at, y: r.value2 })),
borderColor: '#0e7490',
borderWidth: ,
: [, ],
: ,
: ,
},
],
},
: {
...baseChartDefaults,
: {
...baseChartDefaults.,
: { : },
: {
: {
: (, , ),
: (, ),
},
},
},
},
};
}
Rolling average overlay
export function rollingAverage(values: number[], window: number): number[] {
return values.map((_, i) => {
const slice = values.slice(Math.max(0, i - window + 1), i + 1);
return slice.reduce((a, b) => a + b, 0) / slice.length;
});
}
Correlation bar chart
interface CorrelationPair {
type_a: string;
type_b: string;
r: number;
}
export function buildCorrelationChartConfig(
pairs: CorrelationPair[]
): ChartConfiguration {
const sorted = [...pairs].sort((a, b) => Math.abs(b.r) - Math.abs(a.r));
return {
type: 'bar',
data: {
labels: sorted.map((p) => `${p.type_a} / ${p.type_b}`),
datasets: [{
label: 'Pearson r',
data: sorted.map((p) => p.r),
backgroundColor: sorted.map((p) =>
p.r > 0
? `rgba(8,145,178,${0.3 + Math.abs(p.r) * })`
:
),
: ,
}],
},
: {
: ,
: {
: { : -, : , : { : } },
},
},
};
}
Accessibility notes
- Wrap every
<canvas> in a <figure> with an aria-label describing the vital type and date range.
- Provide a data table fallback accessible via a "Show data table" toggle button.
- Status colors (normal / warning / alert) are always paired with text labels in tooltips.
Troubleshooting
Chart not rendering
- Verify all required Chart.js modules are registered before instantiation.
- Confirm
chartjs-adapter-date-fns is imported when using type: 'time' scale.
Annotation band not visible
- Ensure
chartjs-plugin-annotation is registered: Chart.register(annotationPlugin).
- Verify
target_min and target_max are numbers, not null, before adding annotation.