codex/igg-papain-fragment-products.py

igg-papain-fragment-products.py
"""Draw original papain cleavage and constructed product counts.

Tested with Python 3.14 and matplotlib 3.10.7. Run from _media; PNG goes to CWD.
"""
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch

HEAVY = '#2466a6'
LIGHT = '#c96520'
BOND = '#a57400'
CUT = '#b5233d'
INK = '#253446'


def main():
    plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 12})
    fig, ax = plt.subplots(figsize=(10, 6.3), dpi=110, facecolor='white')
    fig.subplots_adjust(left=.035, right=.98, bottom=.08, top=.89)
    ax.set(xlim=(0, 12), ylim=(0, 7))
    ax.axis('off')
    fig.suptitle('Papain cuts the backbones; crosslinks stay with their fragments',
                 fontsize=16, color=INK, y=.97)

    def line(xs, ys, color, width=5):
        ax.plot(xs, ys, color=color, linewidth=width, solid_capstyle='round')

    def arm(x, y):
        line([x, x], [y, y+1.6], HEAVY)
        line([x-.55, x-.55], [y+.18, y+1.6], LIGHT)
        line([x-.55, x], [y+.3, y+.3], BOND, 2.5)
        ax.text(x-.25, y+1.9, '1 binding site', ha='center', fontsize=10, color=INK)

    def stem(x, y):
        for dx in [-.28, .28]:
            line([x+dx, x+dx], [y, y+1.25], HEAVY)
        line([x-.28, x+.28], [y+1.1, y+1.1], BOND, 2.5)

    # Backbone layout is deliberately unrolled, to expose which bonds are cut.
    for x in [1.65, 2.55]:
        line([x, x], [3.6, 6.0], HEAVY)
        lightx = x-.43 if x < 2 else x+.43
        line([lightx, lightx], [4.85, 6.0], LIGHT)
        line([lightx, x], [4.97, 4.97], BOND, 2.5)
        line([x-.23, x+.23], [4.62-.13, 4.62+.13], CUT, 3)
    line([1.65, 2.55], [4.24, 4.24], BOND, 2.5)
    ax.text(2.1, 6.34, 'Intact IgG', ha='center', color=INK, fontsize=14)
    ax.text(2.1, 3.02, '2 continuous H backbones\n+ 2 complete L chains',
            ha='center', color=INK, fontsize=10)
    ax.annotate('two backbone cuts', xy=(2.55, 4.62), xytext=(3.3, 5.42),
                ha='left', fontsize=10, color=CUT,
                arrowprops={'arrowstyle': '->', 'color': CUT})
    ax.annotate('', xy=(6.1, 4.37), xytext=(4.55, 4.37),
                arrowprops={'arrowstyle': '->', 'lw': 2, 'color': INK})
    ax.text(5.2, 4.64, 'papain', ha='center', color=INK, fontsize=11)
    arm(7.05, 4.0)
    arm(9.0, 4.0)
    stem(10.85, 4.0)
    ax.text(6.8, 3.48, 'Fab', ha='center', color=INK, fontsize=14)
    ax.text(8.75, 3.48, 'Fab', ha='center', color=INK, fontsize=14)
    ax.text(10.85, 3.48, 'Fc', ha='center', color=INK, fontsize=14)
    ax.text(10.85, 5.65, '0 binding sites', ha='center', fontsize=10, color=INK)
    ax.text(9.0, 2.98, '6 backbone segments → 3 connected products',
            ha='center', color=INK, fontsize=11)
    ax.plot([.4, 11.6], [2.65, 2.65], color='#c8cdd3', linewidth=1)
    ax.text(6, 2.35, 'Constructed mixture: 5 pmol IgG, 60% completely digested',
            ha='center', color=INK, fontsize=13)
    boxes = [(1.85, 'Intact IgG', '2 pmol', '4 pmol sites', HEAVY),
             (5.85, 'Fab', '6 pmol', '6 pmol sites', LIGHT),
             (9.85, 'Fc', '3 pmol', '0 pmol sites', HEAVY)]
    for x, title, amount, sites, color in boxes:
        ax.add_patch(FancyBboxPatch((x-1.5, .85), 3, 1.12,
                     boxstyle='round,pad=.08,rounding_size=.12',
                     facecolor='white', edgecolor=color, linewidth=1.8))
        ax.text(x, 1.65, title+'  ·  '+amount, ha='center', va='center', color=color, fontsize=12)
        ax.text(x, 1.18, sites, ha='center', va='center', color=INK, fontsize=12)
    ax.text(6, .35, '11 pmol connected products   •   10 pmol binding sites retained in the ideal model',
            ha='center', color=INK, fontsize=11)
    fig.text(.5, .025, 'Schematic, not atomic structure. Gold: retained disulfides. Red: backbone cuts.\n'
             'Complete two-cut events only; small peptides and secondary cleavage excluded.',
             ha='center', fontsize=10, color=INK)
    fig.savefig(Path.cwd() / (Path(__file__).stem + '.png'), facecolor='white', transparent=False)
    plt.close(fig)


if __name__ == '__main__':
    main()