{"spec_id":"manhattan-gwas","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nmanhattan-gwas: Manhattan Plot for GWAS\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\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\"\nRULE = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Okabe-Ito palette for alternating chromosomes\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\"]\n\n# Data generation\nnp.random.seed(42)\n\n# Chromosome lengths (approximate human chromosome sizes in Mb)\nchrom_lengths = {\n    \"1\": 249,\n    \"2\": 243,\n    \"3\": 198,\n    \"4\": 191,\n    \"5\": 182,\n    \"6\": 171,\n    \"7\": 159,\n    \"8\": 145,\n    \"9\": 138,\n    \"10\": 134,\n    \"11\": 135,\n    \"12\": 133,\n    \"13\": 114,\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\nchrom_centers = {}\n\nfor chrom, length in chrom_lengths.items():\n    # Number of SNPs proportional to chromosome length\n    n_snps = int(length * 20)  # ~20 SNPs per Mb\n    positions = np.sort(np.random.randint(1, length * 1_000_000, n_snps))\n\n    # Base p-values (mostly non-significant)\n    pvalues = np.random.uniform(0.001, 1.0, n_snps)\n\n    # Add some significant peaks on specific chromosomes\n    if chrom in [\"2\", \"6\", \"8\", \"15\"]:\n        n_sig = np.random.randint(5, 15)\n        sig_indices = np.random.choice(n_snps, n_sig, replace=False)\n        pvalues[sig_indices] = 10 ** np.random.uniform(-10, -7, n_sig)\n\n    # Add suggestive hits on other chromosomes\n    if chrom in [\"3\", \"11\", \"19\"]:\n        n_sug = np.random.randint(3, 8)\n        sug_indices = np.random.choice(n_snps, n_sug, replace=False)\n        pvalues[sug_indices] = 10 ** np.random.uniform(-6, -5, n_sug)\n\n    # Calculate cumulative positions\n    cum_positions = positions + cumulative_pos\n    chrom_centers[chrom] = cumulative_pos + (length * 1_000_000) / 2\n\n    for pos, cum_pos, pval in zip(positions, cum_positions, pvalues, strict=True):\n        data.append(\n            {\n                \"chromosome\": chrom,\n                \"position\": pos,\n                \"cumulative_position\": cum_pos,\n                \"p_value\": pval,\n                \"neg_log_p\": -np.log10(pval),\n            }\n        )\n\n    cumulative_pos += length * 1_000_000\n\ndf = pd.DataFrame(data)\n\n# Threshold values\ngenome_wide_threshold = -np.log10(5e-8)  # ~7.3\nsuggestive_threshold = -np.log10(1e-5)  # 5.0\n\n# Create chromosome label data\nchrom_label_df = pd.DataFrame([{\"chrom_label\": chrom, \"center\": center} for chrom, center in chrom_centers.items()])\n\n# Create alternating color mapping with Okabe-Ito palette\nchrom_list = list(chrom_lengths.keys())\ncolor_mapping = {}\nfor i, chrom in enumerate(chrom_list):\n    color_mapping[chrom] = IMPRINT[i % len(IMPRINT)]\n\ncolor_scale = alt.Scale(domain=chrom_list, range=[color_mapping[chrom] for chrom in chrom_list])\n\n# Main scatter plot\npoints = (\n    alt.Chart(df)\n    .mark_circle(opacity=0.7, size=80)\n    .encode(\n        x=alt.X(\n            \"cumulative_position:Q\",\n            axis=alt.Axis(title=\"Genomic Position\", labels=False, ticks=False, titleFontSize=22, titleColor=INK),\n            scale=alt.Scale(domain=[0, cumulative_pos]),\n        ),\n        y=alt.Y(\n            \"neg_log_p:Q\",\n            title=\"-log₁₀(p-value)\",\n            axis=alt.Axis(\n                titleFontSize=22,\n                labelFontSize=18,\n                titleColor=INK,\n                labelColor=INK_SOFT,\n                domainColor=INK_SOFT,\n                tickColor=INK_SOFT,\n                gridColor=INK,\n                gridOpacity=0.10,\n            ),\n            scale=alt.Scale(domain=[0, max(df[\"neg_log_p\"]) + 1]),\n        ),\n        color=alt.Color(\"chromosome:N\", scale=color_scale, legend=None),\n        size=alt.condition(\n            alt.datum.neg_log_p > genome_wide_threshold,\n            alt.value(100),  # Larger for significant hits\n            alt.value(60),  # Smaller for others\n        ),\n        tooltip=[\n            \"chromosome:N\",\n            alt.Tooltip(\"position:Q\", format=\",\"),\n            alt.Tooltip(\"p_value:Q\", format=\".2e\"),\n            \"neg_log_p:Q\",\n        ],\n    )\n)\n\n# Genome-wide significance threshold line\ngw_line = (\n    alt.Chart(pd.DataFrame({\"y\": [genome_wide_threshold]}))\n    .mark_rule(strokeDash=[8, 4], color=INK_MUTED, size=2, opacity=0.8)\n    .encode(y=\"y:Q\")\n)\n\n# Suggestive threshold line\nsug_line = (\n    alt.Chart(pd.DataFrame({\"y\": [suggestive_threshold]}))\n    .mark_rule(strokeDash=[4, 4], color=INK_MUTED, size=1.5, opacity=0.6)\n    .encode(y=\"y:Q\")\n)\n\n# Chromosome labels at bottom\nchrom_text = (\n    alt.Chart(chrom_label_df)\n    .mark_text(fontSize=16, baseline=\"top\", dy=10, color=INK_SOFT, fontWeight=\"normal\")\n    .encode(x=alt.X(\"center:Q\", axis=None, scale=alt.Scale(domain=[0, cumulative_pos])), text=\"chrom_label:N\")\n)\n\n# Combine layers\nmain_chart = alt.layer(points, gw_line, sug_line).properties(width=1600, height=850)\n\n# Final chart with labels\nchart = (\n    alt.vconcat(main_chart, chrom_text.properties(width=1600, height=40), spacing=5)\n    .properties(\n        title=alt.Title(\n            \"Manhattan Plot: GWAS Results\",\n            fontSize=28,\n            anchor=\"middle\",\n            color=INK,\n            subtitle=\"Genome-wide association study with significance thresholds\",\n        ),\n        background=PAGE_BG,\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=1)\n    .configure_axis(labelFontSize=18, titleFontSize=22, labelColor=INK_SOFT, titleColor=INK)\n    .configure_title(fontSize=28, color=INK, subtitleFontSize=16, subtitleColor=INK_SOFT)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save with theme-specific filenames\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}