MA Plot for Differential Expression — Chart.js

An MA plot (M-versus-A plot) visualizes the relationship between log fold change (M) and mean average expression (A) when comparing two experimental conditions. Each point represents a gene or feature, with significantly differentially expressed genes highlighted. This plot is a standard diagnostic tool in RNA-seq and microarray analysis for assessing differential expression results and detecting systematic expression-dependent bias.

MA Plot for Differential Expression rendered with Chart.js

Renders

JavaScript source (Chart.js)

// anyplot.ai
// ma-differential-expression: MA Plot for Differential Expression
// Library: chartjs 4.4.7 | JavaScript 22.22.3
// Quality: 86/100 | Created: 2026-06-21
//# anyplot-orientation: landscape

const t = window.ANYPLOT_TOKENS;

// Seeded LCG pseudo-random number generator (deterministic, no Math.random)
function lcg(seed) {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

// Box-Muller transform: uniform -> standard normal
function randn(rng) {
  const u = rng() + 1e-10;
  const v = rng();
  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}

// --- Data ------------------------------------------------------------------
const rng = lcg(42);
const genes = [];

// 4000 background genes: noise increases at low expression levels
for (let i = 0; i < 4000; i++) {
  const a = 0.5 + Math.pow(rng(), 0.65) * 13.5;
  const sigma = 0.12 + 0.55 * Math.exp(-a / 4.5);
  const bias = -0.28 * Math.exp(-a / 2.8);
  const m = randn(rng) * sigma + bias;
  genes.push({ a, m });
}

// 500 true differentially expressed genes (medium-high expression)
const rng2 = lcg(7919);
for (let i = 0; i < 500; i++) {
  const a = 2.5 + rng2() * 10;
  const up = rng2() > 0.45;
  const m = (up ? 1 : -1) * (1.8 + rng2() * 2.5) + randn(rng2) * 0.35;
  genes.push({ a, m });
}

// Classify by |M| > 1.5 significance threshold
const notSig = [], sigUp = [], sigDown = [];
genes.forEach(({ a, m }) => {
  if (m > 1.5) sigUp.push({ x: a, y: m });
  else if (m < -1.5) sigDown.push({ x: a, y: m });
  else notSig.push({ x: a, y: m });
});

// --- LOESS smooth (tricube kernel, bandwidth = 2.5 A-units) ----------------
const smooth = Array.from({ length: 51 }, (_, i) => {
  const ax = 0.5 + (14.0 / 50) * i;
  let ws = 0, wt = 0;
  genes.forEach(({ a, m }) => {
    const d = Math.abs(a - ax) / 2.5;
    if (d < 1) {
      const w = Math.pow(1 - d * d * d, 3);
      ws += w * m;
      wt += w;
    }
  });
  return wt > 2 ? { x: ax, y: ws / wt } : null;
}).filter(Boolean);

// --- Reference lines -------------------------------------------------------
const refZero = [{ x: 0.2, y: 0 }, { x: 14.8, y: 0 }];
const refPos1 = [{ x: 0.2, y: 1 }, { x: 14.8, y: 1 }];
const refNeg1 = [{ x: 0.2, y: -1 }, { x: 14.8, y: -1 }];

// --- Mount -----------------------------------------------------------------
const canvas = document.createElement("canvas");
document.getElementById("container").appendChild(canvas);

// --- Chart -----------------------------------------------------------------
const TITLE = "ma-differential-expression · javascript · chartjs · anyplot.ai";

new Chart(canvas, {
  type: "scatter",
  data: {
    datasets: [
      {
        label: "Not significant (" + notSig.length + ")",
        data: notSig,
        backgroundColor: t.inkSoft + "2E",
        borderWidth: 0,
        pointRadius: 1.5,
        pointHoverRadius: 2.5,
      },
      {
        label: "Up-regulated (" + sigUp.length + ")",
        data: sigUp,
        backgroundColor: t.palette[0] + "CC",
        borderWidth: 0,
        pointRadius: 3.5,
        pointHoverRadius: 5,
      },
      {
        label: "Down-regulated (" + sigDown.length + ")",
        data: sigDown,
        backgroundColor: "#AE3030CC",
        borderWidth: 0,
        pointRadius: 3.5,
        pointHoverRadius: 5,
      },
      {
        label: "M = 0",
        type: "line",
        data: refZero,
        borderColor: t.ink + "99",
        borderWidth: 1.5,
        pointRadius: 0,
        fill: false,
      },
      {
        label: "±1 fold change",
        type: "line",
        data: refPos1,
        borderColor: t.inkSoft + "77",
        borderWidth: 1.5,
        borderDash: [8, 5],
        pointRadius: 0,
        fill: false,
      },
      {
        label: "",
        type: "line",
        data: refNeg1,
        borderColor: t.inkSoft + "77",
        borderWidth: 1.5,
        borderDash: [8, 5],
        pointRadius: 0,
        fill: false,
      },
      {
        label: "LOESS trend",
        type: "line",
        data: smooth,
        borderColor: t.palette[2],
        borderWidth: 4,
        pointRadius: 0,
        fill: false,
        tension: 0.3,
        spanGaps: false,
      },
    ],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: {
      title: {
        display: true,
        text: TITLE,
        color: t.ink,
        font: { size: 22, weight: "500" },
        padding: { top: 10, bottom: 14 },
      },
      legend: {
        position: "top",
        labels: {
          color: t.ink,
          font: { size: 14 },
          usePointStyle: true,
          filter: (item) => item.text !== "",
          padding: 20,
        },
      },
    },
    scales: {
      x: {
        type: "linear",
        min: 0,
        max: 15,
        title: {
          display: true,
          text: "A: Mean Average Expression (log₂)",
          color: t.ink,
          font: { size: 16 },
          padding: { top: 8 },
        },
        ticks: { color: t.inkSoft, font: { size: 13 } },
        grid: { display: false },
        border: { display: false },
      },
      y: {
        type: "linear",
        min: -7,
        max: 7,
        title: {
          display: true,
          text: "M: Log₂ Fold Change",
          color: t.ink,
          font: { size: 16 },
          padding: { bottom: 8 },
        },
        ticks: { color: t.inkSoft, font: { size: 13 } },
        grid: { color: t.grid },
        border: { display: false },
      },
    },
  },
});

Part of MA Plot for Differential Expression on anyplot.ai.

Other implementations