{"spec_id":"funnel-meta-analysis","library":"makie","language":"julia","code":"# anyplot.ai\n# funnel-meta-analysis: Meta-Analysis Funnel Plot for Publication Bias\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 91/100 | Created: 2026-06-10\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# Theme tokens\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst INK_MUTED   = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green (first series)\n    colorant\"#C475FD\",  # 2 — lavender\n    colorant\"#4467A3\",  # 3 — blue\n    colorant\"#BD8233\",  # 4 — ochre\n    colorant\"#AE3030\",  # 5 — matte red\n    colorant\"#2ABCCD\",  # 6 — cyan\n    colorant\"#954477\",  # 7 — rose\n    colorant\"#99B314\",  # 8 — lime\n]\n\n# Data: 20 RCTs on antihypertensive drug vs placebo (log odds ratio of major CV events)\nconst summary_effect = -0.38\nstd_errors = [0.07, 0.09, 0.11, 0.12, 0.14, 0.16, 0.18, 0.20, 0.23, 0.25,\n              0.28, 0.31, 0.33, 0.36, 0.38, 0.41, 0.44, 0.47, 0.50, 0.54]\nn_studies = length(std_errors)\n# Slight small-study asymmetry: smaller trials claim larger benefit (publication bias)\nsmall_study_bias = -0.25 .* (std_errors ./ maximum(std_errors))\neffect_sizes = summary_effect .+ small_study_bias .+ randn(n_studies) .* std_errors\n\n# Funnel 95% confidence limit boundary\nmax_se_plot = maximum(std_errors) * 1.12\nn_pts = 200\nse_grid  = collect(range(0.0, max_se_plot; length=n_pts))\nfunnel_lo = summary_effect .- 1.96 .* se_grid\nfunnel_hi = summary_effect .+ 1.96 .* se_grid\n\n# Funnel fill polygon (trace lo top→bottom, then hi bottom→top)\nfunnel_polygon = [\n    [Point2f(funnel_lo[i], se_grid[i]) for i in 1:n_pts];\n    [Point2f(funnel_hi[i], se_grid[i]) for i in n_pts:-1:1]\n]\n\n# Plot\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(fig[1, 1];\n    title             = \"funnel-meta-analysis · julia · makie · anyplot.ai\",\n    titlesize         = 20,\n    titlecolor        = INK,\n    xlabel            = \"Log Odds Ratio\",\n    ylabel            = \"Standard Error\",\n    xlabelsize        = 14,\n    ylabelsize        = 14,\n    xlabelcolor       = INK,\n    ylabelcolor       = INK,\n    xticklabelsize    = 12,\n    yticklabelsize    = 12,\n    xticklabelcolor   = INK_SOFT,\n    yticklabelcolor   = INK_SOFT,\n    xtickcolor        = INK_SOFT,\n    ytickcolor        = INK_SOFT,\n    backgroundcolor   = PAGE_BG,\n    topspinevisible   = false,\n    rightspinevisible = false,\n    leftspinecolor    = INK_SOFT,\n    bottomspinecolor  = INK_SOFT,\n    xgridvisible      = false,\n    ygridvisible      = false,\n    yreversed         = true,\n)\n\n# Funnel shaded region\npoly!(ax, funnel_polygon;\n    color       = RGBAf(INK_MUTED.r, INK_MUTED.g, INK_MUTED.b, 0.12),\n    strokewidth = 0)\n\n# 95% CI boundary lines\nlines!(ax, funnel_lo, se_grid; color = INK_SOFT, linewidth = 1.5, linestyle = :dash)\nlines!(ax, funnel_hi, se_grid; color = INK_SOFT, linewidth = 1.5, linestyle = :dash)\n\n# Null effect reference line (log OR = 0 ↔ OR = 1)\nvlines!(ax, [0.0]; color = INK_MUTED, linewidth = 1.5, linestyle = :dot)\n\n# Pooled effect line\nvlines!(ax, [summary_effect]; color = IMPRINT_PALETTE[3], linewidth = 2.0)\n\n# Individual study points\nscatter!(ax, effect_sizes, std_errors;\n    color       = IMPRINT_PALETTE[1],\n    markersize  = 14,\n    strokewidth = 1.0,\n    strokecolor = PAGE_BG)\n\n# Annotation: small-study asymmetry signals potential publication bias\ntext!(ax, funnel_hi[end] - 0.05, max_se_plot * 0.92;\n    text      = \"← Asymmetric scatter:\\npossible publication bias\",\n    align     = (:right, :center),\n    fontsize  = 11,\n    color     = INK_MUTED,\n    font      = :italic)\n\n# Axis limits\nylims!(ax, (0.0, max_se_plot))\nxlims!(ax, (funnel_lo[end] - 0.08, funnel_hi[end] + 0.08))\n\n# Legend\nelem_studies = MarkerElement(marker = :circle, color = IMPRINT_PALETTE[1],\n    strokecolor = PAGE_BG, strokewidth = 1.0, markersize = 14)\nelem_pooled  = LineElement(color = IMPRINT_PALETTE[3], linewidth = 2.0)\nelem_null    = LineElement(color = INK_MUTED, linewidth = 1.5, linestyle = :dot)\nelem_ci      = LineElement(color = INK_SOFT,  linewidth = 1.5, linestyle = :dash)\n\nLegend(fig[1, 2],\n    [elem_studies, elem_pooled, elem_null, elem_ci],\n    [\"Individual study (n=20)\",\n     \"Pooled effect (OR = $(round(exp(summary_effect), digits=2)))\",\n     \"Null effect (OR = 1.0)\",\n     \"95% confidence limits\"],\n    framevisible    = true,\n    framecolor      = INK_SOFT,\n    backgroundcolor = ELEVATED_BG,\n    labelsize       = 13,\n    padding         = (12, 12, 10, 10),\n    rowgap          = 6,\n)\n\ncolsize!(fig.layout, 1, Relative(0.80))\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}