Mosaic Subplot Layout with Varying Sizes — Chart.js

A complex subplot layout where different subplots can have varying sizes and arrangements using intuitive ASCII-art style string definitions. Unlike GridSpec approaches that require explicit row/column spanning, mosaic layouts allow defining layouts through visual string patterns (e.g., "AB;CC" creates A and B on top, C spanning below), making complex configurations more readable and maintainable.

Mosaic Subplot Layout with Varying Sizes rendered with Chart.js

Renders

JavaScript source (Chart.js)

// anyplot.ai
// subplot-mosaic: Mosaic Subplot Layout with Varying Sizes
// Library: chartjs 4.4.7 | JavaScript 22.23.2
// Quality: 91/100 | Created: 2026-09-09

const t = window.ANYPLOT_TOKENS;

// --- Data: 30-day weather station dashboard (deterministic LCG) ------------
function lcg(seed) {
  let state = seed >>> 0;
  return function () {
    state = (1103515245 * state + 12345) & 0x7fffffff;
    return state / 0x7fffffff;
  };
}
const rand = lcg(11);

const numDays = 30;
const dayLabels = Array.from({ length: numDays }, (_, i) => `Day ${i + 1}`);
const temperatures = Array.from({ length: numDays }, (_, i) =>
  Number((14 + 6 * Math.sin((i / numDays) * Math.PI) + (rand() - 0.5) * 3).toFixed(1))
);

const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
const rainfallMm = months.map(() => Math.round(40 + rand() * 60));

const windHumidityPoints = Array.from({ length: 35 }, () => {
  const windSpeed = Number((5 + rand() * 25).toFixed(1));
  const humidity = Number(
    Math.max(20, Math.min(95, 75 - windSpeed * 1.1 + (rand() - 0.5) * 25)).toFixed(1)
  );
  return { x: windSpeed, y: humidity };
});

const conditionLabels = ["Sunny", "Cloudy", "Rainy", "Snow"];
const conditionCounts = [13, 9, 6, 2]; // sums to numDays

const directionLabels = ["N", "E", "S", "W"];
const directionCounts = [8, 11, 6, 5]; // sums to numDays

// Air-quality tiers use the traffic-light semantic exception (good/warning/bad)
const aqiLabels = ["Good", "Moderate", "Poor"];
const aqiCounts = [18, 9, 3]; // sums to numDays
const aqiColors = [t.palette[0], t.amber, t.palette[4]];

// --- Layout: "AAA;BBC;DEF" mosaic string -> CSS grid-template-areas --------
// Mirrors the ASCII-art mosaic syntax from the spec (e.g. matplotlib's
// subplot_mosaic "AB;CC"): repeated letters span cells, each unique letter
// becomes one independently-sized Chart.js canvas.
const mosaicPattern = "AAA;BBC;DEF";
const mosaicRows = mosaicPattern.split(";");
const gridTemplateAreas = mosaicRows.map((row) => `"${row.split("").join(" ")}"`).join(" ");

const root = document.createElement("div");
root.style.cssText = `
  width: 100%; height: 100%; box-sizing: border-box;
  display: flex; flex-direction: column;
  padding: 22px 26px; background: ${t.pageBg};
  font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
`;
document.getElementById("container").appendChild(root);

const header = document.createElement("div");
header.textContent = "subplot-mosaic · javascript · chartjs · anyplot.ai";
header.style.cssText = `
  color: ${t.ink}; font-size: 22px; font-weight: 600;
  text-align: center; flex-shrink: 0; margin-bottom: 6px;
`;
root.appendChild(header);

const caption = document.createElement("div");
caption.textContent =
  "Mosaic \"AAA;BBC;DEF\" — the dominant trend spans the full width; supporting panels shrink to match their analytical weight";
caption.style.cssText = `
  color: ${t.inkSoft}; font-size: 13px; font-style: italic;
  text-align: center; flex-shrink: 0; margin-bottom: 16px;
`;
root.appendChild(caption);

const grid = document.createElement("div");
grid.style.cssText = `
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 1.5fr 1.15fr 1fr; grid-template-areas: ${gridTemplateAreas};
  gap: 20px;
`;
root.appendChild(grid);

function makeCell(area) {
  const cell = document.createElement("div");
  cell.style.cssText = `
    grid-area: ${area}; position: relative; min-width: 0; min-height: 0; box-sizing: border-box;
    background: ${t.elevatedBg}; border-radius: 10px; padding: 14px 18px;
  `;
  const canvas = document.createElement("canvas");
  cell.appendChild(canvas);
  grid.appendChild(cell);
  return canvas;
}

const titleFont = (size) => ({ display: true, text: "", color: t.ink, font: { size, weight: "600" }, padding: { bottom: 8 } });
const panelTitle = (text, size) => ({ ...titleFont(size), text });
const axisTicks = (size) => ({ color: t.inkSoft, font: { size } });
const axisTitle = (text, size) => ({ display: true, text, color: t.inkSoft, font: { size } });

