{"spec_id":"volcano-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nvolcano-basic: Volcano Plot for Statistical Significance\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\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# Data - Simulated differential expression results\nnp.random.seed(42)\nn_genes = 500\n\n# Generate log2 fold changes (effect sizes)\nlog2_fc = np.random.normal(0, 1.5, n_genes)\n\n# Generate p-values (most genes not significant)\nbase_pvalues = np.random.beta(1, 10, n_genes)  # Right-skewed toward 1\n\n# Make genes with large fold changes more likely to be significant\nsignificance_boost = np.exp(-np.abs(log2_fc) * 0.5)\npvalues = base_pvalues * significance_boost\npvalues = np.clip(pvalues, 1e-10, 1)\n\n# Calculate -log10(p-value)\nneg_log10_pvalue = -np.log10(pvalues)\n\n# Determine significance categories\nfc_threshold = 1.0  # log2(2) = 1, meaning 2-fold change\npval_threshold = 1.3  # -log10(0.05) ≈ 1.3\n\nsignificant_up = (log2_fc >= fc_threshold) & (neg_log10_pvalue >= pval_threshold)\nsignificant_down = (log2_fc <= -fc_threshold) & (neg_log10_pvalue >= pval_threshold)\n\ncategory = np.where(significant_up, \"Up-regulated\", np.where(significant_down, \"Down-regulated\", \"Not Significant\"))\n\n# Create dataframe\ndf = pd.DataFrame({\"log2_fold_change\": log2_fc, \"neg_log10_pvalue\": neg_log10_pvalue, \"category\": category})\n\n# Threshold lines data\nvline_data = pd.DataFrame({\"x\": [-fc_threshold, fc_threshold]})\nhline_data = pd.DataFrame({\"y\": [pval_threshold]})\n\n# Color mapping using Okabe-Ito palette\ncolor_scale = alt.Scale(\n    domain=[\"Up-regulated\", \"Down-regulated\", \"Not Significant\"],\n    range=[\"#009E73\", \"#AE3030\", INK_SOFT],  # imprint green (up) / red (down) / adaptive gray (n.s.)\n)\n\n# Main scatter plot\nscatter = (\n    alt.Chart(df)\n    .mark_circle(size=80, opacity=0.7)\n    .encode(\n        x=alt.X(\"log2_fold_change:Q\", title=\"Log₂ Fold Change\", scale=alt.Scale(domain=[-6, 6])),\n        y=alt.Y(\"neg_log10_pvalue:Q\", title=\"-Log₁₀(p-value)\"),\n        color=alt.Color(\"category:N\", scale=color_scale, title=\"Significance\"),\n        tooltip=[\"log2_fold_change:Q\", \"neg_log10_pvalue:Q\", \"category:N\"],\n    )\n)\n\n# Vertical threshold lines\nvlines = alt.Chart(vline_data).mark_rule(strokeDash=[8, 4], color=INK_SOFT, strokeWidth=2).encode(x=\"x:Q\")\n\n# Horizontal threshold line\nhline = alt.Chart(hline_data).mark_rule(strokeDash=[8, 4], color=INK_SOFT, strokeWidth=2).encode(y=\"y:Q\")\n\n# Combine all layers\nchart = (\n    (scatter + vlines + hline)\n    .properties(\n        width=1600,\n        height=900,\n        title=alt.Title(\"volcano-basic · altair · anyplot.ai\", fontSize=28, anchor=\"middle\"),\n        background=PAGE_BG,\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=0)\n    .configure_axis(\n        labelFontSize=18,\n        titleFontSize=22,\n        gridOpacity=0.10,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n    )\n    .configure_legend(\n        titleFontSize=18,\n        labelFontSize=16,\n        symbolSize=200,\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n    )\n    .configure_title(color=INK, fontSize=28)\n)\n\n# Save as PNG and HTML with theme-suffixed filenames\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}