{"spec_id":"ma-differential-expression","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// ma-differential-expression: MA Plot for Differential Expression\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 86/100 | Created: 2026-06-21\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Seeded LCG pseudo-random number generator (deterministic, no Math.random)\nfunction lcg(seed) {\n  let s = seed >>> 0;\n  return () => {\n    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;\n    return s / 4294967296;\n  };\n}\n\n// Box-Muller transform: uniform -> standard normal\nfunction randn(rng) {\n  const u = rng() + 1e-10;\n  const v = rng();\n  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);\n}\n\n// --- Data ------------------------------------------------------------------\nconst rng = lcg(42);\nconst genes = [];\n\n// 4000 background genes: noise increases at low expression levels\nfor (let i = 0; i < 4000; i++) {\n  const a = 0.5 + Math.pow(rng(), 0.65) * 13.5;\n  const sigma = 0.12 + 0.55 * Math.exp(-a / 4.5);\n  const bias = -0.28 * Math.exp(-a / 2.8);\n  const m = randn(rng) * sigma + bias;\n  genes.push({ a, m });\n}\n\n// 500 true differentially expressed genes (medium-high expression)\nconst rng2 = lcg(7919);\nfor (let i = 0; i < 500; i++) {\n  const a = 2.5 + rng2() * 10;\n  const up = rng2() > 0.45;\n  const m = (up ? 1 : -1) * (1.8 + rng2() * 2.5) + randn(rng2) * 0.35;\n  genes.push({ a, m });\n}\n\n// Classify by |M| > 1.5 significance threshold\nconst notSig = [], sigUp = [], sigDown = [];\ngenes.forEach(({ a, m }) => {\n  if (m > 1.5) sigUp.push({ x: a, y: m });\n  else if (m < -1.5) sigDown.push({ x: a, y: m });\n  else notSig.push({ x: a, y: m });\n});\n\n// --- LOESS smooth (tricube kernel, bandwidth = 2.5 A-units) ----------------\nconst smooth = Array.from({ length: 51 }, (_, i) => {\n  const ax = 0.5 + (14.0 / 50) * i;\n  let ws = 0, wt = 0;\n  genes.forEach(({ a, m }) => {\n    const d = Math.abs(a - ax) / 2.5;\n    if (d < 1) {\n      const w = Math.pow(1 - d * d * d, 3);\n      ws += w * m;\n      wt += w;\n    }\n  });\n  return wt > 2 ? { x: ax, y: ws / wt } : null;\n}).filter(Boolean);\n\n// --- Reference lines -------------------------------------------------------\nconst refZero = [{ x: 0.2, y: 0 }, { x: 14.8, y: 0 }];\nconst refPos1 = [{ x: 0.2, y: 1 }, { x: 14.8, y: 1 }];\nconst refNeg1 = [{ x: 0.2, y: -1 }, { x: 14.8, y: -1 }];\n\n// --- Mount -----------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart -----------------------------------------------------------------\nconst TITLE = \"ma-differential-expression · javascript · chartjs · anyplot.ai\";\n\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: {\n    datasets: [\n      {\n        label: \"Not significant (\" + notSig.length + \")\",\n        data: notSig,\n        backgroundColor: t.inkSoft + \"2E\",\n        borderWidth: 0,\n        pointRadius: 1.5,\n        pointHoverRadius: 2.5,\n      },\n      {\n        label: \"Up-regulated (\" + sigUp.length + \")\",\n        data: sigUp,\n        backgroundColor: t.palette[0] + \"CC\",\n        borderWidth: 0,\n        pointRadius: 3.5,\n        pointHoverRadius: 5,\n      },\n      {\n        label: \"Down-regulated (\" + sigDown.length + \")\",\n        data: sigDown,\n        backgroundColor: \"#AE3030CC\",\n        borderWidth: 0,\n        pointRadius: 3.5,\n        pointHoverRadius: 5,\n      },\n      {\n        label: \"M = 0\",\n        type: \"line\",\n        data: refZero,\n        borderColor: t.ink + \"99\",\n        borderWidth: 1.5,\n        pointRadius: 0,\n        fill: false,\n      },\n      {\n        label: \"±1 fold change\",\n        type: \"line\",\n        data: refPos1,\n        borderColor: t.inkSoft + \"77\",\n        borderWidth: 1.5,\n        borderDash: [8, 5],\n        pointRadius: 0,\n        fill: false,\n      },\n      {\n        label: \"\",\n        type: \"line\",\n        data: refNeg1,\n        borderColor: t.inkSoft + \"77\",\n        borderWidth: 1.5,\n        borderDash: [8, 5],\n        pointRadius: 0,\n        fill: false,\n      },\n      {\n        label: \"LOESS trend\",\n        type: \"line\",\n        data: smooth,\n        borderColor: t.palette[2],\n        borderWidth: 4,\n        pointRadius: 0,\n        fill: false,\n        tension: 0.3,\n        spanGaps: false,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: TITLE,\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { top: 10, bottom: 14 },\n      },\n      legend: {\n        position: \"top\",\n        labels: {\n          color: t.ink,\n          font: { size: 14 },\n          usePointStyle: true,\n          filter: (item) => item.text !== \"\",\n          padding: 20,\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: 0,\n        max: 15,\n        title: {\n          display: true,\n          text: \"A: Mean Average Expression (log₂)\",\n          color: t.ink,\n          font: { size: 16 },\n          padding: { top: 8 },\n        },\n        ticks: { color: t.inkSoft, font: { size: 13 } },\n        grid: { display: false },\n        border: { display: false },\n      },\n      y: {\n        type: \"linear\",\n        min: -7,\n        max: 7,\n        title: {\n          display: true,\n          text: \"M: Log₂ Fold Change\",\n          color: t.ink,\n          font: { size: 16 },\n          padding: { bottom: 8 },\n        },\n        ticks: { color: t.inkSoft, font: { size: 13 } },\n        grid: { color: t.grid },\n        border: { display: false },\n      },\n    },\n  },\n});\n"}