Faceted Grid Plot — Chart.js

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 Chart.js

Renders

JavaScript source (Chart.js)

// anyplot.ai
// facet-grid: Faceted Grid Plot
// Library: chartjs 4.4.7 | JavaScript 22.23.2
// Quality: 89/100 | Created: 2026-09-05
//# anyplot-orientation: landscape

const t = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic LCG) ------------------------------------
function makeLcg(seed) {
  let state = seed;
  return () => {
    state = (state * 1664525 + 1013904223) % 4294967296;
    return state / 4294967296;
  };
}
const rand = makeLcg(42);
function randNormal(mean, std) {
  const u1 = Math.max(rand(), 1e-9);
  const u2 = rand();
  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
  return mean + z * std;
}
function linearRegression(points) {
  const n = points.length;
  const sumX = points.reduce((s, p) => s + p.x, 0);
  const sumY = points.reduce((s, p) => s + p.y, 0);
  const sumXY = points.reduce((s, p) => s + p.x * p.y, 0);
  const sumXX = points.reduce((s, p) => s + p.x * p.x, 0);
  const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
  const intercept = (sumY - slope * sumX) / n;
  return { slope, intercept };
}

const vehicleClasses = ["Sedan", "SUV"];
const drivetrains = ["FWD", "AWD", "RWD"];
const engineBaseByClass = { Sedan: 2.0, SUV: 3.2 };
const mpgPenaltyByDrivetrain = { FWD: 0, AWD: -3, RWD: -5 };

const POINTS_PER_FACET = 140;
const facets = [];
for (const vehicleClass of vehicleClasses) {
  for (const drivetrain of drivetrains) {
    const points = [];
    for (let i = 0; i < POINTS_PER_FACET; i++) {
      const engineSize = Math.max(
        1.0,
        randNormal(engineBaseByClass[vehicleClass], 0.6),
      );
      const baseMpg = 45 - engineSize * 6 + mpgPenaltyByDrivetrain[drivetrain];
      const fuelEconomy = Math.max(8, baseMpg + randNormal(0, 2.5));
      points.push({ x: engineSize, y: fuelEconomy });
    }
    facets.push({
      row: vehicleClass,
      col: drivetrain,
      points,
      trend: linearRegression(points),
    });
  }
}

