{"spec_id":"manhattan-gwas","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nmanhattan-gwas: Manhattan Plot for GWAS\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\nimport sys\n\nimport numpy as np\nimport pandas as pd\n\n\n# Remove current directory from path to avoid shadowing plotly package\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\nif current_dir in sys.path:\n    sys.path.remove(current_dir)\n\nimport plotly.graph_objects as go\n\n\n# Theme configuration\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Okabe-Ito palette\nIMPRINT = [\n    \"#009E73\",  # bluish green (brand)\n    \"#C475FD\",  # vermillion\n    \"#4467A3\",  # blue\n    \"#BD8233\",  # reddish purple\n    \"#AE3030\",  # orange\n    \"#2ABCCD\",  # sky blue\n    \"#954477\",  # yellow\n]\n\n# Threshold line colors (theme-adaptive)\nTHRESHOLD_COLOR = INK_MUTED\nHIGHLIGHT_COLOR = IMPRINT[1]  # vermillion for significant SNPs\n\n# Data - Simulated GWAS results\nnp.random.seed(42)\n\n# Chromosome lengths (simplified, in Mb)\nchr_lengths = {\n    \"1\": 249,\n    \"2\": 243,\n    \"3\": 198,\n    \"4\": 191,\n    \"5\": 182,\n    \"6\": 171,\n    \"7\": 159,\n    \"8\": 146,\n    \"9\": 141,\n    \"10\": 136,\n    \"11\": 135,\n    \"12\": 134,\n    \"13\": 115,\n    \"14\": 107,\n    \"15\": 102,\n    \"16\": 90,\n    \"17\": 83,\n    \"18\": 80,\n    \"19\": 59,\n    \"20\": 64,\n    \"21\": 47,\n    \"22\": 51,\n}\n\n# Generate SNPs for each chromosome\ndata = []\ncumulative_pos = 0\nchr_centers = {}\n\nfor chrom, length in chr_lengths.items():\n    # Number of SNPs proportional to chromosome length\n    n_snps = int(length * 40)\n    positions = np.sort(np.random.uniform(0, length * 1e6, n_snps))\n\n    # Generate p-values (mostly non-significant, with some peaks)\n    pvalues = np.random.uniform(0, 1, n_snps)\n\n    # Add significant peaks on chromosomes 2, 8, and 15\n    if chrom == \"2\":\n        peak_idx = np.abs(positions - 100e6).argmin()\n        pvalues[peak_idx - 5 : peak_idx + 5] = 10 ** (-np.random.uniform(8, 12, 10))\n    elif chrom == \"8\":\n        peak_idx = np.abs(positions - 70e6).argmin()\n        pvalues[peak_idx - 3 : peak_idx + 3] = 10 ** (-np.random.uniform(7.5, 10, 6))\n    elif chrom == \"15\":\n        peak_idx = np.abs(positions - 50e6).argmin()\n        pvalues[peak_idx - 4 : peak_idx + 4] = 10 ** (-np.random.uniform(9, 14, 8))\n\n    # Calculate cumulative position\n    cumulative_positions = positions + cumulative_pos\n    chr_centers[chrom] = cumulative_pos + (length * 1e6) / 2\n\n    for i in range(n_snps):\n        data.append(\n            {\n                \"chromosome\": chrom,\n                \"position\": positions[i],\n                \"cumulative_pos\": cumulative_positions[i],\n                \"p_value\": pvalues[i],\n                \"neg_log_p\": -np.log10(pvalues[i]),\n            }\n        )\n\n    cumulative_pos += length * 1e6\n\ndf = pd.DataFrame(data)\n\n\n# Alternating chromosome colors (Okabe-Ito positions 1 and 2)\ndef get_chr_color(chrom_num):\n    return IMPRINT[0] if int(chrom_num) % 2 == 1 else IMPRINT[1]\n\n\n# Create figure\nfig = go.Figure()\n\n# Add scatter traces for each chromosome\nfor chrom in chr_lengths.keys():\n    chr_data = df[df[\"chromosome\"] == chrom]\n    color = get_chr_color(chrom)\n\n    fig.add_trace(\n        go.Scatter(\n            x=chr_data[\"cumulative_pos\"],\n            y=chr_data[\"neg_log_p\"],\n            mode=\"markers\",\n            marker={\"size\": 5, \"color\": color, \"opacity\": 0.7},\n            name=f\"Chr {chrom}\",\n            showlegend=False,\n            hovertemplate=(\n                f\"Chr {chrom}<br>Position: %{{customdata[0]:,.0f}} bp<br>-log₁₀(p): %{{y:.2f}}<extra></extra>\"\n            ),\n            customdata=chr_data[[\"position\"]].values,\n        )\n    )\n\n# Genome-wide significance threshold (-log10(5e-8) ≈ 7.3)\nsignificance_threshold = -np.log10(5e-8)\nfig.add_shape(\n    type=\"line\",\n    x0=0,\n    x1=1,\n    xref=\"paper\",\n    y0=significance_threshold,\n    y1=significance_threshold,\n    line={\"color\": THRESHOLD_COLOR, \"width\": 2, \"dash\": \"dash\"},\n)\nfig.add_annotation(\n    text=\"Genome-wide significance (p = 5×10⁻⁸)\",\n    font={\"size\": 16, \"color\": THRESHOLD_COLOR},\n    xref=\"paper\",\n    x=0.99,\n    xanchor=\"right\",\n    yref=\"y\",\n    y=significance_threshold,\n    showarrow=False,\n    yshift=15,\n)\n\n# Suggestive threshold (-log10(1e-5) = 5)\nsuggestive_threshold = 5\nfig.add_shape(\n    type=\"line\",\n    x0=0,\n    x1=1,\n    xref=\"paper\",\n    y0=suggestive_threshold,\n    y1=suggestive_threshold,\n    line={\"color\": INK_MUTED, \"width\": 2, \"dash\": \"dot\"},\n)\nfig.add_annotation(\n    text=\"Suggestive threshold (p = 10⁻⁵)\",\n    font={\"size\": 16, \"color\": INK_MUTED},\n    xref=\"paper\",\n    x=0.99,\n    xanchor=\"right\",\n    yref=\"y\",\n    y=suggestive_threshold,\n    showarrow=False,\n    yshift=15,\n)\n\n# Highlight significant SNPs\nsignificant_snps = df[df[\"neg_log_p\"] > significance_threshold]\nif len(significant_snps) > 0:\n    fig.add_trace(\n        go.Scatter(\n            x=significant_snps[\"cumulative_pos\"],\n            y=significant_snps[\"neg_log_p\"],\n            mode=\"markers\",\n            marker={\"size\": 10, \"color\": HIGHLIGHT_COLOR, \"symbol\": \"diamond\", \"line\": {\"color\": \"white\", \"width\": 1}},\n            name=\"Significant SNPs\",\n            showlegend=True,\n            hovertemplate=(\n                \"Significant SNP<br>\"\n                \"Chr %{customdata[0]}<br>\"\n                \"Position: %{customdata[1]:,.0f} bp<br>\"\n                \"-log₁₀(p): %{y:.2f}<extra></extra>\"\n            ),\n            customdata=significant_snps[[\"chromosome\", \"position\"]].values,\n        )\n    )\n\n# Chromosome tick positions and labels\nchr_positions = [chr_centers[chrom] for chrom in chr_lengths.keys()]\nchr_labels = list(chr_lengths.keys())\n\n# Layout\nfig.update_layout(\n    title={\"text\": \"manhattan-gwas · plotly · pyplots.ai\", \"font\": {\"size\": 28, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"title\": {\"text\": \"Chromosome\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"tickmode\": \"array\",\n        \"tickvals\": chr_positions,\n        \"ticktext\": chr_labels,\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"-log₁₀(p-value)\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    legend={\n        \"yanchor\": \"top\",\n        \"y\": 0.99,\n        \"xanchor\": \"left\",\n        \"x\": 0.01,\n        \"font\": {\"size\": 16, \"color\": INK_SOFT},\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 80, \"r\": 50, \"t\": 80, \"b\": 80},\n    hovermode=\"closest\",\n)\n\n# Save outputs with theme suffix\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}