1(function () { 2 function qs(sel, root) { 3 return (root || document).querySelector(sel); 4 } 5 function qsa(sel, root) { 6 return Array.prototype.slice.call((root || document).querySelectorAll(sel)); 7 } 8 9 var INCH = 2.54; 10 var LB = 0.45359237; 11 var i18n = window.PGC_I18N || {}; 12 var form = qs('#pgc-form'); 13 if (!form) { 14 return; 15 } 16 if (window.Chart && window.ChartZoom) { 17 Chart.register(ChartZoom); 18 } 19 if (window.Chart) { 20 Chart.defaults.font.family = '"Inter", "Open Sans", sans-serif'; 21 Chart.defaults.font.size = 12; 22 Chart.defaults.color = '#5d6b7e'; 23 } 24 25 var charts = {}; 26 var units = 'metric'; 27 var dSel = qs('#dob-d'); 28 var mSel = qs('#dob-m'); 29 var ySel = qs('#dob-y'); 30 var months = i18n.months || ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 31 var nowY = new Date().getFullYear(); 32 var i; 33 34 function addOption(select, value, label) { 35 var o = document.createElement('option'); 36 o.value = String(value); 37 o.textContent = label; 38 select.appendChild(o); 39 } 40 41 for (i = 1; i <= 31; i++) { 42 addOption(dSel, i, i); 43 } 44 for (i = 1; i <= 12; i++) { 45 addOption(mSel, i, months[i - 1]); 46 } 47 for (i = nowY; i >= nowY - 21; i--) { 48 addOption(ySel, i, i); 49 } 50 51 function defaultDob() { 52 dSel.value = '1'; 53 mSel.value = '8'; 54 ySel.value = String(nowY - 10); 55 } 56 57 function setText(sel, text) { 58 qsa(sel).forEach(function (el) { 59 el.textContent = text; 60 }); 61 } 62 63 function setUnits(u, convert) { 64 var prev = units; 65 units = u; 66 qs('#btn-metric').classList.toggle('active', u === 'metric'); 67 qs('#btn-imperial').classList.toggle('active', u === 'imperial'); 68 form.classList.toggle('is-metric', u === 'metric'); 69 form.classList.toggle('is-imperial', u === 'imperial'); 70 var metric = u === 'metric'; 71 setText('.u-h', metric ? i18n.cm : i18n.in); 72 setText('.u-w', metric ? i18n.kg : i18n.lb); 73 setText('.u-c', metric ? i18n.cm : i18n.in); 74 qs('#height-imp').hidden = metric; 75 qsa('.height-metric').forEach(function (el) { 76 el.style.display = metric ? '' : 'none'; 77 }); 78 if (convert && prev !== u) { 79 var h = parseFloat(qs('#height').value); 80 var w = parseFloat(qs('#weight').value); 81 var c = parseFloat(qs('#head').value); 82 if (u === 'imperial') { 83 if (!isNaN(h) && h > 0) { 84 var inches = h / INCH; 85 qs('#height-ft').value = String(Math.floor(inches / 12)); 86 qs('#height-in').value = (inches % 12).toFixed(1); 87 } 88 if (!isNaN(w) && w > 0) { 89 qs('#weight').value = (w / LB).toFixed(2); 90 } 91 if (!isNaN(c) && c > 0) { 92 qs('#head').value = (c / INCH).toFixed(1); 93 } 94 } else { 95 var tot = (parseFloat(qs('#height-ft').value) || 0) * 12 + (parseFloat(qs('#height-in').value) || 0); 96 if (tot > 0) { 97 qs('#height').value = (tot * INCH).toFixed(1); 98 } 99 if (!isNaN(w) && w > 0) {
100 qs('#weight').value = (w * LB).toFixed(2); 101 } 102 if (!isNaN(c) && c > 0) { 103 qs('#head').value = (c * INCH).toFixed(1); 104 } 105 } 106 syncInches(); 107 } 108 persist(); 109 } 110 111 function syncInches() { 112 var tot = (parseFloat(qs('#height-ft').value) || 0) * 12 + (parseFloat(qs('#height-in').value) || 0); 113 qs('#total-in').textContent = tot ? tot.toFixed(1) : '0'; 114 } 115 116 function persist() { 117 var sexEl = qs('input[name="sex"]:checked', form); 118 var data = { 119 units: units, 120 d: dSel.value, 121 m: mSel.value, 122 y: ySel.value, 123 sex: sexEl ? sexEl.value : '', 124 height: qs('#height').value, 125 heightFt: qs('#height-ft').value, 126 heightIn: qs('#height-in').value, 127 weight: qs('#weight').value, 128 head: qs('#head').value, 129 source: qs('#source').value 130 }; 131 try { localStorage.setItem('pgc_form', JSON.stringify(data)); } catch (e) {} 132 } 133 134 function restore() { 135 try { 136 var raw = localStorage.getItem('pgc_form'); 137 if (!raw) { 138 return; 139 } 140 var data = JSON.parse(raw); 141 units = data.units === 'imperial' ? 'imperial' : 'metric'; 142 if (data.d) { dSel.value = String(data.d); } 143 if (data.m) { mSel.value = String(data.m); } 144 if (data.y) { ySel.value = String(data.y); } 145 if (data.sex) { 146 var r = qs('input[name="sex"][value="' + data.sex + '"]', form); 147 if (r) { r.checked = true; } 148 } 149 if (data.height) { qs('#height').value = data.height; } 150 if (data.heightFt) { qs('#height-ft').value = data.heightFt; } 151 if (data.heightIn) { qs('#height-in').value = data.heightIn; } 152 if (data.weight) { qs('#weight').value = data.weight; } 153 if (data.head) { qs('#head').value = data.head; } 154 if (data.source) { 155 var src = qs('#source'); 156 var allowed = false; 157 var opts = src.options; 158 var oi; 159 for (oi = 0; oi < opts.length; oi++) { 160 if (opts[oi].value === data.source) { 161 allowed = true; 162 break; 163 } 164 } 165 src.value = allowed ? data.source : 'who'; 166 } 167 syncInches(); 168 } catch (e) {} 169 } 170 171 function ageMonths(y, m, d) { 172 var dob = new Date(parseInt(y, 10), parseInt(m, 10) - 1, parseInt(d, 10)); 173 var now = new Date(); 174 var monthsAge = (now.getFullYear() - dob.getFullYear()) * 12 + (now.getMonth() - dob.getMonth()); 175 monthsAge += (now.getDate() - dob.getDate()) / 30.4375; 176 return monthsAge; 177 } 178 179 function interp(points, x) { 180 if (!points || !points.length) { 181 return null; 182 } 183 if (x < points[0][0] || x > points[points.length - 1][0]) { 184 if (x < points[0][0] && x >= points[0][0] - 0.6) { 185 return points[0][1]; 186 } 187 if (x > points[points.length - 1][0] && x <= points[points.length - 1][0] + 0.6) { 188 return points[points.length - 1][1]; 189 } 190 return null; 191 } 192 var n; 193 for (n = 0; n < points.length - 1; n++) { 194 var a = points[n]; 195 var b = points[n + 1]; 196 if (x >= a[0] && x <= b[0]) { 197 var t = (b[0] === a[0]) ? 0 : (x - a[0]) / (b[0] - a[0]); 198 return a[1] + t * (b[1] - a[1]); 199 } 200 } 201 return points[points.length - 1][1]; 202 } 203 204 function percentileFromCurves(curves, x, y) { 205 var names = [3, 10, 25, 50, 75, 90, 97]; 206 var ys = []; 207 var n; 208 for (n = 0; n < curves.length; n++) { 209 var v = interp(curves[n].data, x); 210 if (v === null) { 211 return { label: i18n.no_data, value: null }; 212 } 213 ys.push(v); 214 } 215 if (y < ys[0]) { 216 return { label: i18n.below_p3, value: 3 }; 217 } 218 if (y > ys[ys.length - 1]) { 219 return { label: i18n.above_p97, value: 97 }; 220 } 221 for (n = 0; n < ys.length - 1; n++) { 222 if (y >= ys[n] && y <= ys[n + 1]) { 223 var t = (ys[n + 1] === ys[n]) ? 0 : (y - ys[n]) / (ys[n + 1] - ys[n]); 224 var p = names[n] + t * (names[n + 1] - names[n]); 225 return { label: 'P' + Math.round(p), value: p }; 226 } 227 } 228 return { label: 'P50', value: 50 }; 229 } 230 231 function toMetricHeight() { 232 if (units === 'metric') { 233 return parseFloat(qs('#height').value); 234 } 235 var tot = (parseFloat(qs('#height-ft').value) || 0) * 12 + (parseFloat(qs('#height-in').value) || 0); 236 return tot ? tot * INCH : NaN; 237 } 238 function toMetricWeight() { 239 var w = parseFloat(qs('#weight').value); 240 if (isNaN(w)) { 241 return NaN; 242 } 243 return units === 'metric' ? w : w * LB; 244 } 245 function toMetricHead() { 246 var c = parseFloat(qs('#head').value); 247 if (isNaN(c)) { 248 return NaN; 249 } 250 return units === 'metric' ? c : c * INCH; 251 } 252 function yFactor(kind) { 253 if (units === 'metric') { 254 return 1; 255 } 256 return kind === 'weight' ? 1 / LB : 1 / INCH; 257 } 258 function yUnit(kind) { 259 if (kind === 'weight') { 260 return units === 'metric' ? i18n.kg : i18n.lb; 261 } 262 return units === 'metric' ? i18n.cm : i18n.in; 263 } 264 265 function run() { 266 persist(); 267 var err = qs('#form-error'); 268 err.hidden = true; 269 var y = ySel.value; 270 var m = mSel.value; 271 var d = dSel.value; 272 var sexEl = qs('input[name="sex"]:checked', form); 273 var hCm = toMetricHeight(); 274 var wKg = toMetricWeight(); 275 var cCm = toMetricHead();
276 if (!y || !m || !d || !sexEl || (isNaN(hCm) && isNaN(wKg) && isNaN(cCm))) { 277 err.textContent = i18n.need_fields; 278 err.hidden = false; 279 return; 280 } 281 var age = ageMonths(y, m, d); 282 var headInput = qs('#head'); 283 if (age > 36) { 284 headInput.disabled = true; 285 cCm = NaN; 286 } else { 287 headInput.disabled = false; 288 } 289 fetch('/data/percentile/' + qs('#source').value + '.json') 290 .then(function (r) { return r.json(); }) 291 .then(function (json) { 292 var sex = sexEl.value; 293 qs('#results').hidden = false; 294 qs('#age-out').textContent = i18n.age_label + ': ' + age.toFixed(1) + ' ' + i18n.months_short; 295 showMetric('height', json.height && json.height[sex], age, hCm, 'out-h', i18n.pct_height, 'chart-h'); 296 showMetric('weight', json.weight && json.weight[sex], age, wKg, 'out-w', i18n.pct_weight, 'chart-w'); 297 showMetric('head', json.head && json.head[sex], age, cCm, 'out-c', i18n.pct_head, 'chart-c'); 298 }) 299 .catch(function () { 300 err.textContent = i18n.no_data; 301 err.hidden = false; 302 }); 303 } 304
305 function renderStat(el, name, value) { 306 el.textContent = ''; 307 var n = document.createElement('span'); 308 n.className = 'pct-name'; 309 n.textContent = name; 310 var v = document.createElement('span'); 311 v.className = 'pct-value'; 312 v.textContent = value; 313 el.appendChild(n); 314 el.appendChild(v); 315 } 316 317 function showMetric(kind, curves, age, val, outId, label, canvasId) { 318 var out = qs('#' + outId); 319 var block = qs('#block-' + (kind === 'height' ? 'h' : (kind === 'weight' ? 'w' : 'c'))); 320 if (kind === 'head' && (isNaN(val) || !curves)) { 321 out.hidden = true; 322 if (block) { 323 block.hidden = true; 324 } 325 return; 326 } 327 out.hidden = false; 328 if (block) { 329 block.hidden = false; 330 } 331 if (isNaN(val) || !curves) { 332 renderStat(out, label, 'â'); 333 if (block) { 334 block.hidden = true; 335 } 336 return; 337 } 338 var pct = percentileFromCurves(curves, age, val); 339 renderStat(out, label, pct.label); 340 drawChart(canvasId, kind, curves, age, val); 341 } 342 343 function drawChart(id, kind, curves, age, val) { 344 var fac = yFactor(kind); 345 var colors = ['#b50033', '#e07090', '#7aa2c8', '#003b75', '#7aa2c8', '#e07090', '#b50033']; 346 var datasets = []; 347 var n; 348 for (n = 0; n < curves.length; n++) { 349 var mid = Math.floor(curves.length / 2); 350 datasets.push({ 351 label: curves[n].name, 352 data: curves[n].data.map(function (p) { return { x: p[0], y: p[1] * fac }; }), 353 borderColor: colors[n % colors.length], 354 backgroundColor: 'transparent', 355 borderWidth: n === mid ? 2.4 : 1.4, 356 borderDash: n === mid ? [] : [5, 4], 357 pointRadius: 0, 358 tension: 0.3 359 }); 360 } 361 datasets.push({ 362 label: 'â¢', 363 data: [{ x: age, y: val * fac }], 364 borderColor: '#fff', 365 backgroundColor: '#e70041', 366 borderWidth: 2.5, 367 pointRadius: 7, 368 pointHoverRadius: 9, 369 showLine: false 370 }); 371 if (charts[id]) { 372 charts[id].destroy(); 373 } 374 charts[id] = new Chart(qs('#' + id), { 375 type: 'line', 376 data: { datasets: datasets }, 377 options: { 378 responsive: true, 379 maintainAspectRatio: false, 380 interaction: { mode: 'nearest', intersect: false }, 381 plugins: { 382 legend: { 383 display: true, 384 position: 'bottom', 385 labels: { boxWidth: 10, boxHeight: 10, usePointStyle: true, pointStyle: 'line', padding: 14, color: '#5d6b7e' } 386 }, 387 tooltip: { 388 backgroundColor: 'rgba(16, 26, 40, .92)', 389 padding: 10, 390 cornerRadius: 8, 391 titleFont: { weight: '700' }, 392 displayColors: false, 393 callbacks: { 394 label: function (c) { 395 var yv = c.parsed.y; 396 return c.dataset.label + ': ' + (yv && yv.toFixed ? yv.toFixed(1) : yv) + ' ' + yUnit(kind); 397 } 398 } 399 }, 400 zoom: { pan: { enabled: true, mode: 'xy' }, zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' } } 401 }, 402 scales: { 403 x: { 404 type: 'linear', 405 title: { display: true, text: i18n.axis_age, color: '#5d6b7e' }, 406 grid: { color: 'rgba(227, 233, 242, .8)' }, 407 border: { color: '#e3e9f2' }, 408 ticks: { color: '#5d6b7e' } 409 }, 410 y: { 411 title: { display: true, text: yUnit(kind), color: '#5d6b7e' }, 412 grid: { color: 'rgba(227, 233, 242, .8)' }, 413 border: { color: '#e3e9f2' }, 414 ticks: { color: '#5d6b7e' } 415 } 416 } 417 } 418 }); 419 } 420 421 function resetZoom() { 422 Object.keys(charts).forEach(function (k) { 423 if (charts[k] && charts[k].resetZoom) { 424 charts[k].resetZoom(); 425 } 426 }); 427 } 428 429 qs('#btn-metric').addEventListener('click', function () { setUnits('metric', true); }); 430 qs('#btn-imperial').addEventListener('click', function () { setUnits('imperial', true); }); 431 qs('#height-ft').addEventListener('input', syncInches); 432 qs('#height-in').addEventListener('input', syncInches); 433 qs('#reset-zoom').addEventListener('click', resetZoom); 434 form.addEventListener('submit', function (e) { 435 e.preventDefault(); 436 run(); 437 }); 438 form.addEventListener('change', persist); 439 form.addEventListener('input', persist); 440 441 defaultDob();
442 restore(); 443 if (!dSel.value || !mSel.value || !ySel.value) { 444 defaultDob(); 445 } 446 setUnits(units, false); 447})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.