codex/sanger-four-lane-ladder-reading.py

sanger-four-lane-ladder-reading.py
"""Original toy endpoints, not gel measurements. 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, Rectangle


TEMPLATE = "TGCAATCG"  # displayed 3′ to 5′, after the primer-binding region
EXTENSION = "ACGTTAGC"  # synthesized 5′ to 3′
LANES = "ACGT"


def main():
    assert TEMPLATE.translate(str.maketrans("ACGT", "TGCA")) == EXTENSION
    endpoints = {base: [i for i, b in enumerate(EXTENSION, 1) if b == base]
                 for base in LANES}
    assert endpoints == {"A": [1, 6], "C": [2, 8], "G": [3, 7], "T": [4, 5]}
    plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 12})
    fig = plt.figure(figsize=(10.8, 8), dpi=100, facecolor="white")
    ax = fig.add_axes((.05, .24, .9, .59))
    ax.set(xlim=(-1.5, 9.8), ylim=(-.3, 10))
    ax.axis("off")
    fig.suptitle("Four reactions → eight endpoints → one ordered extension", fontsize=17, y=.97)
    fig.text(.5, .91, "Displayed template region: 3′-TGCAATCG-5′   •   primer binds upstream (omitted)",
             ha="center", fontsize=12)
    ax.add_patch(Rectangle((-.45, .55), 4.0, 8.65, facecolor="#eef2f4",
                           edgecolor="#697d8b", lw=1.5))
    for lane, base in enumerate(LANES):
        ax.text(lane, 9.65, base, ha="center", weight="bold", fontsize=16)
        ax.add_patch(Rectangle((lane-.23, 8.78), .46, .18,
                               facecolor="white", edgecolor="#697d8b"))
        for n in endpoints[base]:
            # Increasing extension length is drawn higher: shortest travels farthest.
            ax.plot([lane-.27, lane+.27], [n, n], color="#243c50", lw=4.5,
                    solid_capstyle="butt")
    ax.text(4.1, 9.65, "Added\nposition", ha="center", va="center", fontsize=11)
    ax.text(5.2, 9.65, "Total\nlength", ha="center", va="center", fontsize=11)
    ax.text(7.7, 9.65, "Added prefix: 5′ → 3′", ha="center", fontsize=12)
    for n in range(1, len(EXTENSION)+1):
        ax.text(4.1, n, str(n), ha="center", va="center", fontsize=12)
        ax.text(5.2, n, f"p + {n}", ha="center", va="center", fontsize=12)
        ax.text(6.35, n, EXTENSION[:n-1], va="center", fontfamily="DejaVu Sans Mono",
                fontsize=14, color="#526577")
        # Separate terminal base by the same monospaced character spacing.
        ax.annotate(EXTENSION[n-1], (6.35, n), xytext=((n-1)*8.43, 0),
                    textcoords="offset points", va="center", fontfamily="DejaVu Sans Mono",
                    fontsize=14, weight="bold", color="#ad5620")
    ax.add_patch(FancyArrowPatch((-1.1, 1), (-1.1, 8), arrowstyle="-|>",
                                color="#167052", mutation_scale=18, lw=2))
    ax.text(-1.38, 4.5, "READ bottom → top", rotation=90, ha="center",
            va="center", color="#167052", fontsize=11, weight="bold")
    ax.text(1.5, .05, "Shortest shown: p + 1 nt", ha="center", fontsize=11)
    ax.add_patch(FancyArrowPatch((3.76, 8.45), (3.76, 1.1), arrowstyle="-|>",
                                color="#697d8b", mutation_scale=13, lw=1.5))
    ax.text(3.75, .38, "Migration", ha="center", fontsize=9, color="#526577")
    fig.text(.5, .205, "p = common known primer length; position 1 is ONE ADDED base, not a 1-nt whole molecule.",
             ha="center", fontsize=11, weight="bold")
    fig.text(.5, .158, "New extension: 5′-ACGTTAGC-3′     ⇄     aligned template: 3′-TGCAATCG-5′",
             ha="center", fontsize=13)
    fig.text(.5, .115, "Template written 5′ → 3′: 5′-GCTAACGT-3′  (reverse complement of the readout)",
             ha="center", fontsize=12, color="#167052")
    fig.text(.5, .067, "Denaturing polyacrylamide gel schematic: one-nucleotide differences, not an agarose size ladder.",
             ha="center", fontsize=10, color="#526577")
    fig.text(.5, .035, "Original invented bands. Spacing and darkness are illustrative, not measured migration or quantity.",
             ha="center", fontsize=10, color="#526577")
    fig.savefig(Path(__file__).stem + ".png", facecolor="white", dpi=100)
    plt.close(fig)


if __name__ == "__main__":
    main()