The same plot in 14 other libraries — Python: Altair, Bokeh, lets-plot, Matplotlib, Plotly, Pygal, Seaborn; R: ggplot2; Julia: Makie.jl; JavaScript: Chart.js, D3.js, Apache ECharts, Highcharts, MUI X Charts. Compare all 15 side by side: NMR Spectrum (Nuclear Magnetic Resonance) in Python, R, Julia and JavaScript.
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.

""" anyplot.ai
spectrum-nmr: NMR Spectrum (Nuclear Magnetic Resonance)
Library: plotnine 0.15.5 | Python 3.13.13
Quality: 90/100 | Updated: 2026-06-03
"""
import os
import sys
# This file is named 'plotnine.py' — remove its directory from sys.path so the
# 'plotnine' library is found in site-packages instead of looping back here.
_d = os.path.dirname(os.path.abspath(__file__))
if _d in sys.path:
sys.path.remove(_d)
del _d
import numpy as np
import pandas as pd
from plotnine import (
aes,
annotate,
coord_cartesian,
element_blank,
element_line,
element_rect,
element_text,
geom_area,
geom_line,
geom_point,
geom_segment,
geom_text,
geom_vline,
ggplot,
labs,
scale_x_reverse,
scale_y_continuous,
theme,
theme_minimal,
)
# Theme tokens — Imprint palette, theme-adaptive chrome
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"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
BRAND = "#009E73" # Imprint palette position 1 — ALWAYS first series
# Title with length-aware fontsize
title = "\xb9H NMR of Ethanol · spectrum-nmr · python · plotnine · anyplot.ai"
n = len(title)
title_fontsize = max(8, round(12 * 67 / n)) if n > 67 else 12
# Data — synthetic 1H NMR spectrum of ethanol (CH3CH2OH)
np.random.seed(42)
chemical_shift = np.linspace(-0.5, 5.0, 6000)
w = 0.015 # default Lorentzian peak width
intensity = np.zeros_like(chemical_shift)
# TMS reference peak at 0 ppm (singlet)
w_tms = 0.012
intensity += 0.4 * w_tms**2 / ((chemical_shift - 0.0) ** 2 + w_tms**2)
# CH3 triplet near 1.18 ppm (ratio 1:2:1)
triplet_center = 1.18
j_coupling = 0.07
intensity += 0.7 * w**2 / ((chemical_shift - (triplet_center - j_coupling)) ** 2 + w**2)
intensity += 1.4 * w**2 / ((chemical_shift - triplet_center) ** 2 + w**2)
intensity += 0.7 * w**2 / ((chemical_shift - (triplet_center + j_coupling)) ** 2 + w**2)
# CH2 quartet near 3.69 ppm (ratio 1:3:3:1)
quartet_center = 3.69
intensity += 0.35 * w**2 / ((chemical_shift - (quartet_center - 1.5 * j_coupling)) ** 2 + w**2)
intensity += 1.05 * w**2 / ((chemical_shift - (quartet_center - 0.5 * j_coupling)) ** 2 + w**2)
intensity += 1.05 * w**2 / ((chemical_shift - (quartet_center + 0.5 * j_coupling)) ** 2 + w**2)
intensity += 0.35 * w**2 / ((chemical_shift - (quartet_center + 1.5 * j_coupling)) ** 2 + w**2)
# OH singlet near 2.61 ppm
intensity += 0.55 * w**2 / ((chemical_shift - 2.61) ** 2 + w**2)
# Slight baseline noise
intensity += np.random.normal(0, 0.003, len(chemical_shift))
intensity = np.maximum(intensity, 0)
df = pd.DataFrame({"chemical_shift": chemical_shift, "intensity": intensity})
# Peak annotation data
labels_df = pd.DataFrame(
{
"x": [triplet_center, quartet_center, 2.61, 0.0],
"y": [1.52, 1.17, 0.67, 0.45],
"peak_y": [1.40, 1.05, 0.55, 0.40],
"label": ["CH₃ (triplet)\n1.18 ppm", "CH₂ (quartet)\n3.69 ppm", "OH (singlet)\n2.61 ppm", "TMS\n0.00"],
"group": ["main", "main", "main", "ref"],
}
)
main_labels = labels_df[labels_df["group"] == "main"]
ref_labels = labels_df[labels_df["group"] == "ref"]
vline_df = pd.DataFrame({"xintercept": [triplet_center, quartet_center, 2.61]})
# Plot
plot = (
ggplot(df, aes(x="chemical_shift", y="intensity"))
+ geom_area(fill=BRAND, alpha=0.08)
+ geom_vline(
data=vline_df,
mapping=aes(xintercept="xintercept"),
color=BRAND,
alpha=0.12,
size=0.5,
linetype="dashed",
inherit_aes=False,
)
+ geom_line(color=BRAND, size=1.0)
+ geom_point(data=labels_df, mapping=aes(x="x", y="peak_y"), color=BRAND, size=2.5, shape="o", inherit_aes=False)
+ geom_segment(
data=labels_df,
mapping=aes(x="x", xend="x", y="peak_y", yend="y"),
color=INK_SOFT,
size=0.4,
linetype="dotted",
inherit_aes=False,
)
+ geom_text(
data=main_labels,
mapping=aes(x="x", y="y", label="label"),
size=3.5,
color=INK,
fontweight="bold",
va="bottom",
ha="center",
inherit_aes=False,
)
+ geom_text(
data=ref_labels,
mapping=aes(x="x", y="y", label="label"),
size=3.0,
color=INK_SOFT,
va="bottom",
ha="center",
inherit_aes=False,
)
+ annotate("text", x=4.6, y=0.03, label="baseline", size=2.5, color=INK_MUTED, fontstyle="italic")
+ scale_x_reverse(limits=(5.0, -0.5), breaks=[0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0])
+ scale_y_continuous(breaks=[], limits=(-0.05, 1.72))
+ coord_cartesian(expand=False)
+ labs(x="Chemical Shift (ppm)", y="Intensity", title=title)
+ theme_minimal()
+ theme(
figure_size=(8, 4.5),
text=element_text(size=7, color=INK_SOFT),
axis_title_x=element_text(size=10, color=INK, margin={"t": 8}),
axis_title_y=element_text(size=10, color=INK_MUTED),
axis_text_x=element_text(size=8, color=INK_SOFT),
axis_text_y=element_blank(),
axis_ticks_major_y=element_blank(),
axis_line_x=element_line(color=INK_SOFT, size=0.6),
axis_line_y=element_blank(),
plot_title=element_text(size=title_fontsize, color=INK, margin={"b": 8}),
panel_background=element_rect(fill=PAGE_BG, color="none"),
plot_background=element_rect(fill=PAGE_BG, color="none"),
panel_border=element_blank(),
panel_grid_major_x=element_line(color=INK, size=0.3, alpha=0.15),
panel_grid_major_y=element_blank(),
panel_grid_minor=element_blank(),
plot_margin=0.04,
)
)
plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False)
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/spectrum-nmr/plotnine/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.
{
"spec_id": "spectrum-nmr",
"language": "python",
"library": "plotnine",
"page": "https://anyplot.ai/spectrum-nmr/python/plotnine",
"hub": "https://anyplot.ai/spectrum-nmr",
"code_json": "https://api.anyplot.ai/specs/spectrum-nmr/plotnine/code",
"spec_json": "https://api.anyplot.ai/specs/spectrum-nmr",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/spectrum-nmr/python/plotnine/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/spectrum-nmr/python/plotnine/plot-dark.png",
"quality_score": 90.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of NMR Spectrum (Nuclear Magnetic Resonance) on anyplot.ai.