make me flow charts of this in pdf
cd /home/daytona/workspace && python3 -c " import pdfplumber with pdfplumber.open('attachments/5090786f-a3c2-453e-869e-ee5f46becd38/JOINTS (2).pdf') as pdf: print(f'Pages: {len(pdf.pages)}') for i, page in enumerate(pdf.pages): print(f'\n=== PAGE {i+1} ===') text = page.extract_text() if text: print(text[:3000]) "
mkdir -p /home/daytona/workspace/joints-flowcharts && python3 << 'PYEOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch, FancyArrowPatch import matplotlib.patheffects as pe from pypdf import PdfWriter, PdfReader import io # ─── COLOUR PALETTE ─────────────────────────────────────────────────────────── C_TITLE = "#1a237e" # deep navy C_FALSE = "#4a148c" # deep purple (false joints) C_TRUE = "#01579b" # deep blue (true joints) C_SUB1 = "#7b1fa2" # medium purple C_SUB2 = "#0277bd" # medium blue C_LEAF = "#f3e5f5" # pale purple leaf C_LEAF2 = "#e1f5fe" # pale blue leaf C_ARROW = "#546e7a" C_BG = "#fafafa" C_WHITE = "#ffffff" C_AMPH = "#00695c" # teal for amphiarthroses C_MECH = "#bf360c" # dark orange for mechanics def rounded_box(ax, x, y, w, h, text, facecolor, edgecolor, fontsize=9, bold=False, text_color="white", radius=0.03, wrap=False, zorder=3, ha="center", va="center"): box = FancyBboxPatch((x - w/2, y - h/2), w, h, boxstyle=f"round,pad={radius}", facecolor=facecolor, edgecolor=edgecolor, linewidth=1.4, zorder=zorder) ax.add_patch(box) weight = "bold" if bold else "normal" ax.text(x, y, text, ha=ha, va=va, fontsize=fontsize, color=text_color, weight=weight, wrap=True, zorder=zorder+1, multialignment="center") def arrow(ax, x1, y1, x2, y2, color=C_ARROW, lw=1.5): ax.annotate("", xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle="-|>", color=color, lw=lw, mutation_scale=12), zorder=2) # ══════════════════════════════════════════════════════════════════════════════ # CHART 1 – MASTER CLASSIFICATION TREE # ══════════════════════════════════════════════════════════════════════════════ fig1, ax1 = plt.subplots(figsize=(18, 13)) ax1.set_xlim(0, 18); ax1.set_ylim(0, 13) ax1.axis("off") fig1.patch.set_facecolor(C_BG) ax1.set_facecolor(C_BG) # Title banner ax1.add_patch(FancyBboxPatch((0.3, 11.9), 17.4, 0.9, boxstyle="round,pad=0.05", facecolor=C_TITLE, edgecolor="none")) ax1.text(9, 12.35, "CLASSIFICATION OF JOINTS", ha="center", va="center", fontsize=18, color="white", weight="bold") # ROOT rounded_box(ax1, 9, 11.1, 3.2, 0.65, "JOINTS", C_TITLE, C_TITLE, fontsize=14, bold=True) # Level 1 – two major categories arrow(ax1, 7.4, 10.77, 4.5, 10.35) arrow(ax1, 10.6, 10.77, 13.5, 10.35) rounded_box(ax1, 4.5, 9.95, 4.6, 0.7, "FALSE JOINTS\n(Synarthroses)", C_FALSE, C_FALSE, fontsize=11, bold=True) rounded_box(ax1, 13.5, 9.95, 4.6, 0.7, "TRUE JOINTS\n(Diarthroses)", C_TRUE, C_TRUE, fontsize=11, bold=True) ax1.text(4.5, 9.38, "No joint cavity • Bones continuously connected", ha="center", va="center", fontsize=7.5, color="#555555", style="italic") ax1.text(13.5, 9.38, "Joint cavity present • Articular surfaces separated", ha="center", va="center", fontsize=7.5, color="#555555", style="italic") # ── False joint subtypes ───────────────────────────────────────── sub_x = [1.5, 4.5, 7.5] sub_labels = ["Syndesmoses\n(Fibrous)", "Synchondroses\n(Cartilaginous)", "Synostoses\n(Bony Fusion)"] for sx, sl in zip(sub_x, sub_labels): arrow(ax1, 4.5, 9.6, sx, 8.65) rounded_box(ax1, sx, 8.3, 2.5, 0.65, sl, C_SUB1, C_SUB1, fontsize=9, bold=True) # Syndesmoses leaves syndes = ["Interosseous\nmembrane (radius-ulna)", "Tibiofibular\nsyndesmosis", "Fontanelles\n(newborn skull)", "Gomphosis\n(teeth in socket)"] for i, s in enumerate(syndes): lx = 0.2 + i*0.75 ly = 7.35 arrow(ax1, 1.5, 7.97, lx+0.35, ly+0.25) rounded_box(ax1, lx+0.35, 7.0, 0.65, 0.5, s, C_LEAF, C_SUB1, fontsize=6, text_color="#4a148c") # Synchondroses leaves syncho = ["Epiphyseal\ngrowth plates", "Costal\ncartilage", "Pubic symphysis\n& IVDs"] for i, s in enumerate(syncho): lx = 3.5 + i*0.9 arrow(ax1, 4.5, 7.97, lx+0.4, 7.35+0.25) rounded_box(ax1, lx+0.4, 7.0, 0.8, 0.5, s, C_LEAF, C_SUB1, fontsize=6, text_color="#4a148c") # Synostoses leaves synost = ["Sacrum\n(fused vertebrae)", "Hip bone\n(ilium/ischium/pubis)", "Ossified\nepiphyseal plates"] for i, s in enumerate(synost): lx = 6.7 + i*0.9 arrow(ax1, 7.5, 7.97, lx+0.4, 7.35+0.25) rounded_box(ax1, lx+0.4, 7.0, 0.8, 0.5, s, C_LEAF, C_SUB1, fontsize=6, text_color="#4a148c") # ── True joint subtypes ────────────────────────────────────────── arrow(ax1, 12.0, 9.6, 10.5, 8.65) arrow(ax1, 15.0, 9.6, 16.0, 8.65) rounded_box(ax1, 10.5, 8.3, 2.8, 0.65, "Diarthroses\n(Synovial Joints)", C_SUB2, C_SUB2, fontsize=9, bold=True) rounded_box(ax1, 16.0, 8.3, 2.8, 0.65, "Amphiarthroses\n(Stiff Joints)", C_AMPH, C_AMPH, fontsize=9, bold=True) rounded_box(ax1, 16.0, 7.35, 2.6, 0.55, "Sacroiliac joint\n(very limited movement)", C_LEAF2, C_AMPH, fontsize=7, text_color="#00695c") arrow(ax1, 16.0, 7.97, 16.0, 7.63) # Diarthroses branches arrow(ax1, 9.5, 7.97, 8.5, 7.1) arrow(ax1, 11.5, 7.97, 12.5, 7.1) rounded_box(ax1, 8.5, 6.75, 2.6, 0.6, "Translational\n(Plane Joints)", C_SUB2, C_SUB2, fontsize=8.5, bold=True) rounded_box(ax1, 12.5, 6.75, 2.6, 0.6, "Rotational\n(Axial Joints)", C_SUB2, C_SUB2, fontsize=8.5, bold=True) # Plane joint examples plane_ex = ["Femoropatellar\njoint\n(1 axis)", "Vertebral\nfacet joints\n(1-2 axes)"] for i, s in enumerate(plane_ex): lx = 7.6 + i*1.1 arrow(ax1, 8.5, 6.45, lx+0.5, 5.85) rounded_box(ax1, lx+0.5, 5.5, 1.0, 0.65, s, C_LEAF2, C_SUB2, fontsize=6.5, text_color="#0277bd") # Rotational joint subtypes rot_x = [10.5, 12.2, 13.9, 15.6] rot_labels = ["Hinge\n(1 axis / 2 DoF)\nElbow, knee", "Pivot\n(1 axis / 2 DoF)\nAtlanto-axial,\nprox. radioulnar", "Ellipsoidal/Saddle\n(2 axes / 4 DoF)\nWrist, CMC thumb", "Spheroidal\n(3 axes / 6 DoF)\nHip, shoulder"] for rx, rl in zip(rot_x, rot_labels): arrow(ax1, 12.5, 6.45, rx, 5.85) rounded_box(ax1, rx, 5.35, 1.3, 0.9, rl, C_LEAF2, C_SUB2, fontsize=6, text_color="#01579b") # Footer ax1.text(9, 0.2, "Source: THIEME Atlas of Anatomy – General Anatomy and Musculoskeletal System, pp. 60-71", ha="center", va="center", fontsize=8, color="#888888", style="italic") fig1.tight_layout(pad=0.3) fig1.savefig("/home/daytona/workspace/joints-flowcharts/chart1_classification.pdf", bbox_inches="tight", dpi=150) plt.close(fig1) print("Chart 1 saved.") # ══════════════════════════════════════════════════════════════════════════════ # CHART 2 – STRUCTURE OF A TRUE (SYNOVIAL) JOINT # ══════════════════════════════════════════════════════════════════════════════ fig2, ax2 = plt.subplots(figsize=(16, 12)) ax2.set_xlim(0, 16); ax2.set_ylim(0, 12) ax2.axis("off"); fig2.patch.set_facecolor(C_BG); ax2.set_facecolor(C_BG) ax2.add_patch(FancyBboxPatch((0.3, 10.9), 15.4, 0.9, boxstyle="round,pad=0.05", facecolor=C_TRUE, edgecolor="none")) ax2.text(8, 11.35, "STRUCTURE OF A TRUE (SYNOVIAL) JOINT", ha="center", va="center", fontsize=17, color="white", weight="bold") rounded_box(ax2, 8, 10.1, 3.5, 0.65, "TRUE JOINT (Diarthrosis / Synovial Joint)", C_TRUE, C_TRUE, fontsize=12, bold=True) components = [ ("1. Articular\nSurfaces", "Covered by hyaline cartilage\n1-2 mm (phalanges)\n5-7 mm (femoropatellar)\n*Jaw & sternoclavicular = fibrous cartilage"), ("2. Joint\nCavity", "Narrow space (few mm)\nContains articular recesses\nFilled with synovial fluid"), ("3. Joint\nCapsule", "Closed capsule\nAlar folds, synovial folds,\nsynovial villi\nInner: synovial membrane\n(highly vascular)"), ("4. Synovial\nFluid", "Highly viscous lubricant\nNourishes avascular cartilage\nby diffusion & convection"), ("5. Intra-articular\nStructures", "Menisci (knee)\nArticular discs (jaw, SC, wrist)\nArticular labra (hip, shoulder)"), ("6. Ligaments", "Intra- & extra-capsular\nPrimary joint stabilizers"), ("7. Muscles", "Agonist/antagonist pairs\nCross the joint\nProduce opposite movements"), ("8. Synovial\nBursae", "Often near the joint\nMay communicate with\njoint cavity"), ] positions_x = [2, 5.5, 9, 12.5, 2, 5.5, 9, 12.5] positions_y = [8.0, 8.0, 8.0, 8.0, 5.0, 5.0, 5.0, 5.0] colors_comp = [C_SUB2, C_SUB2, C_SUB2, C_SUB2, C_FALSE, C_AMPH, C_MECH, C_SUB1] for i, (title, desc) in enumerate(components): cx, cy = positions_x[i], positions_y[i] arrow(ax2, 8, 9.77, cx, cy + 1.1) # Header box rounded_box(ax2, cx, cy + 0.72, 3.0, 0.58, title, colors_comp[i], colors_comp[i], fontsize=9, bold=True) # Detail box detail_box = FancyBboxPatch((cx - 1.5, cy - 0.8), 3.0, 1.4, boxstyle="round,pad=0.04", facecolor=C_WHITE, edgecolor=colors_comp[i], linewidth=1.2, zorder=3) ax2.add_patch(detail_box) ax2.text(cx, cy - 0.08, desc, ha="center", va="center", fontsize=6.5, color="#333333", multialignment="center", zorder=4) arrow(ax2, cx, cy + 0.43, cx, cy + 0.21, color=colors_comp[i]) # Important note box note_box = FancyBboxPatch((1.0, 1.0), 14.0, 1.5, boxstyle="round,pad=0.07", facecolor="#fff8e1", edgecolor="#f57f17", linewidth=2, zorder=3) ax2.add_patch(note_box) ax2.text(8, 2.05, "IMPORTANT: While the synovial membrane CAN regenerate (via adjoining connective tissue),\n" "hyaline cartilage CANNOT regenerate — it is avascular, lacks perichondrium,\n" "and is nourished exclusively by synovial fluid.", ha="center", va="center", fontsize=8.5, color="#e65100", weight="bold", multialignment="center", zorder=4) ax2.text(8, 0.3, "Source: THIEME Atlas of Anatomy – General Anatomy and Musculoskeletal System, p. 63", ha="center", fontsize=7.5, color="#888888", style="italic") fig2.tight_layout(pad=0.3) fig2.savefig("/home/daytona/workspace/joints-flowcharts/chart2_true_joint_structure.pdf", bbox_inches="tight", dpi=150) plt.close(fig2) print("Chart 2 saved.") # ══════════════════════════════════════════════════════════════════════════════ # CHART 3 – TYPES OF TRUE JOINTS (by shape / axes / DoF) # ══════════════════════════════════════════════════════════════════════════════ fig3, ax3 = plt.subplots(figsize=(18, 12)) ax3.set_xlim(0, 18); ax3.set_ylim(0, 12) ax3.axis("off"); fig3.patch.set_facecolor(C_BG); ax3.set_facecolor(C_BG) ax3.add_patch(FancyBboxPatch((0.3, 10.9), 17.4, 0.9, boxstyle="round,pad=0.05", facecolor=C_TRUE, edgecolor="none")) ax3.text(9, 11.35, "TYPES OF TRUE JOINTS — SHAPE, AXES & DEGREES OF FREEDOM", ha="center", va="center", fontsize=16, color="white", weight="bold") # Column headers headers = ["JOINT TYPE", "AXES OF\nMOTION", "DEGREES OF\nFREEDOM", "PRIMARY\nMOVEMENTS", "EXAMPLE"] header_x = [1.5, 4.5, 7.0, 10.5, 15.0] col_w = [2.4, 2.0, 2.0, 4.5, 3.5] for hx, hw, ht in zip(header_x, col_w, headers): rounded_box(ax3, hx, 10.1, hw, 0.6, ht, C_TRUE, C_TRUE, fontsize=9.5, bold=True) rows = [ ("Plane Joint\n(Femoropatellar)", "1 translational", "1", "Slide (1 direction)\nin femoral groove", "Patella in\nfemoral groove"), ("Plane Joint\n(Vertebral)", "1-2 translational","2-4","Gliding in\nmultiple directions", "Facet joints\nof spine"), ("Ellipsoidal\nJoint", "2 rotational", "4", "Flexion, Extension\nAbduction, Adduction", "Radiocarpal\n(wrist) joint"), ("Saddle Joint", "2 rotational", "4", "Flexion, Extension\nAbduction, Adduction", "CMC joint\nof thumb"), ("Hinge Joint", "1 rotational", "2", "Flexion,\nExtension", "Elbow, Knee"), ("Pivot Joint", "1 rotational", "2", "Rotation\n(axial)", "Atlanto-axial\nProx. radioulnar"), ("Spheroidal\n(deep socket)", "3 rotational", "6", "Flex/Ext, Abd/Add\nInt./Ext. rotation", "Hip joint"), ("Spheroidal\n(shallow socket)", "3 rotational", "6", "All 6 movements\n+ greater range", "Shoulder\n(glenohumeral)"), ] row_colors = [C_SUB2, "#0288d1", C_FALSE, C_SUB1, C_MECH, "#558b2f", "#00695c", "#4527a0"] ys = [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0] for (jtype, axes, dof, moves, ex), rc, ry in zip(rows, row_colors, ys): row_data = [jtype, axes, dof, moves, ex] for hx, hw, rd in zip(header_x, col_w, row_data): is_type = (hx == 1.5) fc = rc if is_type else C_WHITE ec = rc tc = "white" if is_type else "#222222" rounded_box(ax3, hx, ry, hw, 0.75, rd, fc, ec, fontsize=7.8, bold=is_type, text_color=tc) # Key principle box kp_box = FancyBboxPatch((0.4, 0.5), 17.2, 1.1, boxstyle="round,pad=0.07", facecolor="#e8f5e9", edgecolor="#2e7d32", linewidth=2, zorder=3) ax3.add_patch(kp_box) ax3.text(9, 1.05, "KEY PRINCIPLE: The more congruent the articular surfaces, the more they SLIDE onto each other " "(e.g. shoulder).\nThe less congruent, the more they ROLL off each other (e.g. knee). Most joints use a COMBINATION " "of roll and slide.", ha="center", va="center", fontsize=8.5, color="#1b5e20", weight="bold", multialignment="center", zorder=4) fig3.tight_layout(pad=0.3) fig3.savefig("/home/daytona/workspace/joints-flowcharts/chart3_joint_types.pdf", bbox_inches="tight", dpi=150) plt.close(fig3) print("Chart 3 saved.") # ══════════════════════════════════════════════════════════════════════════════ # CHART 4 – JOINT MECHANICS: STABILITY & POSTURE # ══════════════════════════════════════════════════════════════════════════════ fig4, ax4 = plt.subplots(figsize=(16, 12)) ax4.set_xlim(0, 16); ax4.set_ylim(0, 12) ax4.axis("off"); fig4.patch.set_facecolor(C_BG); ax4.set_facecolor(C_BG) ax4.add_patch(FancyBboxPatch((0.3, 10.9), 15.4, 0.9, boxstyle="round,pad=0.05", facecolor=C_MECH, edgecolor="none")) ax4.text(8, 11.35, "JOINT MECHANICS: STABILITY & FUNCTION", ha="center", va="center", fontsize=17, color="white", weight="bold") rounded_box(ax4, 8, 10.1, 4.0, 0.65, "Joint Stability - 4 Constraint Types", C_MECH, C_MECH, fontsize=12, bold=True) constraints = [ ("Bony\nConstraint", "Shape of articular\nsurfaces"), ("Muscular\nConstraint", "Muscles crossing\nthe joint"), ("Ligamentous\nConstraint", "Ligaments limiting\nrange of motion"), ("Soft-Tissue\nConstraint", "Joint capsule &\nsurrounding tissue"), ] cx_list = [2.5, 6.0, 10.0, 13.5] for i, ((title, desc), cx) in enumerate(zip(constraints, cx_list)): arrow(ax4, 8, 9.77, cx, 8.85) rounded_box(ax4, cx, 8.55, 2.8, 0.55, title, C_MECH, C_MECH, fontsize=9, bold=True) rounded_box(ax4, cx, 7.7, 2.8, 0.65, desc, C_WHITE, C_MECH, fontsize=8, text_color="#333333") arrow(ax4, cx, 8.27, cx, 8.03, color=C_MECH) # Key muscles heading rounded_box(ax4, 8, 6.5, 5.5, 0.65, "KEY MUSCLES FOR ERECT POSTURE", "#37474f", "#37474f", fontsize=12, bold=True) # Joint and muscles joint_muscles = [ ("Ankle\n(Sagittal plane)", ["Triceps surae", "Tibialis anterior"]), ("Knee\n(Stabilization)", ["Quadriceps femoris"]), ("Hip\n(Sagittal plane)", ["Gluteal muscles"]), ("Hip\n(Frontal plane)", ["Gluteus medius", "Gluteus minimus"]), ("Spine\n(Stabilization)", ["Intrinsic back muscles"]), ] mx_list = [1.5, 4.5, 7.5, 10.8, 14.2] for (jname, muscles), mx in zip(joint_muscles, mx_list): arrow(ax4, 8, 6.17, mx, 5.5) rounded_box(ax4, mx, 5.2, 2.5, 0.55, jname, "#37474f", "#37474f", fontsize=8.5, bold=True) for j, m in enumerate(muscles): my = 4.4 - j * 0.65 arrow(ax4, mx, 4.92, mx, my + 0.25, color="#37474f") rounded_box(ax4, mx, my, 2.3, 0.45, m, "#eceff1", "#37474f", fontsize=8, text_color="#333333") # Roll-Slide box at bottom rs_box = FancyBboxPatch((0.8, 0.5), 14.4, 1.35, boxstyle="round,pad=0.07", facecolor="#e3f2fd", edgecolor=C_TRUE, linewidth=2, zorder=3) ax4.add_patch(rs_box) ax4.text(8, 1.17, "ROLL-SLIDE MECHANICS: Congruent surfaces (similar radius) → more sliding (e.g. shoulder)\n" "Incongruent surfaces → more rolling (e.g. knee) | Most joints: COMBINATION of roll + slide", ha="center", va="center", fontsize=9, color="#01579b", weight="bold", multialignment="center", zorder=4) ax4.text(8, 0.2, "Source: THIEME Atlas of Anatomy – General Anatomy and Musculoskeletal System, pp. 64-71", ha="center", fontsize=7.5, color="#888888", style="italic") fig4.tight_layout(pad=0.3) fig4.savefig("/home/daytona/workspace/joints-flowcharts/chart4_mechanics.pdf", bbox_inches="tight", dpi=150) plt.close(fig4) print("Chart 4 saved.") # ══════════════════════════════════════════════════════════════════════════════ # MERGE ALL 4 into one PDF # ══════════════════════════════════════════════════════════════════════════════ writer = PdfWriter() for fname in ["chart1_classification.pdf", "chart2_true_joint_structure.pdf", "chart3_joint_types.pdf", "chart4_mechanics.pdf"]: reader = PdfReader(f"/home/daytona/workspace/joints-flowcharts/{fname}") for page in reader.pages: writer.add_page(page) out_path = "/home/daytona/workspace/joints-flowcharts/JOINTS_Flowcharts.pdf" with open(out_path, "wb") as f: writer.write(f) print(f"\nMerged PDF saved to: {out_path}") PYEOF
JOINTS Flowcharts
PDF Document · PDF
make me flow charts of this in pdf
python3 -c " import pdfplumber path = '/home/daytona/workspace/attachments/5090786f-a3c2-453e-869e-ee5f46becd38/I now have extensive rich content from the THIEME .pdf' with pdfplumber.open(path) as pdf: print(f'Pages: {len(pdf.pages)}') for i, page in enumerate(pdf.pages): print(f'\n=== PAGE {i+1} ===') text = page.extract_text() if text: print(text) "
mkdir -p /home/daytona/workspace/muscles-flowcharts && python3 << 'PYEOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch from pypdf import PdfWriter, PdfReader # ─── COLOUR PALETTE ─────────────────────────────────────────────────────────── C_BG = "#fafafa" C_WHITE = "#ffffff" C_TITLE = "#1a237e" # deep navy C_SKEL = "#b71c1c" # deep red – skeletal C_CARD = "#1b5e20" # deep green – cardiac C_SMO = "#e65100" # deep orange – smooth C_T1 = "#4a148c" # purple – Type I C_T2 = "#0277bd" # blue – Type II C_PENN = "#00695c" # teal – pennate C_HIST = "#bf360c" # dark orange – histology C_FUNC = "#37474f" # dark slate – function C_MECH = "#283593" # dark indigo – mechanics C_TEN = "#4e342e" # brown – tendons C_LEAF = "#fce4ec" C_LEAF2 = "#e3f2fd" C_LEAFG = "#e8f5e9" C_LEAFT = "#fff3e0" C_ARROW = "#546e7a" def rbox(ax, x, y, w, h, text, fc, ec, fs=9, bold=False, tc="white", rad=0.03, zorder=3, ma="center"): box = FancyBboxPatch((x - w/2, y - h/2), w, h, boxstyle=f"round,pad={rad}", facecolor=fc, edgecolor=ec, linewidth=1.5, zorder=zorder) ax.add_patch(box) ax.text(x, y, text, ha="center", va="center", fontsize=fs, color=tc, weight="bold" if bold else "normal", multialignment=ma, zorder=zorder+1) def arr(ax, x1, y1, x2, y2, color=C_ARROW, lw=1.5): ax.annotate("", xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle="-|>", color=color, lw=lw, mutation_scale=12), zorder=2) def title_banner(ax, text, color, fig_w): ax.add_patch(FancyBboxPatch((0.3, 10.9), fig_w - 0.6, 0.9, boxstyle="round,pad=0.05", facecolor=color, edgecolor="none")) ax.text(fig_w/2, 11.35, text, ha="center", va="center", fontsize=16, color="white", weight="bold") # ══════════════════════════════════════════════════════════════════════════════ # CHART 1 – OVERVIEW: TYPES OF MUSCLE TISSUE # ══════════════════════════════════════════════════════════════════════════════ fig1, ax1 = plt.subplots(figsize=(18, 12)) ax1.set_xlim(0, 18); ax1.set_ylim(0, 12); ax1.axis("off") fig1.patch.set_facecolor(C_BG); ax1.set_facecolor(C_BG) title_banner(ax1, "TYPES OF MUSCLE TISSUE — OVERVIEW & COMPARISON", C_TITLE, 18) rbox(ax1, 9, 10.1, 3.0, 0.65, "MUSCLE TISSUE", C_TITLE, C_TITLE, fs=13, bold=True) # Three types types = [("SKELETAL\nMUSCLE", C_SKEL, 3.5), ("CARDIAC\nMUSCLE", C_CARD, 9.0), ("SMOOTH\nMUSCLE", C_SMO, 14.5)] for label, col, tx in types: arr(ax1, 9, 9.77, tx, 9.25) rbox(ax1, tx, 8.95, 3.8, 0.55, label, col, col, fs=11, bold=True) # Feature rows features = [ ("Location", "Attached to skeleton", "Heart wall", "Viscera, blood vessels"), ("Appearance", "Striated", "Striated", "Non-striated"), ("Control", "VOLUNTARY", "Involuntary", "Involuntary"), ("Nuclei", "Multiple, peripheral", "Single, central", "Single, central"), ("Speed", "FAST", "Intermediate", "SLOW"), ("Fatigue", "Yes (phasic)", "No", "No"), ] row_colors = [C_SKEL, C_CARD, C_SMO] fy_start = 8.25 for i, (feat, sk, ca, sm) in enumerate(features): fy = fy_start - i * 0.72 # Feature label rbox(ax1, 1.2, fy, 1.8, 0.52, feat, C_FUNC, C_FUNC, fs=8.5, bold=True) for val, tx, col in [(sk, 3.5, C_SKEL), (ca, 9.0, C_CARD), (sm, 14.5, C_SMO)]: rbox(ax1, tx, fy, 3.5, 0.52, val, C_WHITE, col, fs=8.5, tc="#222222") # Clinical note box note = FancyBboxPatch((0.5, 1.05), 17.0, 1.4, boxstyle="round,pad=0.07", facecolor="#fff8e1", edgecolor="#f57f17", linewidth=2, zorder=3) ax1.add_patch(note) ax1.text(9, 1.75, "PHENOTYPIC PLASTICITY (Pette & Saron, 2001): Fiber type distribution is genetically determined,\n" "but can be influenced by neuromuscular activity.\n" "Endurance athletes (runners, cyclists, rowers) → predominantly Type I (slow-twitch)\n" "Explosive athletes (sprinters, weightlifters) → predominantly Type II (fast-twitch)", ha="center", va="center", fontsize=8.5, color="#e65100", weight="bold", multialignment="center", zorder=4) ax1.text(9, 0.25, "Source: THIEME Atlas of Anatomy – General Anatomy and Musculoskeletal System, Sections 5.1-5.3, pp. 76-81", ha="center", fontsize=7.5, color="#888888", style="italic") fig1.tight_layout(pad=0.3) fig1.savefig("/home/daytona/workspace/muscles-flowcharts/chart1_muscle_types.pdf", bbox_inches="tight", dpi=150) plt.close(fig1) print("Chart 1 done.") # ══════════════════════════════════════════════════════════════════════════════ # CHART 2 – SKELETAL MUSCLE FIBER TYPES & ARCHITECTURE # ══════════════════════════════════════════════════════════════════════════════ fig2, ax2 = plt.subplots(figsize=(18, 13)) ax2.set_xlim(0, 18); ax2.set_ylim(0, 13); ax2.axis("off") fig2.patch.set_facecolor(C_BG); ax2.set_facecolor(C_BG) ax2.add_patch(FancyBboxPatch((0.3, 11.9), 17.4, 0.9, boxstyle="round,pad=0.05", facecolor=C_SKEL, edgecolor="none")) ax2.text(9, 12.35, "SKELETAL MUSCLE CLASSIFICATION: FIBER TYPES & ARCHITECTURE", ha="center", va="center", fontsize=15, color="white", weight="bold") rbox(ax2, 9, 11.1, 3.5, 0.65, "SKELETAL MUSCLE", C_SKEL, C_SKEL, fs=13, bold=True) # Two main branches arr(ax2, 7.3, 10.77, 4.5, 10.2) arr(ax2, 10.7, 10.77, 13.5, 10.2) rbox(ax2, 4.5, 9.85, 4.8, 0.6, "2A. BY FIBER TYPE\n(Metabolic Classification)", C_T1, C_T1, fs=10, bold=True) rbox(ax2, 13.5, 9.85, 4.8, 0.6, "2B. BY ARCHITECTURE\n(Fiber Arrangement)", C_PENN, C_PENN, fs=10, bold=True) # ── Fiber type branch ──────────────────────────────────────────── arr(ax2, 3.0, 9.55, 2.2, 8.8) arr(ax2, 6.0, 9.55, 7.0, 8.8) rbox(ax2, 2.2, 8.45, 3.8, 0.65, "TYPE I — Slow-Twitch (ST)\n'Red / Postural Muscles'", C_T1, C_T1, fs=9, bold=True) rbox(ax2, 7.0, 8.45, 3.8, 0.65, "TYPE II — Fast-Twitch (FT)\n'White / Movement Muscles'", C_T2, C_T2, fs=9, bold=True) t1_props = ["Twitch duration ~100 ms", "Endurance activity", "Slow fatigue", "Large motor units (1000s)", "Rich in myoglobin (RED)", "Abundant mitochondria", "Oxidative (aerobic) metabolism", "Little glycogen (PAS-negative)", "Highly vascularized", "Prone to shortening → needs stretching", "Examples: Intercostals, Trapezius"] t2_props = ["Twitch duration ~30 ms", "Brief intense activity", "Rapid fatigue", "Small motor units (<100)", "Scant myoglobin (WHITE/pale)", "Few mitochondria", "Anaerobic glycolysis", "Abundant glycogen (PAS-positive)", "Small capillary supply", "Prone to atrophy → needs strengthening", "Examples: Gastrocnemius, Tibialis ant."] for i, prop in enumerate(t1_props): py = 7.8 - i * 0.55 arr(ax2, 2.2, 8.12, 2.2, py+0.22, color=C_T1) rbox(ax2, 2.2, py, 3.6, 0.38, prop, C_LEAF, C_T1, fs=7, tc="#4a148c") for i, prop in enumerate(t2_props): py = 7.8 - i * 0.55 arr(ax2, 7.0, 8.12, 7.0, py+0.22, color=C_T2) rbox(ax2, 7.0, py, 3.6, 0.38, prop, C_LEAF2, C_T2, fs=7, tc="#0277bd") # Sub-types IIA / IIB rbox(ax2, 7.0, 1.6, 3.8, 0.5, "TYPE IIA: fast, oxidative-glycolytic\nTYPE IIB: fast, purely glycolytic", C_T2, C_T2, fs=7.5, bold=True) arr(ax2, 7.0, 1.87, 7.0, 2.0, color=C_T2) # Also tonic note rbox(ax2, 4.5, 1.6, 3.5, 0.7, "TONIC FIBERS (special):\nGradual depolarization\nFound only in:\nMuscle spindles & eye muscles", "#fff8e1", "#f57f17", fs=7.5, tc="#e65100") # ── Architecture branch ─────────────────────────────────────────── arr(ax2, 12.5, 9.55, 11.5, 8.8) arr(ax2, 14.5, 9.55, 15.5, 8.8) rbox(ax2, 11.5, 8.45, 3.5, 0.65, "NON-PENNATE\n(Parallel-Fibered)", C_PENN, C_PENN, fs=9, bold=True) rbox(ax2, 15.5, 8.45, 3.5, 0.65, "PENNATE\n(Oblique Fibers)", "#558b2f", "#558b2f", fs=9, bold=True) np_props = ["Fibers parallel to tendon long axis", "Greater force production\n(direct force transmission)", "Physiological CS = Anatomical CS", "Larger range of shortening"] for i, p in enumerate(np_props): py = 7.7 - i * 0.7 arr(ax2, 11.5, 8.12, 11.5, py+0.28, color=C_PENN) rbox(ax2, 11.5, py, 3.3, 0.5, p, C_LEAFG, C_PENN, fs=7.5, tc="#00695c") # Pennate subtypes psubt = [("Unipennate", "Fibers from\none side"), ("Bipennate", "Fibers from\nboth sides"), ("Multipennate", "Multiple\nangles")] for i, (pt, pd) in enumerate(psubt): px = 14.3 + i * 1.35 arr(ax2, 15.5, 8.12, px, 7.42) rbox(ax2, px, 7.15, 1.2, 0.45, pt, "#558b2f", "#558b2f", fs=7.5, bold=True) rbox(ax2, px, 6.55, 1.2, 0.45, pd, C_LEAFG, "#558b2f", fs=7, tc="#333333") pp_props = ["Pennation angle → path gain\n(actual fiber shortening < lifting height)", "Physiological CS > Anatomical CS", "More fibers insert into tendon\n→ greater force development", "Smaller size for similar power\ncompared to non-pennate"] for i, p in enumerate(pp_props): py = 5.85 - i * 0.7 arr(ax2, 15.5, 6.25, 15.5, py+0.25, color="#558b2f") rbox(ax2, 15.5, py, 3.3, 0.5, p, C_LEAFG, "#558b2f", fs=7.5, tc="#00695c") # THIEME quote box q_box = FancyBboxPatch((9.2, 2.4), 8.5, 0.9, boxstyle="round,pad=0.06", facecolor="#e8f5e9", edgecolor="#2e7d32", linewidth=2, zorder=3) ax2.add_patch(q_box) ax2.text(13.45, 2.85, '"If there were only non-pennate muscles, the body, in a lot of places,\nwould not have space for a sufficient number of strong muscles."', ha="center", va="center", fontsize=8, color="#1b5e20", style="italic", multialignment="center", zorder=4) ax2.text(9, 0.2, "Source: THIEME Atlas of Anatomy – General Anatomy and Musculoskeletal System, pp. 76-81", ha="center", fontsize=7.5, color="#888888", style="italic") fig2.tight_layout(pad=0.3) fig2.savefig("/home/daytona/workspace/muscles-flowcharts/chart2_fiber_architecture.pdf", bbox_inches="tight", dpi=150) plt.close(fig2) print("Chart 2 done.") # ══════════════════════════════════════════════════════════════════════════════ # CHART 3 – HISTOLOGICAL ORGANIZATION & FASCIAE # ══════════════════════════════════════════════════════════════════════════════ fig3, ax3 = plt.subplots(figsize=(17, 12)) ax3.set_xlim(0, 17); ax3.set_ylim(0, 12); ax3.axis("off") fig3.patch.set_facecolor(C_BG); ax3.set_facecolor(C_BG) ax3.add_patch(FancyBboxPatch((0.3, 10.9), 16.4, 0.9, boxstyle="round,pad=0.05", facecolor=C_HIST, edgecolor="none")) ax3.text(8.5, 11.35, "HISTOLOGICAL ORGANIZATION OF SKELETAL MUSCLE & FASCIAE", ha="center", va="center", fontsize=15, color="white", weight="bold") rbox(ax3, 8.5, 10.1, 3.5, 0.65, "SKELETAL MUSCLE — Internal Structure", C_HIST, C_HIST, fs=12, bold=True) # Two sections arr(ax3, 6.0, 9.77, 4.0, 9.1) arr(ax3, 11.0, 9.77, 13.0, 9.1) rbox(ax3, 4.0, 8.75, 5.5, 0.6, "CONNECTIVE TISSUE SHEATHS\n(3-layer hierarchical system)", C_HIST, C_HIST, fs=10, bold=True) rbox(ax3, 13.0, 8.75, 5.5, 0.6, "THE MUSCLE FIBER (Cell)", "#4527a0", "#4527a0", fs=10, bold=True) # CT sheaths layers = [ ("ENDOMYSIUM", "Surrounds: Individual muscle fibers\nFunction: Tensile strength\nCarries 200-250 capillaries (300-400/mm²)\n& motor end plates"), ("PERIMYSIUM", "Surrounds: Primary → secondary bundles\n(visible as 'meat fibers')\nFunction: Transmits tensile force to tendons"), ("EPIMYSIUM", "Surrounds: Entire muscle\n(loose CT beneath muscle fascia)\nFunction: Connects to muscle fascia"), ] ly_list = [7.4, 6.0, 4.6] lc_list = [C_HIST, "#e64a19", "#d84315"] for (lname, ldesc), ly, lc in zip(layers, ly_list, lc_list): arr(ax3, 4.0, 8.45, 4.0, ly+0.5) rbox(ax3, 4.0, ly+0.3, 2.5, 0.5, lname, lc, lc, fs=9, bold=True) detail = FancyBboxPatch((1.55, ly - 0.6), 5.0, 0.8, boxstyle="round,pad=0.04", facecolor=C_WHITE, edgecolor=lc, linewidth=1.2, zorder=3) ax3.add_patch(detail) ax3.text(4.05, ly - 0.2, ldesc, ha="center", va="center", fontsize=7, color="#333333", multialignment="center", zorder=4) arr(ax3, 4.0, ly+0.05, 4.0, ly+0.2, color=lc) # Muscle fiber cell details fiber_props = [ ("Size", "Exceptionally large cells\nDiameter: ~60 µm (range 10-100 µm)\nLength: up to 20 cm"), ("Dominant\nStructures", "Myofibrils\nMitochondria\nL-system (SR/longitudinal tubules)\nT-system (transverse tubules)"), ("L-system\n(Longitudinal)", "= Sarcoplasmic reticulum\nArranged lengthwise to myofibrils\nStores Ca²⁺ ions\nReleases Ca²⁺ on excitation → CONTRACTION"), ("T-system\n(Transverse)", "Tubules perpendicular to myofibrils\nConducts action potentials\nfrom cell surface → deep into fiber"), ] fy_list = [7.6, 6.4, 5.1, 3.8] fc_list = ["#4527a0", "#5e35b1", "#7b1fa2", "#8e24aa"] for (fname, fdesc), fy, fc in zip(fiber_props, fy_list, fc_list): arr(ax3, 13.0, 8.45, 13.0, fy+0.52) rbox(ax3, 13.0, fy+0.32, 2.5, 0.5, fname, fc, fc, fs=8.5, bold=True) det = FancyBboxPatch((10.4, fy - 0.55), 5.2, 0.8, boxstyle="round,pad=0.04", facecolor=C_WHITE, edgecolor=fc, linewidth=1.2, zorder=3) ax3.add_patch(det) ax3.text(13.0, fy - 0.15, fdesc, ha="center", va="center", fontsize=7, color="#333333", multialignment="center", zorder=4) arr(ax3, 13.0, fy+0.07, 13.0, fy+0.22, color=fc) # Fascia section arr(ax3, 8.5, 9.77, 8.5, 3.25) rbox(ax3, 8.5, 3.0, 3.0, 0.55, "MUSCLE FASCIAE", "#33691e", "#33691e", fs=10, bold=True) fascia_props = [ "Tough collagenous CT in weblike lattice structure", "Bears loads in ANY direction", "Maintains shape/position of muscles", "Allows adjacent muscles to GLIDE past each other\n(low friction = less force loss)", "Continuous 3D network investing the whole body\n(current anatomic nomenclature)", ] for i, fp in enumerate(fascia_props): fy2 = 2.35 - i * 0.55 arr(ax3, 8.5, 2.72, 8.5, fy2+0.22, color="#33691e") rbox(ax3, 8.5, fy2, 6.0, 0.38, fp, C_LEAFG, "#33691e", fs=7.5, tc="#33691e") ax3.text(8.5, 0.2, "Source: THIEME Atlas of Anatomy – General Anatomy and Musculoskeletal System, pp. 76-81", ha="center", fontsize=7.5, color="#888888", style="italic") fig3.tight_layout(pad=0.3) fig3.savefig("/home/daytona/workspace/muscles-flowcharts/chart3_histology_fasciae.pdf", bbox_inches="tight", dpi=150) plt.close(fig3) print("Chart 3 done.") # ══════════════════════════════════════════════════════════════════════════════ # CHART 4 – FUNCTION, MOTOR UNITS & TENDONS # ══════════════════════════════════════════════════════════════════════════════ fig4, ax4 = plt.subplots(figsize=(18, 13)) ax4.set_xlim(0, 18); ax4.set_ylim(0, 13); ax4.axis("off") fig4.patch.set_facecolor(C_BG); ax4.set_facecolor(C_BG) ax4.add_patch(FancyBboxPatch((0.3, 11.9), 17.4, 0.9, boxstyle="round,pad=0.05", facecolor=C_MECH, edgecolor="none")) ax4.text(9, 12.35, "MUSCLE FUNCTION PRINCIPLES, MOTOR UNITS & TENDONS", ha="center", va="center", fontsize=15, color="white", weight="bold") rbox(ax4, 9, 11.1, 4.0, 0.65, "MUSCLE FUNCTION & ORGANISATION", C_MECH, C_MECH, fs=12, bold=True) # Four sections at level 1 section_x = [2.5, 6.5, 11.5, 15.5] section_labels = ["POSTURAL vs.\nPHASIC FUNCTION", "LEVER\nMECHANICS", "MUSCLE\nACTION ROLES", "MOTOR\nUNITS"] section_cols = [C_SKEL, C_MECH, C_FUNC, C_T2] for sx, sl, sc in zip(section_x, section_labels, section_cols): arr(ax4, 9, 10.77, sx, 10.15) rbox(ax4, sx, 9.85, 3.5, 0.55, sl, sc, sc, fs=10, bold=True) # Postural vs phasic postural = [("POSTURAL muscles\n(Type I dominant)", "Maintain body position vs. gravity\nActive continuously\nResist fatigue\n(e.g. postural back muscles)"), ("PHASIC / MOVEMENT\nmuscles (Type II dominant)", "Rapid, powerful movements\nActivate in bursts\nFatigue quickly\n(e.g. biceps, gastrocnemius)")] for i, (pt, pd) in enumerate(postural): px = 1.2 + i * 2.7 arr(ax4, 2.5, 9.57, px, 8.9) rbox(ax4, px, 8.6, 2.5, 0.55, pt, C_SKEL if i==0 else C_T2, C_SKEL if i==0 else C_T2, fs=7.5, bold=True) det = FancyBboxPatch((px-1.25, 7.0), 2.5, 1.45, boxstyle="round,pad=0.04", facecolor=C_WHITE, edgecolor=C_SKEL if i==0 else C_T2, linewidth=1.2, zorder=3) ax4.add_patch(det) ax4.text(px, 7.72, pd, ha="center", va="center", fontsize=7, color="#333333", multialignment="center", zorder=4) arr(ax4, px, 8.32, px, 8.45, color=C_SKEL if i==0 else C_T2) # Lever mechanics levers = [("ONE-ARM LEVER", "Force & load on SAME side\nof joint rotation\nExample: elbow joint\n(biceps brachii)"), ("TWO-ARM LEVER", "Force on ONE side,\nbody weight on OTHER side\nExample: hip joint")] for i, (lt, ld) in enumerate(levers): lx = 5.5 + i * 2.2 arr(ax4, 6.5, 9.57, lx, 8.9) rbox(ax4, lx, 8.6, 2.0, 0.5, lt, C_MECH, C_MECH, fs=7.5, bold=True) det = FancyBboxPatch((lx-1.0, 7.0), 2.0, 1.45, boxstyle="round,pad=0.04", facecolor=C_WHITE, edgecolor=C_MECH, linewidth=1.2, zorder=3) ax4.add_patch(det) ax4.text(lx, 7.72, ld, ha="center", va="center", fontsize=7, color="#333333", multialignment="center", zorder=4) arr(ax4, lx, 8.32, lx, 8.45, color=C_MECH) # Torque note rbox(ax4, 6.5, 6.45, 3.8, 0.5, "TORQUE = Force × Force arm\n= Load × Load arm (at rest)", "#fffde7", "#f9a825", fs=7.5, tc="#e65100") # Muscle action roles roles = [("AGONIST\n(Prime Mover)", "Primary muscle producing\na movement"), ("SYNERGIST", "Assists agonist\nStabilizes joints\nduring movement"), ("ANTAGONIST", "Opposes agonist\nProvides smooth movement\nvia eccentric contraction"), ("FIXATOR /\nSTABILIZER", "Contracts isometrically\nStabilizes proximal segments\nduring distal movement")] role_colors = ["#b71c1c", "#1b5e20", "#0277bd", "#6a1b9a"] for i, ((rname, rdesc), rc) in enumerate(zip(roles, role_colors)): rx = 10.0 + i * 1.6 arr(ax4, 11.5, 9.57, rx, 8.9) rbox(ax4, rx, 8.6, 1.45, 0.5, rname, rc, rc, fs=7, bold=True) det = FancyBboxPatch((rx-0.73, 7.0), 1.45, 1.45, boxstyle="round,pad=0.04", facecolor=C_WHITE, edgecolor=rc, linewidth=1.2, zorder=3) ax4.add_patch(det) ax4.text(rx, 7.72, rdesc, ha="center", va="center", fontsize=6.5, color="#333333", multialignment="center", zorder=4) arr(ax4, rx, 8.32, rx, 8.45, color=rc) # Motor units arr(ax4, 15.5, 9.57, 15.5, 8.9) rbox(ax4, 15.5, 8.6, 3.5, 0.55, "1 motor neuron +\nall fibers it innervates", C_T2, C_T2, fs=8, bold=True) mu_props = ["All fibers in a unit = SAME type", "Large units (1000s fibers) → Type I\n(postural / slow)", "Small units (<100 fibers) → Type II\n(fine / rapid movement)", "Fiber type determined by\ninnervating neuron"] for i, mp in enumerate(mu_props): my = 7.9 - i * 0.65 arr(ax4, 15.5, 8.32, 15.5, my+0.25, color=C_T2) rbox(ax4, 15.5, my, 3.3, 0.45, mp, C_LEAF2, C_T2, fs=7.5, tc="#0277bd") # ── Tendons section ────────────────────────────────────────────── rbox(ax4, 9, 5.6, 3.5, 0.6, "TENDONS — Types & Insertions", C_TEN, C_TEN, fs=11, bold=True) arr(ax4, 9, 11.0, 9, 5.9, color=C_ARROW) ten_types = [ ("TRACTION TENDONS", "Tensile (pulling) stress\nStrong CT with PARALLEL fibers\nWell vascularized"), ("PRESSURE TENDONS", "Compressive stress\nChange direction around bone\nFibrocartilage on compressed side\n(acts as fulcrum)\nAvascular in compressed area"), ] for i, (tt, td) in enumerate(ten_types): tx = 4.5 + i * 5.5 arr(ax4, 9, 5.3, tx, 4.75) rbox(ax4, tx, 4.45, 3.8, 0.55, tt, C_TEN, C_TEN, fs=8.5, bold=True) det = FancyBboxPatch((tx-1.9, 3.0), 3.8, 1.3, boxstyle="round,pad=0.04", facecolor=C_WHITE, edgecolor=C_TEN, linewidth=1.2, zorder=3) ax4.add_patch(det) ax4.text(tx, 3.65, td, ha="center", va="center", fontsize=7.5, color="#333333", multialignment="center", zorder=4) arr(ax4, tx, 4.17, tx, 4.3, color=C_TEN) # Tendon insertions insert_types = [ ("PERIOSTEAL\nDIAPHYSEAL INSERTION", "Tendon inserts into periosteum\nZones: collagen fibers → fibrocartilage\n→ mineralized fibrocartilage → bone\n(via Sharpey's fibers)"), ("CHONDRAL\nAPOPHYSEAL INSERTION", "Cartilage cells in non-mineralized\nfibrocartilage zone act as taut springs\n= STRETCH PROTECTION principle\nResists transverse shortening during contraction"), ] for i, (it, id_) in enumerate(insert_types): ix = 4.5 + i * 5.5 arr(ax4, ix, 3.0, ix, 2.5) rbox(ax4, ix, 2.2, 3.8, 0.55, it, "#6d4c41", "#6d4c41", fs=7.5, bold=True) det = FancyBboxPatch((ix-1.9, 0.85), 3.8, 1.2, boxstyle="round,pad=0.04", facecolor=C_WHITE, edgecolor="#6d4c41", linewidth=1.2, zorder=3) ax4.add_patch(det) ax4.text(ix, 1.45, id_, ha="center", va="center", fontsize=7, color="#333333", multialignment="center", zorder=4) arr(ax4, ix, 1.92, ix, 2.05, color="#6d4c41") ax4.text(9, 0.2, "Source: THIEME Atlas of Anatomy – General Anatomy and Musculoskeletal System, pp. 76-81", ha="center", fontsize=7.5, color="#888888", style="italic") fig4.tight_layout(pad=0.3) fig4.savefig("/home/daytona/workspace/muscles-flowcharts/chart4_function_motor_tendons.pdf", bbox_inches="tight", dpi=150) plt.close(fig4) print("Chart 4 done.") # ══════════════════════════════════════════════════════════════════════════════ # MERGE all 4 into one PDF # ══════════════════════════════════════════════════════════════════════════════ writer = PdfWriter() for fname in ["chart1_muscle_types.pdf", "chart2_fiber_architecture.pdf", "chart3_histology_fasciae.pdf", "chart4_function_motor_tendons.pdf"]: reader = PdfReader(f"/home/daytona/workspace/muscles-flowcharts/{fname}") for page in reader.pages: writer.add_page(page) out = "/home/daytona/workspace/muscles-flowcharts/MUSCLES_Flowcharts.pdf" with open(out, "wb") as f: writer.write(f) print(f"\nMerged PDF: {out}") PYEOF