{"spec_id":"volcano-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// volcano-basic: Volcano Plot for Statistical Significance\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-09\nimport { ScatterChart } from \"@mui/x-charts/ScatterChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// RNA-seq differential expression: treatment vs. control across 350 genes.\n// Genes with a larger effect size tend to carry stronger evidence (typical\n// volcano funnel), with enough noise that the significance thresholds still\n// have to do real classification work.\nlet seed = 42;\nconst nextRandom = () => {\n  seed = (Math.imul(seed, 1103515245) + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n};\nconst nextGaussian = () => {\n  const u1 = Math.max(nextRandom(), 1e-9);\n  const u2 = nextRandom();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n};\n\nconst P_THRESHOLD = 1.3; // -log10(0.05)\nconst FC_THRESHOLD = 1; // 2-fold change\n\nconst GENE_COUNT = 350;\nconst genes = [];\nfor (let i = 0; i < GENE_COUNT; i += 1) {\n  const log2FoldChange = nextGaussian() * 1.4;\n  const noise = nextGaussian() * 0.8;\n  const negLog10Pvalue = Math.max(\n    0.01,\n    Math.abs(log2FoldChange) * 1.6 + noise + nextRandom() * 0.6,\n  );\n  genes.push({\n    id: i,\n    x: Number(log2FoldChange.toFixed(3)),\n    y: Number(negLog10Pvalue.toFixed(3)),\n  });\n}\n\nconst nonSignificant = [];\nconst upRegulated = [];\nconst downRegulated = [];\ngenes.forEach((gene) => {\n  const isSignificant = gene.y > P_THRESHOLD && Math.abs(gene.x) > FC_THRESHOLD;\n  if (!isSignificant) nonSignificant.push(gene);\n  else if (gene.x > 0) upRegulated.push(gene);\n  else downRegulated.push(gene);\n});\n\nconst xAbsMax = Math.max(...genes.map((g) => Math.abs(g.x)));\nconst xPad = xAbsMax * 0.12;\nconst yMax = Math.max(...genes.map((g) => g.y));\n\n// Significance colors follow the domain convention the spec calls out\n// (up-regulated red, down-regulated blue, non-significant muted gray) rather\n// than the default ordinal Imprint order — see \"Semantic exception\" in\n// default-style-guide.md.\nconst withAlpha = (hex, alpha) => {\n  const n = parseInt(hex.slice(1), 16);\n  const r = (n >> 16) & 255;\n  const g = (n >> 8) & 255;\n  const b = n & 255;\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n};\nconst UP_COLOR = t.palette[4]; // matte red — semantic anchor for up-regulated\nconst DOWN_COLOR = t.palette[2]; // blue — semantic anchor for down-regulated\nconst MUTED = t.theme === \"light\" ? \"#6B6A63\" : \"#A8A79F\"; // theme-adaptive muted anchor\nconst NON_SIG_COLOR = withAlpha(MUTED, 0.45);\nconst THRESHOLD_COLOR = t.amber; // warning/caution anchor for the cutoff lines\n\n// --- Title (fontsize scales with title length, see plot-generator.md) -------\nconst TITLE = \"volcano-basic · javascript · muix · anyplot.ai\";\nconst TITLE_FONTSIZE = Math.round(\n  22 * (TITLE.length > 67 ? 67 / TITLE.length : 1),\n);\nconst TITLE_H = 72;\n\nconst MARKER_SX = { \"& circle\": { stroke: t.pageBg, strokeWidth: 0.5 } };\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  return (\n    <div style={{ width, height, display: \"flex\", flexDirection: \"column\" }}>\n      <div\n        style={{\n          height: TITLE_H,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          fontSize: TITLE_FONTSIZE,\n          fontWeight: 600,\n          color: t.ink,\n          fontFamily: \"Roboto, Helvetica, Arial, sans-serif\",\n        }}\n      >\n        {TITLE}\n      </div>\n      <ScatterChart\n        width={width}\n        height={height - TITLE_H}\n        skipAnimation\n        grid={{ horizontal: true, vertical: true }}\n        margin={{ left: 96, right: 32, top: 16, bottom: 78 }}\n        slotProps={{\n          legend: { position: { vertical: \"top\", horizontal: \"right\" } },\n        }}\n        xAxis={[\n          {\n            min: -(xAbsMax + xPad),\n            max: xAbsMax + xPad,\n            label: \"log₂ Fold Change\",\n            labelStyle: { fontSize: 16, fill: t.ink },\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n          },\n        ]}\n        yAxis={[\n          {\n            min: 0,\n            max: yMax * 1.08,\n            label: \"−log₁₀(p-value)\",\n            labelStyle: { fontSize: 16, fill: t.ink },\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n          },\n        ]}\n        series={[\n          {\n            id: \"non-sig\",\n            label: \"Not significant\",\n            data: nonSignificant,\n            markerSize: 6,\n            color: NON_SIG_COLOR,\n          },\n          {\n            id: \"down\",\n            label: \"Down-regulated\",\n            data: downRegulated,\n            markerSize: 8,\n            color: withAlpha(DOWN_COLOR, 0.85),\n          },\n          {\n            id: \"up\",\n            label: \"Up-regulated\",\n            data: upRegulated,\n            markerSize: 8,\n            color: withAlpha(UP_COLOR, 0.85),\n          },\n        ]}\n        sx={MARKER_SX}\n      >\n        <ChartsReferenceLine\n          y={P_THRESHOLD}\n          label=\"p = 0.05\"\n          labelStyle={{ fontSize: 13, fill: t.inkSoft }}\n          lineStyle={{\n            stroke: THRESHOLD_COLOR,\n            strokeDasharray: \"6 4\",\n            strokeWidth: 1.5,\n          }}\n        />\n        <ChartsReferenceLine\n          x={FC_THRESHOLD}\n          label=\"FC = 2\"\n          labelStyle={{ fontSize: 13, fill: t.inkSoft }}\n          lineStyle={{\n            stroke: THRESHOLD_COLOR,\n            strokeDasharray: \"6 4\",\n            strokeWidth: 1.5,\n          }}\n        />\n        <ChartsReferenceLine\n          x={-FC_THRESHOLD}\n          label=\"FC = -2\"\n          labelStyle={{ fontSize: 13, fill: t.inkSoft }}\n          lineStyle={{\n            stroke: THRESHOLD_COLOR,\n            strokeDasharray: \"6 4\",\n            strokeWidth: 1.5,\n          }}\n        />\n      </ScatterChart>\n    </div>\n  );\n}\n"}