make me flow charts in download pdf form
cd /home/daytona/workspace/attachments/36b3fb2a-09c9-4318-a7df-da48bdbd2f00/ && python3 -c " import pdfplumber with pdfplumber.open('I now have all the content needed Let me compile t.pdf') as pdf: print(f'Pages: {len(pdf.pages)}') for i, page in enumerate(pdf.pages): text = page.extract_text() print(f'--- PAGE {i+1} ---') print(text[:3000] if text else '[no text]') print() "
mkdir -p /home/daytona/workspace/joints-flowcharts && python3 -c "import matplotlib; import reportlab; print('libs ok')"
~/joints-flowcharts/make_flowcharts.py
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 reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
import io, os
OUT_DIR = "/home/daytona/workspace/joints-flowcharts"
# ── colour palette ────────────────────────────────────────────────────────────
C_TITLE = "#1A3A5C" # deep navy
C_FALSE = "#C0392B" # crimson (false joints)
C_TRUE = "#1565C0" # royal blue (true joints)
C_SUB1 = "#E57373" # light red
C_SUB2 = "#64B5F6" # light blue
C_LEAF = "#E3F2FD" # very light blue leaf
C_LEAFR = "#FFEBEE" # very light red leaf
C_AMP = "#6A1B9A" # purple (amphiarthroses)
C_AMPL = "#F3E5F5" # light purple
C_MECH = "#2E7D32" # green (mechanics)
C_MECHL = "#E8F5E9" # light green
C_BG = "#F9FAFB"
C_TEXT = "#212121"
C_WHITE = "#FFFFFF"
C_ARROW = "#546E7A"
def text_box(ax, x, y, w, h, text, bg, fg=C_TEXT, fs=8, bold=False, radius=0.02,
edge="#90A4AE", lw=1.2, wrap=True):
box = FancyBboxPatch((x - w/2, y - h/2), w, h,
boxstyle=f"round,pad=0.01,rounding_size={radius}",
linewidth=lw, edgecolor=edge, facecolor=bg, zorder=3)
ax.add_patch(box)
weight = "bold" if bold else "normal"
ax.text(x, y, text, ha="center", va="center", fontsize=fs,
color=fg, fontweight=weight, zorder=4,
wrap=wrap, multialignment="center",
linespacing=1.3)
def arrow(ax, x0, y0, x1, y1, color=C_ARROW, lw=1.5, style="-|>"):
ax.annotate("", xy=(x1, y1), xytext=(x0, y0),
arrowprops=dict(arrowstyle=style, color=color,
lw=lw, connectionstyle="arc3,rad=0.0"),
zorder=2)
def v_line(ax, x, y0, y1, color=C_ARROW, lw=1.5):
ax.plot([x, x], [y0, y1], color=color, lw=lw, zorder=2)
def h_line(ax, x0, x1, y, color=C_ARROW, lw=1.5):
ax.plot([x0, x1], [y, y], color=color, lw=lw, zorder=2)
def save_fig(fig, name):
path = os.path.join(OUT_DIR, name)
fig.savefig(path, dpi=180, bbox_inches="tight", facecolor=C_BG)
plt.close(fig)
return path
# ══════════════════════════════════════════════════════════════════════════════
# FLOWCHART 1 – Master Classification Tree
# ══════════════════════════════════════════════════════════════════════════════
def chart1():
fig, ax = plt.subplots(figsize=(14, 11))
ax.set_xlim(0, 14); ax.set_ylim(0, 11)
ax.axis("off")
fig.patch.set_facecolor(C_BG)
ax.set_facecolor(C_BG)
# ── title banner ─────────────────────────────────────────────────────────
banner = FancyBboxPatch((0.3, 9.9), 13.4, 0.9,
boxstyle="round,pad=0.02",
linewidth=0, facecolor=C_TITLE, zorder=3)
ax.add_patch(banner)
ax.text(7, 10.35, "JOINTS — MASTER CLASSIFICATION",
ha="center", va="center", fontsize=14, color=C_WHITE,
fontweight="bold", zorder=4)
ax.text(7, 10.0, "General Anatomy & Musculoskeletal System | THIEME Atlas of Anatomy",
ha="center", va="center", fontsize=7.5, color="#B0BEC5", zorder=4)
# ── ROOT ─────────────────────────────────────────────────────────────────
text_box(ax, 7, 9.0, 2.8, 0.55, "JOINTS", C_TITLE, C_WHITE, fs=11, bold=True,
edge=C_TITLE)
# ── Two main branches ────────────────────────────────────────────────────
# FALSE JOINTS
text_box(ax, 3.0, 7.7, 3.2, 0.6, "FALSE JOINTS\n(Synarthroses)", C_FALSE,
C_WHITE, fs=9.5, bold=True, edge=C_FALSE)
# TRUE JOINTS
text_box(ax, 11.0, 7.7, 3.2, 0.6, "TRUE JOINTS\n(Diarthroses)", C_TRUE,
C_WHITE, fs=9.5, bold=True, edge=C_TRUE)
# arrows root -> branches
arrow(ax, 7, 8.72, 3.0, 8.01)
arrow(ax, 7, 8.72, 11.0, 8.01)
# False joints sub-branches
false_subs = [
(1.5, 6.3, "Syndesmoses\n(Fibrous)", C_SUB1),
(3.0, 6.3, "Synchondroses\n(Cartilaginous)", C_SUB1),
(4.6, 6.3, "Synostoses\n(Bony Fusion)", C_SUB1),
]
for (bx, by, btxt, bc) in false_subs:
text_box(ax, bx, by, 1.7, 0.6, btxt, bc, C_WHITE, fs=8.5, bold=True, edge=C_FALSE)
arrow(ax, 3.0, 7.38, bx, by+0.32)
# Syndesmoses leaves
synd_leaves = ["Interosseous\nmembranes", "Tibiofibular\nsyndesmosis",
"Fontanelles", "Gomphosis\n(teeth)"]
sx = [0.55, 1.05, 1.55, 2.05]
for i, (lx, lt) in enumerate(zip(sx, synd_leaves)):
text_box(ax, lx, 5.05, 0.85, 0.58, lt, C_LEAFR, C_TEXT, fs=7, edge=C_SUB1)
arrow(ax, 1.5, 5.97, lx, 5.35)
# Synchondroses leaves
synch_leaves = ["Epiphyseal\ngrowth plates", "Costal\ncartilage",
"Symphyses\n(pubic/IV discs)"]
syx = [2.35, 3.0, 3.65]
for lx, lt in zip(syx, synch_leaves):
text_box(ax, lx, 5.05, 1.0, 0.58, lt, C_LEAFR, C_TEXT, fs=7, edge=C_SUB1)
arrow(ax, 3.0, 5.97, lx, 5.35)
# Synostoses leaves
syn2_leaves = ["Sacrum", "Hip bone", "Ossified\nepliphyses"]
s2x = [4.0, 4.6, 5.2]
for lx, lt in zip(s2x, syn2_leaves):
text_box(ax, lx, 5.05, 0.95, 0.55, lt, C_LEAFR, C_TEXT, fs=7, edge=C_SUB1)
arrow(ax, 4.6, 5.97, lx, 5.34)
# True joints sub-branches
true_subs = [
(9.5, 6.3, "Diarthroses\n(variable DoF)", C_SUB2, C_TRUE),
(12.5, 6.3, "Amphiarthroses\n(stiff, limited)", "#AB47BC", C_AMP),
]
for (bx, by, btxt, bc, ec) in true_subs:
text_box(ax, bx, by, 2.4, 0.6, btxt, bc, C_WHITE, fs=8.5, bold=True, edge=ec)
arrow(ax, 11.0, 7.38, bx, by+0.32)
# Diarthroses -> axis sub-branches
axis_subs = [
(8.1, 4.9, "1-Axis\n(Hinge / Pivot)", "#42A5F5"),
(9.5, 4.9, "2-Axis\n(Ellipsoidal / Saddle)", "#42A5F5"),
(10.9, 4.9, "3-Axis\n(Spheroidal)", "#42A5F5"),
(12.4, 4.9, "Translational\n(Plane joints)", "#42A5F5"),
]
for (bx, by, btxt, bc) in axis_subs:
text_box(ax, bx, by, 1.9, 0.6, btxt, bc, C_WHITE, fs=7.5, bold=True,
edge=C_TRUE)
arrow(ax, 9.5, 5.97, bx, by+0.32)
# Axis leaves
ax1_lv = [("Elbow", "8.0"), ("Knee", "8.45"), ("Atlanto-axial", "8.9"),
("Proximal\nradioulnar", "9.35")]
ax1_x = [7.6, 8.05, 8.5, 8.95]
for lx, lt in zip(ax1_x, [("Elbow\njoint"), ("Knee\njoint"),
("Atlanto-axial\njoint"), ("Proximal\nRadioulnar")]):
text_box(ax, lx, 3.7, 1.0, 0.58, lt, C_LEAF, C_TEXT, fs=7, edge=C_SUB2)
arrow(ax, 8.1, 4.58, lx, 4.0)
ax2_x = [9.05, 9.95]
ax2_lv = ["Radiocarpal\n(wrist) joint", "CMC joint\nof thumb"]
for lx, lt in zip(ax2_x, ax2_lv):
text_box(ax, lx, 3.7, 1.15, 0.58, lt, C_LEAF, C_TEXT, fs=7, edge=C_SUB2)
arrow(ax, 9.5, 4.58, lx, 4.0)
ax3_x = [10.25, 11.55]
ax3_lv = ["Hip joint\n(deep socket)", "Shoulder joint\n(shallow socket)"]
for lx, lt in zip(ax3_x, ax3_lv):
text_box(ax, lx, 3.7, 1.3, 0.58, lt, C_LEAF, C_TEXT, fs=7, edge=C_SUB2)
arrow(ax, 10.9, 4.58, lx, 4.0)
text_box(ax, 12.4, 3.7, 1.4, 0.58, "Femoropatellar\nFacet joints\n(spine)",
C_LEAF, C_TEXT, fs=7, edge=C_SUB2)
arrow(ax, 12.4, 4.58, 12.4, 4.0)
# Amphiarthroses leaf
text_box(ax, 12.5, 4.9, 1.8, 0.58,
"Sacroiliac joint\n(iliosacral)", C_AMPL, C_AMP, fs=7.5, edge=C_AMP)
# already drawn above as sub-branch
# ── legend ───────────────────────────────────────────────────────────────
leg_items = [
(mpatches.Patch(color=C_FALSE, label="False Joints / Synarthroses")),
(mpatches.Patch(color=C_TRUE, label="True Joints / Diarthroses")),
(mpatches.Patch(color=C_AMP, label="Amphiarthroses")),
]
ax.legend(handles=leg_items, loc="lower left", fontsize=7.5,
framealpha=0.85, edgecolor="#CFD8DC")
ax.text(13.8, 0.1, "© THIEME Atlas of Anatomy", ha="right",
va="bottom", fontsize=6.5, color="#9E9E9E")
return save_fig(fig, "chart1_master_classification.png")
# ══════════════════════════════════════════════════════════════════════════════
# FLOWCHART 2 – Structure of a True (Synovial) Joint
# ══════════════════════════════════════════════════════════════════════════════
def chart2():
fig, ax = plt.subplots(figsize=(12, 9))
ax.set_xlim(0, 12); ax.set_ylim(0, 9)
ax.axis("off")
fig.patch.set_facecolor(C_BG)
ax.set_facecolor(C_BG)
# title
banner = FancyBboxPatch((0.3, 8.1), 11.4, 0.75,
boxstyle="round,pad=0.02",
linewidth=0, facecolor=C_TITLE, zorder=3)
ax.add_patch(banner)
ax.text(6, 8.48, "STRUCTURE OF A TRUE (SYNOVIAL) JOINT",
ha="center", va="center", fontsize=13, color=C_WHITE, fontweight="bold")
# central joint node
text_box(ax, 6, 7.15, 3.6, 0.6,
"TRUE JOINT (Diarthrosis / Synovial Joint)",
C_TRUE, C_WHITE, fs=10, bold=True, edge=C_TRUE)
# 8 components radiating out
components = [
# (x, y, title, detail, bg, edge)
(1.7, 5.9, "1. Articular Surfaces",
"Covered by hyaline cartilage\n1-2 mm (phalanges)\n5-7 mm (femoropatellar)\nException: jaw & sternoclavicular\n→ fibrous cartilage",
"#E3F2FD", C_TRUE),
(4.2, 5.9, "2. Joint Cavity",
"Contains articular recesses\nFew millimetres wide\nFilled with synovial fluid",
"#E8F5E9", C_MECH),
(6.0, 5.9, "3. Joint Capsule",
"Closed capsule\nAlar folds, synovial folds,\nsynovial villi\nInner layer = synovial membrane\n(vascular; produces synovial fluid)",
"#FFF3E0", "#E65100"),
(7.8, 5.9, "4. Synovial Fluid",
"Highly viscous\nLubricates & nourishes\navascular cartilage\n(diffusion & convection)",
"#F3E5F5", C_AMP),
(10.3, 5.9, "5. Intra-articular Structures",
"Menisci – crescent-shaped;\nknee only; collagen/fibrocartilage\nArticular discs – divide into\n2 chambers (jaw, SC, wrist)\nLabra – wedge fibrocartilage;\nhip & shoulder sockets",
"#E0F2F1", "#00695C"),
(1.7, 3.6, "6. Ligaments",
"Intra- & extracapsular\nPrimary joint stabilizers\nLimit range of motion",
"#FFF8E1", "#F57F17"),
(4.2, 3.6, "7. Muscles",
"Agonist & antagonist pairs\ncross the joint\nMove it in opposite directions\nSecondary stabilizers",
"#FCE4EC", "#C62828"),
(7.5, 3.6, "8. Synovial Bursae",
"Often near the joint\nMay communicate with\nthe joint cavity\nReduce friction",
"#E8EAF6", "#283593"),
]
for (cx, cy, title, detail, bg, edge) in components:
# title box
text_box(ax, cx, cy+0.42, 2.2, 0.52, title, edge, C_WHITE,
fs=8, bold=True, edge=edge)
# detail box
text_box(ax, cx, cy-0.52, 2.2, 1.42, detail, bg, C_TEXT,
fs=7, edge=edge, lw=1.0)
# connect title to detail
v_line(ax, cx, cy+0.16, cy+0.15)
# arrows from central node to each title box
arrow_targets = [(1.7,6.69),(4.2,6.69),(6.0,6.69),(7.8,6.69),(10.3,6.69),
(1.7,4.44),(4.2,4.44),(7.5,4.44)]
for (tx, ty) in arrow_targets:
arrow(ax, 6, 6.83, tx, ty, lw=1.2)
# important note box
note = ("Key Note: Synovial membrane can regenerate via adjoining\n"
"connective tissue — but hyaline cartilage CANNOT regenerate.\n"
"(Avascular, lacks perichondrium; nourished only by synovial fluid)")
note_box = FancyBboxPatch((1.5, 1.1), 9.0, 0.95,
boxstyle="round,pad=0.02",
linewidth=1.5, edgecolor="#F57F17", facecolor="#FFF8E1", zorder=3)
ax.add_patch(note_box)
ax.text(6, 1.58, note, ha="center", va="center", fontsize=8,
color="#BF360C", fontweight="bold", zorder=4, multialignment="center")
ax.text(11.8, 0.1, "© THIEME Atlas", ha="right",
va="bottom", fontsize=6.5, color="#9E9E9E")
return save_fig(fig, "chart2_true_joint_structure.png")
# ══════════════════════════════════════════════════════════════════════════════
# FLOWCHART 3 – Types of True Joints (Axes, DoF, Examples)
# ══════════════════════════════════════════════════════════════════════════════
def chart3():
fig, ax = plt.subplots(figsize=(13, 10))
ax.set_xlim(0, 13); ax.set_ylim(0, 10)
ax.axis("off")
fig.patch.set_facecolor(C_BG)
ax.set_facecolor(C_BG)
banner = FancyBboxPatch((0.3, 9.1), 12.4, 0.75,
boxstyle="round,pad=0.02",
linewidth=0, facecolor=C_TITLE, zorder=3)
ax.add_patch(banner)
ax.text(6.5, 9.48, "TYPES OF TRUE JOINTS — Classification by Shape & Movement",
ha="center", va="center", fontsize=12.5, color=C_WHITE, fontweight="bold")
# root
text_box(ax, 6.5, 8.35, 3.2, 0.55, "TRUE JOINTS (Diarthroses)",
C_TRUE, C_WHITE, fs=10, bold=True)
# two motion categories
text_box(ax, 3.0, 7.15, 3.0, 0.55,
"TRANSLATIONAL\n(Sliding / Plane)", "#0277BD", C_WHITE, fs=9, bold=True)
text_box(ax, 10.0, 7.15, 3.0, 0.55,
"ROTATIONAL\n(Angular)", "#1565C0", C_WHITE, fs=9, bold=True)
arrow(ax, 6.5, 8.07, 3.0, 7.44)
arrow(ax, 6.5, 8.07, 10.0, 7.44)
# Translational sub-types
trans = [
(1.8, 5.6, "Femoropatellar\n(Plane) Joint",
"1 axis | 1 DoF\nSlide up/down\nin femoral groove\nEx: Patella in groove", "#E1F5FE"),
(4.2, 5.6, "Vertebral\n(Plane) Joint",
"1-2 axes | 2-4 DoF\nGlide in multiple\ndirections\nEx: Facet joints (spine)", "#E1F5FE"),
]
for (bx, by, title, detail, bg) in trans:
text_box(ax, bx, by+0.5, 2.3, 0.55, title, "#0288D1", C_WHITE, fs=8, bold=True, edge="#0277BD")
text_box(ax, bx, by-0.38, 2.3, 1.2, detail, bg, C_TEXT, fs=7.5, edge="#0277BD")
arrow(ax, 3.0, 6.84, bx, by+0.78)
# Rotational sub-types
rot_joints = [
(7.1, 5.6, "Hinge Joint",
"1 axis | 2 DoF\nFlexion, Extension\n\nEx: Elbow, Knee",
"#BBDEFB", "#1565C0"),
(8.85, 5.6, "Pivot Joint",
"1 axis | 2 DoF\nRotation (axial)\n\nEx: Atlanto-axial,\nProximal radioulnar",
"#BBDEFB", "#1565C0"),
(10.5, 5.6, "Ellipsoidal\nJoint",
"2 axes | 4 DoF\nFlex/Ext,\nAbd/Add\nEx: Radiocarpal\n(wrist) joint",
"#C5CAE9", "#283593"),
(12.1, 5.6, "Saddle Joint",
"2 axes | 4 DoF\nFlex/Ext,\nAbd/Add\nEx: CMC joint\nof thumb",
"#C5CAE9", "#283593"),
]
for (bx, by, title, detail, bg, edge) in rot_joints:
text_box(ax, bx, by+0.5, 1.85, 0.55, title, edge, C_WHITE, fs=7.5, bold=True, edge=edge)
text_box(ax, bx, by-0.38, 1.85, 1.2, detail, bg, C_TEXT, fs=7, edge=edge)
arrow(ax, 10.0, 6.84, bx, by+0.78)
# 3-axis spheroidal – special row
text_box(ax, 9.0, 2.95, 2.5, 0.55, "Spheroidal Joint\n(Ball & Socket)",
"#0D47A1", C_WHITE, fs=9, bold=True, edge="#0D47A1")
arrow(ax, 10.0, 6.84, 9.0, 3.24)
sph = [
(7.4, 1.75, "Deep Socket\n(Hip Joint)",
"3 axes | 6 DoF\nFlex/Ext, Abd/Add\nInt/Ext rotation\nMore constrained",
"#E8EAF6", "#283593"),
(10.6, 1.75, "Shallow Socket\n(Shoulder Joint)",
"3 axes | 6 DoF\nAll 6 movements\n+ greater range\nLess constrained",
"#E8EAF6", "#283593"),
]
for (bx, by, title, detail, bg, edge) in sph:
text_box(ax, bx, by+0.48, 2.6, 0.55, title, edge, C_WHITE, fs=8, bold=True, edge=edge)
text_box(ax, bx, by-0.38, 2.6, 1.1, detail, bg, C_TEXT, fs=7.5, edge=edge)
arrow(ax, 9.0, 2.68, bx, by+0.75)
# key principle banner
kp = FancyBboxPatch((0.5, 0.15), 12.0, 0.75,
boxstyle="round,pad=0.02",
linewidth=1.5, edgecolor="#F9A825", facecolor="#FFFDE7", zorder=3)
ax.add_patch(kp)
ax.text(6.5, 0.53,
"Key Principle: More congruent surfaces (similar radius) → more sliding. "
"Less congruent → more rolling. Most joints combine roll + slide.",
ha="center", va="center", fontsize=8, color="#E65100",
fontweight="bold", zorder=4)
ax.text(12.8, 0.05, "© THIEME Atlas", ha="right",
va="bottom", fontsize=6.5, color="#9E9E9E")
return save_fig(fig, "chart3_true_joint_types.png")
# ══════════════════════════════════════════════════════════════════════════════
# FLOWCHART 4 – Joint Mechanics: Stability & Constraints
# ══════════════════════════════════════════════════════════════════════════════
def chart4():
fig, ax = plt.subplots(figsize=(12, 9))
ax.set_xlim(0, 12); ax.set_ylim(0, 9)
ax.axis("off")
fig.patch.set_facecolor(C_BG)
ax.set_facecolor(C_BG)
banner = FancyBboxPatch((0.3, 8.1), 11.4, 0.75,
boxstyle="round,pad=0.02",
linewidth=0, facecolor=C_TITLE, zorder=3)
ax.add_patch(banner)
ax.text(6, 8.48, "JOINT MECHANICS — Stability, Constraints & Erect Posture",
ha="center", va="center", fontsize=13, color=C_WHITE, fontweight="bold")
text_box(ax, 6, 7.2, 4.0, 0.6,
"JOINT STABILITY & FUNCTION",
C_MECH, C_WHITE, fs=10.5, bold=True, edge=C_MECH)
# 4 constraints
text_box(ax, 3.0, 6.05, 3.2, 0.55,
"4 CONSTRAINTS ON JOINT MOTION", "#1B5E20", C_WHITE, fs=9, bold=True)
arrow(ax, 6, 6.88, 3.0, 6.34)
constraints = [
(1.3, 4.8, "1. Bony\nConstraint", "Shape of articular\nsurfaces", "#A5D6A7"),
(2.7, 4.8, "2. Muscular\nConstraint","Muscles crossing\nthe joint", "#A5D6A7"),
(4.1, 4.8, "3. Ligamentous\nConstraint","Ligaments limiting\nrange of motion","#A5D6A7"),
(5.5, 4.8, "4. Soft-tissue\nConstraint","Joint capsule &\nsurrounding tissue","#A5D6A7"),
]
for (cx, cy, title, detail, bg) in constraints:
text_box(ax, cx, cy+0.42, 1.7, 0.58, title, C_MECH, C_WHITE,
fs=7.5, bold=True, edge=C_MECH)
text_box(ax, cx, cy-0.3, 1.7, 0.82, detail, bg, C_TEXT, fs=7.5, edge=C_MECH)
arrow(ax, 3.0, 5.77, cx, cy+0.72)
# key muscles for erect posture
text_box(ax, 9.0, 6.05, 3.2, 0.55,
"ERECT POSTURE — Key Muscles", "#1A237E", C_WHITE, fs=9, bold=True)
arrow(ax, 6, 6.88, 9.0, 6.34)
posture_data = [
(7.35, 4.65, "Triceps Surae\n+ Tibialis Anterior",
"Stabilize ankle\nin sagittal plane"),
(9.0, 4.65, "Quadriceps\nFemoris",
"Stabilizes the knee"),
(10.65, 4.65, "Gluteal Muscles",
"Stabilize hip\n(sagittal plane)"),
(7.35, 3.05, "Gluteus Medius\n& Minimus",
"Stabilize hip\n(frontal plane)"),
(9.0, 3.05, "Intrinsic Back\nMuscles",
"Stabilize spinal\ncolumn"),
]
for (cx, cy, title, detail) in posture_data:
text_box(ax, cx, cy+0.42, 2.3, 0.6, title, "#283593", C_WHITE,
fs=7.5, bold=True, edge="#1A237E")
text_box(ax, cx, cy-0.28, 2.3, 0.78, detail, "#E8EAF6", C_TEXT,
fs=7.5, edge="#283593")
arrow(ax, 9.0, 5.77, cx, cy+0.72)
# Roll-Slide note
rs_box = FancyBboxPatch((0.5, 1.45), 11.0, 0.82,
boxstyle="round,pad=0.02",
linewidth=1.5, edgecolor=C_MECH, facecolor="#E8F5E9", zorder=3)
ax.add_patch(rs_box)
ax.text(6, 1.86,
"Roll-Slide Principle:\n"
"Congruent surfaces (e.g. shoulder) → surfaces slide onto each other\n"
"Less congruent (e.g. knee) → surfaces roll off each other | Most joints = combination",
ha="center", va="center", fontsize=7.5,
color="#1B5E20", fontweight="bold", zorder=4, multialignment="center")
# Amphiarthroses note
amp_box = FancyBboxPatch((0.5, 0.25), 5.0, 0.9,
boxstyle="round,pad=0.02",
linewidth=1.5, edgecolor=C_AMP, facecolor=C_AMPL, zorder=3)
ax.add_patch(amp_box)
ax.text(3.0, 0.7,
"Amphiarthroses — stiff true joints with very limited\n"
"movement due to tight ligamentous constraints.\n"
"Example: Sacroiliac (iliosacral) joint",
ha="center", va="center", fontsize=7.5, color=C_AMP,
fontweight="bold", zorder=4, multialignment="center")
ax.text(11.8, 0.05, "© THIEME Atlas", ha="right",
va="bottom", fontsize=6.5, color="#9E9E9E")
return save_fig(fig, "chart4_joint_mechanics.png")
# ══════════════════════════════════════════════════════════════════════════════
# Assemble all 4 charts into a single PDF
# ══════════════════════════════════════════════════════════════════════════════
def build_pdf(img_paths):
pdf_path = os.path.join(OUT_DIR, "Joints_Flowcharts.pdf")
W, H = A4 # 595.27 x 841.89 pts
c = canvas.Canvas(pdf_path, pagesize=A4)
chart_titles = [
"Flowchart 1: Master Classification of Joints",
"Flowchart 2: Structure of a True Synovial Joint",
"Flowchart 3: Types of True Joints — By Shape & Movement",
"Flowchart 4: Joint Mechanics — Stability & Erect Posture",
]
for i, (img_path, title) in enumerate(zip(img_paths, chart_titles)):
# header
c.setFillColorRGB(0.10, 0.23, 0.36)
c.rect(0, H - 42, W, 42, fill=1, stroke=0)
c.setFillColorRGB(1, 1, 1)
c.setFont("Helvetica-Bold", 13)
c.drawCentredString(W/2, H - 27, title)
c.setFont("Helvetica", 8)
c.setFillColorRGB(0.69, 0.74, 0.77)
c.drawCentredString(W/2, H - 38,
"THIEME Atlas of Anatomy — General Anatomy & Musculoskeletal System")
# image
img = ImageReader(img_path)
iw, ih = img.getSize()
scale = min((W - 40) / iw, (H - 90) / ih)
draw_w = iw * scale
draw_h = ih * scale
x = (W - draw_w) / 2
y = (H - 50) - draw_h
c.drawImage(img_path, x, y, width=draw_w, height=draw_h,
preserveAspectRatio=True)
# footer
c.setFillColorRGB(0.62, 0.71, 0.76)
c.setFont("Helvetica", 7)
c.drawString(30, 15, f"Page {i+1} of {len(img_paths)}")
c.drawRightString(W - 30, 15, "Source: THIEME Atlas — ISBN 9781626237186, pp. 60-71")
if i < len(img_paths) - 1:
c.showPage()
c.save()
return pdf_path
if __name__ == "__main__":
print("Generating flowcharts...")
p1 = chart1()
print(f" ✓ Chart 1: {p1}")
p2 = chart2()
print(f" ✓ Chart 2: {p2}")
p3 = chart3()
print(f" ✓ Chart 3: {p3}")
p4 = chart4()
print(f" ✓ Chart 4: {p4}")
pdf = build_pdf([p1, p2, p3, p4])
print(f" ✓ PDF assembled: {pdf}")
python3 /home/daytona/workspace/joints-flowcharts/make_flowcharts.py 2>&1
Joints Flowcharts
PDF Document · PDF