{"spec_id":"acf-pacf","library":"d3","language":"javascript","code":"// anyplot.ai\n// acf-pacf: Autocorrelation and Partial Autocorrelation (ACF/PACF) Plot\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 90/100 | Created: 2026-06-10\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// Deterministic LCG (no seeded RNG in browser)\nlet _seed = 42;\nfunction lcg() {\n  _seed = (Math.imul(_seed, 1664525) + 1013904223) >>> 0;\n  return _seed / 4294967296;\n}\nfunction randn() {\n  return Math.sqrt(-2 * Math.log(Math.max(lcg(), 1e-12))) * Math.cos(2 * Math.PI * lcg());\n}\n\n// AR(2) series: x_t = 0.7·x_{t-1} − 0.3·x_{t-2} + ε_t\n// Stationary; PACF cuts off after lag 2 — canonical AR-order diagnostic\nconst N = 200;\nconst series = [randn(), 0.7 * randn() + randn()];\nfor (let i = 2; i < N; i++) {\n  series.push(0.7 * series[i - 1] - 0.3 * series[i - 2] + randn());\n}\n\n// ACF: r(k) = Cov(x_t, x_{t-k}) / Var(x)\nfunction computeACF(x, maxLag) {\n  const n = x.length;\n  const mean = x.reduce((s, v) => s + v, 0) / n;\n  const variance = x.reduce((s, v) => s + (v - mean) ** 2, 0) / n;\n  return Array.from({ length: maxLag + 1 }, (_, k) => {\n    if (k === 0) return 1.0;\n    let cov = 0;\n    for (let i = k; i < n; i++) cov += (x[i] - mean) * (x[i - k] - mean);\n    return cov / (n * variance);\n  });\n}\n\n// PACF via Durbin-Levinson: phi_{k,k} is partial autocorrelation at lag k\nfunction computePACF(acf, maxLag) {\n  const phi = Array.from({ length: maxLag + 1 }, () => new Array(maxLag + 1).fill(0));\n  const pacf = new Array(maxLag + 1).fill(0);\n  pacf[0] = 1.0;\n  for (let k = 1; k <= maxLag; k++) {\n    let num = acf[k], den = 1.0;\n    for (let j = 1; j < k; j++) {\n      num -= phi[k - 1][j] * acf[k - j];\n      den -= phi[k - 1][j] * acf[j];\n    }\n    phi[k][k] = Math.abs(den) > 1e-10 ? num / den : 0;\n    for (let j = 1; j < k; j++) {\n      phi[k][j] = phi[k - 1][j] - phi[k][k] * phi[k - 1][k - j];\n    }\n    pacf[k] = phi[k][k];\n  }\n  return pacf;\n}\n\nconst maxLag = 30;\nconst acfVals = computeACF(series, maxLag);\nconst pacfVals = computePACF(acfVals, maxLag);\nconst confBand = 1.96 / Math.sqrt(N);\n\n// Layout\nconst margin = { top: 70, right: 60, bottom: 70, left: 80 };\nconst iw = width - margin.left - margin.right;\nconst panelGap = 40;\nconst panelH = (height - margin.top - margin.bottom - panelGap) / 2;\n\nconst svg = d3\n  .select(\"#container\")\n  .append(\"svg\")\n  .attr(\"width\", width)\n  .attr(\"height\", height);\n\nconst root = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// Draw an ACF or PACF panel\nfunction drawPanel(g, data, yLabel, color, yTop, showXLabels) {\n  const vals = data.map((d) => d.val);\n  const vMin = Math.min(d3.min(vals), -confBand) - 0.08;\n  const vMax = Math.max(d3.max(vals), confBand, yLabel === \"ACF\" ? 1.0 : 0) + 0.08;\n\n  const xScale = d3.scaleLinear().domain([-0.5, maxLag + 0.5]).range([0, iw]);\n  const yScale = d3.scaleLinear().domain([vMin, vMax]).nice().range([panelH, 0]);\n\n  const pg = g.append(\"g\").attr(\"transform\", `translate(0,${yTop})`);\n\n  // Horizontal gridlines\n  for (const tick of yScale.ticks(5)) {\n    pg.append(\"line\")\n      .attr(\"x1\", 0)\n      .attr(\"x2\", iw)\n      .attr(\"y1\", yScale(tick))\n      .attr(\"y2\", yScale(tick))\n      .attr(\"stroke\", t.grid)\n      .attr(\"stroke-width\", 1);\n  }\n\n  // Zero baseline\n  pg.append(\"line\")\n    .attr(\"x1\", 0)\n    .attr(\"x2\", iw)\n    .attr(\"y1\", yScale(0))\n    .attr(\"y2\", yScale(0))\n    .attr(\"stroke\", t.inkSoft)\n    .attr(\"stroke-width\", 1.5)\n    .attr(\"opacity\", 0.5);\n\n  // Confidence band fill (amber, very subtle)\n  pg.append(\"rect\")\n    .attr(\"x\", 0)\n    .attr(\"width\", iw)\n    .attr(\"y\", yScale(confBand))\n    .attr(\"height\", yScale(-confBand) - yScale(confBand))\n    .attr(\"fill\", \"#DDCC77\")\n    .attr(\"opacity\", 0.08);\n\n  // Confidence band dashed boundary lines\n  for (const cb of [confBand, -confBand]) {\n    pg.append(\"line\")\n      .attr(\"x1\", 0)\n      .attr(\"x2\", iw)\n      .attr(\"y1\", yScale(cb))\n      .attr(\"y2\", yScale(cb))\n      .attr(\"stroke\", \"#DDCC77\")\n      .attr(\"stroke-width\", 1.5)\n      .attr(\"stroke-dasharray\", \"8,5\");\n  }\n\n  // Stem lines\n  pg.selectAll(\".stem\")\n    .data(data)\n    .join(\"line\")\n    .attr(\"class\", \"stem\")\n    .attr(\"x1\", (d) => xScale(d.lag))\n    .attr(\"x2\", (d) => xScale(d.lag))\n    .attr(\"y1\", yScale(0))\n    .attr(\"y2\", (d) => yScale(d.val))\n    .attr(\"stroke\", color)\n    .attr(\"stroke-width\", 2.5);\n\n  // Tip markers\n  pg.selectAll(\".dot\")\n    .data(data)\n    .join(\"circle\")\n    .attr(\"class\", \"dot\")\n    .attr(\"cx\", (d) => xScale(d.lag))\n    .attr(\"cy\", (d) => yScale(d.val))\n    .attr(\"r\", 4.5)\n    .attr(\"fill\", color)\n    .attr(\"stroke\", t.pageBg)\n    .attr(\"stroke-width\", 1);\n\n  // Y axis\n  const yAx = pg.append(\"g\").call(d3.axisLeft(yScale).ticks(5).tickSize(5));\n  yAx.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"13px\");\n  yAx.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\n  yAx.select(\".domain\").attr(\"stroke\", t.inkSoft);\n\n  // X axis\n  const xAx = pg\n    .append(\"g\")\n    .attr(\"transform\", `translate(0,${panelH})`)\n    .call(d3.axisBottom(xScale).ticks(10).tickSize(5));\n  if (showXLabels) {\n    xAx.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"13px\");\n  } else {\n    xAx.selectAll(\"text\").remove();\n  }\n  xAx.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\n  xAx.select(\".domain\").attr(\"stroke\", t.inkSoft);\n\n  // Y-axis label (rotated)\n  pg.append(\"text\")\n    .attr(\"transform\", `translate(${-60},${panelH / 2}) rotate(-90)`)\n    .attr(\"text-anchor\", \"middle\")\n    .attr(\"fill\", t.ink)\n    .style(\"font-size\", \"15px\")\n    .style(\"font-weight\", \"600\")\n    .text(yLabel);\n}\n\n// ACF panel: lags 0 … maxLag (lag 0 = 1.0)\ndrawPanel(\n  root,\n  acfVals.map((v, k) => ({ lag: k, val: v })),\n  \"ACF\",\n  t.palette[0],\n  0,\n  false\n);\n\n// PACF panel: lags 1 … maxLag (skip lag 0)\ndrawPanel(\n  root,\n  pacfVals.slice(1).map((v, i) => ({ lag: i + 1, val: v })),\n  \"PACF\",\n  t.palette[2],\n  panelH + panelGap,\n  true\n);\n\n// X-axis label\nsvg\n  .append(\"text\")\n  .attr(\"x\", margin.left + iw / 2)\n  .attr(\"y\", height - 22)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"14px\")\n  .text(\"Lag\");\n\n// Title\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 44)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"Monthly Retail Sales · acf-pacf · javascript · d3 · anyplot.ai\");\n"}