codexpcr-opposed-primers-strand-cycles.py
pcr-opposed-primers-strand-cycles.py
"""Original PCR strand schematic. Tested with Python 3.14 / matplotlib 3.10.7.
Run from _media; saves a matching opaque white PNG in the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch
OLD = '#55565b'
NEW = '#22835c'
FWD = '#1769aa'
REV = '#b33c76'
def strand(ax, left, right, y, direction, color, labels=True, lw=2.7):
start, end = (left, right) if direction == 1 else (right, left)
ax.add_patch(FancyArrowPatch((start, y), (end, y), arrowstyle='-|>',
mutation_scale=12, lw=lw, color=color))
if labels:
ax.text(left - .13, y, "5′" if direction == 1 else "3′",
ha='right', va='center', fontsize=11, color=color)
ax.text(right + .13, y, "3′" if direction == 1 else "5′",
ha='left', va='center', fontsize=11, color=color)
def setup(ax, title):
ax.set_xlim(-.9, 11)
ax.set_ylim(-.1, 3.6)
ax.axis('off')
ax.set_title(title, fontsize=13, loc='left', fontweight='bold', pad=8)
for x in (2, 8):
ax.plot([x, x], [.45, 3], '--', color='#d0d3d5', lw=1, zorder=0)
def main():
plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 11})
fig, axes = plt.subplots(2, 3, figsize=(11, 7.9), dpi=120, facecolor='white')
fig.subplots_adjust(left=.065, right=.96, top=.87, bottom=.12, wspace=.36, hspace=.62)
fig.suptitle('Opposed primers select a bounded DNA interval', fontsize=17, y=.97)
fig.text(.5, .917, 'Arrows mark 5′ → 3′ strand direction; dashed lines mark the eventual outer boundaries.', ha='center', fontsize=11)
for ax, title in zip(axes[0], ['1 Separate strands', '2 Bind opposed primers', '3 Extend paired 3′ ends']):
setup(ax, title)
strand(ax, 0, 10, 2.6, 1, OLD)
strand(ax, 0, 10, .85, -1, OLD)
axes[0, 0].text(5, 1.7, 'Heat separates pairing\nBackbones remain intact', ha='center', va='center', fontsize=10)
strand(axes[0, 1], 7, 8, 2.22, -1, REV)
strand(axes[0, 1], 2, 3, 1.23, 1, FWD)
# Once extended, a primer's former 3′ end is an internal position.
strand(axes[0, 2], 7, 8, 2.22, -1, REV, labels=False)
strand(axes[0, 2], 2, 3, 1.23, 1, FWD, labels=False)
for x, y, label, color in [(8.15, 2.22, "5′", REV), (-.45, 2.22, "3′", NEW),
(1.55, 1.23, "5′", FWD), (10.45, 1.23, "3′", NEW)]:
axes[0, 2].text(x, y, label, color=color, ha='center', va='center')
axes[0, 1].text(5, 1.72, 'Extendible 3′ ends\nface into the interval', ha='center', va='center', fontsize=10)
strand(axes[0, 2], 0, 7, 2.22, -1, NEW, labels=False)
strand(axes[0, 2], 3, 10, 1.23, 1, NEW, labels=False)
axes[0, 2].text(5, .23, 'First copies can extend beyond\nthe opposite primer site.', ha='center', fontsize=10)
titles = ['Cycle 1: one bounded end', 'Cycle 2: first bounded strand', 'Cycle 3: bounded duplex']
for ax, title in zip(axes[1], titles):
setup(ax, title)
strand(axes[1, 0], 0, 10, 2.6, 1, OLD)
strand(axes[1, 0], 0, 7, 2.12, -1, NEW, labels=False)
strand(axes[1, 0], 7, 8, 2.12, -1, REV, labels=False)
axes[1, 0].text(-.45, 2.12, '3′', color=NEW, ha='center', va='center')
axes[1, 0].text(8.45, 2.12, '5′', color=REV, ha='center', va='center')
axes[1, 0].text(5, .7, 'Selected reverse-primer copy\nretains a long left flank.', ha='center', fontsize=10)
strand(axes[1, 1], 0, 8, 2.12, -1, OLD)
strand(axes[1, 1], 2, 3, 2.6, 1, FWD, labels=False)
axes[1, 1].text(1.55, 2.6, '5′', color=FWD, ha='center', va='center')
axes[1, 1].text(8.45, 2.6, '3′', color=NEW, ha='center', va='center')
strand(axes[1, 1], 3, 8, 2.6, 1, NEW, labels=False)
axes[1, 1].text(5, .7, 'Forward-primer copy stops\nat this template’s right end.', ha='center', fontsize=10)
strand(axes[1, 2], 2, 8, 2.6, 1, OLD)
strand(axes[1, 2], 2, 7, 2.12, -1, NEW, labels=False)
strand(axes[1, 2], 7, 8, 2.12, -1, REV, labels=False)
axes[1, 2].text(1.55, 2.12, '3′', color=NEW, ha='center', va='center')
axes[1, 2].text(8.45, 2.12, '5′', color=REV, ha='center', va='center')
axes[1, 2].text(5, .7, 'Both strands span only\nthe primer-defined interval.', ha='center', fontsize=10)
fig.text(.5, .065, 'Gray: template in the displayed step Green: new extension Blue: forward primer Pink: reverse primer', ha='center', fontsize=10)
fig.text(.5, .025, 'Bottom row follows one lineage only; it omits other products. Original schematic, not an observed experiment.', ha='center', fontsize=10)
fig.savefig(Path(__file__).stem + '.png', facecolor='white', transparent=False)
plt.close(fig)
if __name__ == '__main__':
main()