{"spec_id":"spectrum-nmr","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nspectrum-nmr: NMR Spectrum (Nuclear Magnetic Resonance)\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nBRAND = IMPRINT_PALETTE[0]  # #009E73 — spectrum line\n\n# Data — synthetic 1H NMR spectrum of ethanol\nnp.random.seed(42)\nchemical_shift = np.linspace(-0.5, 5.0, 6000)\nw = 0.012  # peak width (standard deviation)\n\n# TMS reference peak at 0 ppm\nintensity = 0.4 * np.exp(-((chemical_shift - 0.0) ** 2) / (2 * w**2))\n\n# CH3 triplet near 1.2 ppm (1:2:1 ratio)\nintensity += 0.55 * np.exp(-((chemical_shift - 1.11) ** 2) / (2 * w**2))\nintensity += 1.10 * np.exp(-((chemical_shift - 1.18) ** 2) / (2 * w**2))\nintensity += 0.55 * np.exp(-((chemical_shift - 1.25) ** 2) / (2 * w**2))\n\n# OH singlet near 2.6 ppm\nintensity += 0.35 * np.exp(-((chemical_shift - 2.61) ** 2) / (2 * 0.015**2))\n\n# CH2 quartet near 3.7 ppm (1:3:3:1 ratio)\nintensity += 0.25 * np.exp(-((chemical_shift - 3.585) ** 2) / (2 * w**2))\nintensity += 0.75 * np.exp(-((chemical_shift - 3.655) ** 2) / (2 * w**2))\nintensity += 0.75 * np.exp(-((chemical_shift - 3.725) ** 2) / (2 * w**2))\nintensity += 0.25 * np.exp(-((chemical_shift - 3.795) ** 2) / (2 * w**2))\n\n# Subtle baseline noise\nintensity += np.random.normal(0, 0.003, len(chemical_shift))\nintensity = np.clip(intensity, 0, None)\n\n# Imprint palette assignment per peak group\npeak_colors = {\n    \"TMS\": IMPRINT_PALETTE[0],  # brand green\n    \"CH₃\": IMPRINT_PALETTE[2],  # blue\n    \"OH\": IMPRINT_PALETTE[1],  # lavender\n    \"CH₂\": IMPRINT_PALETTE[3],  # ochre\n}\n\n\ndef hex_to_rgba(hex_color, alpha):\n    r = int(hex_color[1:3], 16)\n    g = int(hex_color[3:5], 16)\n    b = int(hex_color[5:7], 16)\n    return f\"rgba({r},{g},{b},{alpha})\"\n\n\n# Plot\nfig = go.Figure()\n\n# Main spectrum trace — Imprint brand green\nfill_alpha = 0.07 if THEME == \"light\" else 0.12\nfig.add_trace(\n    go.Scatter(\n        x=chemical_shift,\n        y=intensity,\n        mode=\"lines\",\n        line={\"color\": BRAND, \"width\": 2.5, \"shape\": \"spline\"},\n        fill=\"tozeroy\",\n        fillcolor=hex_to_rgba(BRAND, fill_alpha),\n        hovertemplate=\"δ %{x:.2f} ppm<br>Intensity: %{y:.3f}<extra></extra>\",\n        showlegend=False,\n    )\n)\n\n# Region shadings — Imprint palette, slightly higher opacity on dark for perceptibility\nregion_alpha = 0.07 if THEME == \"light\" else 0.13\npeak_regions = [\n    {\"x0\": -0.05, \"x1\": 0.05, \"color\": peak_colors[\"TMS\"]},\n    {\"x0\": 1.05, \"x1\": 1.32, \"color\": peak_colors[\"CH₃\"]},\n    {\"x0\": 2.53, \"x1\": 2.69, \"color\": peak_colors[\"OH\"]},\n    {\"x0\": 3.55, \"x1\": 3.84, \"color\": peak_colors[\"CH₂\"]},\n]\nshapes = [\n    {\n        \"type\": \"rect\",\n        \"xref\": \"x\",\n        \"yref\": \"paper\",\n        \"x0\": region[\"x0\"],\n        \"x1\": region[\"x1\"],\n        \"y0\": 0,\n        \"y1\": 1,\n        \"fillcolor\": hex_to_rgba(region[\"color\"], region_alpha),\n        \"line\": {\"width\": 0},\n        \"layer\": \"below\",\n    }\n    for region in peak_regions\n]\n\n# Annotations — color-coded, theme-adaptive text and backgrounds\nannotations_data = [\n    {\"x\": 0.0, \"y\": 0.42, \"text\": \"<b>TMS</b><br><i>δ</i> 0.00\", \"key\": \"TMS\", \"ay\": -55},\n    {\"x\": 1.18, \"y\": 1.13, \"text\": \"<b>CH₃</b> triplet<br><i>δ</i> 1.18\", \"key\": \"CH₃\", \"ay\": -50},\n    {\"x\": 2.61, \"y\": 0.38, \"text\": \"<b>OH</b> singlet<br><i>δ</i> 2.61\", \"key\": \"OH\", \"ay\": -55},\n    {\"x\": 3.69, \"y\": 0.78, \"text\": \"<b>CH₂</b> quartet<br><i>δ</i> 3.69\", \"key\": \"CH₂\", \"ay\": -50},\n]\nstyled_annotations = [\n    {\n        \"x\": ann[\"x\"],\n        \"y\": ann[\"y\"],\n        \"text\": ann[\"text\"],\n        \"showarrow\": True,\n        \"arrowhead\": 3,\n        \"arrowsize\": 1.2,\n        \"arrowwidth\": 2,\n        \"arrowcolor\": peak_colors[ann[\"key\"]],\n        \"font\": {\"size\": 11, \"color\": INK, \"family\": \"Arial, sans-serif\"},\n        \"ax\": 0,\n        \"ay\": ann[\"ay\"],\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": peak_colors[ann[\"key\"]],\n        \"borderwidth\": 1.5,\n        \"borderpad\": 4,\n    }\n    for ann in annotations_data\n]\n\n# Title — n=60 < 67 baseline, use default 16px\ntitle_text = \"Ethanol ¹H NMR · spectrum-nmr · python · plotly · anyplot.ai\"\n\n# Layout\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK, \"family\": \"Arial, sans-serif\"},\n    title={\n        \"text\": title_text,\n        \"font\": {\"size\": 16, \"color\": INK, \"family\": \"Arial, sans-serif\"},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Chemical Shift <i>δ</i> (ppm)\", \"font\": {\"size\": 12, \"color\": INK}, \"standoff\": 12},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"autorange\": \"reversed\",\n        \"range\": [5.0, -0.5],\n        \"dtick\": 0.5,\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"showline\": True,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1.5,\n        \"ticks\": \"outside\",\n        \"ticklen\": 8,\n        \"tickwidth\": 1.5,\n        \"tickcolor\": INK_SOFT,\n        \"minor\": {\"dtick\": 0.1, \"ticks\": \"outside\", \"ticklen\": 4, \"tickcolor\": INK_MUTED},\n        \"spikemode\": \"across\",\n        \"spikethickness\": 1,\n        \"spikecolor\": INK_MUTED,\n        \"spikedash\": \"dot\",\n    },\n    yaxis={\n        \"title\": {\"text\": \"Intensity (a.u.)\", \"font\": {\"size\": 12, \"color\": INK}, \"standoff\": 8},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": True,\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"griddash\": \"dot\",\n        \"zeroline\": True,\n        \"zerolinecolor\": INK_SOFT,\n        \"zerolinewidth\": 1.5,\n        \"showline\": True,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1.5,\n        \"ticks\": \"outside\",\n        \"ticklen\": 8,\n        \"tickwidth\": 1.5,\n        \"tickcolor\": INK_SOFT,\n        \"rangemode\": \"tozero\",\n    },\n    template=None,\n    annotations=styled_annotations,\n    shapes=shapes,\n    showlegend=False,\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n    hoverlabel={\"bgcolor\": ELEVATED_BG, \"bordercolor\": BRAND, \"font\": {\"size\": 11, \"color\": INK}},\n    hovermode=\"x unified\",\n)\n\n# Save — landscape 3200×1800\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}