"""Draw an original factorial trace; write the matching PNG in the CWD. Tested with Python 3.14, matplotlib 3.10.7 (no optional dependencies). """ from pathlib import Path import matplotlib.pyplot as plt from matplotlib.patches import FancyBboxPatch BLUE = '#235f91' GREEN = '#287052' INK = '#23313d' def box(ax, x, y, width, text, color=BLUE, face='#eef5fb'): ax.add_patch(FancyBboxPatch((x - width / 2, y - 0.27), width, 0.54, boxstyle='round,pad=0.04', edgecolor=color, facecolor=face, linewidth=1.5)) ax.text(x, y, text, ha='center', va='center', fontsize=12, color=INK) def main(): fig, ax = plt.subplots(figsize=(11, 6.6), dpi=100, facecolor='white') fig.subplots_adjust(left=0, right=1, bottom=0, top=1) ax.set(xlim=(0, 11), ylim=(0, 6.6)) ax.axis('off') ax.text(5.5, 6.22, 'Factorial: descend first, multiply on return', ha='center', fontsize=19, weight='bold', color=INK) ax.text(5.5, 5.84, 'F(0) = 1 • F(n) = n × F(n − 1) for n ≥ 1', ha='center', fontsize=13, color=INK) for x, label in [(1.65, 'Calls descend'), (5.5, 'Deepest stack snapshot'), (9.3, 'Answers return')]: ax.text(x, 5.35, label, ha='center', fontsize=14, weight='bold', color=INK) levels = [4.85, 4.02, 3.19, 2.36, 1.53] for i, n in enumerate(range(4, -1, -1)): color = GREEN if n == 0 else BLUE face = '#eaf5ed' if n == 0 else '#eef5fb' box(ax, 1.65, levels[i], 1.8, f'F({n})', color, face) if i < 4: ax.annotate('', xy=(1.65, levels[i + 1] + 0.32), xytext=(1.65, levels[i] - 0.32), arrowprops={'arrowstyle': '-|>', 'color': BLUE, 'lw': 1.8}) frames = ['F(0): return 1', 'F(1): wait for 1 × result', 'F(2): wait for 2 × result', 'F(3): wait for 3 × result', 'F(4): wait for 4 × result'] for i, text in enumerate(frames): box(ax, 5.5, levels[i], 2.95, text, GREEN if i == 0 else BLUE, '#eaf5ed' if i == 0 else '#eef5fb') ax.text(7.12, 4.85, 'active', ha='left', va='center', fontsize=11, color=GREEN) ax.text(5.5, 0.91, 'Each caller keeps its own n\nand its next instruction.', ha='center', va='center', fontsize=12, color=INK) results = ['4 × 6 = 24', '3 × 2 = 6', '2 × 1 = 2', '1 × 1 = 1', 'F(0) = 1'] for i, label in enumerate(results): box(ax, 9.3, levels[i], 2.15, label, GREEN, '#eaf5ed') if i < 4: ax.annotate('', xy=(9.3, levels[i] - 0.32), xytext=(9.3, levels[i + 1] + 0.32), arrowprops={'arrowstyle': '-|>', 'color': GREEN, 'lw': 1.8}) ax.text(1.65, 0.91, 'Five invocations.\nFour pending multiplications.', ha='center', va='center', fontsize=12, color=INK) ax.text(9.3, 0.91, 'Pop one completed frame;\nresume its caller.', ha='center', va='center', fontsize=12, color=INK) ax.text(5.5, 0.28, 'Reaching the base case starts the return journey; the outer call still has work to do.', ha='center', fontsize=12, color=INK) fig.savefig(Path(__file__).stem + '.png', facecolor='white', transparent=False) plt.close(fig) if __name__ == '__main__': main()