{"spec_id":"curve-dose-response","library":"makie","language":"julia","code":"# anyplot.ai\n# curve-dose-response: Pharmacological Dose-Response Curve\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 90/100 | Created: 2026-06-24\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\"\n\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",\n    colorant\"#C475FD\",\n    colorant\"#4467A3\",\n    colorant\"#BD8233\",\n    colorant\"#AE3030\",\n    colorant\"#2ABCCD\",\n    colorant\"#954477\",\n    colorant\"#99B314\",\n]\n\n# 4PL sigmoid: Bottom + (Top - Bottom) / (1 + (EC50/x)^hill)\nfunction four_pl(x, bottom, top, ec50, hill)\n    return bottom + (top - bottom) / (1.0 + (ec50 / x)^hill)\nend\n\n# Compound A — potent, steep sigmoid (EC50 = 50 nM)\nconst BOTTOM_A = 2.0\nconst TOP_A    = 97.0\nconst EC50_A   = 50e-9\nconst HILL_A   = 1.8\n\n# Compound B — moderate potency, shallower (EC50 = 500 nM)\nconst BOTTOM_B = 3.0\nconst TOP_B    = 91.0\nconst EC50_B   = 500e-9\nconst HILL_B   = 1.2\n\n# Experimental data: 9 concentration points from 1e-9 to 1e-4 M\nconc_data     = exp10.(range(-9.0, -4.0, length=9))\nlog_conc_data = log10.(conc_data)\n\nresponse_a = clamp.([four_pl(c, BOTTOM_A, TOP_A, EC50_A, HILL_A) + 5.0 * randn() for c in conc_data], 0.0, 100.0)\nsem_a      = 2.0 .+ rand(9) .* 2.0\n\nresponse_b = clamp.([four_pl(c, BOTTOM_B, TOP_B, EC50_B, HILL_B) + 5.0 * randn() for c in conc_data], 0.0, 100.0)\nsem_b      = 2.0 .+ rand(9) .* 2.0\n\n# Smooth fitted curves (300 points for visual smoothness)\nconc_fit     = exp10.(range(-9.0, -4.0, length=300))\nlog_conc_fit = log10.(conc_fit)\ncurve_a      = [four_pl(c, BOTTOM_A, TOP_A, EC50_A, HILL_A) for c in conc_fit]\ncurve_b      = [four_pl(c, BOTTOM_B, TOP_B, EC50_B, HILL_B) for c in conc_fit]\n\n# 95% CI band for Compound A (approximate fixed half-width)\nci_upper_a = clamp.(curve_a .+ 7.5, 0.0, 100.0)\nci_lower_a = clamp.(curve_a .- 7.5, 0.0, 100.0)\n\n# EC50 reference positions\nlog_ec50_a = log10(EC50_A)\nlog_ec50_b = log10(EC50_B)\nhalf_max_a = BOTTOM_A + (TOP_A - BOTTOM_A) / 2.0\nhalf_max_b = BOTTOM_B + (TOP_B - BOTTOM_B) / 2.0\n\n# Title font sizing — scale down for long titles\ntitle_str = \"Drug Potency Comparison · curve-dose-response · julia · makie · anyplot.ai\"\ntitlesize = max(13, round(Int, 20 * min(1.0, 67 / length(title_str))))\n\n# Figure\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = title_str,\n    titlesize          = titlesize,\n    titlecolor         = INK,\n    xlabel             = \"Concentration (M)\",\n    ylabel             = \"Response (%)\",\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    xgridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.12),\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.12),\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n    xticks             = ([-9.0, -8.0, -7.0, -6.0, -5.0, -4.0], [\"10⁻⁹\", \"10⁻⁸\", \"10⁻⁷\", \"10⁻⁶\", \"10⁻⁵\", \"10⁻⁴\"]),\n)\n\nxlims!(ax, -9.5, -3.6)\nylims!(ax, -6.0, 108.0)\n\n# Asymptote reference lines (top and bottom plateaus)\nhlines!(ax, [TOP_A, BOTTOM_A]; color = (IMPRINT_PALETTE[1], 0.25), linestyle = :dash, linewidth = 1.2)\nhlines!(ax, [TOP_B, BOTTOM_B]; color = (IMPRINT_PALETTE[2], 0.25), linestyle = :dash, linewidth = 1.2)\n\n# 95% CI band for Compound A\nband!(ax, log_conc_fit, ci_lower_a, ci_upper_a; color = (IMPRINT_PALETTE[1], 0.15))\n\n# Fitted sigmoid curves\nlines!(ax, log_conc_fit, curve_a;\n    color = IMPRINT_PALETTE[1], linewidth = 2.5,\n    label = \"Compound α  (EC50 = 50 nM)\")\nlines!(ax, log_conc_fit, curve_b;\n    color = IMPRINT_PALETTE[2], linewidth = 2.5,\n    label = \"Compound β  (EC50 = 500 nM)\")\n\n# EC50 reference lines — vertical and horizontal dashed\nvlines!(ax, [log_ec50_a]; color = (IMPRINT_PALETTE[1], 0.55), linestyle = :dash, linewidth = 1.5)\nvlines!(ax, [log_ec50_b]; color = (IMPRINT_PALETTE[2], 0.55), linestyle = :dash, linewidth = 1.5)\nhlines!(ax, [half_max_a]; color = (IMPRINT_PALETTE[1], 0.55), linestyle = :dash, linewidth = 1.5)\nhlines!(ax, [half_max_b]; color = (IMPRINT_PALETTE[2], 0.55), linestyle = :dash, linewidth = 1.5)\n\n# Error bars and experimental data markers\nerrorbars!(ax, log_conc_data, response_a, sem_a;\n    color = IMPRINT_PALETTE[1], linewidth = 1.5, whiskerwidth = 8)\nscatter!(ax, log_conc_data, response_a;\n    color = IMPRINT_PALETTE[1], markersize = 10,\n    strokewidth = 1.0, strokecolor = PAGE_BG)\n\nerrorbars!(ax, log_conc_data, response_b, sem_b;\n    color = IMPRINT_PALETTE[2], linewidth = 1.5, whiskerwidth = 8)\nscatter!(ax, log_conc_data, response_b;\n    color = IMPRINT_PALETTE[2], markersize = 10,\n    strokewidth = 1.0, strokecolor = PAGE_BG)\n\n# Legend (upper-left)\naxislegend(ax;\n    position        = :lt,\n    backgroundcolor = ELEVATED_BG,\n    labelcolor      = INK,\n    framecolor      = INK_SOFT,\n    framewidth      = 0.5,\n    padding         = (8, 8, 8, 8),\n    rowgap          = 4,\n)\n\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}