{"spec_id":"gantt-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// gantt-basic: Basic Gantt Chart\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-09-05\nimport { BarChart } from \"@mui/x-charts/BarChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport Box from \"@mui/material/Box\";\nimport Stack from \"@mui/material/Stack\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Website redesign project: 11 tasks across 4 phases, day-offsets from the\n// project kickoff (Jan 5, 2026 = day 0) so the x-axis is a plain linear scale\n// that a valueFormatter renders back as calendar dates.\nconst DAY_MS = 86400000;\nconst toDay = (year, month, date) => Date.UTC(year, month, date) / DAY_MS;\nconst PROJECT_START = toDay(2026, 0, 5);\n\nconst CATEGORIES = [\"Design\", \"Development\", \"Testing\", \"Launch\"];\nconst CATEGORY_COLOR = Object.fromEntries(CATEGORIES.map((category, i) => [category, t.palette[i]]));\n\nconst TASKS = [\n  { task: \"Requirements Gathering\", category: \"Design\", start: [2026, 0, 5], end: [2026, 0, 12] },\n  { task: \"Wireframing\", category: \"Design\", start: [2026, 0, 10], end: [2026, 0, 20] },\n  { task: \"Visual Design\", category: \"Design\", start: [2026, 0, 18], end: [2026, 0, 30] },\n  { task: \"Backend Development\", category: \"Development\", start: [2026, 0, 26], end: [2026, 1, 20] },\n  { task: \"Frontend Development\", category: \"Development\", start: [2026, 0, 29], end: [2026, 1, 24] },\n  { task: \"API Integration\", category: \"Development\", start: [2026, 1, 16], end: [2026, 1, 27] },\n  { task: \"Unit Testing\", category: \"Testing\", start: [2026, 1, 21], end: [2026, 2, 1] },\n  { task: \"QA & Bug Fixes\", category: \"Testing\", start: [2026, 1, 27], end: [2026, 2, 10] },\n  { task: \"User Acceptance Testing\", category: \"Testing\", start: [2026, 2, 6], end: [2026, 2, 14] },\n  { task: \"Deployment\", category: \"Launch\", start: [2026, 2, 12], end: [2026, 2, 16] },\n  { task: \"Post-Launch Monitoring\", category: \"Launch\", start: [2026, 2, 14], end: [2026, 2, 25] },\n].map((row) => ({\n  task: row.task,\n  category: row.category,\n  start: toDay(...row.start) - PROJECT_START,\n  end: toDay(...row.end) - PROJECT_START,\n}));\n\nconst TASK_NAMES = TASKS.map((row) => row.task);\nconst MAX_DAY = Math.max(...TASKS.map((row) => row.end));\nconst STATUS_DAY = toDay(2026, 1, 20) - PROJECT_START;\n\nconst dateFormatter = new Intl.DateTimeFormat(\"en-US\", { month: \"short\", day: \"numeric\", timeZone: \"UTC\" });\nconst formatDay = (offsetDays) => dateFormatter.format(new Date((PROJECT_START + offsetDays) * DAY_MS));\n\nconst TICKS = [];\nfor (let d = 0; d <= MAX_DAY; d += 14) TICKS.push(d);\n\n// --- Series: an invisible \"offset\" segment (start of timeline -> task start)\n// stacked with, per category, a solid \"duration\" segment — null everywhere\n// except the row that belongs to it — so each task lands as a floating bar\n// at its correct start/end day offsets. --------------------------------------\nconst OFFSET_SERIES = {\n  id: \"offset\",\n  stack: \"timeline\",\n  color: \"transparent\",\n  data: TASKS.map((row) => row.start),\n  valueFormatter: () => null,\n};\n\nconst CATEGORY_SERIES = CATEGORIES.map((category) => ({\n  id: category,\n  label: category,\n  stack: \"timeline\",\n  color: CATEGORY_COLOR[category],\n  data: TASKS.map((row) => (row.category === category ? row.end - row.start : null)),\n  valueFormatter: (value, { dataIndex }) =>\n    value == null ? null : `${formatDay(TASKS[dataIndex].start)} – ${formatDay(TASKS[dataIndex].end)}`,\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 TITLE_TOP = 28;\n  const LEGEND_TOP = 76;\n  const CHART_TOP = 104;\n\n  const title = \"Website Redesign Timeline · gantt-basic · javascript · muix · anyplot.ai\";\n  const titleSize = title.length > 67 ? Math.round((26 * 67) / title.length) : 26;\n\n  return (\n    <Box sx={{ position: \"relative\", width: W, height: H, bgcolor: t.pageBg }}>\n      <Box sx={{ position: \"absolute\", top: TITLE_TOP, left: 56, right: 56 }}>\n        <Typography sx={{ color: t.ink, fontSize: titleSize, fontWeight: 500 }}>{title}</Typography>\n      </Box>\n\n      <Stack direction=\"row\" spacing={3} sx={{ position: \"absolute\", top: LEGEND_TOP, left: 56, right: 56 }}>\n        {CATEGORIES.map((category) => (\n          <Stack key={category} direction=\"row\" spacing={1} alignItems=\"center\">\n            <Box sx={{ width: 14, height: 14, borderRadius: \"3px\", bgcolor: CATEGORY_COLOR[category] }} />\n            <Typography sx={{ color: t.inkSoft, fontSize: 14 }}>{category}</Typography>\n          </Stack>\n        ))}\n      </Stack>\n\n      <Box sx={{ position: \"absolute\", top: CHART_TOP, left: 0, right: 0, bottom: 0 }}>\n        <BarChart\n          width={W}\n          height={H - CHART_TOP}\n          layout=\"horizontal\"\n          skipAnimation\n          borderRadius={3}\n          series={[OFFSET_SERIES, ...CATEGORY_SERIES]}\n          grid={{ vertical: true }}\n          xAxis={[\n            {\n              min: 0,\n              max: MAX_DAY + 4,\n              tickInterval: TICKS,\n              valueFormatter: (value) => formatDay(value),\n              label: \"Project Timeline\",\n              labelStyle: { fontSize: 15, fill: t.ink },\n              tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n              disableTicks: true,\n            },\n          ]}\n          yAxis={[\n            {\n              scaleType: \"band\",\n              data: TASK_NAMES,\n              tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n              disableTicks: true,\n              categoryGapRatio: 0.35,\n            },\n          ]}\n          margin={{ top: 16, right: 60, bottom: 56, left: 232 }}\n          slotProps={{ legend: { hidden: true } }}\n          sx={{\n            \"& .MuiChartsAxis-line\": { stroke: t.inkSoft },\n            \"& .MuiChartsGrid-line\": { stroke: t.grid },\n          }}\n        >\n          <ChartsReferenceLine\n            x={STATUS_DAY}\n            label={`Status: ${formatDay(STATUS_DAY)}`}\n            labelAlign=\"end\"\n            lineStyle={{ stroke: t.ink, strokeDasharray: \"6 4\", strokeWidth: 2 }}\n            labelStyle={{ fontSize: 13, fill: t.ink, fontWeight: 600 }}\n          />\n        </BarChart>\n      </Box>\n    </Box>\n  );\n}\n"}