Growth charts: label each percentile curve (3rd, 50th, 97th, …)

Adds a Chart.js plugin that draws the percentile label at the right
end of each reference line, matching the convention on printed
WHO/CDC growth charts. Lets clinicians identify lines at a glance
without using the legend. Canvas gets 30px right-padding so the
labels don't get clipped.
This commit is contained in:
Daniel 2026-04-14 06:55:12 +02:00
parent e32e9977f5
commit de0562060b

View file

@ -811,6 +811,34 @@
var _chartInstances = {};
// Chart.js plugin — draws the percentile label (e.g. "50th") at the
// right end of each percentile curve so the lines are identifiable
// without a legend. Standard on printed WHO/CDC growth charts.
var percentileLabelPlugin = {
id: 'percentileLabels',
afterDatasetsDraw: function(chart) {
var ctx = chart.ctx;
chart.data.datasets.forEach(function(ds, i) {
// Only the percentile line datasets (labels look like "50th")
if (!ds.label || !/^\d+th$/.test(ds.label)) return;
var meta = chart.getDatasetMeta(i);
if (!meta || meta.hidden || !meta.data || !meta.data.length) return;
var last = meta.data[meta.data.length - 1];
if (!last) return;
ctx.save();
ctx.font = '600 10px -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif';
ctx.fillStyle = typeof ds.borderColor === 'string' ? ds.borderColor : '#555';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
// Solidify alpha so labels remain readable even when the line is faint
var m = ctx.fillStyle.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (m) ctx.fillStyle = 'rgb(' + m[1] + ',' + m[2] + ',' + m[3] + ')';
ctx.fillText(ds.label, last.x + 4, last.y);
ctx.restore();
});
}
};
function renderGrowthChart(canvasId, containerId, lmsTable, patientX, patientY, xLabel, yLabel, extraDatasets) {
if (typeof Chart === 'undefined') return; // Chart.js not loaded yet
@ -841,11 +869,16 @@
_chartInstances[canvasId] = new Chart(document.getElementById(canvasId), {
type: 'line',
data: { datasets: datasets },
plugins: [percentileLabelPlugin],
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 1.6,
animation: { duration: 300 },
// Leave ~28px of padding on the right so the percentile labels
// ("50th", "95th", ...) drawn past the last data point are not
// clipped by the canvas edge.
layout: { padding: { right: 30 } },
scales: {
x: {
type: 'linear',