{"spec_id":"manhattan-gwas","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// manhattan-gwas: Manhattan Plot for GWAS\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Tiny LCG so the browser (no seeded Math.random) still reproduces the sample.\nfunction lcg(seed) {\n  let state = seed >>> 0;\n  return function () {\n    state = (1664525 * state + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rand = lcg(42);\n\n// Approximate human chromosome lengths in Mb (autosomes 1-22 + X/Y/MT) —\n// gives chromosomes realistic relative widths along the cumulative\n// genomic-position axis, matching the spec's full chromosome vocabulary.\n// MT's true length (16.5 kb) is thousands of times smaller than Y and would\n// collapse to an invisible sliver whose tick label collides with Y's — given\n// a nominal display width instead, as real Manhattan plots commonly do.\nconst chromLengths = [\n  249, 243, 198, 191, 181, 171, 159, 146, 141, 136, 135, 133, 115, 107, 102, 90, 83, 80, 59, 64, 47, 51, 156, 57, 15,\n];\nconst chromNames = [...Array.from({ length: 22 }, (_, i) => String(i + 1)), \"X\", \"Y\", \"MT\"];\n\nconst offsets = [];\nlet cumulative = 0;\nfor (const length of chromLengths) {\n  offsets.push(cumulative);\n  cumulative += length;\n}\nconst genomeLength = cumulative;\nconst tickPositions = chromLengths.map((length, i) => offsets[i] + length / 2);\n\nconst sigThreshold = -Math.log10(5e-8); // ≈ 7.30, genome-wide significance\nconst suggestiveThreshold = -Math.log10(1e-5); // 5, suggestive association\n\n// A handful of chromosomes carry a simulated association peak (a Gaussian\n// bump in -log10(p) around a random locus, mimicking an LD block).\nconst peakChroms = new Set([1, 5, 10, 14, 18]);\n// Density-based SNP count (per Mb) instead of a flat per-chromosome count —\n// scales naturally across the wide length range from chr1 (249 Mb) down to\n// MT's nominal 15 Mb, landing near the spec's 100k-1M GWAS variant scale\n// while staying renderable as plain (non-boosted) SVG scatter points.\nconst snpsPerMb = 15;\n\nconst oddPoints = [];\nconst evenPoints = [];\nconst sigPoints = [];\n\nchromLengths.forEach((length, c) => {\n  const offset = offsets[c];\n  const bucket = c % 2 === 0 ? oddPoints : evenPoints;\n  const hasPeak = peakChroms.has(c);\n  const peakPos = hasPeak ? rand() * length : null;\n  const peakHeight = hasPeak ? 9 + rand() * 5 : 0;\n  const peakWidth = 2.5 + rand() * 2; // Mb, LD-block scale\n  const snpCount = Math.round(length * snpsPerMb);\n\n  for (let i = 0; i < snpCount; i++) {\n    const pos = rand() * length;\n    // Under the null, p-values are Uniform(0,1), so -log10(p) is exponential\n    // with mean 1/ln(10) — a realistic background association-test noise floor.\n    let negLogP = -Math.log10(1 - rand());\n\n    if (hasPeak) {\n      const dist = pos - peakPos;\n      const bump = peakHeight * Math.exp(-(dist * dist) / (2 * peakWidth * peakWidth));\n      negLogP += bump * (0.7 + rand() * 0.3);\n    }\n\n    const point = [offset + pos, negLogP];\n    if (negLogP >= sigThreshold) {\n      sigPoints.push(point);\n    } else {\n      bucket.push(point);\n    }\n  }\n});\n\n// Subtle alternating background band per chromosome region — reinforces the\n// chromosome boundaries beyond just the point-color alternation.\nconst chromBands = chromLengths.map((length, i) => ({\n  from: offsets[i],\n  to: offsets[i] + length,\n  color: i % 2 === 0 ? \"transparent\" : Highcharts.color(t.ink).setOpacity(0.035).get(),\n}));\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"manhattan-gwas · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    title: { text: \"Chromosome\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: 0,\n    // 5% of blank headroom past MT reserves a data-free strip for the\n    // right-anchored threshold-line labels below, so they never collide with\n    // a randomly placed peak (a fixed pixel offset can't guarantee that).\n    max: genomeLength * 1.05,\n    tickPositions,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineWidth: 0,\n    plotBands: chromBands,\n    labels: {\n      style: { color: t.inkSoft, fontSize: \"13px\" },\n      formatter() {\n        return chromNames[tickPositions.indexOf(this.value)];\n      },\n    },\n  },\n  yAxis: {\n    title: { text: \"−log₁₀(p-value)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: 0,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    plotLines: [\n      {\n        value: sigThreshold,\n        color: t.palette[4],\n        dashStyle: \"Dash\",\n        width: 2,\n        zIndex: 4,\n        label: {\n          text: \"Genome-wide significance (5×10⁻⁸)\",\n          align: \"right\",\n          x: -10,\n          y: -6,\n          style: { color: t.palette[4], fontSize: \"13px\" },\n        },\n      },\n      {\n        value: suggestiveThreshold,\n        color: t.amber,\n        dashStyle: \"Dot\",\n        width: 1.5,\n        zIndex: 4,\n        label: {\n          text: \"Suggestive (1×10⁻⁵)\",\n          align: \"right\",\n          x: -10,\n          y: -6,\n          style: { color: t.amber, fontSize: \"13px\" },\n        },\n      },\n    ],\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  tooltip: {\n    headerFormat: \"\",\n    pointFormat: \"Position {point.x:.1f} Mb<br/>−log₁₀(p): <b>{point.y:.2f}</b>\",\n  },\n  plotOptions: {\n    series: { animation: false, turboThreshold: 0 },\n    scatter: {\n      marker: { radius: 1.8, symbol: \"circle\", states: { hover: { radiusPlus: 2.5 } } },\n    },\n  },\n  series: [\n    {\n      name: \"Odd chromosomes\",\n      data: oddPoints,\n      color: Highcharts.color(t.palette[0]).setOpacity(0.6).get(),\n    },\n    {\n      name: \"Even chromosomes\",\n      data: evenPoints,\n      color: Highcharts.color(t.palette[2]).setOpacity(0.6).get(),\n    },\n    {\n      name: \"Genome-wide significant\",\n      data: sigPoints,\n      color: t.palette[4],\n      marker: { radius: 3.6 },\n      zIndex: 5,\n    },\n  ],\n});\n"}