codex/cytokinesis-ring-and-cell-plate.py

cytokinesis-ring-and-cell-plate.py
"""Original sectional cartoons of cytokinesis. Python 3.14; matplotlib 3.10.7, numpy 2.3.5."""

from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Ellipse, Polygon, Rectangle
import numpy as np


BOUNDARY = "#265b79"
CYTOPLASM = "#eef6fa"
NUCLEUS = "#7154a5"
RING = "#c65b13"
PLATE = "#287b43"


def nucleus(ax, x, y=0):
    ax.add_patch(Circle((x, y), 0.24, facecolor="#e8ddf6", edgecolor=NUCLEUS, lw=1.7))
    ax.text(x, y, r"$2n$" + "\n" + r"$2C$", ha="center", va="center", fontsize=11, color=NUCLEUS)


def frame(ax):
    ax.set(xlim=(-1.8, 1.8), ylim=(-1.05, 1.22), aspect="equal")
    ax.axis("off")


def animal(ax, stage):
    if stage == 0:
        ax.add_patch(Ellipse((0, 0), 2.65, 1.5, fc=CYTOPLASM, ec=BOUNDARY, lw=2))
        waist = 0.70
    elif stage == 1:
        angle = np.linspace(0, 2 * np.pi, 301)
        x = 1.35 * np.cos(angle)
        y = 0.75 * np.sin(angle) * (1 - 0.76 * np.exp(-(x / 0.48) ** 2))
        ax.add_patch(Polygon(np.column_stack((x, y)), closed=True, fc=CYTOPLASM, ec=BOUNDARY, lw=2))
        waist = 0.18
    else:
        for x in (-0.78, 0.78):
            ax.add_patch(Ellipse((x, 0), 1.22, 1.35, fc=CYTOPLASM, ec=BOUNDARY, lw=2))
        waist = None
    for x in (-0.78, 0.78):
        nucleus(ax, x)
    if waist is not None:
        for y in (-waist, waist):
            ax.plot((-0.13, 0.13), (y, y), color=RING, lw=6, solid_capstyle="round")
    if stage == 1:
        ax.annotate("Furrow", (0.03, waist), (0.48, 0.92), fontsize=11, color=RING,
                    arrowprops={"arrowstyle": "->", "color": RING}, ha="center")


def plant(ax, stage):
    ax.add_patch(Rectangle((-1.35, -0.75), 2.7, 1.5, fc=CYTOPLASM, ec=PLATE, lw=5))
    ax.add_patch(Rectangle((-1.29, -0.69), 2.58, 1.38, fill=False, ec=BOUNDARY, lw=1.3))
    for x in (-0.78, 0.78):
        nucleus(ax, x)
    if stage == 0:
        for y in (-0.22, 0, 0.22):
            ax.add_patch(Circle((0, y), 0.065, fc="#b3dabc", ec=PLATE, lw=1))
    else:
        length = 0.39 if stage == 1 else 0.73
        ax.plot((0, 0), (-length, length), color=PLATE, lw=5)
        if stage == 1:
            for sign in (-1, 1):
                ax.annotate("", (0, sign * 0.65), (0, sign * 0.44),
                            arrowprops={"arrowstyle": "->", "color": PLATE, "lw": 1.8})
        else:
            for x in (-0.045, 0.045):
                ax.plot((x, x), (-0.69, 0.69), color=BOUNDARY, lw=1)


def main():
    fig, axes = plt.subplots(2, 3, figsize=(10.8, 6.5), dpi=100, facecolor="white")
    fig.subplots_adjust(left=0.055, right=0.97, top=0.81, bottom=0.15, hspace=0.50, wspace=0.22)
    fig.suptitle("Cytokinesis changes the boundary around two existing nuclei", y=0.97, fontsize=17)
    fig.text(0.5, 0.895, "Animal: inward furrow       •       Plant: outward cell plate", ha="center", fontsize=13)
    for stage in range(3):
        for row in range(2):
            frame(axes[row, stage])
        animal(axes[0, stage], stage)
        plant(axes[1, stage], stage)
        axes[0, stage].set_title(["Ring at the surface", "Furrow deepens", "Separate daughters"][stage], fontsize=12, pad=0)
        axes[1, stage].set_title(["Vesicles at the center", "Plate grows outward", "New wall and boundaries"][stage], fontsize=12, pad=0)
        fig.text([0.184, 0.511, 0.837][stage], 0.105,
                 ["1 cell, 2 nuclei", "1 cell, partition forming", "2 cells, 1 nucleus each"][stage],
                 ha="center", fontsize=12)
    fig.text(0.5, 0.035, "Nuclear DNA total: 4C throughout. Orange: ring intersections. Green: plant partition.\nSectional cartoons; sizes and stage spacing are illustrative.", ha="center", fontsize=11)
    fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()