{"spec_id":"histogram-density","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// histogram-density: Density Histogram\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-09-05\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// Resting heart rate (bpm) for 600 adults, ~Normal(mean=72, std=8).\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\nconst n = 600;\nconst mean = 72;\nconst std = 8;\nconst samples = [];\nfor (let i = 0; i < n; i++) {\n  const u1 = rand();\n  const u2 = rand();\n  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n  samples.push(mean + std * z);\n}\n\nconst binCount = 24;\nconst dataMin = Math.min(...samples);\nconst dataMax = Math.max(...samples);\nconst binWidth = (dataMax - dataMin) / binCount;\n\nconst counts = new Array(binCount).fill(0);\nfor (const value of samples) {\n  const idx = Math.min(binCount - 1, Math.floor((value - dataMin) / binWidth));\n  counts[idx]++;\n}\n\n// Normalize so total bar area equals 1 (density, not raw count).\nconst density = counts.map((c) => c / (n * binWidth));\nconst binCenters = Array.from(\n  { length: binCount },\n  (_, i) => dataMin + (i + 0.5) * binWidth,\n);\nconst labels = binCenters.map((c) => c.toFixed(1));\n\n// Theoretical normal PDF evaluated at each bin center.\nconst normalPdf = (x) =>\n  Math.exp(-0.5 * ((x - mean) / std) ** 2) / (std * Math.sqrt(2 * Math.PI));\nconst pdfValues = binCenters.map(normalPdf);\n\n// Goodness-of-fit residual per bin, used to scriptably shade bars: bins that\n// tightly track the theoretical curve render fully saturated, bins that\n// deviate render lighter — a visual callout for the fit without extra chrome.\nconst residuals = density.map((d, i) => Math.abs(d - pdfValues[i]));\nconst maxResidual = Math.max(...residuals) || 1;\n\nfunction hexToRgba(hex, alpha) {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n// --- Mount -----------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart -----------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"bar\",\n  data: {\n    labels,\n    datasets: [\n      {\n        type: \"bar\",\n        label: \"Empirical density\",\n        data: density,\n        backgroundColor: (ctx) => {\n          const i = ctx.dataIndex;\n          if (i === undefined) return t.palette[0];\n          const fit = 1 - residuals[i] / maxResidual;\n          return hexToRgba(t.palette[0], 0.45 + 0.55 * fit);\n        },\n        borderWidth: 0,\n        borderRadius: 2,\n        categoryPercentage: 1.0,\n        barPercentage: 1.0,\n        order: 2,\n      },\n      {\n        type: \"line\",\n        label: \"Normal PDF (theoretical)\",\n        data: pdfValues,\n        borderColor: t.palette[1],\n        backgroundColor: \"transparent\",\n        borderWidth: 3,\n        pointRadius: 0,\n        tension: 0.35,\n        fill: false,\n        order: 1,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"histogram-density · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 24 },\n      },\n      legend: {\n        labels: {\n          color: t.ink,\n          font: { size: 16 },\n          usePointStyle: true,\n          boxWidth: 24,\n          boxHeight: 12,\n          padding: 20,\n          generateLabels: (chart) =>\n            chart.data.datasets.map((ds, i) => ({\n              text: ds.label,\n              datasetIndex: i,\n              hidden: !chart.isDatasetVisible(i),\n              pointStyle: ds.type === \"line\" ? \"line\" : \"rect\",\n              fillStyle: ds.type === \"line\" ? ds.borderColor : t.palette[0],\n              strokeStyle: ds.type === \"line\" ? ds.borderColor : t.palette[0],\n              lineWidth: ds.type === \"line\" ? 3 : 0,\n            })),\n        },\n      },\n      tooltip: {\n        callbacks: {\n          label: (ctx) => {\n            if (ctx.dataset.type === \"bar\") {\n              const z = ((binCenters[ctx.dataIndex] - mean) / std).toFixed(2);\n              return `${ctx.dataset.label}: ${ctx.parsed.y.toFixed(4)} (z = ${z})`;\n            }\n            return `${ctx.dataset.label}: ${ctx.parsed.y.toFixed(4)}`;\n          },\n        },\n      },\n    },\n    scales: {\n      x: {\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          maxRotation: 0,\n          autoSkip: true,\n          maxTicksLimit: 12,\n        },\n        grid: { display: false },\n        title: {\n          display: true,\n          text: \"Resting Heart Rate (bpm)\",\n          color: t.ink,\n          font: { size: 18 },\n        },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: {\n          display: true,\n          text: \"Density\",\n          color: t.ink,\n          font: { size: 18 },\n        },\n        beginAtZero: true,\n      },\n    },\n  },\n});\n"}