{"spec_id":"ma-differential-expression","library":"muix","language":"javascript","code":"// anyplot.ai\n// ma-differential-expression: MA Plot for Differential Expression\n// Library: muix 7.29.1 | JavaScript 22.22.3\n// Quality: 87/100 | Created: 2026-06-21\n//# anyplot-orientation: landscape\n// anyplot.ai\n// ma-differential-expression: MA Plot for Differential Expression\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: 87/100 | Created: 2026-06-21\n\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ScatterPlot } from \"@mui/x-charts/ScatterChart\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsAxis } from \"@mui/x-charts/ChartsAxis\";\nimport { ChartsLegend } from \"@mui/x-charts/ChartsLegend\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { useDrawingArea } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Deterministic LCG RNG (seed=42) ---\nlet _s = 42;\nconst rand = () => {\n  _s = (1664525 * _s + 1013904223) >>> 0;\n  return _s / 0x100000000;\n};\n\n// --- Simulated RNA-seq differential expression data (MA plot) ---\n// A = mean log2 expression across conditions (x), M = log2 fold change (y)\nconst N_GENES = 10000; // spec-typical whole-transcriptome size\nconst X_MIN = 0;\nconst X_MAX = 6;\nconst Y_MIN = -8;\nconst Y_MAX = 8;\n\nconst allGenes = Array.from({ length: N_GENES }, (_, i) => {\n  const a = rand() * X_MAX;\n  // Classic MA funnel: higher dispersion at low expression\n  const dispersion = 2.5 / (a + 0.8);\n  const m = (rand() - 0.5) * 5 * dispersion + (rand() - 0.5) * 0.3;\n  // Significance: strong LFC combined with adequate expression\n  const padj = Math.exp(-Math.abs(m) * (a + 0.3) * 0.8) * rand();\n  const sig = padj < 0.05 && Math.abs(m) > 0.5;\n  return { id: i, x: a, y: m, sig };\n});\n\n// Split into three scatter series\nconst notSig = allGenes\n  .filter((g) => !g.sig)\n  .map(({ id, x, y }) => ({ id, x, y }));\nconst upReg = allGenes\n  .filter((g) => g.sig && g.y > 0)\n  .map(({ id, x, y }) => ({ id, x, y }));\nconst downReg = allGenes\n  .filter((g) => g.sig && g.y <= 0)\n  .map(({ id, x, y }) => ({ id, x, y }));\n\n// --- Windowed average smoothing (approximates LOESS trend) ---\nconst N_BINS = 22;\nconst smooth = Array.from({ length: N_BINS }, (_, b) => {\n  const cx = X_MIN + ((b + 0.5) * (X_MAX - X_MIN)) / N_BINS;\n  const bw = ((X_MAX - X_MIN) / N_BINS) * 2.2;\n  const pts = allGenes.filter((g) => Math.abs(g.x - cx) < bw);\n  if (pts.length < 5) return null;\n  return { x: cx, y: pts.reduce((s, g) => s + g.y, 0) / pts.length };\n}).filter(Boolean);\n\nconst SMOOTH_X = smooth.map((p) => p.x);\nconst SMOOTH_Y = smooth.map((p) => p.y);\n\n// Semi-transparent color for non-significant genes (8-char hex = RRGGBBAA)\nconst NOT_SIG_COLOR = t.inkSoft + \"66\"; // ~40% opacity\n\n// Triangle overlays for significant genes: adds shape as a second CVD-safe channel\n// (color alone is insufficient for protanope/deuteranope users).\n// Uses useDrawingArea to map data → pixel coordinates inside the ChartContainer SVG.\nconst SignificantMarks = () => {\n  const { left, top, width: dw, height: dh } = useDrawingArea();\n  const scaleX = (a) => left + ((a - X_MIN) / (X_MAX - X_MIN)) * dw;\n  const scaleY = (m) => top + ((Y_MAX - m) / (Y_MAX - Y_MIN)) * dh;\n\n  const R = 7; // circumradius slightly larger than markerSize=5 circles so triangle corners are clearly visible\n  const H = R * 0.866; // R * sin(60°)\n\n  return (\n    <g>\n      {upReg.map((p) => {\n        const x = scaleX(p.x);\n        const y = scaleY(p.y);\n        // Equilateral triangle, apex pointing up\n        return (\n          <polygon\n            key={p.id}\n            points={`${x},${y - R} ${x - H},${y + R * 0.5} ${x + H},${y + R * 0.5}`}\n            fill={t.palette[0]}\n          />\n        );\n      })}\n      {downReg.map((p) => {\n        const x = scaleX(p.x);\n        const y = scaleY(p.y);\n        // Equilateral triangle, apex pointing down\n        return (\n          <polygon\n            key={p.id}\n            points={`${x},${y + R} ${x - H},${y - R * 0.5} ${x + H},${y - R * 0.5}`}\n            fill={t.palette[4]}\n          />\n        );\n      })}\n    </g>\n  );\n};\n\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const TITLE_H = 56;\n  const chartH = height - TITLE_H;\n\n  return (\n    <Box\n      sx={{\n        width,\n        height,\n        display: \"flex\",\n        flexDirection: \"column\",\n        bgcolor: \"background.default\",\n      }}\n    >\n      <Typography\n        sx={{\n          height: TITLE_H,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          fontSize: 22,\n          fontWeight: 500,\n          color: \"text.primary\",\n          px: 4,\n          flexShrink: 0,\n        }}\n      >\n        ma-differential-expression · javascript · muix · anyplot.ai\n      </Typography>\n\n      <ChartContainer\n        width={width}\n        height={chartH}\n        skipAnimation\n        margin={{ top: 12, right: 100, bottom: 72, left: 96 }}\n        series={[\n          {\n            type: \"scatter\",\n            id: \"notSig\",\n            label: \"Not significant\",\n            data: notSig,\n            color: NOT_SIG_COLOR,\n            markerSize: 2.5,\n            xAxisId: \"x\",\n            yAxisId: \"y\",\n          },\n          {\n            type: \"scatter\",\n            id: \"upReg\",\n            label: \"Up-regulated\",\n            data: upReg,\n            color: t.palette[0], // #009E73 brand green — semantic: up\n            markerSize: 5,\n            xAxisId: \"x\",\n            yAxisId: \"y\",\n          },\n          {\n            type: \"scatter\",\n            id: \"downReg\",\n            label: \"Down-regulated\",\n            data: downReg,\n            color: t.palette[4], // #AE3030 matte red — semantic: down\n            markerSize: 5,\n            xAxisId: \"x\",\n            yAxisId: \"y\",\n          },\n          {\n            type: \"line\",\n            id: \"trend\",\n            label: \"Local trend\",\n            data: SMOOTH_Y,\n            color: t.palette[5], // #2ABCCD cyan\n            showMark: false,\n            xAxisId: \"xSmooth\",\n            yAxisId: \"y\",\n            curve: \"catmullRom\",\n          },\n        ]}\n        xAxis={[\n          {\n            id: \"x\",\n            scaleType: \"linear\",\n            min: X_MIN,\n            max: X_MAX,\n            label: \"Mean Expression (log₂ A)\",\n            labelStyle: { fontSize: 16, fill: t.ink },\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n            tickNumber: 7,\n          },\n          {\n            id: \"xSmooth\",\n            scaleType: \"point\",\n            data: SMOOTH_X,\n            min: X_MIN,\n            max: X_MAX,\n            hide: true,\n          },\n        ]}\n        yAxis={[\n          {\n            id: \"y\",\n            scaleType: \"linear\",\n            min: Y_MIN,\n            max: Y_MAX,\n            label: \"Log₂ Fold Change (M)\",\n            labelStyle: { fontSize: 16, fill: t.ink },\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n            tickNumber: 9,\n          },\n        ]}\n      >\n        <ChartsGrid horizontal />\n        <ScatterPlot />\n        <SignificantMarks />\n        <LinePlot />\n        <ChartsReferenceLine\n          y={0}\n          yAxisId=\"y\"\n          lineStyle={{ stroke: t.ink, strokeWidth: 2 }}\n        />\n        <ChartsReferenceLine\n          y={1}\n          yAxisId=\"y\"\n          lineStyle={{\n            stroke: t.inkSoft,\n            strokeWidth: 1.5,\n            strokeDasharray: \"8 4\",\n          }}\n          label=\"log₂FC = 1\"\n          labelStyle={{ fontSize: 12, fill: t.inkSoft }}\n        />\n        <ChartsReferenceLine\n          y={-1}\n          yAxisId=\"y\"\n          lineStyle={{\n            stroke: t.inkSoft,\n            strokeWidth: 1.5,\n            strokeDasharray: \"8 4\",\n          }}\n          label=\"log₂FC = −1\"\n          labelStyle={{ fontSize: 12, fill: t.inkSoft }}\n        />\n        <ChartsLegend\n          position={{ vertical: \"bottom\", horizontal: \"middle\" }}\n          itemMarkWidth={14}\n          itemMarkHeight={14}\n          padding={8}\n        />\n        <ChartsAxis />\n      </ChartContainer>\n    </Box>\n  );\n}\n"}