{"spec_id":"point-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# point-basic: Point Estimate Plot\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 89/100 | Created: 2026-09-05\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 INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst BRAND    = colorant\"#009E73\"  # Imprint palette position 1 — ALWAYS first series\n\n# --- Data: standardized mean difference (SMD) per clinical trial subgroup ---\nsubgroups = [\n    \"Age < 40\", \"Age 40-59\", \"Age 60+\", \"Male\", \"Female\",\n    \"Prior therapy\", \"No prior therapy\",\n]\nn = length(subgroups)\nbaseline_estimate   = [0.42, 0.31, 0.18, 0.28, 0.35, 0.12, 0.46]\nbaseline_half_width = [0.22, 0.16, 0.22, 0.15, 0.18, 0.24, 0.19]\nestimate    = baseline_estimate .+ (rand(n) .- 0.5) .* 0.02\nhalf_width  = baseline_half_width .+ (rand(n) .- 0.5) .* 0.02\nlower_bound = estimate .- half_width\nupper_bound = estimate .+ half_width\n\norder = sortperm(estimate)\nsubgroups   = subgroups[order]\nestimate    = estimate[order]\nlower_bound = lower_bound[order]\nupper_bound = upper_bound[order]\npositions   = 1:n\ncrosses_null = (lower_bound .< 0) .& (upper_bound .> 0)\n\n# --- Plot ---------------------------------------------------------------\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"point-basic · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Standardized Mean Difference (95% CI)\",\n    ylabel             = \"Subgroup\",\n    xlabelsize         = 15,\n    ylabelsize         = 15,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xticklabelsize     = 14,\n    yticklabelsize     = 14,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    ygridvisible       = false,\n    yticks             = (positions, subgroups),\n)\n\nvlines!(ax, [0.0]; color = INK_SOFT, linestyle = :dash, linewidth = 1.5)\nrangebars!(ax, positions, lower_bound, upper_bound;\n    direction = :x, color = BRAND, linewidth = 2.5, whiskerwidth = 14)\n\n# Filled markers = CI excludes the null; open (hollow) markers = CI spans it —\n# a classic forest-plot convention that reads at a glance without adding a\n# second hue (palette stays single-accent).\nsig, nonsig = .!crosses_null, crosses_null\nscatter!(ax, estimate[sig], positions[sig];\n    color = BRAND, markersize = 20, strokewidth = 0)\nscatter!(ax, estimate[nonsig], positions[nonsig];\n    color = PAGE_BG, markersize = 20, strokewidth = 2.5, strokecolor = BRAND)\n\nxlims!(ax, -0.1, 0.85)\nylims!(ax, 0.3, n + 0.7)\n\nLabel(fig[2, 1], \"○ open marker — 95% CI spans the null (SMD = 0)\";\n    color = INK_SOFT, fontsize = 13, halign = :left, padding = (6, 0, 0, 4))\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}