{"spec_id":"histogram-density","library":"muix","language":"javascript","code":"// anyplot.ai\n// histogram-density: Density Histogram\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-05\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { BarPlot } from \"@mui/x-charts/BarChart\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { ChartsLegend } from \"@mui/x-charts/ChartsLegend\";\nimport { axisClasses } from \"@mui/x-charts/ChartsAxis\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst FONT = \"system-ui, -apple-system, sans-serif\";\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Fixed-seed LCG + Box-Muller — the browser has no seeded Math.random.\nlet seed = 7;\nfunction lcg() {\n  seed = (Math.imul(1664525, seed) + 1013904223) >>> 0;\n  return seed / 0x100000000;\n}\nfunction randn() {\n  const u1 = Math.max(lcg(), 1e-10);\n  const u2 = lcg();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// A filling line's package weights (g). Nominal fill is 500g with small,\n// roughly-Gaussian process variability — the classic case for checking\n// observed density against a fitted Normal curve.\nconst N = 400;\nconst NOMINAL_WEIGHT = 500;\nconst PROCESS_SD = 6;\nconst weights = Array.from(\n  { length: N },\n  () => NOMINAL_WEIGHT + randn() * PROCESS_SD\n);\n\n// --- Bin into a density histogram (bar area sums to 1) -----------------------\nconst dataMin = Math.min(...weights);\nconst dataMax = Math.max(...weights);\nconst BIN_COUNT = 18;\nconst binWidth = (dataMax - dataMin) / BIN_COUNT;\nconst binCounts = new Array(BIN_COUNT).fill(0);\nweights.forEach((w) => {\n  const idx = Math.min(BIN_COUNT - 1, Math.floor((w - dataMin) / binWidth));\n  binCounts[idx] += 1;\n});\nconst density = binCounts.map((c) => c / (N * binWidth));\nconst binCenters = binCounts.map((_, i) => dataMin + (i + 0.5) * binWidth);\n\n// --- Fitted Normal PDF, sampled at the same bin centers ----------------------\n// Sampled at the bin centers (not a finer grid) so it shares the histogram's\n// band-scale x-axis as a genuine MUI X combo chart, no manual SVG positioning.\n// The `curve: \"monotoneX\"` on the line series interpolates a smooth spline\n// through those points so the fit still reads as a continuous PDF.\nconst sampleMean = weights.reduce((s, w) => s + w, 0) / N;\nconst sampleSd = Math.sqrt(\n  weights.reduce((s, w) => s + (w - sampleMean) ** 2, 0) / (N - 1)\n);\nconst normalPdf = binCenters.map((x) => {\n  const z = (x - sampleMean) / sampleSd;\n  return Math.exp(-0.5 * z * z) / (sampleSd * Math.sqrt(2 * Math.PI));\n});\n\nconst Y_MAX = Math.max(...density, ...normalPdf) * 1.15;\n\n// Imprint palette colors — canonical order (bars = position 1, curve = position 2)\nconst BRAND = t.palette[0]; // #009E73\nconst CURVE = t.palette[1]; // #C475FD\n\n// Title sizing (scale down for longer-than-67-char titles)\nconst TITLE =\n  \"Package Weight Distribution · histogram-density · javascript · muix · anyplot.ai\";\nconst titleSize = Math.max(11, Math.round(22 * (67 / TITLE.length)));\nconst SUBTITLE = `Mean ${sampleMean.toFixed(1)} g · SD ${sampleSd.toFixed(1)} g — fitted Normal PDF overlay`;\n\n// --- Main component (default-exported — the harness mounts it) --------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const TITLE_H = 76;\n\n  return (\n    <Box\n      sx={{\n        width,\n        height,\n        bgcolor: t.pageBg,\n        display: \"flex\",\n        flexDirection: \"column\",\n        overflow: \"hidden\",\n      }}\n    >\n      <Typography\n        sx={{\n          fontSize: titleSize,\n          fontWeight: 500,\n          color: t.ink,\n          pt: \"16px\",\n          px: \"40px\",\n          pb: 0,\n          lineHeight: 1.2,\n          fontFamily: FONT,\n        }}\n      >\n        {TITLE}\n      </Typography>\n      <Typography\n        sx={{\n          fontSize: 14,\n          fontWeight: 400,\n          color: t.inkSoft,\n          px: \"40px\",\n          pt: \"4px\",\n          pb: 0,\n          lineHeight: 1.2,\n          fontFamily: FONT,\n        }}\n      >\n        {SUBTITLE}\n      </Typography>\n\n      <ChartContainer\n        width={width}\n        height={height - TITLE_H}\n        series={[\n          {\n            type: \"bar\",\n            id: \"observed\",\n            data: density,\n            label: \"Observed density\",\n            color: BRAND,\n          },\n          {\n            type: \"line\",\n            id: \"fitted\",\n            data: normalPdf,\n            label: \"Fitted Normal PDF\",\n            color: CURVE,\n            showMark: false,\n            curve: \"monotoneX\",\n          },\n        ]}\n        xAxis={[\n          {\n            id: \"weight-axis\",\n            scaleType: \"band\",\n            data: binCenters,\n            label: \"Package Weight (g)\",\n            valueFormatter: (v) => v.toFixed(0),\n            tickLabelInterval: (_v, i) => i % 2 === 0,\n            labelStyle: { fontSize: 15, fill: t.ink, fontFamily: FONT },\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft, fontFamily: FONT },\n          },\n        ]}\n        yAxis={[\n          {\n            min: 0,\n            max: Y_MAX,\n            label: \"Density\",\n            labelStyle: { fontSize: 15, fill: t.ink, fontFamily: FONT },\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft, fontFamily: FONT },\n          },\n        ]}\n        margin={{ top: 24, right: 40, bottom: 80, left: 110 }}\n        skipAnimation\n      >\n        <ChartsGrid horizontal sx={{ \"& line\": { stroke: t.grid, strokeWidth: 0.8 } }} />\n        <BarPlot skipAnimation borderRadius={4} />\n        <LinePlot skipAnimation slotProps={{ line: { sx: { strokeWidth: 3.5 } } }} />\n        {/* Softened axis lines/ticks (t.grid instead of full-ink) for a less\n            default-MUI-X, more refined chrome — data ink stays untouched. */}\n        <ChartsXAxis\n          axisId=\"weight-axis\"\n          sx={{\n            [`& .${axisClasses.line}`]: { stroke: t.grid },\n            [`& .${axisClasses.tick}`]: { stroke: t.grid },\n          }}\n        />\n        {/* Explicit axisLabel x offset — the default offset formula assumes\n            short tick labels and clips against our 4-char decimal density values. */}\n        <ChartsYAxis\n          slotProps={{ axisLabel: { x: -72 } }}\n          sx={{\n            [`& .${axisClasses.line}`]: { stroke: t.grid },\n            [`& .${axisClasses.tick}`]: { stroke: t.grid },\n          }}\n        />\n        <ChartsLegend\n          position={{ vertical: \"top\", horizontal: \"right\" }}\n          slotProps={{\n            legend: {\n              labelStyle: { fontSize: 15, fill: t.inkSoft, fontFamily: FONT },\n              itemGap: 20,\n            },\n          }}\n        />\n      </ChartContainer>\n    </Box>\n  );\n}\n"}