{"spec_id":"manhattan-gwas","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nmanhattan-gwas: Manhattan Plot for GWAS\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, Span\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\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\"\n\n# Okabe-Ito palette for data colors\nCHROM_COLOR_1 = \"#009E73\"  # Okabe-Ito position 1 (first series)\nCHROM_COLOR_2 = \"#C475FD\"  # Okabe-Ito position 2 (alternating)\nSIG_COLOR = \"#954477\"  # Okabe-Ito position 7 (significant SNPs highlight)\n\n# Data - Simulated GWAS data with significant peaks\nnp.random.seed(42)\n\nchrom_sizes = {\n    \"1\": 248,\n    \"2\": 242,\n    \"3\": 198,\n    \"4\": 190,\n    \"5\": 181,\n    \"6\": 170,\n    \"7\": 159,\n    \"8\": 145,\n    \"9\": 138,\n    \"10\": 133,\n    \"11\": 135,\n    \"12\": 133,\n    \"13\": 114,\n    \"14\": 107,\n    \"15\": 101,\n    \"16\": 90,\n    \"17\": 83,\n    \"18\": 80,\n    \"19\": 58,\n    \"20\": 64,\n    \"21\": 46,\n    \"22\": 50,\n}\n\n# Generate SNP data\nn_snps_per_chrom = 2000\ndata = []\ncumulative_pos = 0\nchrom_centers = {}\n\nfor chrom, size in chrom_sizes.items():\n    positions = np.sort(np.random.randint(1, size * 1_000_000, n_snps_per_chrom))\n    p_values = np.random.uniform(0.001, 1.0, n_snps_per_chrom)\n\n    if chrom in [\"2\", \"6\", \"11\", \"17\"]:\n        n_significant = np.random.randint(5, 15)\n        peak_indices = np.random.choice(n_snps_per_chrom, n_significant, replace=False)\n        p_values[peak_indices] = 10 ** np.random.uniform(-12, -8, n_significant)\n\n    cumulative_positions = positions + cumulative_pos\n    chrom_centers[chrom] = cumulative_pos + (size * 1_000_000) / 2\n\n    for i in range(n_snps_per_chrom):\n        data.append(\n            {\n                \"chromosome\": chrom,\n                \"position\": positions[i],\n                \"cumulative_pos\": cumulative_positions[i],\n                \"p_value\": p_values[i],\n                \"neg_log_p\": -np.log10(p_values[i]),\n            }\n        )\n\n    cumulative_pos += size * 1_000_000\n\ndf = pd.DataFrame(data)\n\n# Assign alternating colors based on chromosome parity\nchrom_int = df[\"chromosome\"].astype(int)\ndf[\"color\"] = df[\"chromosome\"].apply(lambda x: CHROM_COLOR_1 if int(x) % 2 == 1 else CHROM_COLOR_2)\ndf[\"color_label\"] = df[\"chromosome\"].apply(lambda x: \"Odd chromosome\" if int(x) % 2 == 1 else \"Even chromosome\")\n\n# Highlight significant SNPs\nsignificance_threshold = -np.log10(5e-8)\nsignificant_mask = df[\"neg_log_p\"] >= significance_threshold\ndf.loc[significant_mask, \"color\"] = SIG_COLOR\ndf.loc[significant_mask, \"color_label\"] = \"Significant SNP (p < 5×10⁻⁸)\"\n\n# Adjust point sizes\ndf[\"size\"] = 6\ndf.loc[significant_mask, \"size\"] = 12\n\n# Plot\nsource = ColumnDataSource(df)\n\np = figure(\n    width=4800,\n    height=2700,\n    title=\"manhattan-gwas · bokeh · anyplot.ai\",\n    x_axis_label=\"Genomic Position\",\n    y_axis_label=\"-log₁₀(p-value)\",\n    tools=\"pan,wheel_zoom,box_zoom,reset,save\",\n)\n\n# Scatter plot with legend\np.scatter(\n    x=\"cumulative_pos\",\n    y=\"neg_log_p\",\n    source=source,\n    size=\"size\",\n    color=\"color\",\n    alpha=0.7,\n    line_color=None,\n    legend_field=\"color_label\",\n)\n\n# Significance threshold line\nsignificance_line = Span(\n    location=significance_threshold, dimension=\"width\", line_color=INK_SOFT, line_dash=\"dashed\", line_width=3\n)\np.add_layout(significance_line)\n\n# Suggestive threshold line\nsuggestive_threshold = -np.log10(1e-5)\nsuggestive_line = Span(\n    location=suggestive_threshold, dimension=\"width\", line_color=INK_SOFT, line_dash=\"dotted\", line_width=2\n)\np.add_layout(suggestive_line)\n\n# Threshold labels\nsig_label = Label(\n    x=cumulative_pos * 0.98,\n    y=significance_threshold + 0.3,\n    text=\"p = 5×10⁻⁸\",\n    text_font_size=\"18pt\",\n    text_color=INK_SOFT,\n)\np.add_layout(sig_label)\n\nsug_label = Label(\n    x=cumulative_pos * 0.98, y=suggestive_threshold + 0.3, text=\"p = 1×10⁻⁵\", text_font_size=\"18pt\", text_color=INK_SOFT\n)\np.add_layout(sug_label)\n\n# Add chromosome labels\nfor chrom, center in chrom_centers.items():\n    chrom_label = Label(x=center, y=-0.8, text=chrom, text_font_size=\"16pt\", text_align=\"center\", text_color=INK_SOFT)\n    p.add_layout(chrom_label)\n\n# Add HoverTool for interactivity\nhover = HoverTool(\n    tooltips=[\n        (\"Chromosome\", \"@chromosome\"),\n        (\"Position\", \"@{position:0,0}\"),\n        (\"-log₁₀(p)\", \"@{neg_log_p:.2f}\"),\n        (\"Type\", \"@color_label\"),\n    ]\n)\np.add_tools(hover)\n\n# Style the plot\np.title.text_font_size = \"28pt\"\np.title.text_color = INK\np.xaxis.axis_label_text_font_size = \"22pt\"\np.yaxis.axis_label_text_font_size = \"22pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_font_size = \"0pt\"\np.yaxis.major_label_text_font_size = \"18pt\"\np.yaxis.major_label_text_color = INK_SOFT\n\n# Hide x-axis ticks (using chromosome labels instead)\np.xaxis.major_tick_line_color = None\np.xaxis.minor_tick_line_color = None\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\n# Grid styling\np.xgrid.grid_line_alpha = 0.10\np.ygrid.grid_line_alpha = 0.10\np.xgrid.grid_line_color = INK\np.ygrid.grid_line_color = INK\n\n# Background and borders\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\n\n# Legend styling\nif p.legend:\n    p.legend.background_fill_color = ELEVATED_BG\n    p.legend.border_line_color = INK_SOFT\n    p.legend.label_text_color = INK_SOFT\n    p.legend.label_text_font_size = \"16pt\"\n    p.legend.location = \"top_right\"\n\n# Set y-axis range to accommodate chromosome labels\np.y_range.start = -1.5\np.y_range.end = df[\"neg_log_p\"].max() + 1\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome via Selenium\nW, H = 4800, 2700\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\n\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}