{"spec_id":"line-timeseries-rolling","library":"d3","language":"javascript","code":"// anyplot.ai\n// line-timeseries-rolling: Time Series with Rolling Average Overlay\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 90, right: 60, bottom: 70, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Daily website engagement score over ~120 days with a 14-day rolling average.\nfunction lcg(seed) {\n  let s = seed;\n  return () => {\n    s = (s * 1664525 + 1013904223) % 4294967296;\n    return s / 4294967296;\n  };\n}\nconst rand = lcg(42);\n\nconst numDays = 120;\nconst windowSize = 14;\nconst startDate = new Date(2025, 3, 1);\n\nconst rawSeries = [];\nlet level = 62;\nfor (let i = 0; i < numDays; i++) {\n  const seasonal = 8 * Math.sin((i / numDays) * Math.PI * 2.4);\n  const noise = (rand() - 0.5) * 14;\n  level += (rand() - 0.5) * 1.2;\n  const value = Math.max(5, level + seasonal + noise);\n  const date = new Date(startDate);\n  date.setDate(date.getDate() + i);\n  rawSeries.push({ date, value });\n}\n\nconst rollingSeries = [];\nfor (let i = windowSize - 1; i < rawSeries.length; i++) {\n  let sum = 0;\n  for (let j = i - windowSize + 1; j <= i; j++) sum += rawSeries[j].value;\n  rollingSeries.push({ date: rawSeries[i].date, value: sum / windowSize });\n}\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// --- Scales ---------------------------------------------------------------\nconst x = d3\n  .scaleTime()\n  .domain(d3.extent(rawSeries, (d) => d.date))\n  .range([0, iw]);\nconst y = d3\n  .scaleLinear()\n  .domain([0, d3.max(rawSeries, (d) => d.value) * 1.08])\n  .nice()\n  .range([ih, 0]);\n\n// --- Gridlines (both axes) ---------------------------------------------------\ng.append(\"g\")\n  .attr(\"class\", \"grid grid-y\")\n  .call(d3.axisLeft(y).tickSize(-iw).tickFormat(\"\"))\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\ng.select(\".grid-y .domain\").remove();\n\ng.append(\"g\")\n  .attr(\"class\", \"grid grid-x\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).tickSize(-ih).tickFormat(\"\"))\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\ng.select(\".grid-x .domain\").remove();\n\n// --- Axes -------------------------------------------------------------------\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(8).tickFormat(d3.timeFormat(\"%b %d\")));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6));\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.grid);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\n\n// --- Raw data: thin, semi-transparent line -----------------------------------\nconst rawLine = d3\n  .line()\n  .x((d) => x(d.date))\n  .y((d) => y(d.value))\n  .curve(d3.curveMonotoneX);\n\ng.append(\"path\")\n  .datum(rawSeries)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 1.5)\n  .attr(\"stroke-opacity\", 0.35)\n  .attr(\"d\", rawLine);\n\n// --- Rolling average: prominent, smooth, contrasting color -------------------\nconst rollingLine = d3\n  .line()\n  .x((d) => x(d.date))\n  .y((d) => y(d.value))\n  .curve(d3.curveMonotoneX);\n\n// Subtle halo behind the rolling-average line to lift it further above the noisy raw data.\ng.append(\"path\")\n  .datum(rollingSeries)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 7)\n  .attr(\"stroke-opacity\", 0.6)\n  .attr(\"d\", rollingLine);\n\ng.append(\"path\")\n  .datum(rollingSeries)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[1])\n  .attr(\"stroke-width\", 4)\n  .attr(\"d\", rollingLine);\n\n// --- Legend -------------------------------------------------------------------\nconst legendData = [\n  { label: \"Raw Data\", color: t.palette[0], opacity: 0.35, width: 3 },\n  { label: `Rolling Average (${windowSize}-Day)`, color: t.palette[1], opacity: 1, width: 5 },\n];\nconst legendBoxWidth = 300;\nconst legendBoxHeight = 66;\nconst legend = g.append(\"g\").attr(\"transform\", `translate(${iw - legendBoxWidth - 14}, 14)`);\nlegend\n  .append(\"rect\")\n  .attr(\"width\", legendBoxWidth)\n  .attr(\"height\", legendBoxHeight)\n  .attr(\"rx\", 8)\n  .attr(\"fill\", t.elevatedBg)\n  .attr(\"stroke\", t.grid);\nlegendData.forEach((d, i) => {\n  const row = legend.append(\"g\").attr(\"transform\", `translate(18, ${20 + i * 28})`);\n  row\n    .append(\"line\")\n    .attr(\"x1\", 0)\n    .attr(\"x2\", 32)\n    .attr(\"y1\", 0)\n    .attr(\"y2\", 0)\n    .attr(\"stroke\", d.color)\n    .attr(\"stroke-opacity\", d.opacity)\n    .attr(\"stroke-width\", d.width);\n  row\n    .append(\"text\")\n    .attr(\"x\", 42)\n    .attr(\"y\", 5)\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"15px\")\n    .text(d.label);\n});\n\n// --- Labels -------------------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 55)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Date\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -65)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Engagement Score\");\n\n// --- Title --------------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 48)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"line-timeseries-rolling · javascript · d3 · anyplot.ai\");\n"}