codex/independent-assortment-chromosome-orientation.py

independent-assortment-chromosome-orientation.py
"""Draw two relative homolog orientations, with no crossing over.

Run from _media with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
The two modes identify whole-pole reversal; each has probability one half.
"""
from pathlib import Path

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

COLORS = {"A": "#2468a2", "B": "#2468a2", "a": "#d26722", "b": "#d26722"}


def chromosome(ax, x, y, allele, replicated):
    length = 0.72 if allele.upper() == "A" else 0.48
    if replicated:
        for sign in (-1, 1):
            ax.plot([x - sign * 0.13, x + sign * 0.13],
                    [y - length / 2, y + length / 2], color=COLORS[allele],
                    lw=4, solid_capstyle="round")
    else:
        ax.plot([x, x], [y - length / 2, y + length / 2],
                color=COLORS[allele], lw=4, solid_capstyle="round")
    ax.text(x, y + length / 2 + 0.09, allele, ha="center", fontsize=12)


def main():
    fig, ax = plt.subplots(figsize=(11.5, 6.1), dpi=120)
    fig.patch.set_facecolor("white")
    ax.set_facecolor("white")
    ax.set(xlim=(0, 12), ylim=(0, 7.8))
    ax.axis("off")
    ax.text(6, 7.63, "Independent assortment across different meioses",
            ha="center", va="top", fontsize=18, weight="bold")
    ax.text(6, 7.05,
            "AaBb: A/a and B/b on different chromosome pairs; no crossing over",
            ha="center", fontsize=12)
    ax.text(2, 6.54, "Relative orientation in meiosis I",
            ha="center", fontsize=12, weight="bold")
    ax.text(8.25, 6.54, "Four products after meiosis II",
            ha="center", fontsize=12, weight="bold")
    for y, left_b, right_b, products in [
            (4.86, "B", "b", ("AB", "AB", "ab", "ab")),
            (2.16, "b", "B", ("Ab", "Ab", "aB", "aB"))]:
        ax.add_patch(Ellipse((2, y), 3.1, 1.95, edgecolor="#96a2ad",
                             facecolor="white", lw=1.5))
        ax.plot([2, 2], [y - 0.88, y + 0.88], ls="--", lw=1, color="#96a2ad")
        for xx, alleles in [(1.2, ("A", left_b)), (2.8, ("a", right_b))]:
            chromosome(ax, xx, y + 0.37, alleles[0], True)
            chromosome(ax, xx, y - 0.42, alleles[1], True)
        ax.text(0.15, y, "½", ha="center", fontsize=18, weight="bold")
        ax.annotate("", xy=(4.93, y), xytext=(3.7, y),
                    arrowprops=dict(arrowstyle="->", color="#53616c", lw=1.8))
        for x, product in zip((5.7, 7.35, 9.0, 10.65), products):
            ax.add_patch(Ellipse((x, y), 1.25, 1.45, edgecolor="#96a2ad",
                                 facecolor="white", lw=1.5))
            chromosome(ax, x - 0.21, y, product[0], False)
            chromosome(ax, x + 0.21, y, product[1], False)
            ax.text(x, y - 1.0, product, ha="center", fontsize=13, weight="bold")
    ax.text(6, 0.57, "Across divisions:  P(AB) = P(Ab) = P(aB) = P(ab) = ¼",
            ha="center", fontsize=13, weight="bold")
    ax.text(6, 0.06,
            "Fix A at the left pole: reversing both poles gives the same product set.\n"
            "Colors identify parental origin. Symmetric division model; not four functional eggs.",
            ha="center", va="top", fontsize=10, color="#35434f")
    fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.11)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white",
                transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()