{"spec_id":"histogram-epidemic","library":"muix","language":"javascript","code":"// anyplot.ai\n// histogram-epidemic: Epidemic Curve (Epi Curve)\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-08-26\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { BarPlot } from \"@mui/x-charts/BarChart\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { ChartsLegend } from \"@mui/x-charts/ChartsLegend\";\nimport { ChartsTooltip } from \"@mui/x-charts/ChartsTooltip\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// A fixed-seed LCG keeps the daily noise and the case-type split identical\n// across the light/dark renders.\nlet seed = 42;\nconst lcg = () => {\n  seed = (seed * 1103515245 + 12345) % 2147483648;\n  return seed / 2147483648;\n};\n\nconst DAY_COUNT = 63; // short outbreak → daily bins per the spec's Notes\nconst START_DATE = new Date(2024, 0, 1);\nconst onsetDates = Array.from({ length: DAY_COUNT }, (_, i) => {\n  const d = new Date(START_DATE);\n  d.setDate(d.getDate() + i);\n  return d.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" });\n});\n\n// Two overlapping bell-shaped waves — a propagated (secondary-transmission)\n// outbreak pattern, the shape epi curves are drawn to reveal.\nconst gaussianBump = (day, center, width, peak) => {\n  const z = (day - center) / width;\n  return peak * Math.exp(-0.5 * z * z);\n};\n\nconst dailyCaseCount = Array.from({ length: DAY_COUNT }, (_, day) => {\n  const wave1 = gaussianBump(day, 14, 5, 46);\n  const wave2 = gaussianBump(day, 39, 8, 30);\n  const noise = (lcg() - 0.5) * 6;\n  return Math.max(0, Math.round(wave1 + wave2 + noise));\n});\n\n// Split each day's total into confirmed / probable / suspect so the stack\n// always sums back to `dailyCaseCount`.\nconst confirmedCases = dailyCaseCount.map((n) => Math.round(n * (0.55 + lcg() * 0.1)));\nconst probableCases = dailyCaseCount.map((n, i) =>\n  Math.round((n - confirmedCases[i]) * (0.55 + lcg() * 0.1)),\n);\nconst suspectCases = dailyCaseCount.map((n, i) =>\n  Math.max(0, n - confirmedCases[i] - probableCases[i]),\n);\n\nconst cumulativeCases = dailyCaseCount.reduce((acc, n) => {\n  acc.push((acc.length ? acc[acc.length - 1] : 0) + n);\n  return acc;\n}, []);\n\nconst LOCKDOWN_DAY = 20;\nconst VACCINATION_DAY = 45;\n\nconst series = [\n  {\n    type: \"bar\",\n    xAxisId: \"onset\",\n    yAxisId: \"daily\",\n    stack: \"cases\",\n    data: confirmedCases,\n    label: \"Confirmed\",\n    color: t.palette[0],\n  },\n  {\n    type: \"bar\",\n    xAxisId: \"onset\",\n    yAxisId: \"daily\",\n    stack: \"cases\",\n    data: probableCases,\n    label: \"Probable\",\n    color: t.palette[1],\n  },\n  {\n    type: \"bar\",\n    xAxisId: \"onset\",\n    yAxisId: \"daily\",\n    stack: \"cases\",\n    data: suspectCases,\n    label: \"Suspect\",\n    color: t.palette[2],\n  },\n  {\n    type: \"line\",\n    xAxisId: \"onset\",\n    yAxisId: \"cumulative\",\n    data: cumulativeCases,\n    label: \"Cumulative cases\",\n    color: t.ink,\n    curve: \"monotoneX\",\n    showMark: false,\n  },\n];\n\nconst xAxis = [\n  {\n    id: \"onset\",\n    scaleType: \"band\",\n    data: onsetDates,\n    label: \"Onset date\",\n    tickLabelInterval: (_value, index) => index % 7 === 0,\n  },\n];\n\nconst yAxis = [\n  { id: \"daily\", position: \"left\", label: \"Daily new cases\" },\n  { id: \"cumulative\", position: \"right\", label: \"Cumulative cases\" },\n];\n\nconst title = \"histogram-epidemic · javascript · muix · anyplot.ai\";\nconst TITLE_HEIGHT = 44;\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  const chartHeight = window.ANYPLOT_SIZE.height - TITLE_HEIGHT;\n\n  return (\n    <div style={{ width: window.ANYPLOT_SIZE.width, height: window.ANYPLOT_SIZE.height }}>\n      <Typography\n        align=\"center\"\n        style={{ height: TITLE_HEIGHT, fontSize: 22, fontWeight: 500, color: t.ink }}\n      >\n        {title}\n      </Typography>\n      <ChartContainer\n        width={window.ANYPLOT_SIZE.width}\n        height={chartHeight}\n        series={series}\n        xAxis={xAxis}\n        yAxis={yAxis}\n        margin={{ top: 70, right: 120, bottom: 80, left: 120 }}\n        skipAnimation\n      >\n        <ChartsGrid horizontal sx={{ \"& .MuiChartsGrid-line\": { stroke: t.grid, strokeWidth: 1 } }} />\n        <BarPlot />\n        <LinePlot />\n        <ChartsXAxis\n          axisId=\"onset\"\n          tickLabelStyle={{ fontSize: 14, fill: t.inkSoft }}\n          labelStyle={{ fontSize: 16, fill: t.ink }}\n          sx={{ \"& .MuiChartsAxis-line\": { stroke: t.inkSoft } }}\n        />\n        <ChartsYAxis\n          axisId=\"daily\"\n          tickLabelStyle={{ fontSize: 14, fill: t.inkSoft }}\n          labelStyle={{ fontSize: 16, fill: t.ink }}\n          sx={{ \"& .MuiChartsAxis-line\": { stroke: t.inkSoft } }}\n        />\n        <ChartsYAxis\n          axisId=\"cumulative\"\n          tickLabelStyle={{ fontSize: 14, fill: t.inkSoft }}\n          labelStyle={{ fontSize: 16, fill: t.ink }}\n          // Right-axis label sits well clear of the tick-number column: MUI X's\n          // default label offset is a fixed heuristic based on `tickFontSize`\n          // (12px default), not the actual rendered tick text width, so with our\n          // larger 14px 3-digit ticks (\"700\") the stock offset lands the rotated\n          // \"Cumulative cases\" label directly on top of them.\n          slotProps={{ axisLabel: { x: 62 } }}\n          sx={{ \"& .MuiChartsAxis-line\": { stroke: t.inkSoft } }}\n        />\n        <ChartsReferenceLine\n          x={onsetDates[LOCKDOWN_DAY]}\n          axisId=\"onset\"\n          label=\"Lockdown start\"\n          labelStyle={{ fontSize: 13, fill: t.ink }}\n          lineStyle={{ stroke: t.amber, strokeDasharray: \"6 4\", strokeWidth: 2 }}\n        />\n        <ChartsReferenceLine\n          x={onsetDates[VACCINATION_DAY]}\n          axisId=\"onset\"\n          label=\"Vaccination campaign\"\n          labelStyle={{ fontSize: 13, fill: t.ink }}\n          lineStyle={{ stroke: t.amber, strokeDasharray: \"6 4\", strokeWidth: 2 }}\n        />\n        <ChartsLegend\n          position={{ vertical: \"top\", horizontal: \"middle\" }}\n          direction=\"row\"\n          labelStyle={{ fontSize: 14, fill: t.ink }}\n        />\n        <ChartsTooltip trigger=\"item\" />\n      </ChartContainer>\n    </div>\n  );\n}\n"}