codex/plasmid-circular-vector-feature-map.py

plasmid-circular-vector-feature-map.py
"""Draw an original hypothetical plasmid map. Python 3.14; matplotlib 3.10.7, numpy 2.3.5.

Run from _media; writes the matching PNG to the current working directory.
Coordinates are boundaries, not inclusive base-pair indices. No real sequence is asserted.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Wedge
import numpy as np

LENGTH = 6000
FEATURES = [(200, 700, '#2475b0', 'Origin of replication', '200–700: 500 bp', 'Supports copying in a compatible host'),
            (3200, 4000, '#27845b', 'Selectable marker', '3200–4000: 800 bp', 'Permits recovery under chosen conditions'),
            (1000, 2500, '#d77722', 'Insert', '1000–2500: 1500 bp', 'The chosen DNA fragment under study')]
CUTS = [(1000, 'A'), (2500, 'B'), (4500, 'B')]


def position(coordinate, radius=1.0):
    theta = np.deg2rad(90 - 360 * coordinate / LENGTH)
    return radius * np.array([np.cos(theta), np.sin(theta)])


def main():
    plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 11})
    fig = plt.figure(figsize=(10, 6.4), dpi=130, facecolor='white')
    fig.text(.04, .95, 'Reading a circular plasmid feature map', fontsize=19, weight='bold')
    fig.text(.04, .905, 'Hypothetical engineered DNA • 6000 base pairs • clockwise boundary coordinates', color='#475569')
    ax = fig.add_axes([.015, .15, .59, .71], aspect='equal')
    ax.set(xlim=(-1.55, 1.55), ylim=(-1.5, 1.5))
    ax.axis('off')
    ax.add_patch(Circle((0, 0), 1, fill=False, lw=15, edgecolor='#e2e8f0'))
    for start, stop, color, *_ in FEATURES:
        ax.add_patch(Wedge((0, 0), 1.075, 90-360*stop/LENGTH, 90-360*start/LENGTH,
                           width=.15, facecolor=color, edgecolor='white', linewidth=1))
    for coordinate in (0, 2000, 3000, 4000, 5000):
        p, q = position(coordinate, 1.10), position(coordinate, 1.17)
        ax.plot([p[0], q[0]], [p[1], q[1]], color='#94a3b8', lw=1)
        label = '0 = 6000' if coordinate == 0 else f'{coordinate}'
        xy = position(coordinate, 1.32)
        ax.text(*xy, label, ha='center', va='center', fontsize=10, color='#475569')
    for coordinate, enzyme in CUTS:
        p, q = position(coordinate, .87), position(coordinate, 1.16)
        ax.plot([p[0], q[0]], [p[1], q[1]], color='#172033', lw=2)
        xy = position(coordinate, 1.37)
        ax.text(*xy, f'{enzyme}\n{coordinate}', ha='center', va='center', weight='bold', fontsize=11)
    ax.text(0, .12, '6000 bp', fontsize=23, weight='bold', ha='center', color='#172033')
    ax.text(0, -.13, 'One continuous circle', ha='center', color='#475569')
    ax.text(0, -.32, 'Zero is a label, not an end', ha='center', fontsize=10, color='#475569')
    for y, (start, stop, color, title, interval, job) in zip((.77, .58, .39), FEATURES):
        fig.text(.64, y, title, color=color, fontsize=13, weight='bold')
        fig.text(.64, y-.044, interval, fontsize=11)
        # Deliberate line breaks keep the explanatory column readable.
        wrapped = {'Origin of replication': 'Supports copying in\na compatible host',
                   'Selectable marker': 'Permits recovery under\nchosen conditions',
                   'Insert': 'The chosen DNA fragment\nunder study'}[title]
        fig.text(.64, y-.115, wrapped, color='#475569', fontsize=10, linespacing=1.5)
    fig.text(.64, .19, 'Cut boundaries', fontsize=11, weight='bold')
    fig.text(.64, .13, 'A: 1000     B: 2500, 4500\nAbstract enzymes; no sequence specified', fontsize=9.8, linespacing=1.5)
    fig.text(.04, .045, 'Coloured regions: 500 + 800 + 1500 = 2800 bp; remaining DNA: 3200 bp.\nPosition diagram, not a physical conformation or a real commercial vector.', fontsize=10, color='#475569', linespacing=1.5)
    fig.savefig(Path.cwd() / (Path(__file__).stem + '.png'), facecolor='white', transparent=False)
    plt.close(fig)


if __name__ == '__main__':
    main()