{"spec_id":"facet-grid","library":"muix","language":"javascript","code":"// anyplot.ai\n// facet-grid: Faceted Grid Plot\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-05\n//# anyplot-orientation: landscape\n// anyplot.ai\n// facet-grid: Faceted Grid Plot\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-09-05\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ScatterPlot } from \"@mui/x-charts/ScatterChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst FONT = \"system-ui, -apple-system, 'Segoe UI', sans-serif\";\nconst TITLE = \"facet-grid · javascript · muix · anyplot.ai\";\n\n// Moderate per-facet density (~45 pts) → alpha ~0.8 + a thin edge stroke keeps\n// overlapping points in the denser clusters (Adelie, Gentoo-male) distinguishable.\nfunction hexToRgba(hex, alpha) {\n  const h = hex.replace(\"#\", \"\");\n  const r = parseInt(h.slice(0, 2), 16);\n  const g = parseInt(h.slice(2, 4), 16);\n  const b = parseInt(h.slice(4, 6), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\nconst MARKER_COLOR = hexToRgba(t.palette[0], 0.8);\n\n// --- Deterministic PRNG (LCG) + Box-Muller normal --------------------------\n// The browser has no seeded Math.random(), so every facet's sample is drawn\n// from one shared, deterministic generator.\nfunction makeLcg(seed) {\n  let state = seed >>> 0;\n  return function next() {\n    state = (state * 1664525 + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(42);\nfunction gaussianSample() {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// --- Data: penguin bill measurements, faceted by species (columns) x sex (rows) ---\nconst SPECIES = [\"Adelie\", \"Chinstrap\", \"Gentoo\"];\nconst SEXES = [\"Female\", \"Male\"];\nconst N_PER_FACET = 45;\n\n// Approximate per-group bill length / bill depth stats (mm), Palmer Penguins-like.\nconst GROUP_STATS = {\n  \"Adelie-Female\": { xMean: 37.3, xStd: 1.6, yMean: 17.6, yStd: 0.9 },\n  \"Adelie-Male\": { xMean: 40.4, xStd: 2.0, yMean: 19.1, yStd: 1.0 },\n  \"Chinstrap-Female\": { xMean: 46.6, xStd: 3.0, yMean: 17.6, yStd: 0.9 },\n  \"Chinstrap-Male\": { xMean: 51.5, xStd: 1.6, yMean: 19.3, yStd: 0.8 },\n  \"Gentoo-Female\": { xMean: 45.6, xStd: 2.0, yMean: 14.2, yStd: 0.8 },\n  \"Gentoo-Male\": { xMean: 49.5, xStd: 2.6, yMean: 15.7, yStd: 0.9 },\n};\n\n// facets[row][col] = { species, sex, points } — a 2-row x 3-column grid.\nconst facets = SEXES.map((sex) =>\n  SPECIES.map((species) => {\n    const stats = GROUP_STATS[`${species}-${sex}`];\n    const points = Array.from({ length: N_PER_FACET }, (_, i) => ({\n      id: i,\n      x: stats.xMean + stats.xStd * gaussianSample(),\n      y: stats.yMean + stats.yStd * gaussianSample(),\n    }));\n    return { species, sex, points };\n  }),\n);\n\n// Shared axis domain (with padding) — every facet renders on the same scale\n// so the panels are directly comparable, per the faceted-grid convention.\nconst allPoints = facets.flat().flatMap((facet) => facet.points);\nconst allX = allPoints.map((p) => p.x);\nconst allY = allPoints.map((p) => p.y);\nconst xPad = (Math.max(...allX) - Math.min(...allX)) * 0.08;\nconst yPad = (Math.max(...allY) - Math.min(...allY)) * 0.08;\nconst X_DOMAIN = [Math.min(...allX) - xPad, Math.max(...allX) + xPad];\nconst Y_DOMAIN = [Math.min(...allY) - yPad, Math.max(...allY) + yPad];\n\n// --- Layout constants (CSS px, mount coordinate space) ----------------------\nconst TITLE_H = 56;\nconst Y_LABEL_W = 32;\nconst X_LABEL_H = 32;\nconst COL_STRIP_H = 40;\nconst ROW_STRIP_W = 88;\nconst GAP = 10;\nconst TICK_LABEL_STYLE = { fontSize: 14 };\nconst PANEL_MARGIN = { left: 46, right: 10, top: 8, bottom: 30 };\n\nfunction StripLabel({ children, rotate }) {\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        background: t.elevatedBg,\n        color: t.ink,\n        fontSize: 15,\n        fontWeight: 500,\n        fontFamily: FONT,\n      }}\n    >\n      <span style={rotate ? { transform: \"rotate(90deg)\", whiteSpace: \"nowrap\" } : { whiteSpace: \"nowrap\" }}>\n        {children}\n      </span>\n    </div>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  const { width: W, height: H } = window.ANYPLOT_SIZE;\n  const nCols = SPECIES.length;\n  const nRows = SEXES.length;\n\n  const gridW = W - Y_LABEL_W - ROW_STRIP_W - nCols * GAP;\n  const gridH = H - TITLE_H - X_LABEL_H - COL_STRIP_H - nRows * GAP;\n  const panelW = gridW / nCols;\n  const panelH = gridH / nRows;\n\n  return (\n    <div style={{ width: W, height: H, display: \"flex\", flexDirection: \"column\", fontFamily: FONT }}>\n      <div style={{ height: TITLE_H, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n        <span style={{ fontSize: 22, fontWeight: 600, color: t.ink }}>{TITLE}</span>\n      </div>\n\n      <div style={{ flex: 1, display: \"flex\" }}>\n        <div style={{ width: Y_LABEL_W, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n          <span style={{ transform: \"rotate(-90deg)\", whiteSpace: \"nowrap\", fontSize: 16, fontWeight: 500, color: t.ink }}>\n            Bill Depth (mm)\n          </span>\n        </div>\n\n        <div style={{ flex: 1, display: \"flex\", flexDirection: \"column\" }}>\n          <div\n            style={{\n              display: \"grid\",\n              gridTemplateColumns: `repeat(${nCols}, ${panelW}px) ${ROW_STRIP_W}px`,\n              gridTemplateRows: `${COL_STRIP_H}px repeat(${nRows}, ${panelH}px)`,\n              columnGap: GAP,\n              rowGap: GAP,\n            }}\n          >\n            {SPECIES.map((species, c) => (\n              <div key={`col-${species}`} style={{ gridColumn: c + 1, gridRow: 1 }}>\n                <StripLabel>{species}</StripLabel>\n              </div>\n            ))}\n            <div style={{ gridColumn: nCols + 1, gridRow: 1 }} />\n\n            {SEXES.map((sex, r) =>\n              SPECIES.map((species, c) => {\n                const facet = facets[r][c];\n                const isLeftCol = c === 0;\n                const isBottomRow = r === nRows - 1;\n                return (\n                  <div key={`${species}-${sex}`} style={{ gridColumn: c + 1, gridRow: r + 2 }}>\n                    <ChartContainer\n                      width={panelW}\n                      height={panelH}\n                      skipAnimation\n                      margin={PANEL_MARGIN}\n                      xAxis={[{ id: \"x\", scaleType: \"linear\", min: X_DOMAIN[0], max: X_DOMAIN[1], tickLabelStyle: TICK_LABEL_STYLE }]}\n                      yAxis={[{ id: \"y\", scaleType: \"linear\", min: Y_DOMAIN[0], max: Y_DOMAIN[1], tickLabelStyle: TICK_LABEL_STYLE }]}\n                      series={[\n                        {\n                          id: `${species}-${sex}`,\n                          type: \"scatter\",\n                          color: MARKER_COLOR,\n                          markerSize: 6,\n                          data: facet.points,\n                        },\n                      ]}\n                      sx={{ \"& .MuiScatter-mark\": { stroke: t.pageBg, strokeWidth: 1 } }}\n                    >\n                      <ChartsGrid horizontal vertical sx={{ \"& .MuiChartsGrid-line\": { stroke: t.grid } }} />\n                      <ScatterPlot />\n                      {isLeftCol && <ChartsYAxis axisId=\"y\" />}\n                      {isBottomRow && <ChartsXAxis axisId=\"x\" />}\n                    </ChartContainer>\n                  </div>\n                );\n              }),\n            )}\n\n            {SEXES.map((sex, r) => (\n              <div key={`row-${sex}`} style={{ gridColumn: nCols + 1, gridRow: r + 2 }}>\n                <StripLabel rotate>{sex}</StripLabel>\n              </div>\n            ))}\n          </div>\n\n          <div style={{ height: X_LABEL_H, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n            <span style={{ fontSize: 16, fontWeight: 500, color: t.ink }}>Bill Length (mm)</span>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"}