NMR Spectrum (Nuclear Magnetic Resonance) — lets-plot

An NMR spectrum plots signal intensity versus chemical shift (in ppm) to reveal the electronic environment of atomic nuclei in a molecule. It is the primary analytical tool in organic chemistry for determining molecular structure, with peak positions indicating functional groups and splitting patterns (multiplets) revealing connectivity between neighboring atoms. The x-axis is conventionally reversed (high ppm on the left) and peaks appear as sharp signals rising from a baseline.

NMR Spectrum (Nuclear Magnetic Resonance) rendered with lets-plot

Python source (lets-plot)

""" anyplot.ai
spectrum-nmr: NMR Spectrum (Nuclear Magnetic Resonance)
Library: letsplot 4.10.1 | Python 3.13.13
Quality: 91/100 | Updated: 2026-06-03
"""

import os

import numpy as np
import pandas as pd
from lets_plot import (
    LetsPlot,
    aes,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_area,
    geom_hline,
    geom_line,
    geom_text,
    ggplot,
    ggsize,
    labs,
    layer_tooltips,
    scale_x_reverse,
    scale_y_continuous,
    theme,
    theme_minimal,
)
from lets_plot.export import ggsave


LetsPlot.setup_html()

# Theme tokens (Imprint palette — see prompts/default-style-guide.md)
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
BRAND = "#009E73"  # Imprint palette position 1 — ALWAYS first series

# Data - Synthetic 1H NMR spectrum of ethanol (CH3CH2OH)
np.random.seed(42)
chemical_shift = np.linspace(5.0, -0.5, 5000)
w = 0.008  # Lorentzian half-width for sharp peaks

# Build spectrum by summing Lorentzian peaks: A * w^2 / ((x - c)^2 + w^2)
intensity = np.zeros_like(chemical_shift)

# TMS reference peak at 0 ppm
intensity += 1.0 * w**2 / ((chemical_shift - 0.0) ** 2 + w**2)

# CH3 triplet near 1.18 ppm (J-coupling = 0.06 ppm, intensity ratio 1:2:1)
j = 0.06
for center, amp in [(1.18 - j, 0.75), (1.18, 1.5), (1.18 + j, 0.75)]:
    intensity += amp * w**2 / ((chemical_shift - center) ** 2 + w**2)

# CH2 quartet near 3.69 ppm (J-coupling = 0.06 ppm, intensity ratio 1:3:3:1)
for center, amp in [(3.69 - 1.5 * j, 0.4), (3.69 - 0.5 * j, 1.2), (3.69 + 0.5 * j, 1.2), (3.69 + 1.5 * j, 0.4)]:
    intensity += amp * w**2 / ((chemical_shift - center) ** 2 + w**2)

# OH singlet near 2.61 ppm (broader due to exchange)
w_oh = 0.012
intensity += 0.6 * w_oh**2 / ((chemical_shift - 2.61) ** 2 + w_oh**2)

# Add subtle baseline noise
intensity += np.random.normal(0, 0.003, len(chemical_shift))
intensity = np.clip(intensity, 0, None)

df = pd.DataFrame({"chemical_shift": chemical_shift, "intensity": intensity})

# Peak labels positioned above each peak group
peak_labels = pd.DataFrame(
    {
        "x": [0.0, 1.18, 2.61, 3.69],
        "y": [1.15, 1.58, 0.78, 1.42],
        "label": ["TMS\n0.00 ppm", "CH₃ (triplet)\n1.18 ppm", "OH (singlet)\n2.61 ppm", "CH₂ (quartet)\n3.69 ppm"],
    }
)

# Interactive tooltips — distinctly lets-plot (not available in plotnine)
spectrum_tooltips = (
    layer_tooltips()
    .format("@{chemical_shift}", ".3f")
    .line("δ: @{chemical_shift} ppm")
    .format("@{intensity}", ".4f")
    .line("Intensity: @{intensity} a.u.")
)

# Plot — geom_area fill gives distinctive spectrum appearance; tooltips add interactive HTML layer
plot = (
    ggplot(df, aes(x="chemical_shift", y="intensity"))
    + geom_hline(yintercept=0, color=INK_SOFT, size=0.5)
    + geom_area(fill=BRAND, alpha=0.15)
    + geom_line(color=BRAND, size=1.2, tooltips=spectrum_tooltips)
    + geom_text(data=peak_labels, mapping=aes(x="x", y="y", label="label"), size=5.5, color=INK, fontface="bold")
    + labs(
        x="δ Chemical Shift (ppm)",
        y="Intensity (a.u.)",
        title="Ethanol ¹H NMR · spectrum-nmr · python · letsplot · anyplot.ai",
    )
    + scale_x_reverse(limits=[-0.5, 5.0])
    + scale_y_continuous(expand=[0.02, 0, 0.20, 0])
    + theme_minimal()
    + theme(
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG),
        panel_grid_major_x=element_blank(),
        panel_grid_major_y=element_line(color=INK_SOFT, size=0.2),
        panel_grid_minor=element_blank(),
        axis_title=element_text(size=12, color=INK),
        axis_text=element_text(size=10, color=INK_SOFT),
        plot_title=element_text(size=16, color=INK),
        axis_line=element_line(color=INK_SOFT),
        axis_ticks=element_blank(),
        plot_margin=[30, 40, 20, 20],
    )
    + ggsize(800, 450)
)

# Save
ggsave(plot, f"plot-{THEME}.png", path=".", scale=4)
ggsave(plot, f"plot-{THEME}.html", path=".")

Part of NMR Spectrum (Nuclear Magnetic Resonance) on anyplot.ai.

Other implementations