// Panel A (dominant, full width): 30-day temperature trend
new Chart(makeCell("A"), {
  type: "line",
  data: {
    labels: dayLabels,
    datasets: [
      {
        label: "Avg Temperature",
        data: temperatures,
        borderColor: t.palette[0],
        backgroundColor: (context) => {
          const { ctx, chartArea } = context.chart;
          if (!chartArea) return `${t.palette[0]}00`;
          const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
          gradient.addColorStop(0, `${t.palette[0]}40`);
          gradient.addColorStop(1, `${t.palette[0]}00`);
          return gradient;
        },
        fill: true,
        borderWidth: 2.5,
        pointRadius: 0,
        tension: 0.2,
      },
    ],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: {
      title: panelTitle("Daily Avg Temperature (30 Days)", 19),
      legend: { display: false },
    },
    scales: {
      x: {
        ticks: { ...axisTicks(11), autoSkip: false, callback: (_v, i) => (i % 5 === 0 ? dayLabels[i] : null) },
        grid: { display: false },
      },
      y: { ticks: axisTicks(12), grid: { color: t.grid }, title: axisTitle("°C", 13) },
    },
  },
});

// Panel B (medium, 2 cols): rainfall by month
new Chart(makeCell("B"), {
  type: "bar",
  data: {
    labels: months,
    datasets: [{ label: "Rainfall", data: rainfallMm, backgroundColor: t.palette[0], borderWidth: 0 }],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: { title: panelTitle("Monthly Rainfall", 16), legend: { display: false } },
    scales: {
      x: { ticks: axisTicks(12), grid: { display: false } },
      y: { ticks: axisTicks(11), grid: { color: t.grid }, title: axisTitle("mm", 12), beginAtZero: true },
    },
  },
});

// Panel C (medium, 1 col): wind speed vs. humidity
new Chart(makeCell("C"), {
  type: "scatter",
  data: {
    datasets: [{ label: "Reading", data: windHumidityPoints, backgroundColor: t.palette[0], pointRadius: 6.5 }],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: { title: panelTitle("Wind vs. Humidity", 16), legend: { display: false } },
    scales: {
      x: { ticks: axisTicks(11), grid: { color: t.grid }, title: axisTitle("Wind (km/h)", 12) },
      y: { ticks: axisTicks(11), grid: { color: t.grid }, title: axisTitle("Humidity (%)", 12) },
    },
  },
});

// Panel D (small): weather condition split
new Chart(makeCell("D"), {
  type: "doughnut",
  data: {
    labels: conditionLabels,
    datasets: [{ data: conditionCounts, backgroundColor: t.palette.slice(0, 4), borderColor: t.pageBg, borderWidth: 2 }],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: {
      title: panelTitle("Conditions", 14),
      legend: { position: "bottom", labels: { color: t.inkSoft, font: { size: 12 }, boxWidth: 10, boxHeight: 10 } },
    },
  },
});

// Panel E (small): wind direction frequency
new Chart(makeCell("E"), {
  type: "polarArea",
  data: {
    labels: directionLabels,
    datasets: [{ data: directionCounts, backgroundColor: t.palette.slice(0, 4).map((c) => `${c}CC`), borderColor: t.pageBg, borderWidth: 1.5 }],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: {
      title: panelTitle("Wind Direction", 14),
      legend: { position: "bottom", labels: { color: t.inkSoft, font: { size: 12 }, boxWidth: 10, boxHeight: 10 } },
    },
    scales: { r: { ticks: { display: false }, grid: { color: t.grid }, angleLines: { color: t.grid } } },
  },
});

// Panel F (small): air-quality tiers (traffic-light semantic colors)
new Chart(makeCell("F"), {
  type: "bar",
  data: {
    labels: aqiLabels,
    datasets: [{ data: aqiCounts, backgroundColor: aqiColors, borderWidth: 0, categoryPercentage: 0.7 }],
  },
  options: {
    indexAxis: "y",
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: { title: panelTitle("Air Quality Days", 14), legend: { display: false } },
    scales: {
      x: { ticks: axisTicks(12), grid: { color: t.grid }, beginAtZero: true },
      y: { ticks: axisTicks(12), grid: { display: false } },
    },
  },
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/subplot-mosaic/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": "subplot-mosaic",
  "language": "javascript",
  "library": "chartjs",
  "page": "https://anyplot.ai/subplot-mosaic/javascript/chartjs",
  "hub": "https://anyplot.ai/subplot-mosaic",
  "code_json": "https://api.anyplot.ai/specs/subplot-mosaic/chartjs/code",
  "spec_json": "https://api.anyplot.ai/specs/subplot-mosaic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/subplot-mosaic/javascript/chartjs/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/subplot-mosaic/javascript/chartjs/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/subplot-mosaic/javascript/chartjs/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/subplot-mosaic/javascript/chartjs/plot-dark.html",
  "quality_score": 91.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Mosaic Subplot Layout with Varying Sizes on anyplot.ai.

Other implementations