Faceted Grid Plot — MUI X Charts

A grid of subplots where each cell shows the same type of plot for a different subset of data, split by one or two categorical variables. Enables systematic comparison across multiple dimensions.

Faceted Grid Plot rendered with MUI X Charts

Renders

JavaScript source (MUI X Charts)

// anyplot.ai
// facet-grid: Faceted Grid Plot
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 89/100 | Created: 2026-09-05
//# anyplot-orientation: landscape
// anyplot.ai
// facet-grid: Faceted Grid Plot
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-09-05
import { ChartContainer } from "@mui/x-charts/ChartContainer";
import { ScatterPlot } from "@mui/x-charts/ScatterChart";
import { ChartsXAxis } from "@mui/x-charts/ChartsXAxis";
import { ChartsYAxis } from "@mui/x-charts/ChartsYAxis";
import { ChartsGrid } from "@mui/x-charts/ChartsGrid";

const t = window.ANYPLOT_TOKENS;
const FONT = "system-ui, -apple-system, 'Segoe UI', sans-serif";
const TITLE = "facet-grid · javascript · muix · anyplot.ai";

// Moderate per-facet density (~45 pts) → alpha ~0.8 + a thin edge stroke keeps
// overlapping points in the denser clusters (Adelie, Gentoo-male) distinguishable.
function hexToRgba(hex, alpha) {
  const h = hex.replace("#", "");
  const r = parseInt(h.slice(0, 2), 16);
  const g = parseInt(h.slice(2, 4), 16);
  const b = parseInt(h.slice(4, 6), 16);
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
const MARKER_COLOR = hexToRgba(t.palette[0], 0.8);

// --- Deterministic PRNG (LCG) + Box-Muller normal --------------------------
// The browser has no seeded Math.random(), so every facet's sample is drawn
// from one shared, deterministic generator.
function makeLcg(seed) {
  let state = seed >>> 0;
  return function next() {
    state = (state * 1664525 + 1013904223) >>> 0;
    return state / 4294967296;
  };
}
const rand = makeLcg(42);
function gaussianSample() {
  const u1 = Math.max(rand(), 1e-9);
  const u2 = rand();
  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}

// --- Data: penguin bill measurements, faceted by species (columns) x sex (rows) ---
const SPECIES = ["Adelie", "Chinstrap", "Gentoo"];
const SEXES = ["Female", "Male"];
const N_PER_FACET = 45;

// Approximate per-group bill length / bill depth stats (mm), Palmer Penguins-like.
const GROUP_STATS = {
  "Adelie-Female": { xMean: 37.3, xStd: 1.6, yMean: 17.6, yStd: 0.9 },
  "Adelie-Male": { xMean: 40.4, xStd: 2.0, yMean: 19.1, yStd: 1.0 },
  "Chinstrap-Female": { xMean: 46.6, xStd: 3.0, yMean: 17.6, yStd: 0.9 },
  "Chinstrap-Male": { xMean: 51.5, xStd: 1.6, yMean: 19.3, yStd: 0.8 },
  "Gentoo-Female": { xMean: 45.6, xStd: 2.0, yMean: 14.2, yStd: 0.8 },
  "Gentoo-Male": { xMean: 49.5, xStd: 2.6, yMean: 15.7, yStd: 0.9 },
};

// facets[row][col] = { species, sex, points } — a 2-row x 3-column grid.
const facets = SEXES.map((sex) =>
  SPECIES.map((species) => {
    const stats = GROUP_STATS[`${species}-${sex}`];
    const points = Array.from({ length: N_PER_FACET }, (_, i) => ({
      id: i,
      x: stats.xMean + stats.xStd * gaussianSample(),
      y: stats.yMean + stats.yStd * gaussianSample(),
    }));
    return { species, sex, points };
  }),
);

// Shared axis domain (with padding) — every facet renders on the same scale
// so the panels are directly comparable, per the faceted-grid convention.
const allPoints = facets.flat().flatMap((facet) => facet.points);
const allX = allPoints.map((p) => p.x);
const allY = allPoints.map((p) => p.y);
const xPad = (Math.max(...allX) - Math.min(...allX)) * 0.08;
const yPad = (Math.max(...allY) - Math.min(...allY)) * 0.08;
const X_DOMAIN = [Math.min(...allX) - xPad, Math.max(...allX) + xPad];
const Y_DOMAIN = [Math.min(...allY) - yPad, Math.max(...allY) + yPad];

// --- Layout constants (CSS px, mount coordinate space) ----------------------
const TITLE_H = 56;
const Y_LABEL_W = 32;
const X_LABEL_H = 32;
const COL_STRIP_H = 40;
const ROW_STRIP_W = 88;
const GAP = 10;
const TICK_LABEL_STYLE = { fontSize: 14 };
const PANEL_MARGIN = { left: 46, right: 10, top: 8, bottom: 30 };

function StripLabel({ children, rotate }) {
  return (
    <div
      style={{
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        background: t.elevatedBg,
        color: t.ink,
        fontSize: 15,
        fontWeight: 500,
        fontFamily: FONT,
      }}
    >
      <span style={rotate ? { transform: "rotate(90deg)", whiteSpace: "nowrap" } : { whiteSpace: "nowrap" }}>
        {children}
      </span>
    </div>
  );
}

// --- Chart (default-exported component — the harness mounts it) -------------
export default function Chart() {
  const { width: W, height: H } = window.ANYPLOT_SIZE;
  const nCols = SPECIES.length;
  const nRows = SEXES.length;

  const gridW = W - Y_LABEL_W - ROW_STRIP_W - nCols * GAP;
  const gridH = H - TITLE_H - X_LABEL_H - COL_STRIP_H - nRows * GAP;
  const panelW = gridW / nCols;
  const panelH = gridH / nRows;

  return (
    <div style={{ width: W, height: H, display: "flex", flexDirection: "column", fontFamily: FONT }}>
      <div style={{ height: TITLE_H, display: "flex", alignItems: "center", justifyContent: "center" }}>
        <span style={{ fontSize: 22, fontWeight: 600, color: t.ink }}>{TITLE}</span>
      </div>

      <div style={{ flex: 1, display: "flex" }}>
        <div style={{ width: Y_LABEL_W, display: "flex", alignItems: "center", justifyContent: "center" }}>
          <span style={{ transform: "rotate(-90deg)", whiteSpace: "nowrap", fontSize: 16, fontWeight: 500, color: t.ink }}>
            Bill Depth (mm)
          </span>
        </div>

        <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
          <div
            style={{
              display: "grid",
              gridTemplateColumns: `repeat(${nCols}, ${panelW}px) ${ROW_STRIP_W}px`,
              gridTemplateRows: `${COL_STRIP_H}px repeat(${nRows}, ${panelH}px)`,
              columnGap: GAP,
              rowGap: GAP,
            }}
          >
            {SPECIES.map((species, c) => (
              <div key={`col-${species}`} style={{ gridColumn: c + 1, gridRow: 1 }}>
                <StripLabel>{species}</StripLabel>
              </div>
            ))}
            <div style={{ gridColumn: nCols + 1, gridRow: 1 }} />

            {SEXES.map((sex, r) =>
              SPECIES.map((species, c) => {
                const facet = facets[r][c];
                const isLeftCol = c === 0;
                const isBottomRow = r === nRows - 1;
                return (
                  <div key={`${species}-${sex}`} style={{ gridColumn: c + 1, gridRow: r + 2 }}>
                    <ChartContainer
                      width={panelW}
                      height={panelH}
                      skipAnimation
                      margin={PANEL_MARGIN}
                      xAxis={[{ id: "x", scaleType: "linear", min: X_DOMAIN[0], max: X_DOMAIN[1], tickLabelStyle: TICK_LABEL_STYLE }]}
                      yAxis={[{ id: "y", scaleType: "linear", min: Y_DOMAIN[0], max: Y_DOMAIN[1], tickLabelStyle: TICK_LABEL_STYLE }]}
                      series={[
                        {
                          id: `${species}-${sex}`,
                          type: "scatter",
                          color: MARKER_COLOR,
                          markerSize: 6,
                          data: facet.points,
                        },
                      ]}
                      sx={{ "& .MuiScatter-mark": { stroke: t.pageBg, strokeWidth: 1 } }}
                    >
                      <ChartsGrid horizontal vertical sx={{ "& .MuiChartsGrid-line": { stroke: t.grid } }} />
                      <ScatterPlot />
                      {isLeftCol && <ChartsYAxis axisId="y" />}
                      {isBottomRow && <ChartsXAxis axisId="x" />}
                    </ChartContainer>
                  </div>
                );
              }),
            )}

            {SEXES.map((sex, r) => (
              <div key={`row-${sex}`} style={{ gridColumn: nCols + 1, gridRow: r + 2 }}>
                <StripLabel rotate>{sex}</StripLabel>
              </div>
            ))}
          </div>

          <div style={{ height: X_LABEL_H, display: "flex", alignItems: "center", justifyContent: "center" }}>
            <span style={{ fontSize: 16, fontWeight: 500, color: t.ink }}>Bill Length (mm)</span>
          </div>
        </div>
      </div>
    </div>
  );
}

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/facet-grid/muix/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "facet-grid",
  "language": "javascript",
  "library": "muix",
  "page": "https://anyplot.ai/facet-grid/javascript/muix",
  "hub": "https://anyplot.ai/facet-grid",
  "code_json": "https://api.anyplot.ai/specs/facet-grid/muix/code",
  "spec_json": "https://api.anyplot.ai/specs/facet-grid",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/facet-grid/javascript/muix/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/facet-grid/javascript/muix/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/facet-grid/javascript/muix/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/facet-grid/javascript/muix/plot-dark.html",
  "quality_score": 89.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Faceted Grid Plot on anyplot.ai.

Other implementations