codex/atrial-flow-component-timelines.py

atrial-flow-component-timelines.py
"""Illustrative atrial parcel histories; not measured clinical trajectories.
Python 3.14, Matplotlib 3.10.7. Run in _media; PNG is written in cwd.
Each cycle lasts 1 s, with 0.4 s ventricular systole and 0.6 s diastole.
"""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from matplotlib.patches import Patch

fig, ax = plt.subplots(figsize=(9.2, 5.4), dpi=100, facecolor='white')
for left in (-1, 0):
    ax.axvspan(left, left+.4, color='#f7e3d0', zorder=0)
    ax.axvspan(left+.4, left+1, color='#e2eef7', zorder=0)
labels = ['PV backflow', 'Residual volume', 'Retained inflow',
          'Delayed ejection', 'Reservoir flow', 'Conduit flow']
# Each row is an independent constructed history. Repeated crossings are absent.
rows = [(.58, .88, 'pv'), (-1, 1, 'stay'), (-.42, 1, 'stay'),
        (-.20, .62, 'mv'), (.16, .78, 'mv'), (.52, .91, 'mv')]
for i, (start, end, exit_kind) in enumerate(rows):
    y = 5-i
    ax.plot([start, end], [y, y], color='#254e66', lw=3, solid_capstyle='butt')
    if start > -1:
        ax.plot(start, y, 'o', color='#16805c', ms=7, zorder=4)
    else:
        ax.plot(start, y, marker='<', color='#254e66', ms=7, zorder=4)
    marker, color = ('s', '#254e66') if exit_kind == 'stay' else ('>', '#9b3b37' if exit_kind == 'pv' else '#643fa0')
    ax.plot(end, y, marker=marker, color=color, ms=8, zorder=4)
    if exit_kind == 'pv':
        ax.text(-.96, y+.16, 'Leaves toward lungs', fontsize=9, color=color)
    elif exit_kind == 'stay':
        ax.text(.98, y+.16, 'Still inside', fontsize=9, ha='right', color=color)
    else:
        ax.text(end+.03, y+.16, 'MV exit', fontsize=9, ha='right', color=color)
ax.axvline(0, color='#777777', lw=1, ls='--')
ax.set(xlim=(-1.05, 1.05), ylim=(-.55, 5.65), yticks=range(6),
       yticklabels=labels[::-1], xticks=[-1, -.6, 0, .4, 1],
       xlabel='Time (s): constructed two-cycle observation window')
ax.tick_params(axis='y', length=0, labelsize=10)
ax.spines[['top', 'right', 'left']].set_visible(False)
ax.grid(axis='x', color='white', lw=1)
fig.suptitle('Atrial flow components: entry and exit timing matters', fontsize=14, y=.96)
handles = [Patch(color='#f7e3d0', label='Ventricular systole'),
           Patch(color='#e2eef7', label='Ventricular diastole'),
           Line2D([], [], color='#16805c', marker='o', ls='', label='PV entry')]
fig.legend(handles=handles, loc='upper center', bbox_to_anchor=(.59, .91), ncol=3, frameon=False, fontsize=9)
fig.text(.59, .025, 'PV = pulmonary vein; MV = mitral valve. Lines indicate occupancy, not speed.',
         ha='center', fontsize=9)
fig.subplots_adjust(left=.22, right=.96, bottom=.14, top=.79)
fig.savefig(Path('atrial-flow-component-timelines.png'), dpi=100, facecolor='white')
plt.close(fig)