codex/sanger-dntp-ddntp-chain-extension.py

sanger-dntp-ddntp-chain-extension.py
"""Original feature schematic. Run from _media; Python 3.14, matplotlib 3.10.7."""

from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, FancyBboxPatch


def box(ax, xy, width, height, text, color="#eef3f7", edge="#526577", size=12):
    ax.add_patch(FancyBboxPatch(xy, width, height, boxstyle="round,pad=0.04",
                              facecolor=color, edgecolor=edge, linewidth=1.5))
    ax.text(xy[0] + width / 2, xy[1] + height / 2, text,
            ha="center", va="center", fontsize=size)


def arrow(ax, start, end, color="#526577"):
    ax.add_patch(FancyArrowPatch(start, end, arrowstyle="-|>",
                                mutation_scale=15, color=color, linewidth=1.8))


def main():
    plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 12})
    fig, axes = plt.subplots(1, 2, figsize=(10.8, 7.2), dpi=100)
    fig.patch.set_facecolor("white")
    fig.suptitle("Chain termination: the NEXT addition needs a 3′-OH", fontsize=18, y=.965)
    green, red = "#167052", "#b23a3a"
    for ax, is_dd in zip(axes, [False, True]):
        color = red if is_dd else green
        name = "ddNTP: 2′,3′-dideoxy" if is_dd else "dNTP: already 2′-deoxy"
        end = "H" if is_dd else "OH"
        ax.set(xlim=(0, 10), ylim=(0, 12))
        ax.axis("off")
        ax.text(5, 11.4, name, ha="center", fontsize=15, weight="bold", color=color)
        ax.text(5, 10.78, "Free substrate", ha="center", fontsize=11)
        box(ax, (3.2, 8.25), 3.0, 1.85, "Sugar\n2′: H\n3′: " + end,
            color="#f6f8fa", edge=color, size=14)
        box(ax, (7.2, 8.85), 2.05, .8, "Base", size=13)
        ax.plot([6.25, 7.15], [9.25, 9.25], color="#526577", lw=1.5)
        ax.text(6.67, 9.6, "1′", ha="center", fontsize=11)
        for x in [.35, 1.12, 1.89]:
            box(ax, (x, 8.87), .56, .76, "P", color="#fff1cd", size=12)
        ax.plot([.95, 1.08], [9.25, 9.25], color="#526577")
        ax.plot([1.72, 1.85], [9.25, 9.25], color="#526577")
        ax.plot([2.49, 3.15], [9.25, 9.25], color="#526577", lw=1.5)
        ax.text(2.82, 9.6, "5′", ha="center", fontsize=11)
        ax.text(1.4, 8.5, "Triphosphate", ha="center", fontsize=10)
        ax.text(5, 7.7, "Both substrates can be incorporated", ha="center", fontsize=12)
        arrow(ax, (5, 7.35), (5, 6.65))
        ax.text(5, 6.3, "Previous strand end supplies 3′-OH", ha="center", fontsize=11)
        ax.text(5, 5.8, "It joins the incoming 5′ phosphate", ha="center", fontsize=11)
        box(ax, (.35, 4.45), 3.0, .7, "5′—old strand", color="#e5edf5", size=11)
        ax.plot([3.4, 4.05], [4.8, 4.8], color="#526577", lw=2)
        ax.text(4.68, 4.8, "O—P—O", ha="center", va="center", fontsize=11)
        ax.plot([5.3, 5.65], [4.8, 4.8], color="#526577", lw=2)
        box(ax, (5.7, 4.4), 1.9, .8, "Added\nunit", edge=color, size=11)
        ax.plot([7.65, 7.95], [4.8, 4.8], color=color, lw=2)
        ax.text(8.65, 4.8, "3′-" + end, ha="center", va="center",
                fontsize=14, color=color, weight="bold")
        ax.text(5, 3.85, "Incorporated unit: one backbone phosphate", ha="center", fontsize=10)
        if is_dd:
            ax.plot([4.8, 5.2], [3.35, 2.95], color=red, lw=3)
            ax.plot([4.8, 5.2], [2.95, 3.35], color=red, lw=3)
            ax.text(5, 2.35, "No terminal 3′-OH", ha="center", color=red, weight="bold")
            ax.text(5, 1.75, "No next phosphodiester bond", ha="center", color=red, fontsize=12)
            ax.text(5, 1.2, "Extension stops here", ha="center", color=red, fontsize=12)
        else:
            arrow(ax, (5, 3.35), (5, 2.8), green)
            ax.text(5, 2.35, "Terminal 3′-OH remains", ha="center", color=green, weight="bold")
            ax.text(5, 1.75, "Next nucleotide can join", ha="center", color=green, fontsize=12)
            ax.text(5, 1.2, "Extension can continue", ha="center", color=green, fontsize=12)
    fig.text(.5, .065, "Feature schematic: most atoms and stereochemistry omitted; P denotes a phosphate group.",
             ha="center", fontsize=10, color="#526577")
    fig.text(.5, .035, "Incorporation releases the substrate’s two outer phosphates; it does not leave a triphosphate in the chain.",
             ha="center", fontsize=10, color="#526577")
    fig.subplots_adjust(top=.9, bottom=.1, left=.035, right=.985, wspace=.08)
    fig.savefig(Path(__file__).stem + ".png", facecolor="white", dpi=100)
    plt.close(fig)


if __name__ == "__main__":
    main()