{"spec_id":"box-grouped","library":"muix","language":"javascript","code":"// anyplot.ai\n// box-grouped: Grouped Box Plot\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-08-18\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Deterministic data (tiny fixed-seed LCG — the browser has no seeded RNG) ---\nfunction makeLcg(seed) {\n  let state = seed >>> 0;\n  return function next() {\n    state = (state * 1664525 + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\n\nfunction randomNormal(rng, mean, std) {\n  const u1 = Math.max(rng(), 1e-9);\n  const u2 = rng();\n  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n  return mean + z * std;\n}\n\nconst rng = makeLcg(42);\nconst departments = [\"Engineering\", \"Sales\", \"Marketing\", \"Support\"];\nconst levels = [\"Junior\", \"Mid\", \"Senior\"];\nconst levelParams = {\n  Junior: { mean: 52, std: 9 },\n  Mid: { mean: 68, std: 8 },\n  Senior: { mean: 82, std: 7 },\n};\nconst departmentOffset = {\n  Engineering: 5,\n  Sales: -4,\n  Marketing: 2,\n  Support: -6,\n};\n\n// productivity index per department + seniority level (0-100 composite score)\nconst rawValues = {};\ndepartments.forEach((department, di) => {\n  rawValues[department] = {};\n  levels.forEach((level, li) => {\n    const { mean, std } = levelParams[level];\n    const sampleCount = 46;\n    const values = [];\n    for (let i = 0; i < sampleCount; i++) {\n      const value = randomNormal(rng, mean + departmentOffset[department], std);\n      values.push(Math.min(100, Math.max(0, value)));\n    }\n    // A handful of groups get a deliberate extreme performer so the chart\n    // demonstrates the outlier convention the spec asks for.\n    if ((di + li) % 2 === 0) {\n      values.push(Math.min(100, Math.max(0, mean + departmentOffset[department] + 3.4 * std)));\n    }\n    rawValues[department][level] = values;\n  });\n});\n\nfunction quantile(sortedValues, q) {\n  const position = (sortedValues.length - 1) * q;\n  const base = Math.floor(position);\n  const rest = position - base;\n  return sortedValues[base + 1] !== undefined\n    ? sortedValues[base] + rest * (sortedValues[base + 1] - sortedValues[base])\n    : sortedValues[base];\n}\n\nfunction boxStatistics(values) {\n  const sorted = [...values].sort((a, b) => a - b);\n  const q1 = quantile(sorted, 0.25);\n  const median = quantile(sorted, 0.5);\n  const q3 = quantile(sorted, 0.75);\n  const iqr = q3 - q1;\n  const lowerFence = q1 - 1.5 * iqr;\n  const upperFence = q3 + 1.5 * iqr;\n  const inliers = sorted.filter((value) => value >= lowerFence && value <= upperFence);\n  return {\n    q1,\n    median,\n    q3,\n    whiskerMin: inliers.length ? inliers[0] : q1,\n    whiskerMax: inliers.length ? inliers[inliers.length - 1] : q3,\n    outliers: sorted.filter((value) => value < lowerFence || value > upperFence),\n  };\n}\n\nconst boxGroups = departments.map((department) => ({\n  department,\n  boxes: levels.map((level) => ({ level, stats: boxStatistics(rawValues[department][level]) })),\n}));\n\nconst allValues = departments.flatMap((department) => levels.flatMap((level) => rawValues[department][level]));\nconst dataMin = Math.min(...allValues);\nconst dataMax = Math.max(...allValues);\nconst valuePadding = (dataMax - dataMin) * 0.12;\nconst yMin = Math.max(0, Math.floor((dataMin - valuePadding) / 5) * 5);\nconst yMax = Math.min(100, Math.ceil((dataMax + valuePadding) / 5) * 5);\n\nfunction BoxWhiskerLayer() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n\n  const bandwidth = xScale.bandwidth();\n  const groupWidth = bandwidth * 0.86;\n  const boxSlot = groupWidth / levels.length;\n  const boxWidth = boxSlot * 0.66;\n  const capWidth = boxWidth * 0.6;\n\n  // Dashed median trend per level makes the consistent Senior > Mid > Junior\n  // pattern an explicit visual thread rather than something left for the\n  // reader to infer box-by-box.\n  const levelTrends = levels.map((level, levelIndex) => ({\n    level,\n    color: t.palette[levelIndex],\n    points: boxGroups.map((group) => {\n      const bandStart = xScale(group.department) ?? 0;\n      const groupStart = bandStart + (bandwidth - groupWidth) / 2;\n      const cx = groupStart + boxSlot * (levelIndex + 0.5);\n      return { x: cx, y: yScale(group.boxes[levelIndex].stats.median) };\n    }),\n  }));\n\n  return (\n    <g>\n      {levelTrends.map((trend) => (\n        <polyline\n          key={`trend-${trend.level}`}\n          points={trend.points.map((p) => `${p.x},${p.y}`).join(\" \")}\n          fill=\"none\"\n          stroke={trend.color}\n          strokeWidth={1.5}\n          strokeDasharray=\"5 4\"\n          opacity={0.4}\n        />\n      ))}\n      <text x={(xScale.range()?.[0] ?? 0) + 2} y={yScale(yMax) + 16} fontSize={12} fontStyle=\"italic\" fill={t.inkSoft}>\n        Senior medians (dashed) consistently outperform Mid and Junior\n      </text>\n      {boxGroups.map((group) => {\n        const bandStart = xScale(group.department) ?? 0;\n        const groupStart = bandStart + (bandwidth - groupWidth) / 2;\n        return group.boxes.map((box, levelIndex) => {\n          const cx = groupStart + boxSlot * (levelIndex + 0.5);\n          const color = t.palette[levelIndex];\n          const yQ1 = yScale(box.stats.q1);\n          const yQ3 = yScale(box.stats.q3);\n          const yMedian = yScale(box.stats.median);\n          const yWhiskerHigh = yScale(box.stats.whiskerMax);\n          const yWhiskerLow = yScale(box.stats.whiskerMin);\n          return (\n            <g key={`${group.department}-${box.level}`}>\n              <line x1={cx} x2={cx} y1={yWhiskerHigh} y2={yQ3} stroke={t.ink} strokeWidth={1.5} opacity={0.55} />\n              <line x1={cx} x2={cx} y1={yQ1} y2={yWhiskerLow} stroke={t.ink} strokeWidth={1.5} opacity={0.55} />\n              <line\n                x1={cx - capWidth / 2}\n                x2={cx + capWidth / 2}\n                y1={yWhiskerHigh}\n                y2={yWhiskerHigh}\n                stroke={t.ink}\n                strokeWidth={1.5}\n                opacity={0.55}\n              />\n              <line\n                x1={cx - capWidth / 2}\n                x2={cx + capWidth / 2}\n                y1={yWhiskerLow}\n                y2={yWhiskerLow}\n                stroke={t.ink}\n                strokeWidth={1.5}\n                opacity={0.55}\n              />\n              <rect\n                x={cx - boxWidth / 2}\n                y={yQ3}\n                width={boxWidth}\n                height={Math.max(yQ1 - yQ3, 1)}\n                rx={2}\n                fill={color}\n                fillOpacity={0.72}\n                stroke={color}\n                strokeWidth={1.75}\n              />\n              <line\n                x1={cx - boxWidth / 2}\n                x2={cx + boxWidth / 2}\n                y1={yMedian}\n                y2={yMedian}\n                stroke={t.ink}\n                strokeWidth={2.25}\n              />\n              {box.stats.outliers.map((value, outlierIndex) => (\n                <circle\n                  key={outlierIndex}\n                  cx={cx}\n                  cy={yScale(value)}\n                  r={4.5}\n                  fill=\"none\"\n                  stroke={color}\n                  strokeWidth={1.75}\n                />\n              ))}\n            </g>\n          );\n        });\n      })}\n    </g>\n  );\n}\n\nconst titleText = \"box-grouped · javascript · muix · anyplot.ai\";\nconst titleHeight = 56;\nconst legendHeight = 34;\nconst chartWidth = window.ANYPLOT_SIZE.width;\nconst chartHeight = window.ANYPLOT_SIZE.height - titleHeight - legendHeight - 20;\n\nexport default function Chart() {\n  return (\n    <div style={{ width: chartWidth, height: window.ANYPLOT_SIZE.height, display: \"flex\", flexDirection: \"column\" }}>\n      <div style={{ height: titleHeight, display: \"flex\", alignItems: \"center\", paddingLeft: 8 }}>\n        <span style={{ fontSize: 22, fontWeight: 500, color: t.ink }}>{titleText}</span>\n      </div>\n      <div style={{ height: legendHeight, display: \"flex\", alignItems: \"center\", gap: 24, paddingLeft: 8 }}>\n        {levels.map((level, i) => (\n          <div key={level} style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n            <span style={{ width: 14, height: 14, borderRadius: 3, background: t.palette[i], display: \"inline-block\" }} />\n            <span style={{ fontSize: 14, color: t.inkSoft }}>{level}</span>\n          </div>\n        ))}\n      </div>\n      <div style={{ height: 20 }} />\n      <ChartContainer\n        width={chartWidth}\n        height={chartHeight}\n        series={[]}\n        xAxis={[\n          {\n            scaleType: \"band\",\n            data: departments,\n            categoryGapRatio: 0.4,\n            disableTicks: true,\n            label: \"Department\",\n            labelStyle: { fontSize: 16 },\n            tickLabelStyle: { fontSize: 14 },\n          },\n        ]}\n        yAxis={[\n          {\n            min: yMin,\n            max: yMax,\n            disableTicks: true,\n            label: \"Productivity Index (0-100 scale)\",\n            labelStyle: { fontSize: 16 },\n            tickLabelStyle: { fontSize: 14 },\n          },\n        ]}\n      >\n        <ChartsGrid horizontal />\n        <BoxWhiskerLayer />\n        <ChartsXAxis />\n        <ChartsYAxis />\n      </ChartContainer>\n    </div>\n  );\n}\n"}