A calibration curve plotting absorbance versus concentration following Beer-Lambert law (A = εlc). Measured calibration standards are shown as scatter points with a linear regression fit line. The regression equation (y = mx + b) and R² value are displayed on the plot. An example unknown sample is marked with dashed lines extending to both axes, demonstrating how the curve is used to determine concentration from a measured absorbance. This plot is fundamental in analytical chemistry for quantitative spectrophotometric analysis.

""" anyplot.ai
calibration-beer-lambert: Beer-Lambert Calibration Curve
Library: matplotlib 3.10.9 | Python 3.13.13
Quality: 90/100 | Updated: 2026-06-03
"""
import os
import matplotlib.patheffects as pe
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
from matplotlib.patches import FancyBboxPatch
from scipy import stats
# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
# Imprint palette positions 1 and 2
STANDARDS_COLOR = "#009E73" # position 1 — calibration standards and fit
UNKNOWN_COLOR = "#C475FD" # position 2 — unknown sample
# Data
np.random.seed(42)
concentrations = np.array([0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0])
molar_absorptivity = 0.045
absorbances = molar_absorptivity * concentrations + np.random.normal(0, 0.008, len(concentrations))
absorbances[0] = 0.003
# Linear regression
slope, intercept, r_value, p_value, std_err = stats.linregress(concentrations, absorbances)
r_squared = r_value**2
# Regression line and 95% prediction interval
conc_fit = np.linspace(-0.5, 14, 200)
abs_fit = slope * conc_fit + intercept
n = len(concentrations)
conc_mean = np.mean(concentrations)
residual_std = np.sqrt(np.sum((absorbances - slope * concentrations - intercept) ** 2) / (n - 2))
se_pred = residual_std * np.sqrt(1 + 1 / n + (conc_fit - conc_mean) ** 2 / np.sum((concentrations - conc_mean) ** 2))
t_crit = stats.t.ppf(0.975, n - 2)
pred_upper = abs_fit + t_crit * se_pred
pred_lower = abs_fit - t_crit * se_pred
# Unknown sample at ~9.5 mg/L (distinct from sibling implementations)
unknown_absorbance = 0.43
unknown_concentration = (unknown_absorbance - intercept) / slope
# Plot — 3200×1800 px landscape canvas
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)
# Prediction interval band
ax.fill_between(
conc_fit, pred_lower, pred_upper, alpha=0.12, color=STANDARDS_COLOR, label="95% Prediction Interval", zorder=1
)
# Fit line — PathEffects halo makes it pop above overlapping markers
(fit_line,) = ax.plot(conc_fit, abs_fit, color=STANDARDS_COLOR, linewidth=2.5, zorder=3, label="Linear Fit")
fit_line.set_path_effects([pe.withStroke(linewidth=5, foreground=PAGE_BG)])
# Calibration standards
ax.scatter(
concentrations,
absorbances,
s=100,
color=STANDARDS_COLOR,
edgecolors=PAGE_BG,
linewidth=1.0,
zorder=5,
label="Standards",
)
# Unknown sample dashed guide lines
ax.plot(
[unknown_concentration, unknown_concentration],
[-0.02, unknown_absorbance],
linestyle="--",
color=UNKNOWN_COLOR,
linewidth=1.8,
alpha=0.7,
zorder=2,
)
ax.plot(
[-0.5, unknown_concentration],
[unknown_absorbance, unknown_absorbance],
linestyle="--",
color=UNKNOWN_COLOR,
linewidth=1.8,
alpha=0.7,
zorder=2,
)
# Unknown sample marker
ax.scatter(
[unknown_concentration],
[unknown_absorbance],
s=120,
color=UNKNOWN_COLOR,
edgecolors=PAGE_BG,
linewidth=1.0,
zorder=6,
marker="D",
label="Unknown Sample",
)
# Regression equation annotation — FancyBboxPatch background + two text elements
# for visual hierarchy: R² line rendered bold in brand green
ann_patch = FancyBboxPatch(
(0.035, 0.775),
0.265,
0.175,
boxstyle="round,pad=0.01",
transform=ax.transAxes,
facecolor=ELEVATED_BG,
edgecolor=STANDARDS_COLOR,
alpha=0.95,
linewidth=1.0,
zorder=7,
)
ax.add_patch(ann_patch)
ax.text(
0.055,
0.925,
f"y = {slope:.4f}x + {intercept:.4f}",
transform=ax.transAxes,
fontsize=8,
fontfamily="monospace",
verticalalignment="top",
color=INK,
zorder=8,
)
ax.text(
0.055,
0.845,
f"R² = {r_squared:.4f}",
transform=ax.transAxes,
fontsize=9,
fontfamily="monospace",
fontweight="bold",
verticalalignment="top",
color=STANDARDS_COLOR,
zorder=8,
)
# Unknown annotation with arrow
ax.annotate(
f"Unknown: {unknown_concentration:.1f} mg/L",
xy=(unknown_concentration, unknown_absorbance),
xytext=(unknown_concentration - 1.5, unknown_absorbance + 0.13),
fontsize=8,
fontweight="semibold",
color=UNKNOWN_COLOR,
arrowprops={"arrowstyle": "-|>", "color": UNKNOWN_COLOR, "lw": 1.5, "mutation_scale": 10},
zorder=7,
)
# Axis value markers at the unknown's intercept points
ax.annotate(
f"{unknown_concentration:.1f}",
xy=(unknown_concentration, -0.02),
fontsize=8,
color=UNKNOWN_COLOR,
fontweight="bold",
ha="center",
va="top",
)
ax.annotate(
f"{unknown_absorbance:.2f}",
xy=(-0.5, unknown_absorbance),
fontsize=8,
color=UNKNOWN_COLOR,
fontweight="bold",
ha="left",
va="bottom",
)
# Style
title = "calibration-beer-lambert · python · matplotlib · anyplot.ai"
title_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12
ax.set_title(title, fontsize=title_fontsize, fontweight="medium", pad=10, color=INK)
ax.set_xlabel("Concentration (mg/L)", fontsize=10, labelpad=8, color=INK)
ax.set_ylabel("Absorbance", fontsize=10, labelpad=8, color=INK)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT, length=0)
for spine in ["top", "right"]:
ax.spines[spine].set_visible(False)
for spine in ["bottom", "left"]:
ax.spines[spine].set_color(INK_SOFT)
# Custom absorbance formatter — two decimal places on y-axis ticks
ax.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.2f"))
ax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)
ax.xaxis.grid(True, alpha=0.08, linewidth=0.5, color=INK)
ax.set_xlim(-0.5, 14)
ax.set_ylim(-0.02, 0.65)
leg = ax.legend(fontsize=8, loc="lower right")
if leg:
leg.get_frame().set_facecolor(ELEVATED_BG)
leg.get_frame().set_edgecolor(INK_SOFT)
plt.setp(leg.get_texts(), color=INK_SOFT)
plt.tight_layout()
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)
Part of Beer-Lambert Calibration Curve on anyplot.ai.