{"spec_id":"band-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// band-basic: Basic Band Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-08-24\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// 30-day revenue forecast with a widening 95% confidence interval — the\n// classic fan-chart shape: uncertainty compounds the further out the\n// forecast reaches.\nlet seed = 42;\nconst rand = () => {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n};\n\nconst days = Array.from({ length: 45 }, (_, i) => i + 1);\nconst revenueCenter = days.map(\n  (day) => 100 + 2.4 * day + 14 * Math.sin(day / 6)\n);\nconst revenueUpper = days.map((day, i) => {\n  const spread = 3 + 0.85 * day + (rand() - 0.5) * 2.5;\n  return revenueCenter[i] + spread;\n});\nconst revenueLower = days.map((day, i) => {\n  const spread = 3 + 0.85 * day + (rand() - 0.5) * 2.5;\n  return revenueCenter[i] - spread;\n});\n\nconst ciWidthAtEnd = revenueUpper[days.length - 1] - revenueLower[days.length - 1];\n\n// --- Mount -----------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Custom plugin: callout labeling the CI width at the forecast horizon --\n// Chart.js core exposes a plugin hook (afterDraw) rather than requiring the\n// unpinned chartjs-plugin-annotation package — a distinctive, library-native\n// way to reinforce the widening-uncertainty story with a direct callout.\nconst ciCalloutPlugin = {\n  id: \"ciCallout\",\n  afterDraw(chart) {\n    const { ctx, scales } = chart;\n    const lastIndex = days.length - 1;\n    const x = scales.x.getPixelForValue(lastIndex);\n    const yTop = scales.y.getPixelForValue(revenueUpper[lastIndex]);\n    const yBottom = scales.y.getPixelForValue(revenueLower[lastIndex]);\n\n    ctx.save();\n    ctx.strokeStyle = `${t.palette[0]}80`;\n    ctx.lineWidth = 1;\n    ctx.setLineDash([3, 3]);\n    ctx.beginPath();\n    ctx.moveTo(x, yTop);\n    ctx.lineTo(x, yBottom);\n    ctx.stroke();\n\n    ctx.setLineDash([]);\n    ctx.fillStyle = t.ink;\n    ctx.font = \"600 14px sans-serif\";\n    ctx.textAlign = \"right\";\n    ctx.textBaseline = \"bottom\";\n    ctx.fillText(`±$${(ciWidthAtEnd / 2).toFixed(0)}K CI`, x - 10, yTop - 8);\n    ctx.restore();\n  },\n};\n\n// --- Chart -----------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  plugins: [ciCalloutPlugin],\n  data: {\n    labels: days,\n    datasets: [\n      {\n        label: \"\",\n        data: revenueLower,\n        borderWidth: 0,\n        pointRadius: 0,\n        fill: false,\n        tension: 0.35,\n      },\n      {\n        label: \"95% Confidence Interval\",\n        data: revenueUpper,\n        borderWidth: 1.5,\n        borderColor: `${t.palette[0]}66`,\n        pointRadius: 0,\n        backgroundColor: (context) => {\n          const { chart } = context;\n          const { chartArea } = chart;\n          if (!chartArea) return `${t.palette[0]}59`;\n          const gradient = chart.ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);\n          gradient.addColorStop(0, `${t.palette[0]}59`); // ~35% alpha near the line\n          gradient.addColorStop(1, `${t.palette[0]}33`); // ~20% alpha toward the axis\n          return gradient;\n        },\n        fill: \"-1\",\n        tension: 0.35,\n      },\n      {\n        label: \"Forecast\",\n        data: revenueCenter,\n        borderColor: t.palette[0],\n        backgroundColor: t.palette[0],\n        borderWidth: 3,\n        pointRadius: 0,\n        fill: false,\n        tension: 0.35,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"band-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n      },\n      legend: {\n        labels: {\n          color: t.ink,\n          font: { size: 16 },\n          filter: (item) => item.text,\n        },\n      },\n    },\n    scales: {\n      x: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { display: false },\n        title: { display: true, text: \"Day\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: {\n          display: true,\n          text: \"Revenue ($K)\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n      },\n    },\n  },\n});\n"}