codexbiochemical-dialysis-membrane-exchange.py
biochemical-dialysis-membrane-exchange.py
"""Original dialysis schematic; run from _media (Python 3.14, matplotlib 3.10)."""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyArrowPatch, FancyBboxPatch, Rectangle
def ion(ax, x, y, sign):
color = "#137caf" if sign == "+" else "#c45b19"
ax.add_patch(Circle((x, y), 0.105, facecolor=color, edgecolor="white", lw=0.7))
ax.text(x, y, sign, ha="center", va="center", color="white", fontsize=11, weight="bold")
def protein(ax, x, y, radius=0.29):
ax.add_patch(Circle((x, y), radius, facecolor="#7852a5", edgecolor="#4f3275", lw=1.2))
ax.text(x, y, "P", ha="center", va="center", color="white", fontsize=15, weight="bold")
def arrow(ax, start, end, color="#334c62", style="->", lw=1.7):
ax.add_patch(FancyArrowPatch(start, end, arrowstyle=style, mutation_scale=13, lw=lw, color=color))
def main():
fig, ax = plt.subplots(figsize=(10.4, 5.7), dpi=120, facecolor="white")
ax.set(xlim=(0, 12), ylim=(0, 6.5))
ax.set_axis_off()
ax.text(6, 6.18, "Dialysis: change the solution around a retained protein", ha="center", fontsize=17, weight="bold")
ax.add_patch(Rectangle((0.45, 1.4), 4.8, 3.9, facecolor="#eaf5fa", edgecolor="#59758a", lw=1.4))
ax.text(2.85, 5.0, "External bath (dialysate)", ha="center", fontsize=12)
bag = FancyBboxPatch((1.35, 2.02), 2.0, 2.27, boxstyle="round,pad=0.05,rounding_size=0.28", facecolor="white", edgecolor="#52656e", lw=3)
ax.add_patch(bag)
ax.text(2.35, 3.95, "Sample", ha="center", fontsize=12)
protein(ax, 1.86, 3.08)
protein(ax, 2.7, 2.7)
for x, y, sign in [(2.55, 3.48, "+"), (2.95, 3.1, "−"), (1.7, 2.47, "+"), (2.2, 2.65, "−"), (2.97, 3.62, "+"), (2.17, 3.54, "−"), (4.3, 3.6, "+"), (4.45, 2.35, "−")]:
ion(ax, x, y, sign)
arrow(ax, (3.48, 3.15), (4.1, 3.15))
ax.text(4.22, 2.95, "Net salt\nmovement", fontsize=10, ha="center", va="top")
ax.text(2.85, 1.73, "Bag wall = semipermeable membrane", ha="center", fontsize=10)
ax.text(2.85, 0.83, "Fresh bath removes escaped salt\nand renews the concentration difference", ha="center", fontsize=11)
arrow(ax, (3.4, 4.15), (6.08, 5.35), color="#8a979f", lw=1.1)
arrow(ax, (3.4, 2.18), (6.08, 1.75), color="#8a979f", lw=1.1)
ax.text(6.9, 5.65, "Enlarged wall", ha="center", fontsize=12, weight="bold")
ax.text(5.85, 4.77, "Sample side", ha="center", fontsize=11)
ax.text(8.02, 5.08, "Bath side", ha="left", fontsize=11)
# Openings represent passage schematically, not measured pore geometry.
for low, high in [(1.7, 2.78), (3.2, 3.9), (4.32, 4.92)]:
ax.add_patch(Rectangle((6.5, low), 0.75, high - low, facecolor="#cbd4d9", edgecolor="#52656e", lw=1))
ion(ax, 5.9, 4.1, "+")
ion(ax, 7.82, 2.97, "−")
arrow(ax, (6.08, 4.1), (7.72, 4.1), color="#137caf")
arrow(ax, (7.62, 2.99), (5.96, 2.99), color="#c45b19")
protein(ax, 5.85, 2.12, radius=0.34)
arrow(ax, (6.23, 2.12), (6.43, 2.12), color="#7852a5")
ax.plot([6.45, 6.45], [1.94, 2.30], color="#7852a5", lw=2.5)
ax.text(8.12, 4.09, "Small salt ions cross\nin both directions", fontsize=12, va="center")
ax.text(8.12, 2.22, "Large protein\nis retained", fontsize=12, va="center", color="#624089")
ax.text(8.3, 0.8, "Schematic only: symbols and gaps are not to scale.\nMWCO is a retention rating, not a sharp pore diameter.", ha="center", fontsize=10, color="#46535d")
fig.subplots_adjust(left=0.015, right=0.99, bottom=0.015, top=0.99)
fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()