codex/glucose-anomers-ring-chain-connection.py

glucose-anomers-ring-chain-connection.py
"""Original anomer comparison with a shared open-chain connection.

Python 3.14, matplotlib 3.10.7. Output is basename.png in CWD.
Haworth diagrams are symbolic configurations, not flat molecular geometries.
"""
from pathlib import Path

import matplotlib.pyplot as plt


INK = "#223344"
OXYGEN = "#166280"
ANOMERIC = "#aa3b20"


def label(ax, x, y, s, size=13, color=INK, **kwargs):
    ax.text(x, y, s, fontsize=size, color=color, ha="center", va="center", **kwargs)


def line(ax, a, b, color=INK, lw=1.8):
    ax.plot([a[0], b[0]], [a[1], b[1]], color=color, lw=lw, solid_capstyle="round")


def ring(ax, cx, cy, beta):
    points = {1:(cx+1.75,cy), 2:(cx+.88,cy-.62), 3:(cx-.88,cy-.62),
              4:(cx-1.75,cy), 5:(cx-.88,cy+.62), "O":(cx+.88,cy+.62)}
    order = [1, 2, 3, 4, 5, "O", 1]
    for a,b in zip(order, order[1:]):
        line(ax, points[a], points[b], lw=3.5 if a in [1,2,3] else 1.6)
    for n,(x,y) in points.items():
        label(ax, x, y, "O" if n == "O" else f"C{n}", 12,
              OXYGEN if n == "O" else ANOMERIC if n == 1 else INK,
              bbox={"facecolor":"white", "edgecolor":"none", "pad":1.2}, zorder=4)
    # Up/down refers to the reference ring plane, not axial/equatorial.
    for n, up in [(1,beta), (2,False), (3,True), (4,False), (5,True)]:
        x,y=points[n]
        sign=1 if up else -1
        col=ANOMERIC if n == 1 else OXYGEN if n == 5 else INK
        length, offset = (.55,.78) if n == 3 else (.89,1.12)
        line(ax,(x,y+sign*.12),(x,y+sign*length),col)
        label(ax,x,y+sign*offset,r"C6: CH$_2$OH" if n == 5 else "OH",12,col,
              bbox={"facecolor":"white", "edgecolor":"none", "pad":.7})
    label(ax,cx,cy+2.28,"β-D-glucopyranose" if beta else "α-D-glucopyranose",18,weight="bold")
    label(ax,cx,cy-2.10,"C1 OH up: same face as C6" if beta else "C1 OH down: opposite face to C6",12,ANOMERIC)


def open_chain(ax, x, y):
    # Compact Fischer formula with explicit aldehyde and all retained stereocenters.
    for i in range(5):
        line(ax,(x,y-i*.57-.10),(x,y-(i+1)*.57+.10))
    for i in range(6):
        yy=y-i*.57
        label(ax,x-1.16,yy,f"C{i+1}",10,ANOMERIC if i == 0 else INK)
        if i == 0:
            label(ax,x,yy,"CHO",12,ANOMERIC,bbox={"facecolor":"white","edgecolor":"none","pad":1})
        elif i == 5:
            label(ax,x,yy,r"CH$_2$OH",12,bbox={"facecolor":"white","edgecolor":"none","pad":1})
        else:
            right=i != 2
            line(ax,(x-.54,yy),(x+.54,yy))
            label(ax,x-.77,yy,"H" if right else "HO",11)
            label(ax,x+.77,yy,"OH" if right else "H",11,OXYGEN if i == 4 else INK)


def main():
    plt.rcParams.update({"font.family":"DejaVu Sans"})
    fig,ax=plt.subplots(figsize=(11,8),dpi=140,facecolor="white")
    fig.subplots_adjust(left=.02,right=.98,bottom=.03,top=.98)
    ax.set(xlim=(0,16),ylim=(0,12))
    ax.axis("off")
    label(ax,8,11.57,"Anomers share the same six carbon atoms",21,weight="bold")
    ring(ax,4,8.2,False)
    ring(ax,12,8.2,True)
    open_chain(ax,8,4.83)
    for a,b in [((5.9,6.45),(7.05,4.78)),((10.1,6.45),(8.95,4.78))]:
        ax.annotate("",xy=b,xytext=a,arrowprops={"arrowstyle":"<->","lw":1.7,"color":OXYGEN,
                                                            "shrinkA":0,"shrinkB":0})
    label(ax,4.3,4.6,"Open C1–O5 bond;\nrestore C1 aldehyde",12,OXYGEN)
    label(ax,11.9,4.6,"C5 OH can close\nonto either C1 face",12,OXYGEN)
    label(ax,4.2,2.95,"Same C2/C3/C4 OH:\ndown / up / down",12)
    label(ax,12,2.95,"O5 becomes ring oxygen;\nC6 stays outside the ring",12,OXYGEN)
    label(ax,8,1.30,"Open-chain D-glucose (minor form in water)",12)
    label(ax,8,.76,"Haworth rings encode above/below; real rings are puckered. Ring C–H bonds omitted.",11)
    label(ax,8,.30,"Fischer crosses: horizontal toward you, vertical away. Arrows show connectivity, not rates.",11)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"),facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()