{"spec_id":"calibration-beer-lambert","library":"altair","language":"python","code":"\"\"\" anyplot.ai\ncalibration-beer-lambert: Beer-Lambert Calibration Curve\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — position 1 (brand green) for calibration series, position 4 (ochre) for unknown\nBRAND = \"#009E73\"\nUNKNOWN_COLOR = \"#BD8233\"\n\n# Data — UV-Vis spectrophotometry calibration standards\nnp.random.seed(42)\nconcentrations = np.array([0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0])\nepsilon_l = 0.045\ntrue_absorbance = epsilon_l * concentrations\nmeasured_absorbance = true_absorbance + np.random.normal(0, 0.008, len(concentrations))\nmeasured_absorbance[0] = max(measured_absorbance[0], 0.002)\n\n# Regression stats for prediction interval and annotation\nn = len(concentrations)\nx_mean = np.mean(concentrations)\ny_mean = np.mean(measured_absorbance)\nss_xx = np.sum((concentrations - x_mean) ** 2)\nss_xy = np.sum((concentrations - x_mean) * (measured_absorbance - y_mean))\nslope = ss_xy / ss_xx\nintercept = y_mean - slope * x_mean\nresiduals = measured_absorbance - (slope * concentrations + intercept)\nss_res = np.sum(residuals**2)\nss_tot = np.sum((measured_absorbance - y_mean) ** 2)\nr_squared = 1 - ss_res / ss_tot\n\n# Prediction interval (95%, df=6)\nx_fit = np.linspace(0, 15, 200)\ny_fit = slope * x_fit + intercept\nmse = ss_res / (n - 2)\nse_pred = np.sqrt(mse * (1 + 1 / n + (x_fit - x_mean) ** 2 / ss_xx))\nt_val = 2.447\nupper = y_fit + t_val * se_pred\nlower = y_fit - t_val * se_pred\n\n# Unknown sample determination\nunknown_absorbance = 0.38\nunknown_concentration = (unknown_absorbance - intercept) / slope\n\n# DataFrames\nstandards_df = pd.DataFrame({\"Concentration (mg/L)\": concentrations, \"Absorbance\": measured_absorbance})\nfit_df = pd.DataFrame({\"Concentration (mg/L)\": x_fit, \"Absorbance\": y_fit, \"Upper\": upper, \"Lower\": lower})\nunknown_point_df = pd.DataFrame({\"Concentration (mg/L)\": [unknown_concentration], \"Absorbance\": [unknown_absorbance]})\nunknown_hline_df = pd.DataFrame(\n    {\"Concentration (mg/L)\": [0, unknown_concentration], \"Absorbance\": [unknown_absorbance, unknown_absorbance]}\n)\nunknown_vline_df = pd.DataFrame(\n    {\"Concentration (mg/L)\": [unknown_concentration, unknown_concentration], \"Absorbance\": [0, unknown_absorbance]}\n)\n\n# Shared scales\nx_scale = alt.Scale(domain=[0, 15.5], nice=False)\ny_scale = alt.Scale(domain=[0, 0.68])\n\n# Prediction interval band (muted brand green)\nband = (\n    alt.Chart(fit_df)\n    .mark_area(opacity=0.12, color=BRAND)\n    .encode(x=alt.X(\"Concentration (mg/L):Q\", scale=x_scale), y=alt.Y(\"Lower:Q\", scale=y_scale), y2=\"Upper:Q\")\n)\n\n# Regression line — idiomatic Altair transform_regression\nreg_line = (\n    alt.Chart(standards_df)\n    .mark_line(color=BRAND, strokeWidth=2.5)\n    .transform_regression(\"Concentration (mg/L)\", \"Absorbance\")\n    .encode(x=alt.X(\"Concentration (mg/L):Q\", scale=x_scale), y=alt.Y(\"Absorbance:Q\", scale=y_scale))\n)\n\n# Calibration standard points with hover highlighting\nhighlight = alt.selection_point(on=\"pointerover\", nearest=True, empty=False)\n\npoints = (\n    alt.Chart(standards_df)\n    .mark_point(filled=True, color=BRAND, stroke=PAGE_BG, strokeWidth=1.5)\n    .encode(\n        x=alt.X(\n            \"Concentration (mg/L):Q\",\n            scale=x_scale,\n            title=\"Concentration (mg/L)\",\n            axis=alt.Axis(values=[0, 2, 4, 6, 8, 10, 12, 14]),\n        ),\n        y=alt.Y(\n            \"Absorbance:Q\",\n            scale=y_scale,\n            title=\"Absorbance\",\n            axis=alt.Axis(values=[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6], format=\".1f\"),\n        ),\n        size=alt.condition(highlight, alt.value(280), alt.value(200)),\n        tooltip=[alt.Tooltip(\"Concentration (mg/L):Q\", format=\".1f\"), alt.Tooltip(\"Absorbance:Q\", format=\".4f\")],\n    )\n    .add_params(highlight)\n)\n\n# Unknown sample dashed projection lines\nh_line = (\n    alt.Chart(unknown_hline_df)\n    .mark_line(color=UNKNOWN_COLOR, strokeWidth=1.8, strokeDash=[8, 6])\n    .encode(x=alt.X(\"Concentration (mg/L):Q\", scale=x_scale), y=alt.Y(\"Absorbance:Q\", scale=y_scale))\n)\n\nv_line = (\n    alt.Chart(unknown_vline_df)\n    .mark_line(color=UNKNOWN_COLOR, strokeWidth=1.8, strokeDash=[8, 6])\n    .encode(x=alt.X(\"Concentration (mg/L):Q\", scale=x_scale), y=alt.Y(\"Absorbance:Q\", scale=y_scale))\n)\n\n# Unknown sample point (diamond marker)\nunknown_pt = (\n    alt.Chart(unknown_point_df)\n    .mark_point(size=220, filled=True, color=UNKNOWN_COLOR, stroke=PAGE_BG, strokeWidth=1.5, shape=\"diamond\")\n    .encode(\n        x=alt.X(\"Concentration (mg/L):Q\", scale=x_scale),\n        y=alt.Y(\"Absorbance:Q\", scale=y_scale),\n        tooltip=[\n            alt.Tooltip(\"Concentration (mg/L):Q\", title=\"Predicted Conc.\", format=\".2f\"),\n            alt.Tooltip(\"Absorbance:Q\", title=\"Measured Abs.\", format=\".4f\"),\n        ],\n    )\n)\n\n# Regression equation annotation — lighter weight for secondary info\neq_text = f\"y = {slope:.4f}x + {intercept:.4f}    R² = {r_squared:.4f}\"\nannotation_df = pd.DataFrame({\"Concentration (mg/L)\": [1.0], \"Absorbance\": [0.055], \"text\": [eq_text]})\n\neq_label = (\n    alt.Chart(annotation_df)\n    .mark_text(fontSize=10, align=\"left\", fontWeight=\"normal\", color=INK_SOFT)\n    .encode(x=alt.X(\"Concentration (mg/L):Q\", scale=x_scale), y=alt.Y(\"Absorbance:Q\", scale=y_scale), text=\"text:N\")\n)\n\n# Unknown sample label\nunknown_label_df = pd.DataFrame(\n    {\n        \"Concentration (mg/L)\": [unknown_concentration + 0.4],\n        \"Absorbance\": [unknown_absorbance + 0.028],\n        \"text\": [f\"Unknown ({unknown_concentration:.1f} mg/L)\"],\n    }\n)\n\nunknown_label = (\n    alt.Chart(unknown_label_df)\n    .mark_text(fontSize=10, align=\"left\", fontWeight=\"bold\", color=UNKNOWN_COLOR)\n    .encode(x=alt.X(\"Concentration (mg/L):Q\", scale=x_scale), y=alt.Y(\"Absorbance:Q\", scale=y_scale), text=\"text:N\")\n)\n\n# Title with scaled font size for length\ntitle_str = \"calibration-beer-lambert · python · altair · anyplot.ai\"\ntitle_fs = round(16 * min(1.0, 67 / len(title_str)))\n\n# Compose layers and configure theme-adaptive chrome\nchart = (\n    alt.layer(band, reg_line, points, h_line, v_line, unknown_pt, eq_label, unknown_label)\n    .properties(\n        width=620, height=320, background=PAGE_BG, title=alt.Title(title_str, fontSize=title_fs, fontWeight=\"bold\")\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(\n        labelFontSize=10,\n        titleFontSize=12,\n        titleColor=INK,\n        labelColor=INK_SOFT,\n        grid=False,\n        domainColor=INK_SOFT,\n        domainWidth=0.6,\n        tickColor=INK_SOFT,\n        tickSize=5,\n        tickWidth=0.6,\n    )\n    .configure_title(color=INK)\n    .interactive()\n)\n\n# Save PNG\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n# Pad to exactly 3200 × 1800 (landscape target)\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\n# Save HTML\nchart.save(f\"plot-{THEME}.html\")\n"}