Arrhenius Plot for Reaction Kinetics — Matplotlib

An Arrhenius plot displays ln(k) versus 1/T to determine the activation energy of a chemical reaction from experimental rate constant data. The Arrhenius equation predicts a linear relationship on this transformed scale, where the slope equals -Ea/R (activation energy divided by the gas constant). This visualization is fundamental in physical chemistry and chemical engineering for characterizing reaction kinetics and comparing catalytic performance.

Arrhenius Plot for Reaction Kinetics rendered with Matplotlib

Python source (Matplotlib)

""" anyplot.ai
line-arrhenius: Arrhenius Plot for Reaction Kinetics
Library: matplotlib 3.11.0 | Python 3.13.14
Quality: 90/100 | Updated: 2026-06-24
"""

import os

import matplotlib.patheffects as pe
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
from matplotlib.colors import LinearSegmentedColormap


# 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 sequential colormap for continuous temperature data (#009E73 → #4467A3)
imprint_seq = LinearSegmentedColormap.from_list("imprint_seq", ["#009E73", "#4467A3"])

# Data — first-order decomposition reaction rate constants at various temperatures
temperature_K = np.array([300, 350, 400, 450, 500, 550, 600])
activation_energy_true = 75000  # J/mol (75 kJ/mol)
R_gas = 8.314  # J/(mol·K)
pre_exponential = 1e13  # s⁻¹

np.random.seed(42)
rate_constant_k = (
    pre_exponential
    * np.exp(-activation_energy_true / (R_gas * temperature_K))
    * np.exp(np.random.normal(0, 0.15, len(temperature_K)))
)

inv_temperature = 1000 / temperature_K  # 1000/T for cleaner x-axis values
ln_k = np.log(rate_constant_k)

# Linear regression
coeffs = np.polyfit(1 / temperature_K, ln_k, 1)
slope, intercept = coeffs
ln_k_predicted = slope * (1 / temperature_K) + intercept
ss_res = np.sum((ln_k - ln_k_predicted) ** 2)
ss_tot = np.sum((ln_k - np.mean(ln_k)) ** 2)
r_squared = 1 - ss_res / ss_tot
activation_energy = -slope * R_gas / 1000  # kJ/mol

inv_temp_fit = np.linspace(1 / temperature_K.max(), 1 / temperature_K.min(), 200)
ln_k_fit = slope * inv_temp_fit + intercept

# Confidence band (±2 SE)
residual_se = np.sqrt(ss_res / (len(temperature_K) - 2))
inv_T_arr = 1 / temperature_K
x_mean = np.mean(inv_T_arr)
s_xx = np.sum((inv_T_arr - x_mean) ** 2)
se_fit = residual_se * np.sqrt(1 / len(inv_T_arr) + (inv_temp_fit - x_mean) ** 2 / s_xx)

# Plot — figsize=(8,4.5) dpi=400 → exactly 3200×1800 px
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG, constrained_layout=True)
ax.set_facecolor(PAGE_BG)

# 95% confidence band
ax.fill_between(
    inv_temp_fit * 1000,
    ln_k_fit - 2 * se_fit,
    ln_k_fit + 2 * se_fit,
    color=INK_SOFT,
    alpha=0.12,
    zorder=1,
    label="95% confidence band",
)

# Regression line (Imprint blue #4467A3)
ax.plot(
    inv_temp_fit * 1000,
    ln_k_fit,
    color="#4467A3",
    linewidth=2.5,
    alpha=0.90,
    label="Linear fit",
    zorder=2,
    path_effects=[pe.withStroke(linewidth=4, foreground=PAGE_BG, alpha=0.5)],
)

# Data points — Imprint sequential colormap by temperature
scatter = ax.scatter(
    inv_temperature,
    ln_k,
    s=180,
    c=temperature_K,
    cmap=imprint_seq,
    vmin=temperature_K.min(),
    vmax=temperature_K.max(),
    edgecolors=PAGE_BG,
    linewidth=1.5,
    zorder=4,
    label="Experimental data",
)

# Colorbar — temperature scale
cbar = fig.colorbar(scatter, ax=ax, pad=0.02, aspect=25, shrink=0.80)
cbar.set_label("Temperature (K)", fontsize=8, labelpad=8, color=INK_SOFT)
cbar.ax.tick_params(labelsize=7, colors=INK_SOFT)
cbar.outline.set_visible(False)

# Annotation — Ea and R²
mid_idx = len(inv_temp_fit) // 3
ax.annotate(
    f"$E_a$ = {activation_energy:.1f} kJ/mol\n$R^2$ = {r_squared:.4f}",
    xy=(inv_temp_fit[mid_idx] * 1000, ln_k_fit[mid_idx]),
    xytext=(35, 45),
    textcoords="offset points",
    fontsize=8,
    fontweight="medium",
    color=INK,
    bbox={
        "boxstyle": "round,pad=0.4",
        "facecolor": ELEVATED_BG,
        "edgecolor": "#4467A3",
        "alpha": 0.95,
        "linewidth": 1.2,
    },
    arrowprops={"arrowstyle": "-|>", "color": "#4467A3", "lw": 1.5, "connectionstyle": "arc3,rad=0.15"},
)

# Style
title = "line-arrhenius · python · matplotlib · anyplot.ai"
title_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12

ax.set_xlabel("1000 / T  (K⁻¹)", fontsize=10, labelpad=8, color=INK)
ax.set_ylabel("ln(k)", fontsize=10, labelpad=8, color=INK)
ax.set_title(
    title,
    fontsize=title_fontsize,
    fontweight="medium",
    pad=10,
    color=INK,
    path_effects=[pe.withStroke(linewidth=3, foreground=PAGE_BG, alpha=0.8)],
)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_linewidth(0.7)
ax.spines["left"].set_color(INK_SOFT)
ax.spines["bottom"].set_linewidth(0.7)
ax.spines["bottom"].set_color(INK_SOFT)
ax.yaxis.grid(True, alpha=0.15, linewidth=0.6, color=INK)
ax.xaxis.grid(True, alpha=0.08, linewidth=0.4, color=INK)
ax.xaxis.set_major_formatter(ticker.FormatStrFormatter("%.1f"))

# Legend
leg = ax.legend(fontsize=7, framealpha=0.95, edgecolor=INK_SOFT, loc="upper right", fancybox=False)
if leg:
    leg.get_frame().set_facecolor(ELEVATED_BG)
    plt.setp(leg.get_texts(), color=INK_SOFT)

# Secondary x-axis — temperature in K
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
temp_ticks = np.array([600, 550, 500, 450, 400, 350, 300])
tick_positions = 1000 / temp_ticks
ax2.set_xticks(tick_positions)
ax2.set_xticklabels([f"{t} K" for t in temp_ticks], fontsize=7)
ax2.set_xlabel("Temperature (K)", fontsize=9, labelpad=10, color=INK)
ax2.spines["right"].set_visible(False)
ax2.spines["top"].set_linewidth(0.7)
ax2.spines["top"].set_color(INK_SOFT)
ax2.tick_params(axis="x", labelsize=7, colors=INK_SOFT, labelcolor=INK_SOFT)

# Save — no bbox_inches so figsize×dpi → exactly 3200×1800
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)
plt.close()

Part of Arrhenius Plot for Reaction Kinetics on anyplot.ai.

Other implementations