{"spec_id":"line-stepwise","library":"muix","language":"javascript","code":"// anyplot.ai\n// line-stepwise: Step Line Plot\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 87/100 | Created: 2026-09-05\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { Box, Typography } from \"@mui/material\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Smart-thermostat setpoint schedule, logged every 15 minutes over a day. The\n// schedule engine issues a new setpoint at fixed hours, but the telemetry\n// logger only samples on a fixed cadence — the exact moment a command took\n// effect sits somewhere between two consecutive readings, so mid-point step\n// alignment (`curve=\"step\"`) represents that uncertainty honestly, unlike\n// \"stepAfter\" (which would claim the change landed exactly on a sample).\nconst SAMPLES_PER_HOUR = 4;\nconst SCHEDULE = [\n  { hours: 6, setpoint: 62 }, // overnight sleep mode\n  { hours: 2, setpoint: 68 }, // morning warm-up\n  { hours: 9, setpoint: 65 }, // away / eco during work hours\n  { hours: 5, setpoint: 72 }, // evening comfort\n  { hours: 2, setpoint: 62 }, // night wind-down\n];\n\nconst hoursOfDay = [];\nconst setpoints = [];\nlet hour = 0;\nfor (const block of SCHEDULE) {\n  const samples = block.hours * SAMPLES_PER_HOUR;\n  for (let i = 0; i < samples; i += 1) {\n    hoursOfDay.push(hour);\n    setpoints.push(block.setpoint);\n    hour += 1 / SAMPLES_PER_HOUR;\n  }\n}\n\nfunction formatHour(h) {\n  const wholeHour = Math.floor(h);\n  const minutes = Math.round((h - wholeHour) * 60);\n  return `${String(wholeHour).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}`;\n}\n\nconst TITLE = \"line-stepwise · javascript · muix · anyplot.ai\";\nconst TITLE_H = 56;\nconst AREA_GRADIENT_ID = \"stepAreaFillGradient\";\n\n// The evening-comfort plateau is the schedule's warmest setpoint — call it\n// out with a reference line so the chart has a clear focal point beyond the\n// step shape itself.\nconst PEAK_SETPOINT = Math.max(...SCHEDULE.map((b) => b.setpoint));\n\n// --- Chart (default-exported component — the harness mounts it) ------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n\n  return (\n    <Box sx={{ width, height, display: \"flex\", flexDirection: \"column\" }}>\n      <Typography sx={{ fontSize: 22, fontWeight: 600, color: \"text.primary\", px: \"40px\", pt: \"10px\", lineHeight: 1 }}>\n        {TITLE}\n      </Typography>\n      <LineChart\n        width={width}\n        height={height - TITLE_H}\n        skipAnimation\n        series={[\n          {\n            data: setpoints,\n            color: t.palette[0],\n            curve: \"step\",\n            area: true,\n            showMark: false,\n            valueFormatter: (v) => `${v}°F`,\n          },\n        ]}\n        xAxis={[\n          {\n            data: hoursOfDay,\n            scaleType: \"linear\",\n            label: \"Time of Day\",\n            min: 0,\n            max: 24,\n            tickNumber: 8,\n            valueFormatter: (h) => formatHour(h),\n            tickLabelStyle: { fontSize: 14 },\n            labelStyle: { fontSize: 16 },\n          },\n        ]}\n        yAxis={[\n          {\n            min: 55,\n            max: 78,\n            label: \"Setpoint (°F)\",\n            tickLabelStyle: { fontSize: 14 },\n            labelStyle: { fontSize: 16 },\n          },\n        ]}\n        margin={{ top: 20, right: 56, bottom: 60, left: 70 }}\n        grid={{ horizontal: true }}\n        slotProps={{ legend: { hidden: true } }}\n        sx={{\n          \"& .MuiLineElement-root\": { strokeWidth: 3 },\n          \"& .MuiAreaElement-root\": { fill: `url(#${AREA_GRADIENT_ID})` },\n          \"& .MuiChartsGrid-line\": { stroke: t.grid, strokeWidth: 1 },\n        }}\n      >\n        <defs>\n          <linearGradient id={AREA_GRADIENT_ID} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n            <stop offset=\"0%\" stopColor={t.palette[0]} stopOpacity={0.35} />\n            <stop offset=\"100%\" stopColor={t.palette[0]} stopOpacity={0.03} />\n          </linearGradient>\n        </defs>\n        <ChartsReferenceLine\n          y={PEAK_SETPOINT}\n          label={`Peak ${PEAK_SETPOINT}°F · evening comfort`}\n          labelAlign=\"start\"\n          lineStyle={{ stroke: t.ink, strokeDasharray: \"6 4\", strokeOpacity: 0.5 }}\n          labelStyle={{ fill: t.inkSoft, fontSize: 13 }}\n        />\n      </LineChart>\n    </Box>\n  );\n}\n"}