{"spec_id":"scatter-regression-lowess","library":"makie","language":"julia","code":"# anyplot.ai\n# scatter-regression-lowess: Scatter Plot with LOWESS Regression\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 86/100 | Created: 2026-09-09\n\nusing CairoMakie\nusing Colors\nusing Random\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 BRAND       = colorant\"#009E73\"  # Imprint palette position 1 — scatter points\nconst CURVE_COLOR = colorant\"#4467A3\"  # Imprint palette position 3 — LOWESS fit\n\n# --- Data: light-response curve of net photosynthesis -------------------------\nRandom.seed!(42)\nn = 200\nlight_intensity = sort(rand(n) .* 2000)  # PAR, μmol photons m⁻² s⁻¹\ntrue_response = 13.5 .* (1 .- exp.(-light_intensity ./ 280)) .- 0.0016 .* light_intensity\nnet_photosynthesis = true_response .+ randn(n) .* 0.9  # μmol CO2 m⁻² s⁻¹\n\n# --- LOWESS smoothing: local weighted linear regression, tricube weights ------\n# A pointwise 95% confidence band is derived from the weighted-regression standard\n# error at each evaluation point (weighted residual variance scaled by the local\n# effective sample size sw^2 / sum(w^2)).\nfrac = 0.35\nk = ceil(Int, frac * n)\neval_x = collect(range(minimum(light_intensity), maximum(light_intensity); length = 200))\nfitted_y = similar(eval_x)\nband_lo = similar(eval_x)\nband_hi = similar(eval_x)\n\nfor (i, x0) in enumerate(eval_x)\n    dist = abs.(light_intensity .- x0)\n    d_max = sort(dist)[k]\n    w = ifelse.(dist .<= d_max, (1 .- clamp.(dist ./ d_max, 0, 1) .^ 3) .^ 3, 0.0)\n\n    sw = sum(w)\n    sx = sum(w .* light_intensity)\n    sy = sum(w .* net_photosynthesis)\n    sxx = sum(w .* light_intensity .^ 2)\n    sxy = sum(w .* light_intensity .* net_photosynthesis)\n\n    slope = (sw * sxy - sx * sy) / (sw * sxx - sx^2)\n    intercept = (sy - slope * sx) / sw\n    fitted_y[i] = intercept + slope * x0\n\n    resid = net_photosynthesis .- (intercept .+ slope .* light_intensity)\n    weighted_var = sum(w .* resid .^ 2) / sw\n    effective_n = sw^2 / sum(w .^ 2)\n    se = sqrt(weighted_var / effective_n)\n    band_lo[i] = fitted_y[i] - 1.96 * se\n    band_hi[i] = fitted_y[i] + 1.96 * se\nend\n\n# --- Plot -----------------------------------------------------------------\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = \"scatter-regression-lowess · julia · makie · anyplot.ai\",\n    titlesize         = 20,\n    titlecolor        = INK,\n    xlabel            = \"Light Intensity (μmol photons m⁻² s⁻¹)\",\n    ylabel            = \"Net Photosynthesis (μmol CO₂ m⁻² s⁻¹)\",\n    xlabelsize        = 14,\n    ylabelsize        = 14,\n    xticklabelsize    = 12,\n    yticklabelsize    = 12,\n    xlabelcolor       = INK,\n    ylabelcolor       = INK,\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.15),\n    ygridcolor        = RGBAf(INK.r, INK.g, INK.b, 0.15),\n)\n\nband!(ax, eval_x, band_lo, band_hi; color = (CURVE_COLOR, 0.35))\nscatter!(\n    ax, light_intensity, net_photosynthesis;\n    color = (BRAND, 0.55), markersize = 9,\n    strokecolor = PAGE_BG, strokewidth = 1,\n)\nlines!(ax, eval_x, fitted_y; color = CURVE_COLOR, linewidth = 3)\n\n# `band!` has no automatic legend entry in Makie, so the legend is built explicitly\n# from proxy elements that mirror the actual plot styling.\nlegend_elements = [\n    MarkerElement(color = (BRAND, 0.55), marker = :circle, markersize = 9, strokecolor = PAGE_BG, strokewidth = 1),\n    LineElement(color = CURVE_COLOR, linewidth = 3),\n    PolyElement(color = (CURVE_COLOR, 0.35)),\n]\nlegend_labels = [\"Observations\", \"LOWESS fit\", \"95% CI\"]\naxislegend(ax, legend_elements, legend_labels; position = :lt, backgroundcolor = ELEVATED_BG, labelcolor = INK, framevisible = false)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}