{"spec_id":"qq-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// qq-basic: Basic Q-Q Plot\n// Library: d3 7.9.0 | JavaScript 22.23.1\n// Quality: 89/100 | Created: 2026-07-24\n//# anyplot-orientation: square\n\n// --- Theme + mount ------------------------------------------------------\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Deterministic RNG (mulberry32) + standard-normal sampling ----------\nfunction mulberry32(seed) {\n  let s = seed >>> 0;\n  return function () {\n    s = (s + 0x6d2b79f5) | 0;\n    let x = Math.imul(s ^ (s >>> 15), 1 | s);\n    x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;\n    return ((x ^ (x >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\n// Acklam's rational approximation of the standard-normal inverse CDF (probit).\nfunction probit(p) {\n  const a = [-3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2, 1.38357751867269e2, -3.066479806614716e1, 2.506628277459239];\n  const b = [-5.447609879822406e1, 1.615858368580409e2, -1.556989798598866e2, 6.680131188771972e1, -1.328068155288572e1];\n  const c = [-7.784894002430293e-3, -3.223964580411365e-1, -2.400758277161838, -2.549732539343734, 4.374664141464968, 2.938163982698783];\n  const d = [7.784695709041462e-3, 3.224671290700398e-1, 2.445134137142996, 3.754408661907416];\n  const pLow = 0.02425;\n  if (p < pLow) {\n    const q = Math.sqrt(-2 * Math.log(p));\n    return (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);\n  }\n  if (p <= 1 - pLow) {\n    const q = p - 0.5;\n    const r = q * q;\n    return ((((( a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);\n  }\n  const q = Math.sqrt(-2 * Math.log(1 - p));\n  return -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);\n}\n\n// --- Data: reaction times (ms) from a cognitive-experiment trial --------\n// Right-skewed by construction (typical of RT data) so the sample visibly\n// bows away from the normal reference line in the upper tail, plus two\n// explicit slow-trial outliers.\nconst rng = mulberry32(20260724);\nconst MU0 = 250;\nconst SIGMA0 = 35;\nconst SKEW = 20;\nconst n0 = 158;\nconst sample = [];\nfor (let i = 0; i < n0; i += 2) {\n  const u1 = rng();\n  const u2 = rng();\n  const mag = Math.sqrt(-2 * Math.log(u1));\n  const z0 = mag * Math.cos(2 * Math.PI * u2);\n  const z1 = mag * Math.sin(2 * Math.PI * u2);\n  for (const z of [z0, z1]) {\n    const value = MU0 + SIGMA0 * z + SKEW * Math.max(z, 0) ** 2;\n    sample.push(value);\n  }\n}\nsample.push(455, 480); // two slow-trial outliers\nsample.sort((a, b) => a - b);\n\nconst n = sample.length;\nconst muHat = d3.mean(sample);\nconst sigmaHat = d3.deviation(sample);\nconst points = sample.map((value, i) => {\n  const p = (i + 0.5) / n;\n  return { theoretical: muHat + sigmaHat * probit(p), sample: value };\n});\n\n// --- Layout ---------------------------------------------------------------\n// Equal left+right / top+bottom margin sums keep the plot area square so the\n// y = x reference line renders at a true visual 45°.\nconst margin = { top: 150, right: 110, bottom: 100, left: 160 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Scales -----------------------------------------------------------------\nconst allValues = points.flatMap((d) => [d.theoretical, d.sample]);\nconst [rawMin, rawMax] = d3.extent(allValues);\nconst pad = (rawMax - rawMin) * 0.06;\nconst domain = [rawMin - pad, rawMax + pad];\n\nconst x = d3.scaleLinear().domain(domain).range([0, iw]);\nconst y = d3.scaleLinear().domain(domain).range([ih, 0]);\n\n// --- SVG mount --------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Gridlines (both axes, subtle) ------------------------------------------\ng.append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(7).tickSize(-ih).tickFormat(\"\"))\n  .call((sel) => sel.select(\".domain\").remove())\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\n\ng.append(\"g\")\n  .call(d3.axisLeft(y).ticks(7).tickSize(-iw).tickFormat(\"\"))\n  .call((sel) => sel.select(\".domain\").remove())\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\n\n// --- Reference line y = x (normal distribution fit to the sample) ----------\ng.append(\"line\")\n  .attr(\"x1\", x(domain[0]))\n  .attr(\"y1\", y(domain[0]))\n  .attr(\"x2\", x(domain[1]))\n  .attr(\"y2\", y(domain[1]))\n  .attr(\"stroke\", t.ink)\n  .attr(\"stroke-width\", 2.5)\n  .attr(\"stroke-dasharray\", \"9,6\")\n  .attr(\"opacity\", 0.55);\n\ng.append(\"text\")\n  .attr(\"x\", x(domain[1]) - 14)\n  .attr(\"y\", y(domain[1]) - 14)\n  .attr(\"text-anchor\", \"end\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"14px\")\n  .style(\"font-style\", \"italic\")\n  .text(\"Normal fit (y = x)\");\n\n// --- Sample points ------------------------------------------------------\ng.selectAll(\"circle\")\n  .data(points)\n  .join(\"circle\")\n  .attr(\"cx\", (d) => x(d.theoretical))\n  .attr(\"cy\", (d) => y(d.sample))\n  .attr(\"r\", 6)\n  .attr(\"fill\", t.palette[0])\n  .attr(\"fill-opacity\", 0.72)\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1.2);\n\n// --- Outlier callout (the two explicit slow-trial outliers) ----------------\nconst outlierValues = [455, 480];\nconst outlierPts = points.filter((d) => outlierValues.includes(d.sample));\nconst outlierCx = d3.mean(outlierPts, (d) => x(d.theoretical));\nconst outlierTopY = d3.min(outlierPts, (d) => y(d.sample));\ng.append(\"text\")\n  .attr(\"x\", outlierCx)\n  .attr(\"y\", outlierTopY - 20)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"13px\")\n  .style(\"font-style\", \"italic\")\n  .text(\"2 slow-trial outliers\");\n\n// --- Axes ----------------------------------------------------------------\nconst xAxis = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`).call(d3.axisBottom(x).ticks(7));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(7));\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.inkSoft);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\n\n// --- Axis labels -----------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", margin.left + iw / 2)\n  .attr(\"y\", height - 34)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"17px\")\n  .text(\"Theoretical Quantiles (ms)\");\n\nsvg.append(\"text\")\n  .attr(\"transform\", `translate(${44},${margin.top + ih / 2}) rotate(-90)`)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"17px\")\n  .text(\"Sample Quantiles (ms)\");\n\n// --- Title -------------------------------------------------------------\nconst title = \"Reaction Times vs Normal · qq-basic · javascript · d3 · anyplot.ai\";\nconst titleFontSize = Math.round(22 * Math.min(1, 67 / title.length));\nsvg.append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 66)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", `${titleFontSize}px`)\n  .style(\"font-weight\", \"600\")\n  .text(title);\n"}