"""Draw a schematic Western blot workflow; no experimental measurements. Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5. Run from _media; the matching PNG is written to the current directory. """ from pathlib import Path import matplotlib.pyplot as plt from matplotlib.patches import FancyArrowPatch, Rectangle, Circle BLUE = "#1764a0" INK = "#203040" ORANGE = "#c65a16" def box(ax, x, y, w, h, color="white", edge=INK, lw=1.4): ax.add_patch(Rectangle((x, y), w, h, facecolor=color, edgecolor=edge, lw=lw)) def arrow(ax, a, b, color=INK, lw=1.8): ax.add_patch(FancyArrowPatch(a, b, arrowstyle="-|>", mutation_scale=15, color=color, lw=lw)) def antibody(ax, x, y, color, scale=1): ax.plot([x, x, x - .14 * scale], [y + .39 * scale, y + .17 * scale, y], color=color, lw=3) ax.plot([x, x + .14 * scale], [y + .17 * scale, y], color=color, lw=3) def main(): fig, ax = plt.subplots(figsize=(12, 7.4), dpi=110, facecolor="white") ax.set(xlim=(0, 12), ylim=(0, 7.4)) ax.axis("off") ax.text(6, 7.08, "Western blot: separation → transfer → detection", ha="center", fontsize=19, weight="bold", color=INK) ax.text(6, 6.68, "SDS-PAGE workflow • schematic symbols, no measured bands", ha="center", fontsize=12, color=INK) for x, title in [(0.2, "1 Separate"), (4.35, "2 Transfer"), (8.45, "3 Detect")]: box(ax, x, 2.35, 3.35, 3.92, edge="#b8c5ce") ax.text(x + 1.675, 5.9, title, ha="center", fontsize=16, weight="bold", color=INK) box(ax, .72, 3.18, 2.18, 2.18, "#edf5fa") for x in [1.08, 1.67, 2.25]: box(ax, x, 5.16, .25, .14, "white", BLUE) for x, ys in [(1.08, [4.7, 4.1, 3.58]), (1.67, [4.7, 3.77]), (2.25, [4.4, 4.1, 3.58])]: for y in ys: ax.plot([x - .02, x + .3], [y, y], color=BLUE, lw=4) arrow(ax, (3.12, 5.1), (3.12, 3.5), BLUE) ax.text(1.8, 2.8, "Anionic protein complexes\nmigrate through a gel", ha="center", fontsize=11, color=INK) # Section through the stack: negatively charged SDS-protein complexes # travel from the gel towards the membrane on the anode side. box(ax, 4.62, 3.43, .13, 1.72, "#dbe4ea") box(ax, 5.08, 3.43, .47, 1.72, "#edf5fa", BLUE) box(ax, 6.3, 3.43, .12, 1.72, "#f2dbc9", ORANGE) box(ax, 7.3, 3.43, .13, 1.72, "#dbe4ea") for y in [3.78, 4.27, 4.76]: ax.add_patch(Circle((5.31, y), .07, color=BLUE)) arrow(ax, (5.61, y), (6.19, y), BLUE, 1.4) ax.add_patch(Circle((6.36, y), .06, color=BLUE)) ax.text(4.68, 5.42, "−", ha="center", fontsize=20, color=INK) ax.text(7.36, 5.42, "+", ha="center", fontsize=20, color=INK) ax.text(4.68, 3.1, "cathode", ha="center", fontsize=10, color=INK) ax.text(5.3, 5.24, "gel", ha="center", fontsize=11, color=BLUE) ax.text(6.36, 5.25, "membrane", ha="center", fontsize=10, color=ORANGE) ax.text(7.36, 3.1, "anode", ha="center", fontsize=10, color=INK) ax.text(6.025, 2.65, "Transfer across gel thickness\nClose contact preserves positions", ha="center", fontsize=10.5, color=INK) box(ax, 8.92, 3.65, 2.44, .15, "#f2dbc9", ORANGE) ax.add_patch(Circle((9.52, 3.85), .13, facecolor=BLUE, edgecolor=BLUE)) antibody(ax, 9.7, 3.96, BLUE, 1.3) antibody(ax, 9.7, 4.4, ORANGE, 1.15) ax.add_patch(Circle((9.85, 4.88), .12, facecolor="#f0b429", edgecolor=ORANGE)) ax.text(10.13, 5.12, "label", fontsize=11, color=ORANGE) ax.text(10.16, 4.67, "secondary", fontsize=11, color=ORANGE) ax.text(10.16, 4.15, "primary", fontsize=11, color=BLUE) ax.text(9.5, 3.27, "retained target", fontsize=11, color=BLUE) ax.text(10.12, 2.67, "Block → probe → wash\nLabel produces the readout", ha="center", fontsize=11, color=INK) arrow(ax, (3.62, 4.25), (4.27, 4.25)) arrow(ax, (7.79, 4.25), (8.38, 4.25)) for x, title, detail in [(.2, "Position", "Reference lanes estimate apparent mass"), (4.35, "Retention", "Check the receiving sheet and residual gel"), (8.45, "Recognition", "Positive, negative and no-primary controls")]: ax.text(x + 1.675, 1.85, title, ha="center", fontsize=13, weight="bold", color=INK) ax.text(x + 1.675, 1.35, detail.replace(" estimate ", "\nestimate ") .replace(" and residual", "\nand residual") .replace(" and no-primary", "\nand no-primary"), ha="center", fontsize=10.5, color=INK) ax.text(6, .53, "Band identity and amount require controls; darkness alone is insufficient.", ha="center", fontsize=12, color=INK) fig.subplots_adjust(left=.015, right=.985, bottom=.025, top=.985) fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False) plt.close(fig) if __name__ == "__main__": main()