// Shared axis range across every facet, per the spec's "same axes scales by default"
const allX = facets.flatMap((f) => f.points.map((p) => p.x));
const allY = facets.flatMap((f) => f.points.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_MIN = Math.floor(Math.min(...allX) - xPad);
const X_MAX = Math.ceil(Math.max(...allX) + xPad);
const Y_MIN = Math.floor(Math.min(...allY) - yPad);
const Y_MAX = Math.ceil(Math.max(...allY) + yPad);

// --- Scaffolding -------------------------------------------------------------
const style = document.createElement("style");
style.textContent = "#container, #container * { box-sizing: border-box; }";
document.head.appendChild(style);

const container = document.getElementById("container");
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.padding = "20px 28px 16px 28px";
container.style.background = t.pageBg;
container.style.fontFamily =
  "system-ui, -apple-system, Helvetica, Arial, sans-serif";

const title = document.createElement("div");
title.textContent = "facet-grid · javascript · chartjs · anyplot.ai";
title.style.color = t.ink;
title.style.fontSize = "22px";
title.style.fontWeight = "600";
title.style.textAlign = "center";
title.style.marginBottom = "14px";
container.appendChild(title);

const body = document.createElement("div");
body.style.display = "flex";
body.style.flex = "1";
body.style.minHeight = "0";
container.appendChild(body);

const yAxisLabel = document.createElement("div");
yAxisLabel.textContent = "Fuel Economy (mpg)";
yAxisLabel.style.color = t.ink;
yAxisLabel.style.fontSize = "16px";
yAxisLabel.style.writingMode = "vertical-rl";
yAxisLabel.style.transform = "rotate(180deg)";
yAxisLabel.style.display = "flex";
yAxisLabel.style.alignItems = "center";
yAxisLabel.style.justifyContent = "center";
yAxisLabel.style.padding = "0 8px";
body.appendChild(yAxisLabel);

const gridArea = document.createElement("div");
gridArea.style.display = "flex";
gridArea.style.flexDirection = "column";
gridArea.style.flex = "1";
gridArea.style.minWidth = "0";
body.appendChild(gridArea);

// Column strip labels (top), with a trailing spacer matching the row-label column
const colHeaderRow = document.createElement("div");
colHeaderRow.style.display = "flex";
colHeaderRow.style.gap = "6px";
for (const drivetrain of drivetrains) {
  const cell = document.createElement("div");
  cell.textContent = `Drivetrain: ${drivetrain}`;
  cell.style.flex = "1";
  cell.style.textAlign = "center";
  cell.style.color = t.ink;
  cell.style.fontSize = "15px";
  cell.style.fontWeight = "600";
  cell.style.background = t.elevatedBg;
  cell.style.borderRadius = "6px";
  cell.style.padding = "5px 6px";
  cell.style.marginBottom = "6px";
  colHeaderRow.appendChild(cell);
}
const colHeaderSpacer = document.createElement("div");
colHeaderSpacer.style.width = "32px";
colHeaderSpacer.style.flexShrink = "0";
colHeaderRow.appendChild(colHeaderSpacer);
gridArea.appendChild(colHeaderRow);

const rowsWrap = document.createElement("div");
rowsWrap.style.display = "flex";
rowsWrap.style.flexDirection = "column";
rowsWrap.style.flex = "1";
rowsWrap.style.minHeight = "0";
rowsWrap.style.gap = "6px";
gridArea.appendChild(rowsWrap);

vehicleClasses.forEach((vehicleClass, rowIdx) => {
  const rowDiv = document.createElement("div");
  rowDiv.style.display = "flex";
  rowDiv.style.flex = "1";
  rowDiv.style.minHeight = "0";
  rowDiv.style.gap = "6px";

  drivetrains.forEach((drivetrain, colIdx) => {
    const cellWrap = document.createElement("div");
    cellWrap.style.position = "relative";
    cellWrap.style.flex = "1";
    cellWrap.style.minWidth = "0";
    cellWrap.style.background = t.elevatedBg;
    cellWrap.style.border = `1px solid ${t.grid}`;
    cellWrap.style.borderRadius = "8px";
    cellWrap.style.overflow = "hidden";

    const canvas = document.createElement("canvas");
    cellWrap.appendChild(canvas);
    rowDiv.appendChild(cellWrap);

    const facet = facets.find(
      (f) => f.row === vehicleClass && f.col === drivetrain,
    );
    const isLeftCol = colIdx === 0;
    const isBottomRow = rowIdx === vehicleClasses.length - 1;
    const { slope, intercept } = facet.trend;
    const trendLine = [
      { x: X_MIN, y: slope * X_MIN + intercept },
      { x: X_MAX, y: slope * X_MAX + intercept },
    ];

    new Chart(canvas, {
      type: "scatter",
      data: {
        datasets: [
          {
            label: "points",
            data: facet.points,
            backgroundColor: `${t.palette[0]}B3`,
            pointRadius: 4,
            pointHoverRadius: 5,
            order: 1,
          },
          {
            type: "line",
            label: "trend",
            data: trendLine,
            borderColor: t.inkSoft,
            borderWidth: 1.5,
            borderDash: [6, 4],
            pointRadius: 0,
            pointHoverRadius: 0,
            fill: false,
            tension: 0,
            order: 0,
          },
        ],
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        animation: false,
        plugins: {
          legend: { display: false },
          title: { display: false },
          tooltip: {
            filter: (item) => item.datasetIndex === 0,
            callbacks: {
              title: () => `${vehicleClass} · ${drivetrain}`,
              label: (ctx) =>
                `${ctx.parsed.x.toFixed(1)}L → ${ctx.parsed.y.toFixed(1)} mpg`,
            },
          },
        },
        scales: {
          x: {
            min: X_MIN,
            max: X_MAX,
            ticks: {
              display: isBottomRow,
              color: t.inkSoft,
              font: { size: 13 },
              maxTicksLimit: 5,
            },
            grid: { color: t.grid },
          },
          y: {
            min: Y_MIN,
            max: Y_MAX,
            ticks: {
              display: isLeftCol,
              color: t.inkSoft,
              font: { size: 13 },
              maxTicksLimit: 5,
            },
            grid: { color: t.grid },
          },
        },
      },
    });
  });

  const rowLabel = document.createElement("div");
  rowLabel.textContent = `Class: ${vehicleClass}`;
  rowLabel.style.width = "32px";
  rowLabel.style.flexShrink = "0";
  rowLabel.style.display = "flex";
  rowLabel.style.alignItems = "center";
  rowLabel.style.justifyContent = "center";
  rowLabel.style.writingMode = "vertical-rl";
  rowLabel.style.color = t.ink;
  rowLabel.style.fontSize = "15px";
  rowLabel.style.fontWeight = "600";
  rowLabel.style.background = t.elevatedBg;
  rowLabel.style.borderRadius = "6px";
  rowDiv.appendChild(rowLabel);

  rowsWrap.appendChild(rowDiv);
});

const xAxisLabel = document.createElement("div");
xAxisLabel.textContent = "Engine Size (L)";
xAxisLabel.style.color = t.ink;
xAxisLabel.style.fontSize = "16px";
xAxisLabel.style.textAlign = "center";
xAxisLabel.style.marginTop = "10px";
gridArea.appendChild(xAxisLabel);

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/facet-grid/chartjs/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": "chartjs",
  "page": "https://anyplot.ai/facet-grid/javascript/chartjs",
  "hub": "https://anyplot.ai/facet-grid",
  "code_json": "https://api.anyplot.ai/specs/facet-grid/chartjs/code",
  "spec_json": "https://api.anyplot.ai/specs/facet-grid",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/facet-grid/javascript/chartjs/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/facet-grid/javascript/chartjs/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/facet-grid/javascript/chartjs/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/facet-grid/javascript/chartjs/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