{"spec_id":"spectrum-nmr","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nspectrum-nmr: NMR Spectrum (Nuclear Magnetic Resonance)\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — canonical order, first series always #009E73\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\npeak_palette = {\"CH₂ quartet\": IMPRINT[0], \"OH singlet\": IMPRINT[1], \"CH₃ triplet\": IMPRINT[2], \"TMS\": IMPRINT[3]}\n\n# Seaborn theme — warm off-white / near-black surfaces, serif font\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n        \"font.family\": \"serif\",\n    },\n)\n\n# Data — synthetic 1H NMR spectrum of ethanol (Lorentzian peak shapes)\nnp.random.seed(42)\nppm = np.linspace(-0.5, 5.5, 6000)\nwidth = 0.012\n\nspectrum = np.zeros_like(ppm)\n\n# TMS reference at 0 ppm\nw_tms = 0.01\nspectrum += 0.4 * w_tms**2 / ((ppm - 0.0) ** 2 + w_tms**2)\n\n# CH3 triplet at 1.18 ppm (1:2:1 ratio, J = 0.07 ppm)\nj_ch3 = 0.07\nspectrum += 0.5 * width**2 / ((ppm - (1.18 - j_ch3)) ** 2 + width**2)\nspectrum += 1.0 * width**2 / ((ppm - 1.18) ** 2 + width**2)\nspectrum += 0.5 * width**2 / ((ppm - (1.18 + j_ch3)) ** 2 + width**2)\n\n# CH2 quartet at 3.69 ppm (1:3:3:1 ratio, J = 0.07 ppm)\nj_ch2 = 0.07\nspectrum += 0.25 * width**2 / ((ppm - (3.69 - 1.5 * j_ch2)) ** 2 + width**2)\nspectrum += 0.75 * width**2 / ((ppm - (3.69 - 0.5 * j_ch2)) ** 2 + width**2)\nspectrum += 0.75 * width**2 / ((ppm - (3.69 + 0.5 * j_ch2)) ** 2 + width**2)\nspectrum += 0.25 * width**2 / ((ppm - (3.69 + 1.5 * j_ch2)) ** 2 + width**2)\n\n# OH singlet at 2.61 ppm (slightly broad due to proton exchange)\nw_oh = 0.025\nspectrum += 0.35 * w_oh**2 / ((ppm - 2.61) ** 2 + w_oh**2)\n\nspectrum += np.random.normal(0, 0.003, len(ppm))\nspectrum = np.clip(spectrum, 0, None)\n\n# DataFrame with region labels (vectorized with np.select)\ndf = pd.DataFrame({\"Chemical Shift (ppm)\": ppm, \"Intensity\": spectrum})\nconditions = [\n    (ppm >= 3.5) & (ppm <= 3.9),\n    (ppm >= 2.4) & (ppm <= 2.8),\n    (ppm >= 1.0) & (ppm <= 1.4),\n    (ppm >= -0.1) & (ppm <= 0.1),\n]\nchoices = [\"CH₂ quartet\", \"OH singlet\", \"CH₃ triplet\", \"TMS\"]\ndf[\"Region\"] = np.select(conditions, choices, default=\"Baseline\")\n\n# Plot — 3200×1800 px canvas (figsize=(8,4.5) × dpi=400)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Baseline (thin, muted — plotted separately)\nsns.lineplot(\n    data=df[df[\"Region\"] == \"Baseline\"],\n    x=\"Chemical Shift (ppm)\",\n    y=\"Intensity\",\n    color=INK_SOFT,\n    linewidth=0.6,\n    ax=ax,\n    legend=False,\n)\n\n# Signal peaks — single hue-based lineplot (idiomatic seaborn)\nhue_order = [\"CH₂ quartet\", \"OH singlet\", \"CH₃ triplet\", \"TMS\"]\nsns.lineplot(\n    data=df[df[\"Region\"] != \"Baseline\"],\n    x=\"Chemical Shift (ppm)\",\n    y=\"Intensity\",\n    hue=\"Region\",\n    hue_order=hue_order,\n    palette=peak_palette,\n    linewidth=2.2,\n    ax=ax,\n)\n\n# Rug marks at peak centers (distinctive seaborn feature)\nsns.rugplot(\n    data=pd.DataFrame({\"Chemical Shift (ppm)\": [0.0, 1.18, 2.61, 3.69]}),\n    x=\"Chemical Shift (ppm)\",\n    height=0.04,\n    linewidth=1.8,\n    color=INK_SOFT,\n    ax=ax,\n)\n\n# Semi-transparent fill under each peak region\nfor region in hue_order:\n    mask = df[\"Region\"] == region\n    ax.fill_between(\n        df.loc[mask, \"Chemical Shift (ppm)\"], df.loc[mask, \"Intensity\"], alpha=0.12, color=peak_palette[region]\n    )\n\n# NMR convention: high ppm on left\nax.invert_xaxis()\nax.set_xlim(5.5, -0.5)\n\n# Peak annotations with color-matched connectors\nmax_intensity = spectrum.max()\npeak_annotations = [\n    (0.0, \"TMS · 0.00 ppm\", 0.26, peak_palette[\"TMS\"]),\n    (1.18, \"CH₃ · 1.18 ppm\", 0.18, peak_palette[\"CH₃ triplet\"]),\n    (2.61, \"OH · 2.61 ppm\", 0.32, peak_palette[\"OH singlet\"]),\n    (3.69, \"CH₂ · 3.69 ppm\", 0.30, peak_palette[\"CH₂ quartet\"]),\n]\nfor peak_ppm, label, offset_frac, color in peak_annotations:\n    peak_idx = np.argmin(np.abs(ppm - peak_ppm))\n    peak_intensity = spectrum[peak_idx]\n    text_y = peak_intensity + max_intensity * offset_frac\n    ax.annotate(\n        label,\n        xy=(peak_ppm, peak_intensity),\n        xytext=(peak_ppm, text_y),\n        fontsize=8,\n        fontweight=\"medium\",\n        ha=\"center\",\n        va=\"bottom\",\n        color=color,\n        arrowprops={\"arrowstyle\": \"-\", \"color\": color, \"lw\": 0.8},\n    )\n\n# Style\ntitle = \"¹H NMR Spectrum of Ethanol · spectrum-nmr · python · seaborn · anyplot.ai\"\ntitle_n = len(title)\ntitle_fs = max(8, round(12 * 67 / title_n)) if title_n > 67 else 12\n\nax.set_title(title, fontsize=title_fs, fontweight=\"semibold\", pad=10, color=INK)\nax.set_xlabel(\"Chemical Shift (ppm)\", fontsize=10, color=INK)\nax.set_ylabel(\"Intensity\", fontsize=10, color=INK)\nax.set_yticks([])\nax.set_ylim(-0.02, max_intensity * 1.55)\nax.tick_params(axis=\"x\", labelsize=8, colors=INK_SOFT)\n\n# Legend\nlegend = ax.legend(\n    loc=\"upper right\",\n    fontsize=8,\n    frameon=True,\n    framealpha=0.92,\n    edgecolor=INK_SOFT,\n    facecolor=ELEVATED_BG,\n    title=\"Peak Assignment\",\n    title_fontsize=8,\n)\nlegend.get_title().set_fontweight(\"semibold\")\nlegend.get_title().set_color(INK)\nfor text in legend.get_texts():\n    text.set_color(INK_SOFT)\n\nsns.despine(ax=ax, left=True)\n\n# Save — bbox_inches omitted so canvas stays exactly figsize × dpi = 3200 × 1800 px\nfig.subplots_adjust(top=0.88, bottom=0.13, left=0.06, right=0.97)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}