{"spec_id":"box-notched","library":"muix","language":"javascript","code":"// anyplot.ai\n// box-notched: Notched Box Plot\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-08-18\nimport * as React from \"react\";\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\";\nimport Typography from \"@mui/material/Typography\";\n\n// @mui/x-charts (community, v7) has no built-in box-plot component — the\n// notched box is composed from the library's own low-level primitives\n// (ChartContainer + useXScale/useYScale) rather than any other charting lib.\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Deterministic PRNG (LCG + Box-Muller, no seeded RNG in the browser) ----\nlet seed = 42;\nfunction nextUniform() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\nfunction nextGaussian() {\n  const u1 = Math.max(nextUniform(), 1e-9);\n  const u2 = nextUniform();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// --- Data: annual salary distributions across departments -------------------\nconst departmentSpecs = [\n  { name: \"Engineering\", n: 55, mean: 92000, sd: 14000 },\n  { name: \"Finance\", n: 45, mean: 88000, sd: 11000 },\n  { name: \"Sales\", n: 60, mean: 74000, sd: 16000 },\n  { name: \"Marketing\", n: 42, mean: 68000, sd: 9000 },\n  { name: \"Support\", n: 38, mean: 58000, sd: 7000 },\n];\n\nfunction quantile(sorted, q) {\n  const pos = (sorted.length - 1) * q;\n  const base = Math.floor(pos);\n  const rest = pos - base;\n  return sorted[base + 1] !== undefined\n    ? sorted[base] + rest * (sorted[base + 1] - sorted[base])\n    : sorted[base];\n}\n\nfunction computeBoxStats(values) {\n  const sorted = [...values].sort((a, b) => a - b);\n  const n = sorted.length;\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((v) => v >= lowerFence && v <= upperFence);\n  const outliers = sorted.filter((v) => v < lowerFence || v > upperFence);\n  const notch = (1.57 * iqr) / Math.sqrt(n);\n  return {\n    n,\n    q1,\n    median,\n    q3,\n    whiskerLow: inliers[0],\n    whiskerHigh: inliers[inliers.length - 1],\n    outliers,\n    notch,\n  };\n}\n\nconst salaryGroups = departmentSpecs.map((spec) => {\n  const values = Array.from({ length: spec.n }, () =>\n    Math.max(28000, Math.round(spec.mean + spec.sd * nextGaussian()))\n  );\n  return { name: spec.name, values, stats: computeBoxStats(values) };\n});\n\nconst allValues = salaryGroups.flatMap((g) => g.values);\nconst yMin = Math.floor(Math.min(...allValues) / 5000) * 5000 - 5000;\nconst yMax = Math.ceil(Math.max(...allValues) / 5000) * 5000 + 5000;\n\n// --- Notched box glyphs, drawn from the axis scales into the chart's <svg> --\nfunction NotchedBoxes({ groups, colors, pageBg }) {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const bandwidth = xScale.bandwidth ? xScale.bandwidth() : 0;\n  const boxWidth = bandwidth * 0.55;\n  const notchInset = boxWidth * 0.32;\n\n  return (\n    <g>\n      {groups.map((g, i) => {\n        const color = colors[i % colors.length];\n        const cx = xScale(g.name) + bandwidth / 2;\n        const xLeft = cx - boxWidth / 2;\n        const xRight = cx + boxWidth / 2;\n        const xNotchLeft = cx - notchInset;\n        const xNotchRight = cx + notchInset;\n        const yQ3 = yScale(g.stats.q3);\n        const yQ1 = yScale(g.stats.q1);\n        const yMed = yScale(g.stats.median);\n        const yMedTop = yScale(g.stats.median + g.stats.notch);\n        const yMedBot = yScale(g.stats.median - g.stats.notch);\n        const yWhiskerHigh = yScale(g.stats.whiskerHigh);\n        const yWhiskerLow = yScale(g.stats.whiskerLow);\n        const capHalf = boxWidth * 0.25;\n        const boxPath = [\n          `M ${xLeft} ${yQ3}`,\n          `L ${xRight} ${yQ3}`,\n          `L ${xRight} ${yMedTop}`,\n          `L ${xNotchRight} ${yMed}`,\n          `L ${xRight} ${yMedBot}`,\n          `L ${xRight} ${yQ1}`,\n          `L ${xLeft} ${yQ1}`,\n          `L ${xLeft} ${yMedBot}`,\n          `L ${xNotchLeft} ${yMed}`,\n          `L ${xLeft} ${yMedTop}`,\n          \"Z\",\n        ].join(\" \");\n\n        return (\n          <g key={g.name}>\n            <line x1={cx} y1={yWhiskerHigh} x2={cx} y2={yQ3} stroke={color} strokeWidth={2} />\n            <line x1={cx} y1={yQ1} x2={cx} y2={yWhiskerLow} stroke={color} strokeWidth={2} />\n            <line\n              x1={cx - capHalf}\n              y1={yWhiskerHigh}\n              x2={cx + capHalf}\n              y2={yWhiskerHigh}\n              stroke={color}\n              strokeWidth={2}\n            />\n            <line\n              x1={cx - capHalf}\n              y1={yWhiskerLow}\n              x2={cx + capHalf}\n              y2={yWhiskerLow}\n              stroke={color}\n              strokeWidth={2}\n            />\n            <path\n              d={boxPath}\n              fill={color}\n              fillOpacity={0.45}\n              stroke={color}\n              strokeWidth={2.5}\n              strokeLinejoin=\"round\"\n            />\n            <line\n              x1={xNotchLeft}\n              y1={yMed}\n              x2={xNotchRight}\n              y2={yMed}\n              stroke={t.ink}\n              strokeWidth={3}\n            />\n            {g.stats.outliers.map((v, j) => (\n              <circle\n                key={j}\n                cx={cx}\n                cy={yScale(v)}\n                r={6}\n                fill={color}\n                fillOpacity={0.85}\n                stroke={pageBg}\n                strokeWidth={1.5}\n              />\n            ))}\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) ------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const titleHeight = 56;\n  const chartHeight = height - titleHeight;\n\n  return (\n    <div style={{ width, height, display: \"flex\", flexDirection: \"column\" }}>\n      <div style={{ height: titleHeight, display: \"flex\", alignItems: \"center\", paddingLeft: 8 }}>\n        <Typography sx={{ fontSize: 22, fontWeight: 600, color: t.ink }}>\n          box-notched · javascript · muix · anyplot.ai\n        </Typography>\n      </div>\n      <div style={{ position: \"relative\", width, height: chartHeight }}>\n        {/* Rendered outside ChartsYAxis: its built-in label offset is a fixed\n            pixel constant, not aware of the wider tick labels (\"$130k\") here,\n            and would overlap them. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: 20,\n            top: \"50%\",\n            transform: \"translate(-50%, -50%) rotate(-90deg)\",\n            fontSize: 16,\n            fontWeight: 500,\n            color: t.ink,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          Annual Salary\n        </div>\n        <ChartContainer\n          width={width}\n          height={chartHeight}\n          series={[]}\n          skipAnimation\n          margin={{ left: 110, right: 24, top: 24, bottom: 56 }}\n          xAxis={[\n            {\n              scaleType: \"band\",\n              data: salaryGroups.map((g) => g.name),\n              categoryGapRatio: 0.4,\n            },\n          ]}\n          yAxis={[\n            {\n              min: yMin,\n              max: yMax,\n              valueFormatter: (v) => `$${Math.round(v / 1000)}k`,\n            },\n          ]}\n        >\n          <ChartsGrid horizontal />\n          <NotchedBoxes groups={salaryGroups} colors={t.palette} pageBg={t.pageBg} />\n          <ChartsXAxis label=\"Department\" tickLabelStyle={{ fontSize: 14 }} labelStyle={{ fontSize: 16 }} />\n          <ChartsYAxis tickLabelStyle={{ fontSize: 14 }} />\n        </ChartContainer>\n      </div>\n    </div>\n  );\n}\n"}