{"spec_id":"line-annotated-events","library":"muix","language":"javascript","code":"// anyplot.ai\n// line-annotated-events: Annotated Line Plot with Event Markers\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 96/100 | Created: 2026-09-05\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { useXScale, useDrawingArea } from \"@mui/x-charts/hooks\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG PRNG — no fetch, no Math.random) ----\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\nconst DAYS = 270; // roughly Jan through Sep\nconst START_DATE = new Date(2025, 0, 1);\nconst dates: Date[] = [];\nfor (let i = 0; i < DAYS; i += 1) {\n  const d = new Date(START_DATE);\n  d.setDate(d.getDate() + i);\n  dates.push(d);\n}\n\n// Product/operational events that shift the daily-active-user trend. `bump`\n// adds a one-time step to the running total the day the event lands — most\n// launches step the level up, but the outage steps it down, showing both\n// impact directions.\nconst EVENTS = [\n  { dayIndex: 34, label: \"Beta Launch\", bump: 6 },\n  { dayIndex: 96, label: \"Mobile App Release\", bump: 13 },\n  { dayIndex: 130, label: \"Service Outage\", bump: -15 },\n  { dayIndex: 162, label: \"API v2 Launch\", bump: 9 },\n  { dayIndex: 228, label: \"Enterprise Tier\", bump: 17 },\n];\nconst bumpByDay = new Map(EVENTS.map((e) => [e.dayIndex, e.bump]));\n\nconst dailyActiveUsers: number[] = [];\nlet level = 42; // thousands of daily active users\nfor (let i = 0; i < DAYS; i += 1) {\n  const drift = 0.11;\n  const noise = (rand() - 0.5) * 1.6;\n  level = Math.max(5, level + drift + noise + (bumpByDay.get(i) ?? 0));\n  dailyActiveUsers.push(Math.round(level * 10) / 10);\n}\n\nconst dataset = dates.map((date, i) => ({ date, dau: dailyActiveUsers[i] }));\n\n// One tick per calendar month — the time scale's \"auto\" tick picker otherwise\n// crams in a tick every ~9 days, repeating the same month label many times.\nconst monthTicks: Date[] = [];\n{\n  const cursor = new Date(dates[0].getFullYear(), dates[0].getMonth(), 1);\n  const last = dates[dates.length - 1];\n  while (cursor <= last) {\n    monthTicks.push(new Date(cursor));\n    cursor.setMonth(cursor.getMonth() + 1);\n  }\n}\n\nconst TITLE = \"line-annotated-events · javascript · muix · anyplot.ai\";\n\n// Subtle shaded region marking the sustained growth phase after the largest\n// step-up (Enterprise Tier) — a deliberate accent beyond the reference lines\n// themselves, reading the scale through the chart's own x-axis via hooks.\nfunction GrowthPhaseBand({ from, color }: { from: Date; color: string }) {\n  const xScale = useXScale();\n  const { top, height, left, width } = useDrawingArea();\n  const xStart = xScale(from);\n  if (xStart == null || Number.isNaN(xStart)) return null;\n  const bandWidth = left + width - xStart;\n  if (bandWidth <= 0) return null;\n  return (\n    <rect\n      x={xStart}\n      y={top}\n      width={bandWidth}\n      height={height}\n      fill={color}\n      fillOpacity={0.07}\n      pointerEvents=\"none\"\n    />\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) -----------\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n  const titleSize =\n    TITLE.length > 67 ? Math.max(14, Math.round((22 * 67) / TITLE.length)) : 22;\n\n  const TITLE_H = 64;\n  const chartH = H - TITLE_H;\n\n  return (\n    <Box\n      sx={{\n        width: W,\n        height: H,\n        bgcolor: t.pageBg,\n        display: \"flex\",\n        flexDirection: \"column\",\n        fontFamily: \"'Roboto', 'Helvetica Neue', Arial, sans-serif\",\n        boxSizing: \"border-box\",\n      }}\n    >\n      <Box\n        sx={{\n          height: TITLE_H,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n        }}\n      >\n        <Typography sx={{ color: t.ink, fontSize: titleSize, fontWeight: 600 }}>\n          {TITLE}\n        </Typography>\n      </Box>\n\n      <LineChart\n        width={W}\n        height={chartH}\n        skipAnimation\n        dataset={dataset}\n        series={[\n          {\n            dataKey: \"dau\",\n            label: \"Daily Active Users\",\n            showMark: false,\n            color: t.palette[0],\n            curve: \"monotoneX\",\n            valueFormatter: (v: number | null) =>\n              v == null ? \"\" : `${v.toFixed(1)}k`,\n          },\n        ]}\n        xAxis={[\n          {\n            dataKey: \"date\",\n            scaleType: \"time\",\n            label: \"Date\",\n            valueFormatter: (d: Date) =>\n              d.toLocaleDateString(\"en-US\", { month: \"short\" }),\n            tickInterval: monthTicks,\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n            labelStyle: { fontSize: 16, fill: t.ink },\n          },\n        ]}\n        yAxis={[\n          {\n            label: \"Daily Active Users (thousands)\",\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n            labelStyle: { fontSize: 16, fill: t.ink },\n          },\n        ]}\n        grid={{ horizontal: true }}\n        margin={{ top: 76, right: 40, bottom: 64, left: 96 }}\n        slotProps={{ legend: { hidden: true } }}\n        sx={{\n          \"& .MuiLineElement-root\": { strokeWidth: 3 },\n          \"& .MuiChartsAxis-line\": { stroke: t.inkSoft, strokeOpacity: 0.3 },\n          \"& .MuiChartsGrid-line\": { stroke: t.grid },\n        }}\n      >\n        <GrowthPhaseBand\n          from={dates[EVENTS[EVENTS.length - 1].dayIndex]}\n          color={t.palette[0]}\n        />\n        {EVENTS.map((event, i) => (\n          <ChartsReferenceLine\n            key={event.label}\n            x={dates[event.dayIndex]}\n            label={event.label}\n            labelAlign={i % 2 === 0 ? \"start\" : \"end\"}\n            spacing={{ x: 8, y: 12 }}\n            lineStyle={{\n              stroke: t.ink,\n              strokeDasharray: \"6 4\",\n              strokeWidth: 1.5,\n              strokeOpacity: 0.6,\n            }}\n            labelStyle={{ fontSize: 14, fontWeight: 600, fill: t.ink }}\n          />\n        ))}\n      </LineChart>\n    </Box>\n  );\n}\n"}