From de0562060b736aff5a0908e76be7108bdc5aeb7a Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 14 Apr 2026 06:55:12 +0200 Subject: [PATCH] =?UTF-8?q?Growth=20charts:=20label=20each=20percentile=20?= =?UTF-8?q?curve=20(3rd,=2050th,=2097th,=20=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- public/js/calculators.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/public/js/calculators.js b/public/js/calculators.js index 133b4d7..3a8917e 100644 --- a/public/js/calculators.js +++ b/public/js/calculators.js @@ -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',