{"spec_id":"pp-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// pp-basic: Probability-Probability (P-P) Plot\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 89/100 | Created: 2026-06-09\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: 200 samples from a right-skewed distribution vs normal reference ---\n// Deterministic LCG for reproducibility (no seeded Math.random in browser)\nlet seed = 987654321;\nfunction lcg() {\n  seed = (seed * 1664525 + 1013904223) >>> 0;\n  return seed / 0x100000000;\n}\n\nfunction randn() {\n  const u1 = Math.max(lcg(), 1e-10);\n  const u2 = lcg();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// 200 samples with slight right skew: z + 0.3*z^2 shifts mass rightward\nconst n = 200;\nconst rawData = Array.from({ length: n }, () => {\n  const z = randn();\n  return z + 0.3 * z * z;\n});\n\n// Sort ascending for empirical CDF\nconst sorted = [...rawData].sort((a, b) => a - b);\n\n// Fit normal: sample mean and standard deviation\nconst mean = sorted.reduce((s, v) => s + v, 0) / n;\nconst std = Math.sqrt(sorted.reduce((s, v) => s + (v - mean) ** 2, 0) / n);\n\n// Error function (Abramowitz & Stegun, max error ~1.5e-7)\nfunction erf(x) {\n  const sign = x >= 0 ? 1 : -1;\n  const a = Math.abs(x);\n  const t1 = 1 / (1 + 0.3275911 * a);\n  const poly =\n    (((1.061405429 * t1 - 1.453152027) * t1 + 1.421413741) * t1 - 0.284496736) * t1 + 0.254829592;\n  return sign * (1 - poly * t1 * Math.exp(-a * a));\n}\n\nfunction normCDF(x, mu, sigma) {\n  return 0.5 * (1 + erf((x - mu) / (sigma * Math.SQRT2)));\n}\n\n// P-P coordinates: (theoretical CDF, empirical CDF) for each sorted observation\nconst ppPoints = sorted.map((val, i) => ({\n  x: normCDF(val, mean, std),\n  y: (i + 1) / (n + 1), // Hazen plotting position\n}));\n\n// 45-degree reference line: perfect distributional fit\nconst refLine = [\n  { x: 0, y: 0 },\n  { x: 1, y: 1 },\n];\n\n// KS 95% confidence band: ±1.36/sqrt(n) from the diagonal\n// Highlights whether S-curve deviation is statistically significant\nconst ksEps = 1.36 / Math.sqrt(n);\nconst xs = Array.from({ length: 101 }, (_, i) => i / 100);\nconst upperBand = xs.map((x) => ({ x, y: Math.min(1, x + ksEps) }));\nconst lowerBand = xs.map((x) => ({ x, y: Math.max(0, x - ksEps) }));\n\n// --- Mount -----------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// Suppress grid lines at axis boundaries (value=0 and value=1) to achieve\n// an L-shaped frame — removes the top and right box spines that Chart.js\n// draws by default when tick grid lines coincide with the chart edge.\nconst innerGrid = (ctx) =>\n  ctx.tick.value <= 0 || ctx.tick.value >= 1 ? \"transparent\" : t.grid;\n\n// --- Chart -----------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: {\n    datasets: [\n      {\n        // Fill from upperBand to lowerBand (index +1) for the confidence envelope\n        label: \"95% confidence band\",\n        data: upperBand,\n        type: \"line\",\n        backgroundColor: t.palette[0] + \"20\",\n        borderColor: \"transparent\",\n        borderWidth: 0,\n        pointRadius: 0,\n        fill: \"+1\",\n        tension: 0,\n        order: 3,\n      },\n      {\n        label: \"_lower\",\n        data: lowerBand,\n        type: \"line\",\n        borderColor: \"transparent\",\n        borderWidth: 0,\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n        order: 3,\n      },\n      {\n        label: \"Reference (perfect fit)\",\n        data: refLine,\n        type: \"line\",\n        borderColor: t.inkSoft,\n        borderWidth: 2,\n        borderDash: [8, 5],\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n        order: 2,\n      },\n      {\n        label: \"Empirical vs Theoretical CDF\",\n        data: ppPoints,\n        backgroundColor: t.palette[0] + \"a6\",\n        borderColor: t.pageBg,\n        borderWidth: 1,\n        pointRadius: 5,\n        pointHoverRadius: 7,\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: \"pp-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"600\" },\n        padding: { top: 10, bottom: 20 },\n      },\n      legend: {\n        labels: {\n          color: t.ink,\n          font: { size: 14 },\n          boxWidth: 14,\n          padding: 16,\n          filter: (item) => !item.text.startsWith(\"_\"),\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: 0,\n        max: 1,\n        border: { display: true, color: t.inkSoft },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          maxTicksLimit: 6,\n        },\n        grid: { color: innerGrid },\n        title: {\n          display: true,\n          text: \"Theoretical CDF (Normal)\",\n          color: t.ink,\n          font: { size: 16 },\n          padding: { top: 12 },\n        },\n      },\n      y: {\n        type: \"linear\",\n        min: 0,\n        max: 1,\n        border: { display: true, color: t.inkSoft },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          maxTicksLimit: 6,\n        },\n        grid: { color: innerGrid },\n        title: {\n          display: true,\n          text: \"Empirical CDF\",\n          color: t.ink,\n          font: { size: 16 },\n          padding: { bottom: 12 },\n        },\n      },\n    },\n  },\n});\n"}