{"spec_id":"histogram-epidemic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// histogram-epidemic: Epidemic Curve (Epi Curve)\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-08-26\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Simulates a 70-day foodborne-then-propagated outbreak of symptom-onset dates:\n// a sharp point-source peak (contaminated batch) followed by a broader second\n// wave (person-to-person spread). Confirmed/probable/suspect mirrors real\n// surveillance triage; the confirmed share rises over time as lab capacity\n// catches up with the caseload.\nlet seed = 42;\nconst nextRand = () => {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n};\n\nconst DAYS = 70;\nconst START_DATE = new Date(\"2024-04-01T00:00:00Z\");\nconst MONTH_ABBR = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n\nconst gaussian = (x, mean, std, amplitude) => amplitude * Math.exp(-((x - mean) ** 2) / (2 * std * std));\n\nconst dateLabels = [];\nconst confirmed = [];\nconst probable = [];\nconst suspect = [];\nconst cumulative = [];\nlet runningTotal = 0;\n\nfor (let day = 0; day < DAYS; day++) {\n  const d = new Date(START_DATE);\n  d.setUTCDate(d.getUTCDate() + day);\n  dateLabels.push(`${MONTH_ABBR[d.getUTCMonth()]} ${d.getUTCDate()}`);\n\n  const pointSource = gaussian(day, 12, 3.5, 38);\n  const propagatedWave = gaussian(day, 42, 10, 52);\n  const dailyTotal = Math.max(0, Math.round(pointSource + propagatedWave + (nextRand() - 0.5) * 5));\n\n  // Lab confirmation capacity improves as the outbreak matures.\n  const confirmedShare = 0.45 + 0.35 * (day / (DAYS - 1));\n  const confirmedCount = Math.round(dailyTotal * confirmedShare);\n  const remaining = dailyTotal - confirmedCount;\n  const probableCount = Math.round(remaining * 0.6);\n  const suspectCount = remaining - probableCount;\n\n  confirmed.push(confirmedCount);\n  probable.push(probableCount);\n  suspect.push(suspectCount);\n\n  runningTotal += confirmedCount + probableCount + suspectCount;\n  cumulative.push(runningTotal);\n}\n\n// Intervention events called out on the epi curve, per surveillance convention.\nconst interventions = [\n  { dayIndex: 16, label: \"Contaminated batch recalled\" },\n  { dayIndex: 46, label: \"Contact-tracing expanded\" },\n];\n\n// Dashed vertical markers + labels for the intervention events, drawn with\n// Chart.js's native per-chart plugin hook (no external annotation plugin).\nconst interventionMarkerPlugin = {\n  id: \"interventionMarkers\",\n  afterDraw(chart) {\n    const { ctx, chartArea, scales } = chart;\n    ctx.save();\n    interventions.forEach((event, i) => {\n      const x = scales.x.getPixelForValue(dateLabels[event.dayIndex], event.dayIndex);\n      ctx.strokeStyle = t.amber;\n      ctx.lineWidth = 2;\n      ctx.setLineDash([6, 4]);\n      ctx.beginPath();\n      ctx.moveTo(x, chartArea.top);\n      ctx.lineTo(x, chartArea.bottom);\n      ctx.stroke();\n\n      ctx.setLineDash([]);\n      ctx.fillStyle = t.ink;\n      ctx.font = \"600 14px sans-serif\";\n      ctx.textAlign = \"left\";\n      ctx.textBaseline = \"top\";\n      ctx.fillText(event.label, x + 8, chartArea.top + 8 + i * 20);\n    });\n    ctx.restore();\n  },\n};\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart -------------------------------------------------------------------\nnew Chart(canvas, {\n  data: {\n    labels: dateLabels,\n    datasets: [\n      {\n        type: \"bar\",\n        label: \"Confirmed\",\n        data: confirmed,\n        backgroundColor: t.palette[0],\n        stack: \"cases\",\n        yAxisID: \"y\",\n      },\n      {\n        type: \"bar\",\n        label: \"Probable\",\n        data: probable,\n        backgroundColor: t.palette[1],\n        stack: \"cases\",\n        yAxisID: \"y\",\n      },\n      {\n        type: \"bar\",\n        label: \"Suspect\",\n        data: suspect,\n        backgroundColor: t.palette[2],\n        stack: \"cases\",\n        yAxisID: \"y\",\n      },\n      {\n        type: \"line\",\n        label: \"Cumulative cases\",\n        data: cumulative,\n        borderColor: t.palette[3],\n        backgroundColor: t.palette[3],\n        borderWidth: 2.5,\n        pointRadius: 0,\n        tension: 0.2,\n        yAxisID: \"y1\",\n      },\n    ],\n  },\n  plugins: [interventionMarkerPlugin],\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    interaction: { intersect: false, mode: \"index\" },\n    layout: { padding: { top: 40, right: 12, bottom: 4, left: 4 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"histogram-epidemic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"bold\" },\n        padding: { top: 4, bottom: 18 },\n      },\n      legend: {\n        labels: { color: t.ink, font: { size: 16 }, usePointStyle: true, pointStyle: \"rectRounded\", padding: 18 },\n      },\n    },\n    scales: {\n      x: {\n        stacked: true,\n        ticks: { color: t.inkSoft, font: { size: 14 }, maxTicksLimit: 12, maxRotation: 0 },\n        grid: { display: false },\n        title: { display: true, text: \"Symptom onset date\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        stacked: true,\n        beginAtZero: true,\n        position: \"left\",\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"New cases per day\", color: t.ink, font: { size: 16 } },\n      },\n      y1: {\n        beginAtZero: true,\n        position: \"right\",\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { display: false },\n        title: { display: true, text: \"Cumulative cases\", color: t.ink, font: { size: 16 } },\n      },\n    },\n  },\n});\n"}