codex/thin-layer-chromatography-origin-readout.py

thin-layer-chromatography-origin-readout.py
"""Original stylized TLC readout; Python 3.14, matplotlib 3.10.7, numpy 2.3.5.

Run from _media; coordinates are chosen examples, not measured pigment positions.
"""

from pathlib import Path

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


ORIGIN_CM = 1
FRONT_CM = 11
SPOTS_CM = {"A": 4, "B": 8}


def main():
    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
    ink, orange, teal = "#26384b", "#bd5519", "#087e8b"
    fig, ax = plt.subplots(figsize=(9.2, 6.8))
    fig.subplots_adjust(left=.10, right=.97, bottom=.16, top=.80)
    fig.suptitle("Thin-layer chromatography: start every distance at the origin",
                 fontsize=15, y=.96, color=ink)
    fig.text(.5, .895, "Stylized chosen positions • anonymous components • not experimental data",
             ha="center", fontsize=11, color="#536170")
    ax.set(xlim=(-1.4, 10.5), ylim=(-.8, 12.6),
           ylabel="Absolute height above plate bottom (cm)")
    ax.set_yticks([0, 1, 4, 8, 11, 12])
    ax.set_xticks([])
    for spine in ["top", "right", "bottom"]:
        ax.spines[spine].set_visible(False)
    ax.spines["left"].set_color("#a0acb4")
    ax.add_patch(Rectangle((0, 0), 4, 12, facecolor="#faf8f0",
                           edgecolor="#6a7783", linewidth=1.5))
    ax.axhline(0, xmin=.118, xmax=.50, color="#6a7783", linewidth=.8, linestyle=":")
    ax.plot([0, 4], [ORIGIN_CM] * 2, color=ink, linewidth=1.4)
    ax.plot([0, 4], [FRONT_CM] * 2, color="#477eaa", linewidth=1.7, linestyle="--")
    ax.text(4.25, 11.55, r"Marked solvent front: $y=11$ cm", color="#326b99")
    ax.text(4.25, .25, r"Pencil origin: $y_0=1$ cm", color=ink)
    ax.text(1.95, -.60, "Plate bottom: y = 0 cm", ha="center", fontsize=10, color=ink)
    for label, y, color in [("A", SPOTS_CM["A"], orange), ("B", SPOTS_CM["B"], teal)]:
        ax.add_patch(Ellipse((2, y), .90, .48, facecolor=color, alpha=.82,
                             edgecolor=color, linewidth=1.2))
        ax.plot(2, y, "+", color="white", markersize=8)
        ax.text(.28, y + .42, f"Spot {label}: y = {y} cm", color=color, fontsize=10)
        ax.plot([2.6, 4], [y, y], color=color, alpha=.5, linestyle=":")
    measurements = [(4.8, SPOTS_CM["A"], orange, "3 cm"),
                    (6.4, SPOTS_CM["B"], teal, "7 cm"),
                    (8.5, FRONT_CM, "#326b99", "10 cm")]
    ax.plot([4, 9.05], [ORIGIN_CM] * 2, color=ink, linewidth=.8, linestyle=":")
    for x, top, color, text in measurements:
        ax.annotate("", xy=(x, top), xytext=(x, ORIGIN_CM),
                    arrowprops={"arrowstyle": "<->", "color": color, "lw": 1.7})
        ax.text(x + .18, (top + ORIGIN_CM) / 2, text, color=color,
                va="center", fontsize=12)
    fig.text(.5, .09, r"$R_{f,A}=(4-1)/(11-1)=0.3$          $R_{f,B}=(8-1)/(11-1)=0.7$",
             ha="center", fontsize=14, color=ink)
    fig.text(.5, .035, "Origin-to-spot centre ÷ origin-to-front; never measure from the plate bottom.",
             ha="center", fontsize=10, color="#536170")
    fig.savefig(Path(__file__).stem + ".png", dpi=110, facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()