{"spec_id":"calibration-beer-lambert","library":"makie","language":"julia","code":"# anyplot.ai\n# calibration-beer-lambert: Beer-Lambert Calibration Curve\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 91/100 | Created: 2026-06-03\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\nusing Printf\n\nRandom.seed!(42)\n\n# Theme tokens (Imprint palette — theme-adaptive chrome)\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\"\n\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — Imprint brand green (always 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 — nitrate colorimetric assay, UV-Vis at 540 nm (environmental water testing)\nconcentrations = Float64[0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 15.0]  # mg/L\ntrue_abs = 0.065 .* concentrations  # Beer-Lambert: A = εlc, εl ≈ 0.065 L/mg\nnoise = randn(length(concentrations)) .* 0.008\nabsorbance = true_abs .+ noise\nabsorbance[1] = max(0.0, absorbance[1])  # blank must be non-negative\n\n# Linear regression (ordinary least squares)\nn = length(concentrations)\nx_mean = mean(concentrations)\ny_mean = mean(absorbance)\nsxx = sum((concentrations .- x_mean) .^ 2)\nsxy = sum((concentrations .- x_mean) .* (absorbance .- y_mean))\nslope = sxy / sxx\nintercept = y_mean - slope * x_mean\n\n# Goodness of fit\ny_pred_cal = slope .* concentrations .+ intercept\nss_res = sum((absorbance .- y_pred_cal) .^ 2)\nss_tot = sum((absorbance .- y_mean) .^ 2)\nr_squared = 1.0 - ss_res / ss_tot\n\n# Regression line and 95% prediction interval\nx_fit = collect(range(0.0, 16.5, length=300))\ny_fit = slope .* x_fit .+ intercept\ns2 = ss_res / (n - 2)  # mean squared error\nse_pred = sqrt.(s2 .* (1.0 ./ n .+ (x_fit .- x_mean) .^ 2 ./ sxx))\nt_crit = 2.447  # t(0.025, df=6) for 95% prediction interval\ny_upper = y_fit .+ t_crit .* se_pred\ny_lower = y_fit .- t_crit .* se_pred\n\n# Unknown sample: determine concentration from measured absorbance\nunknown_abs = 0.520\nunknown_conc = (unknown_abs - intercept) / slope\n\n# Annotation string\neq_str = @sprintf(\"y = %.4fx %+.4f\\nR² = %.4f\", slope, intercept, r_squared)\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              = \"calibration-beer-lambert · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Concentration (mg/L)\",\n    ylabel             = \"Absorbance\",\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.12),\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.12),\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n)\n\n# 95% prediction interval band\nband!(ax, x_fit, y_lower, y_upper;\n    color = (IMPRINT_PALETTE[1], 0.15),\n    label = \"95% Prediction interval\")\n\n# Regression line\nlines!(ax, x_fit, y_fit;\n    color     = IMPRINT_PALETTE[1],\n    linewidth = 2.5,\n    label     = \"Linear fit\")\n\n# Calibration standard points\nscatter!(ax, concentrations, absorbance;\n    color       = IMPRINT_PALETTE[1],\n    markersize  = 14,\n    strokecolor = PAGE_BG,\n    strokewidth = 1,\n    label       = \"Calibration standards\",\n)\n\n# Dashed guide lines for unknown sample determination (no legend entry)\nlines!(ax, [0.0, unknown_conc], [unknown_abs, unknown_abs];\n    color     = IMPRINT_PALETTE[3],\n    linewidth = 1.5,\n    linestyle = :dash,\n)\nlines!(ax, [unknown_conc, unknown_conc], [0.0, unknown_abs];\n    color     = IMPRINT_PALETTE[3],\n    linewidth = 1.5,\n    linestyle = :dash,\n)\n\n# Unknown sample marker\nscatter!(ax, [unknown_conc], [unknown_abs];\n    color       = IMPRINT_PALETTE[3],\n    markersize  = 16,\n    marker      = :diamond,\n    strokecolor = PAGE_BG,\n    strokewidth = 1,\n    label       = \"Unknown sample (A = 0.520)\",\n)\n\n# Regression equation and R² annotation (lower-right area, data coordinates)\ntext!(ax, 15.8, 0.04;\n    text     = eq_str,\n    align    = (:right, :bottom),\n    color    = INK_MUTED,\n    fontsize = 15,\n)\n\nxlims!(ax, -0.5, 17.0)\nylims!(ax, -0.05, 1.15)\n\n# Legend — identifies all four visual elements for unfamiliar readers\naxislegend(ax;\n    position        = :lt,\n    labelcolor      = INK,\n    labelsize       = 12,\n    framecolor      = INK_SOFT,\n    backgroundcolor = ELEVATED_BG,\n    framevisible    = true,\n)\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}