How to understand cbc report
how to understand CBC complete blood count report normal values
| Group | Normal Range |
|---|---|
| Adult males | 13.5 - 17.5 g/dL |
| Adult females | 12.0 - 15.5 g/dL |
| Children | varies by age |
| Index | What it means | Normal | Low means | High means |
|---|---|---|---|---|
| MCV (Mean Corpuscular Volume) | Average RBC size | 80-100 fL | Microcytic (small) - iron deficiency, thalassemia | Macrocytic (large) - B12/folate deficiency |
| MCH (Mean Corpuscular Hemoglobin) | Avg hemoglobin per cell | 27-33 pg | Hypochromic - iron deficiency | - |
| MCHC (Mean Corpuscular Hgb Concentration) | Hemoglobin concentration in cells | 32-36 g/dL | Iron deficiency | Spherocytosis |
| RDW (Red Cell Distribution Width) | Variation in RBC size | 11.5-14.5% | (low rarely matters) | Mixed deficiency, early iron/B12 deficiency |
| Cell Type | Normal % | Function | Elevated in | Low in |
|---|---|---|---|---|
| Neutrophils | 50-70% | Fight bacteria | Bacterial infection, stress, steroids | Viral infection, chemo |
| Lymphocytes | 20-40% | Fight viruses, antibodies | Viral infections, CLL | HIV, steroids |
| Monocytes | 2-8% | Cleanup, chronic infection | TB, fungal infections, monocytic leukemia | - |
| Eosinophils | 1-4% | Allergies, parasites | Allergies, asthma, parasitic infections | Steroids |
| Basophils | 0.01-0.08 × 10⁹/L | Inflammation/allergy | CML, hypothyroidism, chronic inflammation | - |
When WBC is abnormal, always look at the differential - it tells you which immune cells are affected, pointing to the cause. - Robbins & Kumar Basic Pathology
| Platelet Count | Clinical Risk |
|---|---|
| 50,000-100,000 | Minor bleeding risk |
| 20,000-50,000 | Moderate bleeding risk |
| <20,000 | Spontaneous bleeding risk - urgent |
| Pattern | What to Think |
|---|---|
| Low Hgb + Low MCV + Low MCH | Iron deficiency anemia |
| Low Hgb + High MCV | B12 or folate deficiency |
| High WBC + high neutrophils | Bacterial infection |
| High WBC + high lymphocytes | Viral infection |
| Low WBC + low platelets + low Hgb (pancytopenia) | Bone marrow failure, leukemia, aplastic anemia - urgent |
| Low platelets only | ITP, dengue, drug-induced |
| High RBC + high Hgb + high Hct | Polycythemia vera or secondary polycythemia |
Note: This guide is for educational understanding only. Always discuss your specific results with your doctor, who interprets them in the context of your symptoms, history, and other tests.
Create a quick-reference CBC cheat sheet PDF I can print
mkdir -p /home/daytona/workspace/cbc-cheatsheet && ls /home/daytona/workspace/cb…
~/cbc-cheatsheet/generate_cbc_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import PageBreak
OUTPUT = "/home/daytona/workspace/cbc-cheatsheet/CBC_Cheat_Sheet.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
C_NAVY = colors.HexColor("#0B2C5F") # header / title bar
C_BLUE = colors.HexColor("#1565C0") # section titles
C_LBLUE = colors.HexColor("#E3F0FB") # alternating row tint
C_RED = colors.HexColor("#B71C1C") # "low" labels
C_GREEN = colors.HexColor("#1B5E20") # "normal" labels
C_ORANGE = colors.HexColor("#E65100") # "high" labels
C_GREY = colors.HexColor("#F5F5F5") # light grey rows
C_WHITE = colors.white
C_GOLD = colors.HexColor("#F9A825") # accent line
# ── Document ────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
topMargin=12*mm, bottomMargin=12*mm,
leftMargin=14*mm, rightMargin=14*mm,
title="CBC Quick-Reference Cheat Sheet",
author="Orris Medical Assistant"
)
styles = getSampleStyleSheet()
def style(name="Normal", **kw):
return ParagraphStyle(name, parent=styles[name], **kw)
H1 = style("Heading1", fontSize=18, textColor=C_WHITE, alignment=TA_CENTER,
spaceAfter=0, spaceBefore=0, leading=22, fontName="Helvetica-Bold")
H2 = style("Heading2", fontSize=10, textColor=C_BLUE, spaceBefore=6,
spaceAfter=2, fontName="Helvetica-Bold")
BODY = style("Normal", fontSize=7.5, leading=10, textColor=colors.HexColor("#222222"))
SMALL = style("Normal", fontSize=6.5, leading=9, textColor=colors.HexColor("#444444"),
fontName="Helvetica-Oblique")
FOOT = style("Normal", fontSize=6, textColor=colors.HexColor("#888888"),
alignment=TA_CENTER)
def cell(text, bold=False, color=colors.black, size=7.5, align=TA_LEFT, bg=None):
fn = "Helvetica-Bold" if bold else "Helvetica"
p = Paragraph(f"<font name='{fn}' color='{color.hexval() if hasattr(color,'hexval') else color}' size='{size}'>{text}</font>",
ParagraphStyle("c", alignment=align, leading=size+2, spaceBefore=1, spaceAfter=1))
return p
def section_title(text):
return Paragraph(f"<b>{text}</b>",
ParagraphStyle("st", fontSize=10, textColor=C_WHITE, fontName="Helvetica-Bold",
alignment=TA_LEFT, leading=14, leftIndent=4, spaceBefore=0, spaceAfter=0))
def make_section_header(text):
tbl = Table([[section_title(text)]], colWidths=[182*mm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_NAVY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("ROUNDEDCORNERS", [3,3,3,3]),
]))
return tbl
# ════════════════════════════════════════════════════════════════════════════
# Title banner
# ════════════════════════════════════════════════════════════════════════════
title_table = Table(
[[Paragraph("<b>CBC — Complete Blood Count</b><br/><font size='9'>Quick-Reference Cheat Sheet</font>", H1)]],
colWidths=[182*mm]
)
title_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_NAVY),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [5,5,5,5]),
]))
# ════════════════════════════════════════════════════════════════════════════
# Helper: coloured badge
# ════════════════════════════════════════════════════════════════════════════
def badge(text, color):
hex_c = color.hexval() if hasattr(color, 'hexval') else str(color)
return Paragraph(
f"<font name='Helvetica-Bold' color='{hex_c}' size='7.5'>{text}</font>",
ParagraphStyle("b", alignment=TA_CENTER, leading=10)
)
# ════════════════════════════════════════════════════════════════════════════
# 1. RED BLOOD CELL PANEL
# ════════════════════════════════════════════════════════════════════════════
rbc_header = [
[cell("Parameter", bold=True), cell("Normal Range", bold=True),
cell("↓ Low = ...", bold=True, color=C_RED), cell("↑ High = ...", bold=True, color=C_ORANGE)]
]
rbc_data = [
["Hemoglobin\n(Hgb)", "M: 13.5–17.5 g/dL\nF: 12.0–15.5 g/dL", "Anemia, bleeding,\nmalnutrition", "Polycythemia, dehydration,\nhigh altitude"],
["Hematocrit\n(Hct / PCV)", "M: 41–53%\nF: 36–46%", "Anemia (parallels Hgb)", "Same as high Hgb"],
["RBC Count", "M: 4.5–5.9 M/µL\nF: 4.0–5.2 M/µL", "Anemia, chronic\ndisease", "Polycythemia vera,\nCOPD, smoking"],
["MCV\n(cell size)", "80–100 fL", "Microcytic → iron def,\nthalassemia", "Macrocytic → B12/folate\ndeficiency, alcohol"],
["MCH", "27–33 pg", "Hypochromic →\niron deficiency", "Hyperchromic →\nspherocytosis"],
["MCHC", "32–36 g/dL", "Iron deficiency\nanemia", "Hereditary\nspherocytosis"],
["RDW", "11.5–14.5%", "Uniform cells\n(rarely clinically low)", "Mixed deficiency,\nearly anemia"],
["Reticulocytes", "0.5–1.5%", "Bone marrow failure,\naplastic anemia", "Hemolysis, acute\nbleed, iron therapy"],
]
rbc_rows = rbc_header[:]
for i, row in enumerate(rbc_data):
rbc_rows.append([
cell(row[0], bold=True, size=7.5),
cell(row[1], size=7.5),
badge(row[2], C_RED),
badge(row[3], C_ORANGE)
])
rbc_col_widths = [33*mm, 38*mm, 55*mm, 56*mm]
rbc_table = Table(rbc_rows, colWidths=rbc_col_widths, repeatRows=1)
rbc_style = TableStyle([
("BACKGROUND", (0,0), (-1,0), C_BLUE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 8),
("ALIGN", (0,0), (-1,0), "CENTER"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_WHITE, C_LBLUE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BBDEFB")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
])
rbc_table.setStyle(rbc_style)
# ════════════════════════════════════════════════════════════════════════════
# 2. WHITE BLOOD CELL PANEL
# ════════════════════════════════════════════════════════════════════════════
wbc_total_header = [[cell("Total WBC Count", bold=True), cell("Normal: 4,000–11,000 cells/µL (4.0–11.0 × 10⁹/L)", size=7.5),
cell("Low (Leukopenia) → viral infx, chemo, autoimmune", size=7.5, color=C_RED),
cell("High (Leukocytosis) → bacterial infx, steroids, leukemia", size=7.5, color=C_ORANGE)]]
wbc_total = Table(wbc_total_header, colWidths=rbc_col_widths)
wbc_total.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), C_BLUE),
("TEXTCOLOR", (0,0), (0,0), C_WHITE),
("FONTNAME", (0,0), (0,0), "Helvetica-Bold"),
("BACKGROUND", (1,0), (-1,0), C_LBLUE),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BBDEFB")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("FONTSIZE", (1,0), (-1,0), 7.5),
]))
diff_header = [[cell("Cell Type", bold=True), cell("Normal %", bold=True),
cell("Function", bold=True), cell("↑ Elevated in", bold=True),
cell("↓ Low in", bold=True)]]
diff_data = [
["Neutrophils", "50–70%", "Kill bacteria\n(first responders)", "Bacterial infx, stress,\nsteroids, MI", "Viral infx, chemo,\nautoimmune (neutropenia)"],
["Lymphocytes", "20–40%", "Viral defense,\nantibody production", "Viral infx, CLL,\nmononucleosis", "HIV/AIDS, steroids,\nradiation"],
["Monocytes", "2–8%", "Phagocytosis,\nchronic infections", "TB, fungal infx,\nmonocytic leukemia", "Aplastic anemia,\nsteroids (rare)"],
["Eosinophils", "1–4%", "Allergies,\nparasite killing", "Allergies, asthma,\nparasites, eosinophilia", "Steroids, Cushing's\nsyndrome"],
["Basophils", "0.01–0.08×10⁹/L","Allergy / inflammation\nmediators", "CML, hypothyroidism,\nchronic inflammation", "Hyperthyroidism,\nacute infection"],
]
diff_col_widths = [28*mm, 22*mm, 36*mm, 48*mm, 48*mm]
diff_rows = diff_header[:]
for i, row in enumerate(diff_data):
diff_rows.append([
cell(row[0], bold=True),
cell(row[1], size=7.5),
cell(row[2], size=7),
badge(row[3], C_ORANGE),
badge(row[4], C_RED)
])
diff_table = Table(diff_rows, colWidths=diff_col_widths, repeatRows=1)
diff_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_BLUE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 8),
("ALIGN", (0,0), (-1,0), "CENTER"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_WHITE, C_LBLUE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BBDEFB")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
# ════════════════════════════════════════════════════════════════════════════
# 3. PLATELET PANEL
# ════════════════════════════════════════════════════════════════════════════
plt_header = [[cell("Parameter", bold=True), cell("Normal Range", bold=True),
cell("↓ Low", bold=True), cell("↑ High", bold=True)]]
plt_data = [
["Platelet Count\n(PLT)", "150,000–400,000/µL\n(150–400 × 10⁹/L)",
"Thrombocytopenia: ITP, dengue,\nchemo, liver disease, DIC",
"Thrombocytosis: infection,\niron def, ET, inflammation"],
["MPV\n(Mean Platelet Vol)", "7–13 fL",
"Small platelets → chemo,\napplastic anemia",
"Large platelets → ITP,\ndengue recovery, hyperthyroid"],
]
plt_col_widths = [33*mm, 38*mm, 55*mm, 56*mm]
plt_rows = plt_header[:]
for row in plt_data:
plt_rows.append([
cell(row[0], bold=True),
cell(row[1], size=7.5),
badge(row[2], C_RED),
badge(row[3], C_ORANGE)
])
plt_table = Table(plt_rows, colWidths=plt_col_widths)
plt_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_BLUE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 8),
("ALIGN", (0,0), (-1,0), "CENTER"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_WHITE, C_LBLUE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BBDEFB")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
plt_risk_header = [[cell("Platelet Count", bold=True), cell("Bleeding Risk", bold=True),
cell("Clinical Action", bold=True)]]
plt_risk_data = [
[">100,000", badge("Minimal", C_GREEN), cell("Routine monitoring")],
["50,000–100,000", badge("Minor risk", colors.HexColor("#F57F17")), cell("Caution with procedures")],
["20,000–50,000", badge("Moderate", C_ORANGE), cell("Avoid invasive procedures")],
["<20,000", badge("SPONTANEOUS\nBLEEDING", C_RED), cell("Urgent — notify doctor immediately")],
]
plt_risk_col_widths = [45*mm, 45*mm, 92*mm]
plt_risk_rows = plt_risk_header[:]
for i, row in enumerate(plt_risk_data):
plt_risk_rows.append(row)
plt_risk_table = Table(plt_risk_rows, colWidths=plt_risk_col_widths)
plt_risk_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#37474F")),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 8),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_WHITE, C_GREY]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CFD8DC")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,1), (0,-1), "CENTER"),
("BACKGROUND", (0,4), (-1,4), colors.HexColor("#FFEBEE")), # last row highlight
]))
# ════════════════════════════════════════════════════════════════════════════
# 4. CLINICAL PATTERNS
# ════════════════════════════════════════════════════════════════════════════
pat_header = [[cell("CBC Pattern", bold=True), cell("Think of ...", bold=True),
cell("Key next step", bold=True)]]
pat_data = [
["↓ Hgb + ↓ MCV + ↓ MCH", "Iron deficiency anemia", "Serum ferritin, iron studies"],
["↓ Hgb + ↑ MCV", "B12 or folate deficiency", "Serum B12, folate, peripheral smear"],
["↓ Hgb + normal MCV", "Anemia of chronic disease, acute loss", "Reticulocyte count, CRP"],
["↑ WBC + ↑ neutrophils", "Bacterial infection / sepsis", "Blood culture, CRP, procalcitonin"],
["↑ WBC + ↑ lymphocytes", "Viral infection (EBV, CMV, COVID)", "Peripheral smear, viral serology"],
["↓ WBC + ↓ Hgb + ↓ Platelets\n(PANCYTOPENIA)", "Bone marrow failure, leukemia,\naaplastic anemia — URGENT", "Urgent haematology referral, bone\nmarrow biopsy"],
["↓ Platelets only", "ITP, dengue, drug-induced, DIC", "Peripheral smear, dengue NS1/IgM"],
["↑ RBC + ↑ Hgb + ↑ Hct", "Polycythemia vera or secondary", "JAK2 mutation, EPO level"],
["↑ WBC with blasts on smear", "Acute leukemia — URGENT", "Urgent haematology referral"],
]
pat_col_widths = [55*mm, 62*mm, 65*mm]
pat_rows = pat_header[:]
for i, row in enumerate(pat_data):
is_urgent = "URGENT" in row[0] or "URGENT" in row[1]
bg = colors.HexColor("#FFEBEE") if is_urgent else (C_WHITE if i%2==0 else C_LBLUE)
pat_rows.append([
cell(row[0], bold=is_urgent, color=(C_RED if is_urgent else colors.black), size=7.5),
cell(row[1], bold=is_urgent, color=(C_RED if is_urgent else colors.black), size=7.5),
cell(row[2], size=7.5),
])
pat_table = Table(pat_rows, colWidths=pat_col_widths, repeatRows=1)
pat_style = TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#37474F")),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 8),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_WHITE, C_LBLUE]),
("BACKGROUND", (0,4), (-1,4), colors.HexColor("#FFEBEE")),
("BACKGROUND", (0,7), (-1,7), colors.HexColor("#FFEBEE")),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CFD8DC")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
])
pat_table.setStyle(pat_style)
# ════════════════════════════════════════════════════════════════════════════
# 5. TIPS BOX
# ════════════════════════════════════════════════════════════════════════════
tips_data = [
[cell("✔ Tips for Reading Any CBC Report", bold=True, size=8, color=C_WHITE)],
[Paragraph(
"1. <b>Lab ranges vary</b> — always use the reference range printed on YOUR report, not memorised numbers.<br/>"
"2. <b>Trends matter more than single values</b> — a declining count over weeks is more concerning than one borderline result.<br/>"
"3. <b>Context is everything</b> — a Hgb of 11 means different things in a pregnant woman vs. an elderly man.<br/>"
"4. <b>Flags (H/L)</b> on a report are triggers to look further, not automatic diagnoses.<br/>"
"5. <b>A CBC rarely gives a final diagnosis alone</b> — it directs the next test.<br/>"
"6. <b>Pancytopenia</b> (all three lines low) always requires urgent investigation.",
ParagraphStyle("tips_body", fontSize=7.5, leading=11, textColor=colors.HexColor("#1A237E"),
leftIndent=4, spaceBefore=2, spaceAfter=2)
)]
]
tips_table = Table(tips_data, colWidths=[182*mm])
tips_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#1565C0")),
("BACKGROUND", (0,1), (-1,1), colors.HexColor("#E8EAF6")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 0.8, C_BLUE),
]))
# ════════════════════════════════════════════════════════════════════════════
# 6. ANEMIA QUICK GUIDE mini-box
# ════════════════════════════════════════════════════════════════════════════
anemia_header = [[cell("Anemia Type", bold=True), cell("MCV", bold=True),
cell("Key Cause", bold=True), cell("Distinguishing Test", bold=True)]]
anemia_data = [
["Microcytic (small)", "< 80 fL", "Iron deficiency, thalassemia, lead poisoning", "Serum ferritin, Hgb electrophoresis"],
["Normocytic (normal)", "80–100 fL", "Chronic disease, acute blood loss, renal disease", "Reticulocyte count, EPO level"],
["Macrocytic (large)", "> 100 fL", "B12/folate deficiency, alcohol, hypothyroidism", "Serum B12, folate, TSH"],
]
anemia_col_widths = [40*mm, 20*mm, 65*mm, 57*mm]
anemia_rows = anemia_header[:]
for i, row in enumerate(anemia_data):
anemia_rows.append([cell(row[0], bold=True), cell(row[1]), cell(row[2]), cell(row[3])])
anemia_table = Table(anemia_rows, colWidths=anemia_col_widths)
anemia_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#00796B")),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_WHITE, colors.HexColor("#E0F2F1")]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#80CBC4")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
]))
# ════════════════════════════════════════════════════════════════════════════
# Footer disclaimer
# ════════════════════════════════════════════════════════════════════════════
footer = Paragraph(
"⚕ This cheat sheet is for educational reference only. Always interpret results in clinical context with a qualified healthcare provider.",
FOOT
)
# ════════════════════════════════════════════════════════════════════════════
# Assemble story
# ════════════════════════════════════════════════════════════════════════════
story = [
title_table,
Spacer(1, 3*mm),
# ── RBC ──────────────────────────────────────────────────────────────
make_section_header("🩸 RED BLOOD CELL (RBC) PANEL — Oxygen Carrying Capacity"),
Spacer(1, 1*mm),
rbc_table,
Spacer(1, 3*mm),
# ── Anemia sub-guide ─────────────────────────────────────────────────
make_section_header("🔬 ANEMIA QUICK CLASSIFICATION BY MCV"),
Spacer(1, 1*mm),
anemia_table,
Spacer(1, 3*mm),
# ── WBC ──────────────────────────────────────────────────────────────
make_section_header("🛡️ WHITE BLOOD CELL (WBC) PANEL — Immune System"),
Spacer(1, 1*mm),
wbc_total,
Spacer(1, 1.5*mm),
diff_table,
Spacer(1, 3*mm),
# ── Platelets ─────────────────────────────────────────────────────────
make_section_header("🩹 PLATELET PANEL — Clotting & Bleeding"),
Spacer(1, 1*mm),
plt_table,
Spacer(1, 1.5*mm),
Paragraph("<b>Platelet Count vs. Bleeding Risk</b>",
ParagraphStyle("sub", fontSize=8, textColor=colors.HexColor("#37474F"),
fontName="Helvetica-Bold", spaceBefore=2, spaceAfter=2)),
plt_risk_table,
Spacer(1, 3*mm),
# ── Patterns ─────────────────────────────────────────────────────────
make_section_header("🧩 CLINICAL PATTERNS — What Does This CBC Suggest?"),
Spacer(1, 1*mm),
pat_table,
Spacer(1, 3*mm),
# ── Tips ─────────────────────────────────────────────────────────────
tips_table,
Spacer(1, 3*mm),
HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#BBDEFB")),
Spacer(1, 2*mm),
footer,
]
doc.build(story)
print(f"PDF saved to: {OUTPUT}")
cd /home/daytona/workspace/cbc-cheatsheet && python generate_cbc_pdf.py 2>&1
ls -lh /home/daytona/workspace/cbc-cheatsheet/CBC_Cheat_Sheet.pdf
CBC Cheat Sheet
PDF Document · PDF
| Section | What's in it |
|---|---|
| RBC Panel | Hgb, Hct, RBC count, MCV, MCH, MCHC, RDW, Reticulocytes - normal ranges + causes of high/low |
| Anemia Classification | Microcytic / Normocytic / Macrocytic with key causes and next tests |
| WBC Panel | Total WBC count + full differential (Neutrophils, Lymphocytes, Monocytes, Eosinophils, Basophils) |
| Platelet Panel | PLT count, MPV + a bleeding risk table by platelet level |
| Clinical Patterns | 9 common CBC patterns mapped to diagnoses (urgent ones highlighted in red) |
| Tips box | 6 practical reminders for interpreting any CBC |
Flowchart give me
~/cbc-cheatsheet/generate_anemia_flowchart.py
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
fig, ax = plt.subplots(figsize=(18, 26))
ax.set_xlim(0, 18)
ax.set_ylim(0, 26)
ax.axis("off")
fig.patch.set_facecolor("#F8FAFF")
# ── Colour palette ──────────────────────────────────────────────────────────
C_NAVY = "#0B2C5F"
C_BLUE = "#1565C0"
C_LBLUE = "#BBDEFB"
C_TEAL = "#00796B"
C_LTEAL = "#E0F2F1"
C_RED = "#C62828"
C_LRED = "#FFEBEE"
C_ORANGE = "#E65100"
C_LORANGE= "#FFF3E0"
C_GREEN = "#2E7D32"
C_LGREEN = "#E8F5E9"
C_PURPLE = "#6A1B9A"
C_LPURPLE= "#F3E5F5"
C_AMBER = "#F57F17"
C_LAMBER = "#FFFDE7"
C_GREY = "#37474F"
C_LGREY = "#ECEFF1"
C_WHITE = "#FFFFFF"
def box(ax, x, y, w, h, text, bg="#FFFFFF", edge="#1565C0", fontsize=10,
bold=False, text_color="#000000", radius=0.25, wrap=True, va="center"):
rect = FancyBboxPatch((x - w/2, y - h/2), w, h,
boxstyle=f"round,pad=0.05,rounding_size={radius}",
facecolor=bg, edgecolor=edge, linewidth=1.6, zorder=3)
ax.add_patch(rect)
weight = "bold" if bold else "normal"
ax.text(x, y, text, ha="center", va=va, fontsize=fontsize, color=text_color,
fontweight=weight, zorder=4, wrap=wrap,
multialignment="center",
bbox=dict(boxstyle="square,pad=0", fc="none", ec="none"))
def diamond(ax, x, y, w, h, text, bg="#FFF9C4", edge="#F9A825",
fontsize=9.5, text_color="#000000"):
from matplotlib.patches import Polygon
import numpy as np
pts = np.array([[x, y+h/2], [x+w/2, y], [x, y-h/2], [x-w/2, y]])
poly = Polygon(pts, closed=True, facecolor=bg, edgecolor=edge, linewidth=1.8, zorder=3)
ax.add_patch(poly)
ax.text(x, y, text, ha="center", va="center", fontsize=fontsize,
color=text_color, fontweight="bold", zorder=4, multialignment="center")
def arrow(ax, x1, y1, x2, y2, label="", color="#455A64", lw=1.8):
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle="-|>", color=color, lw=lw,
mutation_scale=16),
zorder=2)
if label:
mx, my = (x1+x2)/2, (y1+y2)/2
ax.text(mx + 0.15, my, label, fontsize=8.5, color=color,
fontstyle="italic", fontweight="bold", zorder=5)
# ══════════════════════════════════════════════════════════════════════════════
# TITLE
# ══════════════════════════════════════════════════════════════════════════════
title_rect = FancyBboxPatch((0.3, 24.6), 17.4, 1.1,
boxstyle="round,pad=0.1,rounding_size=0.3",
facecolor=C_NAVY, edgecolor=C_NAVY, linewidth=0, zorder=3)
ax.add_patch(title_rect)
ax.text(9, 25.22, "ANEMIA WORKUP FLOWCHART", ha="center", va="center",
fontsize=19, color="white", fontweight="bold", zorder=4)
ax.text(9, 24.82, "CBC-Based Diagnostic Approach", ha="center", va="center",
fontsize=11.5, color=C_LBLUE, zorder=4)
# ══════════════════════════════════════════════════════════════════════════════
# START
# ══════════════════════════════════════════════════════════════════════════════
box(ax, 9, 23.8, 5, 0.7,
"Patient with suspected Anemia\n(Fatigue, pallor, breathlessness)",
bg=C_LGREY, edge=C_GREY, fontsize=10, bold=True, text_color=C_GREY)
arrow(ax, 9, 23.44, 9, 22.8)
# ══════════════════════════════════════════════════════════════════════════════
# CBC ORDER
# ══════════════════════════════════════════════════════════════════════════════
box(ax, 9, 22.5, 5.5, 0.55,
"Order CBC → Check Hemoglobin (Hgb)",
bg=C_BLUE, edge=C_BLUE, fontsize=10.5, bold=True, text_color=C_WHITE)
arrow(ax, 9, 22.22, 9, 21.55)
# ══════════════════════════════════════════════════════════════════════════════
# DIAMOND 1: Is Hgb Low?
# ══════════════════════════════════════════════════════════════════════════════
diamond(ax, 9, 21.1, 5.2, 0.85,
"Is Hgb LOW?\n(M < 13.5 | F < 12.0 g/dL)",
bg=C_LAMBER, edge=C_AMBER, fontsize=10, text_color=C_GREY)
# NO branch (right)
arrow(ax, 11.6, 21.1, 14.5, 21.1, color=C_GREEN)
box(ax, 16, 21.1, 3, 0.65,
"✔ Hgb Normal\nNo anemia on CBC",
bg=C_LGREEN, edge=C_GREEN, fontsize=9, bold=False, text_color=C_GREEN)
ax.text(12.8, 21.25, "NO", fontsize=9, color=C_GREEN, fontweight="bold")
# YES branch (down)
arrow(ax, 9, 20.67, 9, 20.05, color=C_RED)
ax.text(9.18, 20.35, "YES", fontsize=9, color=C_RED, fontweight="bold")
# ══════════════════════════════════════════════════════════════════════════════
# CONFIRM + RETICULOCYTE
# ══════════════════════════════════════════════════════════════════════════════
box(ax, 9, 19.7, 6, 0.55,
"Confirm with Hematocrit (Hct) | Check MCV + Reticulocyte Count",
bg=C_LBLUE, edge=C_BLUE, fontsize=9.5, bold=True, text_color=C_NAVY)
arrow(ax, 9, 19.42, 9, 18.75)
# ══════════════════════════════════════════════════════════════════════════════
# DIAMOND 2: MCV
# ══════════════════════════════════════════════════════════════════════════════
diamond(ax, 9, 18.3, 5.4, 0.85,
"What is the MCV\n(Mean Corpuscular Volume)?",
bg=C_LAMBER, edge=C_AMBER, fontsize=10, text_color=C_GREY)
# ── THREE BRANCHES FROM MCV ──────────────────────────────────────────────────
# LEFT: LOW MCV (<80)
arrow(ax, 6.3, 18.3, 3.0, 18.3, color=C_RED)
ax.text(4.3, 18.5, "< 80 fL\n(Microcytic)", fontsize=8.5, color=C_RED,
fontweight="bold", ha="center")
# MIDDLE: NORMAL MCV
arrow(ax, 9, 17.87, 9, 17.2, color=C_TEAL)
ax.text(9.2, 17.52, "80–100 fL\n(Normocytic)", fontsize=8.5, color=C_TEAL,
fontweight="bold")
# RIGHT: HIGH MCV (>100)
arrow(ax, 11.7, 18.3, 15.0, 18.3, color=C_PURPLE)
ax.text(13.0, 18.5, "> 100 fL\n(Macrocytic)", fontsize=8.5, color=C_PURPLE,
fontweight="bold", ha="center")
# ════════════════════════════
# LEFT BRANCH: MICROCYTIC
# ════════════════════════════
box(ax, 3, 17.65, 4.8, 0.7,
"MICROCYTIC ANEMIA\nCheck: Serum Ferritin + Iron Studies",
bg=C_LRED, edge=C_RED, fontsize=9.5, bold=True, text_color=C_RED)
arrow(ax, 3, 17.3, 3, 16.65, color=C_RED)
diamond(ax, 3, 16.2, 4.8, 0.85,
"Serum Ferritin\nLow or Normal?",
bg="#FFF8E1", edge=C_AMBER, fontsize=9.5, text_color=C_GREY)
# Ferritin LOW → Iron deficiency
arrow(ax, 3, 15.77, 3, 15.1, color=C_RED)
ax.text(3.18, 15.42, "LOW", fontsize=8.5, color=C_RED, fontweight="bold")
box(ax, 3, 14.7, 4.6, 0.72,
"⚠ IRON DEFICIENCY ANEMIA\n→ Oral iron, investigate source\n (diet, GI blood loss, menorrhagia)",
bg=C_LRED, edge=C_RED, fontsize=9, bold=True, text_color=C_RED)
# Ferritin NORMAL/HIGH → Thalassemia / ACD
arrow(ax, 0.6, 16.2, 0.2, 16.2, color=C_ORANGE)
arrow(ax, 0.2, 16.2, 0.2, 13.65, color=C_ORANGE)
arrow(ax, 0.2, 13.65, 0.6, 13.65, color=C_ORANGE)
ax.text(0.22, 14.9, "NORMAL/\nHIGH", fontsize=8, color=C_ORANGE,
fontweight="bold", rotation=90)
box(ax, 2.5, 13.65, 4.2, 0.82,
"Thalassemia / Lead Poisoning\n→ Hgb Electrophoresis, blood lead level\n RDW: low in thal, high in iron def",
bg=C_LORANGE, edge=C_ORANGE, fontsize=8.8, bold=False, text_color=C_ORANGE)
# ════════════════════════════
# MIDDLE BRANCH: NORMOCYTIC
# ════════════════════════════
box(ax, 9, 16.85, 5.2, 0.65,
"NORMOCYTIC ANEMIA\nCheck: Reticulocyte Count",
bg=C_LTEAL, edge=C_TEAL, fontsize=9.5, bold=True, text_color=C_TEAL)
arrow(ax, 9, 16.52, 9, 15.82, color=C_TEAL)
diamond(ax, 9, 15.38, 5.0, 0.85,
"Reticulocyte Count\nHigh or Low?",
bg="#FFF8E1", edge=C_AMBER, fontsize=9.5, text_color=C_GREY)
# Retic HIGH → Hemolytic / Blood loss
arrow(ax, 11.5, 15.38, 14.4, 15.38, color=C_ORANGE)
ax.text(12.5, 15.55, "HIGH\n(> 2%)", fontsize=8.5, color=C_ORANGE, fontweight="bold")
box(ax, 16, 15.38, 3.6, 0.88,
"Hemolytic Anemia / Acute Blood Loss\n→ Peripheral smear, LDH,\n Coombs test, bilirubin",
bg=C_LORANGE, edge=C_ORANGE, fontsize=8.8, text_color=C_ORANGE)
# Retic LOW → Hypoproliferative
arrow(ax, 9, 14.95, 9, 14.28, color=C_TEAL)
ax.text(9.18, 14.6, "LOW\n(< 0.5%)", fontsize=8.5, color=C_TEAL, fontweight="bold")
diamond(ax, 9, 13.85, 5.2, 0.75,
"Check: Renal function + Chronic disease\nmarkers (CRP, ESR)",
bg="#FFF8E1", edge=C_AMBER, fontsize=8.8, text_color=C_GREY)
# CKD
arrow(ax, 6.4, 13.85, 4.2, 13.85, color=C_BLUE)
ax.text(4.9, 14.02, "Renal\nDisease", fontsize=8, color=C_BLUE, fontweight="bold")
box(ax, 3.1, 13.85, 3.8, 0.72,
"Anemia of CKD\n→ Check EPO level\n Treat with ESA + iron",
bg=C_LBLUE, edge=C_BLUE, fontsize=8.5, text_color=C_NAVY)
# ACD
arrow(ax, 9, 13.47, 9, 12.8, color=C_GREY)
ax.text(9.2, 13.12, "Inflammation/\nChronic disease", fontsize=8, color=C_GREY, fontweight="bold")
box(ax, 9, 12.45, 5, 0.62,
"Anemia of Chronic Disease (ACD)\n→ Normal/high ferritin, low serum iron\n Treat underlying condition",
bg=C_LGREY, edge=C_GREY, fontsize=8.8, text_color=C_GREY)
# Bone marrow
arrow(ax, 11.6, 13.85, 13.5, 13.85, color=C_PURPLE)
ax.text(12.2, 14.02, "Pancytopenia?", fontsize=8, color=C_PURPLE, fontweight="bold")
box(ax, 15.3, 13.85, 4.0, 0.72,
"Bone Marrow Failure\n→ Aplastic anemia / MDS\n Bone marrow biopsy — URGENT",
bg=C_LPURPLE, edge=C_PURPLE, fontsize=8.5, text_color=C_PURPLE)
# ════════════════════════════
# RIGHT BRANCH: MACROCYTIC
# ════════════════════════════
box(ax, 15, 17.65, 4.8, 0.7,
"MACROCYTIC ANEMIA\nCheck: B12, Folate, TSH, LFTs",
bg=C_LPURPLE, edge=C_PURPLE, fontsize=9.5, bold=True, text_color=C_PURPLE)
arrow(ax, 15, 17.3, 15, 16.65, color=C_PURPLE)
diamond(ax, 15, 16.2, 4.8, 0.85,
"B12 / Folate\nLow?",
bg="#FFF8E1", edge=C_AMBER, fontsize=9.5, text_color=C_GREY)
# B12/Folate LOW
arrow(ax, 15, 15.77, 15, 15.12, color=C_PURPLE)
ax.text(15.18, 15.42, "LOW", fontsize=8.5, color=C_PURPLE, fontweight="bold")
box(ax, 15, 14.7, 4.6, 0.88,
"Megaloblastic Anemia\n→ B12: IM hydroxocobalamin\n→ Folate: oral folic acid 5 mg/day\n Check for pernicious anemia (anti-IF Ab)",
bg=C_LPURPLE, edge=C_PURPLE, fontsize=8.8, text_color=C_PURPLE)
# B12/Folate NORMAL
arrow(ax, 17.4, 16.2, 17.8, 16.2, color=C_GREY)
arrow(ax, 17.8, 16.2, 17.8, 14.1, color=C_GREY)
arrow(ax, 17.8, 14.1, 17.5, 14.1, color=C_GREY)
ax.text(17.82, 15.1, "NORMAL", fontsize=8, color=C_GREY, fontweight="bold", rotation=90)
box(ax, 16.3, 14.1, 3.2, 0.72,
"Consider: Alcohol, Liver\ndisease, Hypothyroidism,\nMedications (methotrexate)",
bg=C_LGREY, edge=C_GREY, fontsize=8.3, text_color=C_GREY)
# ══════════════════════════════════════════════════════════════════════════════
# SEVERITY BOX (bottom)
# ══════════════════════════════════════════════════════════════════════════════
sev_rect = FancyBboxPatch((0.4, 9.9), 17.2, 2.0,
boxstyle="round,pad=0.1,rounding_size=0.3",
facecolor="#FFF8E1", edgecolor=C_AMBER, linewidth=1.8, zorder=3)
ax.add_patch(sev_rect)
ax.text(9, 11.7, "ANEMIA SEVERITY (by Hemoglobin)",
ha="center", va="center", fontsize=11, fontweight="bold", color=C_GREY, zorder=4)
sev_cols = [
("Mild", "10–12 g/dL (F)\n10–13.5 g/dL (M)", "#43A047", "#E8F5E9"),
("Moderate", "8–10 g/dL", "#F57F17", "#FFF3E0"),
("Severe", "< 8 g/dL", "#E53935", "#FFEBEE"),
("Very Severe","< 6.5 g/dL\n(Transfusion likely)", "#B71C1C", "#FCE4EC"),
]
xs = [2.5, 6.3, 10.1, 14.2]
for i, (label, val, ec, fc) in enumerate(sev_cols):
r = FancyBboxPatch((xs[i]-2.1, 10.05), 4.0, 1.4,
boxstyle="round,pad=0.05,rounding_size=0.2",
facecolor=fc, edgecolor=ec, linewidth=1.5, zorder=4)
ax.add_patch(r)
ax.text(xs[i]+(-0.1), 10.87, label, ha="center", va="center",
fontsize=10.5, fontweight="bold", color=ec, zorder=5)
ax.text(xs[i]+(-0.1), 10.38, val, ha="center", va="center",
fontsize=8.8, color=ec, zorder=5, multialignment="center")
# ══════════════════════════════════════════════════════════════════════════════
# LEGEND
# ══════════════════════════════════════════════════════════════════════════════
legend_rect = FancyBboxPatch((0.4, 7.2), 17.2, 2.4,
boxstyle="round,pad=0.1,rounding_size=0.3",
facecolor=C_LGREY, edgecolor=C_GREY, linewidth=1.2, zorder=3)
ax.add_patch(legend_rect)
ax.text(9, 9.45, "LEGEND",
ha="center", va="center", fontsize=10.5, fontweight="bold", color=C_GREY, zorder=4)
legend_items = [
(C_LRED, C_RED, "Microcytic / Iron def."),
(C_LTEAL, C_TEAL, "Normocytic"),
(C_LPURPLE,C_PURPLE,"Macrocytic / B12/Folate"),
(C_LORANGE,C_ORANGE,"Hemolytic / Acute blood loss"),
(C_LGREY, C_GREY, "Non-specific / Other"),
(C_LBLUE, C_BLUE, "Anemia of CKD"),
]
lx = [1.2, 4.1, 7.2, 10.5, 13.9, 16.1]
for i, (fc, ec, lbl) in enumerate(legend_items):
r = FancyBboxPatch((lx[i]-0.1, 7.85), 0.65, 0.55,
boxstyle="round,pad=0.05",
facecolor=fc, edgecolor=ec, linewidth=1.4, zorder=4)
ax.add_patch(r)
ax.text(lx[i] + 0.72, 8.13, lbl, va="center", fontsize=8.2, color=C_GREY, zorder=5)
# DIAMOND legend
from matplotlib.patches import Polygon
import numpy as np
dx, dy = 1.0, 7.42
pts = np.array([[dx, dy+0.28],[dx+0.33,dy],[dx,dy-0.28],[dx-0.33,dy]])
ax.add_patch(Polygon(pts, closed=True, facecolor="#FFF8E1", edgecolor=C_AMBER, lw=1.4, zorder=4))
ax.text(dx+0.5, dy, "Decision point (diamond)", va="center", fontsize=8.2, color=C_GREY, zorder=5)
rx = 4.5
rr = FancyBboxPatch((rx, 7.18), 1.5, 0.5,
boxstyle="round,pad=0.05",
facecolor=C_WHITE, edgecolor=C_BLUE, lw=1.4, zorder=4)
ax.add_patch(rr)
ax.text(rx+1.7, 7.43, "Action / Result (rectangle)", va="center", fontsize=8.2, color=C_GREY, zorder=5)
# ══════════════════════════════════════════════════════════════════════════════
# FOOTER
# ══════════════════════════════════════════════════════════════════════════════
foot_rect = FancyBboxPatch((0.4, 6.5), 17.2, 0.5,
boxstyle="round,pad=0.05,rounding_size=0.2",
facecolor=C_NAVY, edgecolor=C_NAVY, lw=0, zorder=3)
ax.add_patch(foot_rect)
ax.text(9, 6.76,
"⚕ For educational reference only. Interpret results in clinical context with a qualified healthcare provider.",
ha="center", va="center", fontsize=8.5, color=C_LBLUE, zorder=4)
# ══════════════════════════════════════════════════════════════════════════════
plt.tight_layout(pad=0)
out = "/home/daytona/workspace/cbc-cheatsheet/Anemia_Workup_Flowchart.png"
plt.savefig(out, dpi=180, bbox_inches="tight", facecolor=fig.get_facecolor())
print(f"Saved: {out}")
cd /home/daytona/workspace/cbc-cheatsheet && python generate_anemia_flowchart.py…
ls -lh /home/daytona/workspace/cbc-cheatsheet/Anemia_Workup_Flowchart.png

Anemia Workup Flowchart
Image · PNG
Cbc interapertion flowchart
~/cbc-cheatsheet/generate_cbc_flowchart.py
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch, Polygon
import numpy as np
fig, ax = plt.subplots(figsize=(22, 34))
ax.set_xlim(0, 22)
ax.set_ylim(0, 34)
ax.axis("off")
fig.patch.set_facecolor("#F0F4FF")
# ── Palette ─────────────────────────────────────────────────────────────────
NAVY = "#0B2C5F"; BLUE = "#1565C0"; LBLUE = "#BBDEFB"; LLBLUE = "#E3F0FB"
RED = "#B71C1C"; LRED = "#FFEBEE"; DRED = "#C62828"
ORANGE = "#E65100"; LORANG = "#FFF3E0"
GREEN = "#1B5E20"; LGREEN = "#E8F5E9"; MGREEN = "#2E7D32"
TEAL = "#00695C"; LTEAL = "#E0F2F1"
PURPLE = "#6A1B9A"; LPURPL = "#F3E5F5"
AMBER = "#F57F17"; LAMBER = "#FFFDE7"
GREY = "#37474F"; LGREY = "#ECEFF1"
WHITE = "#FFFFFF"; GOLD = "#F9A825"
MAROON = "#880E4F"; LMARRN = "#FCE4EC"
# ── Helpers ──────────────────────────────────────────────────────────────────
def rbox(ax, x, y, w, h, text, bg=WHITE, ec=BLUE, fs=9, bold=False, tc="#111111", lw=1.6, alpha=1.0):
rect = FancyBboxPatch((x-w/2, y-h/2), w, h,
boxstyle="round,pad=0.06,rounding_size=0.18",
facecolor=bg, edgecolor=ec, linewidth=lw, zorder=3, alpha=alpha)
ax.add_patch(rect)
ax.text(x, y, text, ha="center", va="center", fontsize=fs,
fontweight="bold" if bold else "normal", color=tc, zorder=4,
multialignment="center", linespacing=1.3)
def diam(ax, x, y, w, h, text, bg=LAMBER, ec=AMBER, fs=9, tc=GREY):
pts = np.array([[x, y+h/2],[x+w/2, y],[x, y-h/2],[x-w/2, y]])
ax.add_patch(Polygon(pts, closed=True, facecolor=bg, edgecolor=ec, lw=1.8, zorder=3))
ax.text(x, y, text, ha="center", va="center", fontsize=fs,
fontweight="bold", color=tc, zorder=4, multialignment="center", linespacing=1.3)
def arr(ax, x1,y1,x2,y2, color=GREY, lw=1.6):
ax.annotate("", xy=(x2,y2), xytext=(x1,y1),
arrowprops=dict(arrowstyle="-|>", color=color, lw=lw, mutation_scale=14), zorder=2)
def lbl(ax, x, y, text, color=GREY, fs=8.2, rot=0, ha="left"):
ax.text(x, y, text, fontsize=fs, color=color, fontweight="bold",
rotation=rot, ha=ha, va="center", zorder=5)
def hline(ax, y, color=LGREY, lw=0.8):
ax.axhline(y, color=color, lw=lw, zorder=1)
# ════════════════════════════════════════════════════════════════════════════
# TITLE BANNER
# ════════════════════════════════════════════════════════════════════════════
tb = FancyBboxPatch((0.3, 32.4), 21.4, 1.35,
boxstyle="round,pad=0.1,rounding_size=0.3",
facecolor=NAVY, edgecolor=NAVY, lw=0, zorder=3)
ax.add_patch(tb)
ax.text(11, 33.18, "COMPLETE BLOOD COUNT (CBC) — INTERPRETATION FLOWCHART",
ha="center", va="center", fontsize=17, fontweight="bold", color=WHITE, zorder=4)
ax.text(11, 32.67, "Step-by-step approach: RBC Panel | WBC Panel | Platelet Panel",
ha="center", va="center", fontsize=10.5, color=LBLUE, zorder=4)
# ════════════════════════════════════════════════════════════════════════════
# ENTRY
# ════════════════════════════════════════════════════════════════════════════
rbox(ax, 11, 31.8, 8, 0.62,
"CBC Result Received — Systematic Interpretation",
bg=BLUE, ec=BLUE, fs=11, bold=True, tc=WHITE, lw=0)
arr(ax, 11, 31.49, 11, 30.9)
# 3-panel column headers
for cx, label, bg, ec in [(3.7,"🩸 PANEL 1: RED BLOOD CELLS",LRED,RED),
(11,"🛡 PANEL 2: WHITE BLOOD CELLS",LLBLUE,BLUE),
(18.3,"🩹 PANEL 3: PLATELETS",LTEAL,TEAL)]:
rbox(ax, cx, 30.6, 6.8, 0.58, label, bg=bg, ec=ec, fs=10.5, bold=True,
tc=RED if "RED" in label else (BLUE if "WHITE" in label else TEAL), lw=2)
# vertical dividers
for xd in [7.35, 14.65]:
ax.plot([xd,xd],[4.5,30.3], color=LGREY, lw=1.2, ls="--", zorder=1)
# ════════════════════════════════════════════════════════════════════════════
# ───────────────── PANEL 1: RBC ──────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
arr(ax, 3.7, 30.31, 3.7, 29.78)
rbox(ax, 3.7, 29.5, 6.0, 0.52,
"Check Hemoglobin (Hgb)\nM: 13.5–17.5 | F: 12.0–15.5 g/dL",
bg=LRED, ec=RED, fs=9, tc=DRED, bold=True)
arr(ax, 3.7, 29.24, 3.7, 28.65)
diam(ax, 3.7, 28.2, 5.6, 0.8, "Is Hgb LOW?", bg=LAMBER, ec=AMBER, fs=10, tc=GREY)
# NO → right
arr(ax, 6.5, 28.2, 7.2, 28.2, color=MGREEN)
rbox(ax, 7.0, 28.2, 0.01, 0.01, "", bg="none", ec="none") # dummy
ax.annotate("", xy=(7.15,28.2), xytext=(6.5,28.2),
arrowprops=dict(arrowstyle="-|>", color=MGREEN, lw=1.4, mutation_scale=12), zorder=2)
lbl(ax, 6.55, 28.37, "NO → Normal Hgb", MGREEN, fs=8)
# YES ↓
arr(ax, 3.7, 27.8, 3.7, 27.22, color=RED)
lbl(ax, 3.82, 27.5, "YES", RED)
rbox(ax, 3.7, 26.95, 6.0, 0.48,
"ANEMIA CONFIRMED\nCheck MCV to classify type",
bg=LRED, ec=RED, fs=9, bold=True, tc=DRED)
arr(ax, 3.7, 26.71, 3.7, 26.15)
diam(ax, 3.7, 25.7, 5.8, 0.82, "MCV Value?", bg=LAMBER, ec=AMBER, fs=10, tc=GREY)
# ── MCV < 80 (left) ──
arr(ax, 0.8, 25.7, 0.4, 25.7, color=RED); arr(ax, 0.4, 25.7, 0.4, 24.7, color=RED); arr(ax, 0.4, 24.7, 0.62, 24.7, color=RED)
lbl(ax, 0.08, 25.2, "< 80\n(Micro)", RED, fs=7.8, rot=90, ha="center")
rbox(ax, 2.0, 24.7, 2.8, 0.72,
"MICROCYTIC\nCheck Ferritin",
bg=LRED, ec=RED, fs=8.5, bold=True, tc=RED)
arr(ax, 2.0, 24.34, 2.0, 23.78)
diam(ax, 2.0, 23.38, 2.8, 0.74, "Ferritin?", bg=LAMBER, ec=AMBER, fs=8.5)
arr(ax, 2.0, 23.01, 2.0, 22.48, color=RED); lbl(ax, 2.1, 22.72, "LOW", RED, fs=7.8)
rbox(ax, 2.0, 22.12, 2.8, 0.6, "Iron Def.\nAnemia", bg=LRED, ec=RED, fs=8.5, bold=True, tc=RED)
arr(ax, 2.0, 21.82, 2.0, 21.35); lbl(ax, 2.1, 21.58, "Rx: Oral\nFerrous sulfate", GREY, fs=7.5)
rbox(ax, 2.0, 21.05, 2.8, 0.55, "Oral Iron\n+ Investigate\ncause", bg=LGREY, ec=GREY, fs=7.8)
arr(ax, 3.4, 23.38, 3.8, 23.38, color=ORANGE); lbl(ax, 3.45, 23.55, "NORM/HIGH", ORANGE, fs=7.5)
arr(ax, 4.8, 23.38, 5.2, 23.38, color=ORANGE)
rbox(ax, 5.9, 23.38, 1.6, 0.6, "Thalassemia /\nLead poison.\nHgb electrophrs", bg=LORANG, ec=ORANGE, fs=7.5, tc=ORANGE)
# ── MCV 80-100 (down) ──
arr(ax, 3.7, 25.29, 3.7, 24.72, color=TEAL); lbl(ax, 3.82, 25.0, "80–100\n(Normo)", TEAL, fs=7.8)
rbox(ax, 3.7, 24.42, 3.2, 0.54,
"NORMOCYTIC\nCheck Reticulocytes",
bg=LTEAL, ec=TEAL, fs=8.5, bold=True, tc=TEAL)
arr(ax, 3.7, 24.15, 3.7, 23.6)
diam(ax, 3.7, 23.2, 3.2, 0.72, "Retics?", bg=LAMBER, ec=AMBER, fs=8.5)
arr(ax, 2.1, 23.2, 1.62, 23.2, color=ORANGE); lbl(ax, 1.2, 23.37, "HIGH", ORANGE, fs=7.8, ha="center")
rbox(ax, 0.95, 22.62, 1.5, 0.62, "Hemolytic\nor Blood\nLoss", bg=LORANG, ec=ORANGE, fs=7.5, tc=ORANGE)
arr(ax, 3.7, 22.84, 3.7, 22.3, color=BLUE); lbl(ax, 3.82, 22.56, "LOW", BLUE, fs=7.8)
rbox(ax, 3.7, 21.95, 3.2, 0.6, "CKD / Chronic\nDisease / Aplasia", bg=LLBLUE, ec=BLUE, fs=8, tc=BLUE)
# ── MCV > 100 (right) ──
arr(ax, 6.6, 25.7, 7.0, 25.7, color=PURPLE)
lbl(ax, 5.85, 25.87, "> 100\n(Macro)", PURPLE, fs=7.8)
rbox(ax, 5.85, 24.7, 2.8, 0.54,
"MACROCYTIC\nCheck B12 / Folate",
bg=LPURPL, ec=PURPLE, fs=8.5, bold=True, tc=PURPLE)
arr(ax, 5.85, 24.43, 5.85, 23.87)
diam(ax, 5.85, 23.45, 2.8, 0.76, "B12/Folate?", bg=LAMBER, ec=AMBER, fs=8.5)
arr(ax, 5.85, 23.07, 5.85, 22.5, color=PURPLE); lbl(ax, 5.97, 22.77, "LOW", PURPLE, fs=7.8)
rbox(ax, 5.85, 22.12, 2.8, 0.6, "Megaloblastic\nAnemia\nB12/Folate Rx", bg=LPURPL, ec=PURPLE, fs=8, bold=True, tc=PURPLE)
arr(ax, 7.25, 23.45, 7.6, 23.45, color=GREY); lbl(ax, 7.1, 23.62, "NORM", GREY, fs=7.5)
rbox(ax, 7.1, 22.75, 0.01,0.01,"",bg="none",ec="none") # spacer
ax.text(7.22, 23.05, "Alcohol / Liver\nHypothyroid\nMeds", fontsize=7.5, color=GREY,
va="center", zorder=4)
# ── RBC HIGH ──
arr(ax, 3.7, 27.8, 3.7, 27.22) # already drawn for LOW path; add HIGH note
rbox(ax, 6.5, 27.2, 1.2, 0.48, "Hgb HIGH\n→ Polycythemia\nCheck JAK2",
bg=LORANG, ec=ORANGE, fs=7.5, tc=ORANGE)
# ── RDW callout ──
rbox(ax, 3.7, 20.75, 6.0, 0.7,
"Also check RDW (Normal: 11.5–14.5%)\n↑ RDW = Mixed deficiency / early anemia stages",
bg=LGREY, ec=GREY, fs=8, tc=GREY)
# ════════════════════════════════════════════════════════════════════════════
# ───────────────── PANEL 2: WBC ──────────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
arr(ax, 11, 30.31, 11, 29.78)
rbox(ax, 11, 29.5, 6.2, 0.52,
"Check Total WBC Count\nNormal: 4,000–11,000 cells/µL",
bg=LLBLUE, ec=BLUE, fs=9, bold=True, tc=NAVY)
arr(ax, 11, 29.24, 11, 28.65)
diam(ax, 11, 28.2, 5.6, 0.8, "WBC Count?", bg=LAMBER, ec=AMBER, fs=10, tc=GREY)
# NORMAL
arr(ax, 13.8, 28.2, 14.5, 28.2, color=MGREEN)
lbl(ax, 13.35, 28.37, "4–11 K\nNORMAL", MGREEN, fs=7.8)
rbox(ax, 14.5, 28.07, 0.01, 0.01, "", bg="none", ec="none")
ax.text(14.55, 28.2, "No primary WBC\ndisorder on count\nalone", fontsize=7.8, color=MGREEN, va="center")
# HIGH (Leukocytosis)
arr(ax, 11, 27.8, 11, 27.22, color=RED); lbl(ax, 11.1, 27.5, "> 11K\nHIGH", RED, fs=7.8)
rbox(ax, 11, 26.92, 5.4, 0.52,
"LEUKOCYTOSIS — Check Differential",
bg=LRED, ec=RED, fs=9.5, bold=True, tc=RED)
arr(ax, 11, 26.66, 11, 26.1)
diam(ax, 11, 25.65, 5.6, 0.82, "Which cell is HIGH?", bg=LAMBER, ec=AMBER, fs=9.5, tc=GREY)
# Neutrophilia (left)
arr(ax, 8.2, 25.65, 7.9, 25.65, color=DRED); arr(ax, 7.9, 25.65, 7.9, 24.9, color=DRED); arr(ax, 7.9, 24.9, 8.12, 24.9, color=DRED)
lbl(ax, 7.55, 25.65, "Neutro-\nphilia", DRED, fs=7.5, ha="center")
rbox(ax, 9.3, 24.9, 2.7, 0.65, "Neutrophilia\nBacterial Infx\nStress / Steroids", bg=LRED, ec=DRED, fs=8, tc=DRED)
# Lymphocytosis (straight down)
arr(ax, 11, 25.24, 11, 24.7, color=BLUE); lbl(ax, 11.12, 24.95, "Lympho-\ncytosis", BLUE, fs=7.5)
rbox(ax, 11, 24.35, 2.8, 0.62, "Viral Infx\nEBV / CMV\nCLL (chronic)", bg=LLBLUE, ec=BLUE, fs=8, tc=NAVY)
# Eosinophilia (right)
arr(ax, 13.8, 25.65, 14.1, 25.65, color=PURPLE); arr(ax, 14.1, 25.65, 14.1, 24.9, color=PURPLE); arr(ax, 14.1, 24.9, 13.92, 24.9, color=PURPLE)
lbl(ax, 13.88, 25.65, "Eosino-\nphilia", PURPLE, fs=7.5)
rbox(ax, 12.7, 24.35, 2.7, 0.62, "Allergy / Asthma\nParasitic Infx\nEosinophilia syn.", bg=LPURPL, ec=PURPLE, fs=8, tc=PURPLE)
# LOW (Leukopenia)
arr(ax, 8.2, 28.2, 7.8, 28.2, color=TEAL); arr(ax, 7.8, 28.2, 7.8, 27.35, color=TEAL); arr(ax, 7.8, 27.35, 8.05, 27.35, color=TEAL)
lbl(ax, 7.48, 27.75, "< 4K\nLOW", TEAL, fs=7.8)
rbox(ax, 9.2, 27.35, 2.7, 0.65, "LEUKOPENIA\nViral Infx\nChemo / Autoimmune", bg=LTEAL, ec=TEAL, fs=8, tc=TEAL)
# BLASTS warning
rbox(ax, 11, 23.55, 5.4, 0.6,
"⚠ BLASTS on smear? → Acute Leukemia — URGENT referral",
bg=LRED, ec=RED, fs=8.5, bold=True, tc=RED, lw=2)
# Left-shift box
rbox(ax, 11, 22.8, 5.8, 0.58,
"Left Shift (↑ Bands/Immature neutrophils)\n→ Severe bacterial infection / Sepsis",
bg=LORANG, ec=ORANGE, fs=8.5, tc=ORANGE)
# Differential table (small)
diff_header_bg = NAVY
diff_data = [
("Cell", "Normal %", "↑ Think", "↓ Think"),
("Neutrophil","50–70%", "Bacterial / Steroids", "Viral / Chemo"),
("Lymphocyte","20–40%", "Viral / CLL", "HIV / Radiation"),
("Monocyte", "2–8%", "TB / Fungi", "Aplasia"),
("Eosinophil","1–4%", "Allergy / Parasites", "Steroids"),
("Basophil", "0.5–1%", "CML / Hypothyroid", "Hyperthyroid"),
]
col_xs = [8.35, 9.55, 11.0, 12.6]
col_ws = [1.15, 0.95, 1.55, 1.6]
row_ys = [i * 0.365 for i in range(len(diff_data))]
base_y = 21.65
for ri, row in enumerate(diff_data):
y = base_y - row_ys[ri]
for ci, (val, cw) in enumerate(zip(row, col_ws)):
cx2 = col_xs[ci] + cw/2
bg2 = NAVY if ri == 0 else (LLBLUE if ri % 2 == 0 else WHITE)
tc2 = WHITE if ri == 0 else (NAVY if ci == 0 else (DRED if ci == 2 else (TEAL if ci == 3 else GREY)))
rbox(ax, cx2, y, cw-0.05, 0.33, val,
bg=bg2, ec=LBLUE if ri > 0 else NAVY, fs=7.5 if ri > 0 else 8,
bold=(ri == 0 or ci == 0), tc=tc2, lw=0.6)
ax.text(11, 19.44, "WBC Differential Quick Reference",
ha="center", va="center", fontsize=8, color=GREY, fontstyle="italic", zorder=4)
# ════════════════════════════════════════════════════════════════════════════
# ───────────────── PANEL 3: PLATELETS ────────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
arr(ax, 18.3, 30.31, 18.3, 29.78)
rbox(ax, 18.3, 29.5, 6.2, 0.52,
"Check Platelet Count (PLT)\nNormal: 150,000–400,000/µL",
bg=LTEAL, ec=TEAL, fs=9, bold=True, tc=TEAL)
arr(ax, 18.3, 29.24, 18.3, 28.65)
diam(ax, 18.3, 28.2, 5.6, 0.8, "PLT Count?", bg=LAMBER, ec=AMBER, fs=10, tc=GREY)
# HIGH (Thrombocytosis) → right
arr(ax, 21.1, 28.2, 21.5, 28.2, color=ORANGE)
lbl(ax, 20.75, 28.37, "> 400K\nHIGH", ORANGE, fs=7.8)
ax.text(21.55, 28.2,
"Thrombocytosis\n• Reactive: Infx, Iron def,\n Inflammation\n• Essential Thrombocythemia",
fontsize=7.8, color=ORANGE, va="center", zorder=4)
# LOW → Thrombocytopenia
arr(ax, 18.3, 27.8, 18.3, 27.22, color=RED); lbl(ax, 18.42, 27.5, "< 150K\nLOW", RED, fs=7.8)
rbox(ax, 18.3, 26.92, 5.4, 0.52,
"THROMBOCYTOPENIA",
bg=LRED, ec=RED, fs=10, bold=True, tc=RED)
arr(ax, 18.3, 26.66, 18.3, 26.1)
diam(ax, 18.3, 25.65, 5.2, 0.82, "Peripheral Smear\n+ Clinical context?", bg=LAMBER, ec=AMBER, fs=9, tc=GREY)
# Cause branches
causes = [
(15.5, 25.05, "ITP\n(Immune)", PURPLE, LPURPL),
(17.5, 25.05, "Dengue\nFever", TEAL, LTEAL),
(19.4, 25.05, "Chemo /\nDrug-induced", BLUE, LLBLUE),
(21.2, 25.05, "DIC /\nSepsis", RED, LRED),
]
for cx2, cy2, ltext, ec2, bg2 in causes:
arr(ax, 18.3, 25.24, cx2, cy2+0.28, color=ec2)
rbox(ax, cx2, cy2, 1.7, 0.52, ltext, bg=bg2, ec=ec2, fs=8, bold=True, tc=ec2)
# Bleeding risk table
risk_data = [
("> 100K", "Minimal", MGREEN, LGREEN),
("50–100K", "Minor risk", AMBER, LAMBER),
("20–50K", "Moderate", ORANGE, LORANG),
("< 20K", "SPONTANEOUS\nBLEEDING", RED, LRED),
]
rx2_base = 23.85; ry2_base = 24.4; rh = 0.48
rbox(ax, 18.3, ry2_base+0.25+len(risk_data)*rh/2+0.2, 5.2, 0.42,
"Platelet Count → Bleeding Risk", bg=GREY, ec=GREY, fs=9, bold=True, tc=WHITE)
for i, (cnt, risk, tc2, bg2) in enumerate(risk_data):
y2 = ry2_base + (len(risk_data)-1-i)*rh
rbox(ax, 16.55, y2, 2.3, rh-0.04, cnt, bg=bg2, ec=tc2, fs=9, bold=True, tc=tc2)
rbox(ax, 20.05, y2, 2.7, rh-0.04, risk, bg=bg2, ec=tc2, fs=9, bold=True, tc=tc2)
ax.text(16.55, 22.14, "Count", ha="center", fontsize=8, color=GREY, fontweight="bold")
ax.text(20.05, 22.14, "Bleeding Risk", ha="center", fontsize=8, color=GREY, fontweight="bold")
# MPV note
rbox(ax, 18.3, 21.6, 5.4, 0.55,
"MPV (Mean Platelet Vol, Normal 7–13 fL)\n↑ MPV = Large platelets (ITP, recovery) | ↓ MPV = Small (aplasia, chemo)",
bg=LGREY, ec=GREY, fs=8, tc=GREY)
# ════════════════════════════════════════════════════════════════════════════
# ───────────── PANCYTOPENIA (cross-panel) ────────────────────────────────
# ════════════════════════════════════════════════════════════════════════════
pcy = 20.1
pan_rect = FancyBboxPatch((0.4, pcy-0.42), 21.2, 0.84,
boxstyle="round,pad=0.08,rounding_size=0.2",
facecolor=LRED, edgecolor=RED, lw=2.2, zorder=3)
ax.add_patch(pan_rect)
ax.text(11, pcy,
"⚠ PANCYTOPENIA (ALL THREE LINES LOW): Hgb ↓ + WBC ↓ + Platelets ↓"
" → Bone Marrow Failure / Aplastic Anemia / Leukemia / MDS — URGENT haematology referral",
ha="center", va="center", fontsize=9.5, fontweight="bold", color=RED, zorder=4, multialignment="center")
# ════════════════════════════════════════════════════════════════════════════
# SEVERITY / ANEMIA TABLE (bottom-left)
# ════════════════════════════════════════════════════════════════════════════
sev_title_y = 19.25
rbox(ax, 4.2, sev_title_y, 7.6, 0.44, "Anemia Severity by Hgb (g/dL)",
bg=GREY, ec=GREY, fs=9.5, bold=True, tc=WHITE)
sev = [("Mild","10–12 (F) / 10–13.5 (M)",MGREEN,LGREEN),
("Moderate","8–10",ORANGE,LORANG),
("Severe","< 8",RED,LRED),
("Critical / Transfuse","< 6.5",MAROON,LMARRN)]
for i,(s,v,tc2,bg2) in enumerate(sev):
y2 = sev_title_y - 0.48*(i+1)
rbox(ax, 2.4, y2, 3.4, 0.43, s, bg=bg2, ec=tc2, fs=9, bold=True, tc=tc2)
rbox(ax, 5.8, y2, 4.1, 0.43, v, bg=WHITE, ec=tc2, fs=9, tc=tc2)
# ════════════════════════════════════════════════════════════════════════════
# CLINICAL PATTERNS TABLE (bottom-centre)
# ════════════════════════════════════════════════════════════════════════════
pt_title_y = 19.25
rbox(ax, 11.3, pt_title_y, 7.2, 0.44, "Common CBC Patterns → Diagnosis",
bg=NAVY, ec=NAVY, fs=9.5, bold=True, tc=WHITE)
patterns = [
("↓ Hgb + ↓ MCV + ↓ Ferritin", "Iron Deficiency Anemia", RED),
("↓ Hgb + ↑ MCV", "B12/Folate Deficiency", PURPLE),
("↑ WBC + ↑ Neutrophils", "Bacterial Infection", DRED),
("↑ WBC + ↑ Lymphocytes", "Viral Infection", BLUE),
("↓ PLT only", "ITP / Dengue", TEAL),
("↑ RBC + ↑ Hgb + ↑ Hct", "Polycythemia Vera", ORANGE),
("↑ WBC with BLASTS", "Acute Leukemia — URGENT",RED),
]
for i,(pat,dx,tc2) in enumerate(patterns):
y2 = pt_title_y - 0.45*(i+1)
bg2 = LRED if "URGENT" in dx else (LLBLUE if i%2==0 else WHITE)
rbox(ax, 9.35, y2, 3.6, 0.41, pat, bg=bg2, ec=tc2, fs=8, tc=tc2)
rbox(ax, 13.05, y2, 3.5, 0.41, dx, bg=bg2, ec=tc2, fs=8, bold=True, tc=tc2)
# ════════════════════════════════════════════════════════════════════════════
# TIPS BOX (bottom-right)
# ════════════════════════════════════════════════════════════════════════════
tips = [
"1. Always compare to YOUR lab's reference range",
"2. Trends over time > single values",
"3. Pancytopenia = urgent investigation always",
"4. H/L flags guide — don't diagnose alone",
"5. CBC directs next test, rarely final Dx",
"6. Context: age, sex, meds, chronic illness",
]
tr = FancyBboxPatch((15.1, 15.85), 6.5, 3.4,
boxstyle="round,pad=0.1,rounding_size=0.25",
facecolor="#E8F0FE", edgecolor=NAVY, lw=1.8, zorder=3)
ax.add_patch(tr)
ax.text(18.35, 19.08, "✔ Tips for Reading Any CBC",
ha="center", fontsize=10, fontweight="bold", color=NAVY, zorder=4)
for i, t in enumerate(tips):
ax.text(15.42, 18.65 - i*0.5, t, fontsize=8.5, color=GREY, zorder=4, va="center")
# ════════════════════════════════════════════════════════════════════════════
# LEGEND
# ════════════════════════════════════════════════════════════════════════════
leg_rect = FancyBboxPatch((0.3, 14.4), 21.4, 1.2,
boxstyle="round,pad=0.08,rounding_size=0.2",
facecolor=LGREY, edgecolor=GREY, lw=1.2, zorder=3)
ax.add_patch(leg_rect)
ax.text(11, 15.45, "LEGEND", ha="center", fontsize=9.5, fontweight="bold", color=GREY, zorder=4)
leg_items = [
(LRED,RED,"RBC/Anemia"), (LLBLUE,BLUE,"WBC"),
(LTEAL,TEAL,"Platelets"), (LPURPL,PURPLE,"Macrocytic"),
(LORANG,ORANGE,"Hemolytic"), (LGREEN,MGREEN,"Normal"),
(LAMBER,AMBER,"Decision\n(diamond)"),
]
lxs = [1.5, 4.0, 6.5, 9.0, 11.5, 14.0, 16.5]
for i,(bg2,ec2,lab) in enumerate(leg_items):
if "diamond" in lab.lower():
pts2 = np.array([[lxs[i],14.88],[lxs[i]+0.35,14.65],[lxs[i],14.42],[lxs[i]-0.35,14.65]])
ax.add_patch(Polygon(pts2, closed=True, facecolor=bg2, edgecolor=ec2, lw=1.4, zorder=4))
else:
ax.add_patch(FancyBboxPatch((lxs[i]-0.38, 14.49), 0.72, 0.38,
boxstyle="round,pad=0.04", facecolor=bg2, edgecolor=ec2, lw=1.4, zorder=4))
ax.text(lxs[i]+0.55, 14.65, lab, fontsize=8, color=GREY, va="center", zorder=5)
# ════════════════════════════════════════════════════════════════════════════
# FOOTER
# ════════════════════════════════════════════════════════════════════════════
fb = FancyBboxPatch((0.3, 13.8), 21.4, 0.5,
boxstyle="round,pad=0.05,rounding_size=0.2",
facecolor=NAVY, edgecolor=NAVY, lw=0, zorder=3)
ax.add_patch(fb)
ax.text(11, 14.06,
"⚕ For educational reference only. All CBC results must be interpreted in clinical context by a qualified healthcare provider.",
ha="center", va="center", fontsize=8.5, color=LBLUE, zorder=4)
# ════════════════════════════════════════════════════════════════════════════
plt.tight_layout(pad=0)
out = "/home/daytona/workspace/cbc-cheatsheet/CBC_Interpretation_Flowchart.png"
plt.savefig(out, dpi=180, bbox_inches="tight", facecolor=fig.get_facecolor())
print(f"Saved: {out}")
cd /home/daytona/workspace/cbc-cheatsheet && python generate_cbc_flowchart.py 2>…
cd /home/daytona/workspace/cbc-cheatsheet && sed -i 's/🩸 PANEL 1:/[RBC] PANEL…
python3 -c " content = open('/home/daytona/workspace/cbc-cheatsheet/generate_cbc…

CBC Interpretation Flowchart
Image · PNG
| Panel | What it shows |
|---|---|
| [RBC] Panel 1 | Hgb → Is it low? → MCV branch (Microcytic/Normocytic/Macrocytic) → each leads to specific diagnosis + treatment |
| [WBC] Panel 2 | WBC count → High/Low/Normal → Differential (which cell?) → Neutrophilia / Lymphocytosis / Eosinophilia + WBC differential quick-reference mini table |
| [PLT] Panel 3 | PLT count → High/Low → Causes of thrombocytopenia + Bleeding risk table by platelet count |
| Bottom section | Pancytopenia warning bar, Anemia severity table, 7 Common CBC patterns, 6 clinical tips |
Flowchart in different pattern plz
~/cbc-cheatsheet/generate_cbc_flowchart_v2.py
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch, Polygon
from matplotlib.patheffects import withStroke
import numpy as np
# ── Canvas ───────────────────────────────────────────────────────────────────
fig, ax = plt.subplots(figsize=(20, 30))
ax.set_xlim(0, 20)
ax.set_ylim(0, 30)
ax.axis("off")
fig.patch.set_facecolor("#0D1117") # dark background
# ── Dark-theme palette ────────────────────────────────────────────────────────
BG = "#0D1117"
CARD = "#161B22"
BORDER = "#30363D"
RED = "#FF6B6B"; DRED = "#FF4444"; LRED = "#2D1515"
BLUE = "#58A6FF"; LBLUE = "#1A2840"; DBLUE = "#388BFD"
GREEN = "#3FB950"; LGREEN= "#122419"
AMBER = "#D29922"; LAMBER= "#2D2110"
PURPLE = "#BC8CFF"; LPURP = "#1E1430"
TEAL = "#39D0D0"; LTEAL = "#0E2828"
ORANGE = "#FFA657"; LORAN = "#2D1E0A"
PINK = "#FF7EE2"; LPINK = "#2D1025"
WHITE = "#E6EDF3"; GREY = "#8B949E"; DGREY = "#21262D"
# ── Drawing helpers ───────────────────────────────────────────────────────────
def pill(ax, x, y, w, h, text, bg=CARD, ec=BLUE, fs=9.5, tc=WHITE,
bold=False, lw=1.8, style="round,pad=0.08,rounding_size=0.28"):
p = FancyBboxPatch((x-w/2, y-h/2), w, h, boxstyle=style,
facecolor=bg, edgecolor=ec, linewidth=lw, zorder=4)
ax.add_patch(p)
ax.text(x, y, text, ha="center", va="center", fontsize=fs,
fontweight="bold" if bold else "normal", color=tc, zorder=5,
multialignment="center", linespacing=1.35)
def hexbox(ax, x, y, w, h, text, bg=LAMBER, ec=AMBER, fs=9, tc=AMBER):
"""Diamond / decision node"""
pts = np.array([[x, y+h/2],[x+w/2, y],[x, y-h/2],[x-w/2, y]])
ax.add_patch(Polygon(pts, closed=True, facecolor=bg, edgecolor=ec, lw=2.0, zorder=4))
ax.text(x, y, text, ha="center", va="center", fontsize=fs,
fontweight="bold", color=tc, zorder=5, multialignment="center", linespacing=1.3)
def dot(ax, x, y, r=0.18, color=BLUE):
c = plt.Circle((x, y), r, color=color, zorder=6)
ax.add_patch(c)
def arr(ax, x1,y1,x2,y2, color=GREY, lw=1.8, style="-|>"):
ax.annotate("", xy=(x2,y2), xytext=(x1,y1),
arrowprops=dict(arrowstyle=style, color=color, lw=lw,
mutation_scale=14), zorder=3)
def curved_arr(ax, x1,y1,x2,y2, color=GREY, lw=1.5, rad=0.3):
ax.annotate("", xy=(x2,y2), xytext=(x1,y1),
arrowprops=dict(arrowstyle="-|>", color=color, lw=lw,
mutation_scale=13,
connectionstyle=f"arc3,rad={rad}"), zorder=3)
def tag(ax, x, y, text, color=AMBER, fs=8.2):
ax.text(x, y, text, fontsize=fs, color=color, fontweight="bold",
ha="center", va="center", zorder=6)
def glow_text(ax, x, y, text, color=WHITE, fs=14, bold=True):
t = ax.text(x, y, text, ha="center", va="center", fontsize=fs,
fontweight="bold" if bold else "normal", color=color, zorder=7)
t.set_path_effects([withStroke(linewidth=4, foreground=BG)])
def section_band(ax, y, h, label, color, alpha=0.07):
r = FancyBboxPatch((0.15, y-h/2), 19.7, h,
boxstyle="round,pad=0.05,rounding_size=0.2",
facecolor=color, edgecolor=color, alpha=alpha,
linewidth=1, zorder=1)
ax.add_patch(r)
ax.text(0.42, y, label, fontsize=7.5, color=color, fontweight="bold",
rotation=90, va="center", ha="center", alpha=0.7, zorder=2)
# ════════════════════════════════════════════════════════════════════════════
# TITLE
# ════════════════════════════════════════════════════════════════════════════
title_bg = FancyBboxPatch((0.2, 28.6), 19.6, 1.2,
boxstyle="round,pad=0.1,rounding_size=0.3",
facecolor=LBLUE, edgecolor=BLUE, lw=2, zorder=3)
ax.add_patch(title_bg)
glow_text(ax, 10, 29.28, "CBC INTERPRETATION FLOWCHART", WHITE, fs=18, bold=True)
ax.text(10, 28.84, "Complete Blood Count | Step-by-Step Decision Guide",
ha="center", va="center", fontsize=10.5, color=BLUE, zorder=4)
# ════════════════════════════════════════════════════════════════════════════
# START NODE
# ════════════════════════════════════════════════════════════════════════════
dot(ax, 10, 28.0, r=0.22, color=BLUE)
arr(ax, 10, 27.78, 10, 27.35, color=BLUE)
pill(ax, 10, 27.05, 9.5, 0.55,
"CBC Result in Hand — Follow each panel systematically",
bg=DBLUE, ec=BLUE, fs=10.5, tc=WHITE, bold=True, lw=2)
# Spine arrow down
arr(ax, 10, 26.77, 10, 26.2, color=BLUE, lw=2)
# ════════════════════════════════════════════════════════════════════════════
# SECTION BANDS (background)
# ════════════════════════════════════════════════════════════════════════════
section_band(ax, 24.5, 3.6, "RBC", RED, alpha=0.09)
section_band(ax, 18.2, 5.5, "WBC", BLUE, alpha=0.07)
section_band(ax, 11.6, 4.4, "PLT", TEAL, alpha=0.07)
section_band(ax, 5.5, 3.8, "Summary", PURPLE, alpha=0.06)
# ════════════════════════════════════════════════════════════════════════════
# ██████ PANEL 1: RBC ██████
# ════════════════════════════════════════════════════════════════════════════
pill(ax, 10, 26.0, 5.0, 0.5,
"PANEL 1 — RED BLOOD CELLS",
bg=LRED, ec=RED, fs=11, tc=RED, bold=True, lw=2)
arr(ax, 10, 25.75, 10, 25.2, color=RED)
# CHECK HGB
pill(ax, 10, 24.95, 7.5, 0.52,
"Check Hemoglobin (Hgb) Male > 13.5 | Female > 12.0 g/dL",
bg=CARD, ec=RED, fs=9.5, tc=WHITE, lw=1.5)
arr(ax, 10, 24.69, 10, 24.1, color=RED)
# DECISION: Hgb low?
hexbox(ax, 10, 23.75, 6.0, 0.65, "Is Hgb LOW?", bg=LRED, ec=RED, fs=10, tc=RED)
# NO branch (right) ──────────────────────────────────────────────────────────
arr(ax, 13.0, 23.75, 15.2, 23.75, color=GREEN, lw=1.6)
tag(ax, 14.0, 23.95, "NO", GREEN, fs=8.5)
pill(ax, 16.8, 23.75, 3.0, 0.52,
"Hgb Normal\nNo anemia",
bg=LGREEN, ec=GREEN, fs=8.5, tc=GREEN, lw=1.4)
# YES branch (down) ──────────────────────────────────────────────────────────
arr(ax, 10, 23.42, 10, 22.85, color=RED)
tag(ax, 10.35, 23.12, "YES", RED, fs=8.5)
# HIGH HGB (left)
arr(ax, 7.0, 23.75, 4.8, 23.75, color=ORANGE, lw=1.5)
tag(ax, 5.9, 23.95, "HIGH?", ORANGE, fs=8.5)
pill(ax, 3.3, 23.75, 3.2, 0.52,
"Polycythemia\nCheck JAK2 / EPO",
bg=LORAN, ec=ORANGE, fs=8.5, tc=ORANGE, lw=1.4)
# MCV DECISION ────────────────────────────────────────────────────────────────
pill(ax, 10, 22.58, 5.5, 0.48,
"ANEMIA confirmed -> Check MCV",
bg=LRED, ec=RED, fs=9, tc=RED, bold=True)
arr(ax, 10, 22.34, 10, 21.78, color=RED)
hexbox(ax, 10, 21.45, 6.2, 0.68, "MCV Value?", bg=LRED, ec=RED, fs=10, tc=RED)
# ── MICROCYTIC (<80) ── LEFT ─────────────────────────────────────────────────
arr(ax, 6.9, 21.45, 4.5, 21.45, color=DRED, lw=1.6)
tag(ax, 5.7, 21.68, "< 80 fL", DRED, fs=8.2)
pill(ax, 3.2, 21.45, 3.8, 0.52, "MICROCYTIC\nCheck Ferritin", bg=LRED, ec=DRED, fs=9, tc=DRED, bold=True)
arr(ax, 3.2, 21.19, 3.2, 20.62, color=DRED)
hexbox(ax, 3.2, 20.28, 3.5, 0.65, "Ferritin?", bg=LRED, ec=DRED, fs=9, tc=DRED)
# Low ferritin
arr(ax, 3.2, 19.95, 3.2, 19.42, color=DRED); tag(ax, 3.5, 19.68, "LOW", DRED, fs=8)
pill(ax, 3.2, 19.12, 3.8, 0.55, "Iron Deficiency\nAnemia\nOral Iron Rx", bg=LRED, ec=DRED, fs=8.5, tc=RED, bold=True)
# Normal ferritin
arr(ax, 4.95, 20.28, 6.35, 20.28, color=AMBER, lw=1.4); tag(ax, 5.65, 20.48, "NORM", AMBER, fs=8)
pill(ax, 7.4, 20.28, 2.1, 0.55, "Thalassemia\nLead poison.\nHgb electro.", bg=LAMBER, ec=AMBER, fs=7.8, tc=AMBER)
# ── NORMOCYTIC (80-100) ── MIDDLE ────────────────────────────────────────────
arr(ax, 10, 21.11, 10, 20.55, color=TEAL); tag(ax, 10.35, 20.82, "80-100", TEAL, fs=8.2)
pill(ax, 10, 20.28, 3.8, 0.5, "NORMOCYTIC\nCheck Reticulocytes", bg=LTEAL, ec=TEAL, fs=9, tc=TEAL, bold=True)
arr(ax, 10, 20.03, 10, 19.47, color=TEAL)
hexbox(ax, 10, 19.15, 3.8, 0.65, "Reticulocytes?", bg=LTEAL, ec=TEAL, fs=9, tc=TEAL)
# Retic HIGH
arr(ax, 11.9, 19.15, 13.8, 19.15, color=ORANGE, lw=1.4); tag(ax, 12.85, 19.38, "HIGH", ORANGE, fs=8)
pill(ax, 15.2, 19.15, 2.7, 0.55, "Hemolytic\nAnemia /\nAcute loss", bg=LORAN, ec=ORANGE, fs=8.2, tc=ORANGE)
# Retic LOW
arr(ax, 10, 18.82, 10, 18.28, color=BLUE); tag(ax, 10.35, 18.55, "LOW", BLUE, fs=8)
pill(ax, 10, 17.98, 4.0, 0.55, "CKD / Chronic Disease\nAplastic Anemia", bg=LBLUE, ec=BLUE, fs=8.5, tc=BLUE)
# ── MACROCYTIC (>100) ── RIGHT ───────────────────────────────────────────────
arr(ax, 13.1, 21.45, 15.5, 21.45, color=PURPLE, lw=1.6)
tag(ax, 14.2, 21.68, "> 100 fL", PURPLE, fs=8.2)
pill(ax, 17.2, 21.45, 3.8, 0.52, "MACROCYTIC\nCheck B12 / Folate", bg=LPURP, ec=PURPLE, fs=9, tc=PURPLE, bold=True)
arr(ax, 17.2, 21.19, 17.2, 20.62, color=PURPLE)
hexbox(ax, 17.2, 20.28, 3.4, 0.65, "B12 / Folate?", bg=LPURP, ec=PURPLE, fs=9, tc=PURPLE)
arr(ax, 17.2, 19.95, 17.2, 19.42, color=PURPLE); tag(ax, 17.55, 19.68, "LOW", PURPLE, fs=8)
pill(ax, 17.2, 19.12, 3.5, 0.55, "Megaloblastic\nAnemia\nB12 / Folate Rx", bg=LPURP, ec=PURPLE, fs=8.5, tc=PURPLE, bold=True)
arr(ax, 15.5, 20.28, 14.6, 20.28, color=GREY, lw=1.3); tag(ax, 15.05, 20.48, "NORM", GREY, fs=8)
pill(ax, 13.5, 20.28, 1.8, 0.55, "Alcohol\nLiver / Thyroid\nMeds", bg=DGREY, ec=GREY, fs=7.5, tc=GREY)
# ════════════════════════════════════════════════════════════════════════════
# ██████ PANEL 2: WBC ██████
# ════════════════════════════════════════════════════════════════════════════
# Connector from Panel 1 bottom to Panel 2
arr(ax, 10, 17.68, 10, 17.15, color=BLUE, lw=2)
pill(ax, 10, 16.9, 5.0, 0.5,
"PANEL 2 — WHITE BLOOD CELLS",
bg=LBLUE, ec=BLUE, fs=11, tc=BLUE, bold=True, lw=2)
arr(ax, 10, 16.65, 10, 16.08, color=BLUE)
pill(ax, 10, 15.82, 7.5, 0.48,
"Check Total WBC Count Normal: 4,000 – 11,000 cells/uL",
bg=CARD, ec=BLUE, fs=9.5, tc=WHITE, lw=1.5)
arr(ax, 10, 15.58, 10, 15.02, color=BLUE)
hexbox(ax, 10, 14.68, 6.0, 0.65, "WBC Count?", bg=LBLUE, ec=BLUE, fs=10, tc=BLUE)
# LOW (Leukopenia) ── LEFT
arr(ax, 7.0, 14.68, 4.8, 14.68, color=TEAL, lw=1.6); tag(ax, 5.9, 14.9, "< 4K", TEAL, fs=8.2)
pill(ax, 3.2, 14.68, 3.5, 0.52, "LEUKOPENIA\nViral / Chemo\nAutoimmune", bg=LTEAL, ec=TEAL, fs=8.5, tc=TEAL, bold=True)
# HIGH (Leukocytosis) ── RIGHT
arr(ax, 13.0, 14.68, 15.2, 14.68, color=RED, lw=1.6); tag(ax, 14.0, 14.9, "> 11K", RED, fs=8.2)
pill(ax, 16.8, 14.68, 3.4, 0.52, "LEUKOCYTOSIS\nCheck Differential", bg=LRED, ec=RED, fs=8.5, tc=RED, bold=True)
# NORMAL
arr(ax, 10, 14.35, 10, 13.78, color=GREEN); tag(ax, 10.4, 14.05, "4-11K", GREEN, fs=8)
pill(ax, 10, 13.52, 4.0, 0.48, "WBC Normal Range\nReview Differential anyway", bg=DGREY, ec=GREEN, fs=8.5, tc=GREEN)
# DIFFERENTIAL TABLE ──────────────────────────────────────────────────────────
arr(ax, 10, 13.28, 10, 12.72, color=BLUE)
pill(ax, 10, 12.48, 7.5, 0.45, "WBC Differential Breakdown", bg=LBLUE, ec=BLUE, fs=9.5, tc=BLUE, bold=True)
diff_items = [
("Neutrophil 50-70%", "Bacterial infx / Steroids", "Viral / Chemo", RED, TEAL),
("Lymphocyte 20-40%", "Viral / EBV / CLL", "HIV / Steroids", BLUE, RED),
("Monocyte 2-8%", "TB / Fungal / Mono. leukemia","Aplasia", AMBER, GREY),
("Eosinophil 1-4%", "Allergy / Parasites", "Cushing's", PURPLE, GREY),
("Basophil 0.5-1%", "CML / Hypothyroid", "Hyperthyroid", PINK, GREY),
]
col_x = [2.5, 8.0, 13.8, 16.6, 18.8] # label, normal, up-cause, up-sym, down-sym
col_w = [4.8, 5.5, 2.5, 2.0, 2.0]
for ri, (cell_lbl, up, dn, uc, dc) in enumerate(diff_items):
ry = 11.9 - ri * 0.52
pill(ax, 4.8, ry, 4.8, 0.44, cell_lbl, bg=DGREY, ec=BORDER, fs=8, tc=WHITE, lw=0.8)
pill(ax, 10.4, ry, 5.5, 0.44, f"HIGH: {up}", bg=LRED if uc==RED else (LBLUE if uc==BLUE else LAMBER if uc==AMBER else LPURP if uc==PURPLE else LPINK), ec=uc, fs=7.8, tc=uc, lw=0.8)
pill(ax, 16.0, ry, 5.0, 0.44, f"LOW: {dn}", bg=LTEAL if dc==TEAL else DGREY, ec=dc, fs=7.8, tc=dc, lw=0.8)
# Blasts warning
pill(ax, 10, 9.1, 12.0, 0.5,
"!! BLASTS on peripheral smear -> ACUTE LEUKEMIA -> URGENT haematology referral",
bg=LRED, ec=RED, fs=9.5, tc=RED, bold=True, lw=2.2)
# ════════════════════════════════════════════════════════════════════════════
# ██████ PANEL 3: PLATELETS ██████
# ════════════════════════════════════════════════════════════════════════════
arr(ax, 10, 8.85, 10, 8.32, color=TEAL, lw=2)
pill(ax, 10, 8.08, 5.0, 0.5,
"PANEL 3 — PLATELETS",
bg=LTEAL, ec=TEAL, fs=11, tc=TEAL, bold=True, lw=2)
arr(ax, 10, 7.83, 10, 7.28, color=TEAL)
pill(ax, 10, 7.02, 7.5, 0.48,
"Check Platelet Count (PLT) Normal: 150,000 – 400,000 /uL",
bg=CARD, ec=TEAL, fs=9.5, tc=WHITE, lw=1.5)
arr(ax, 10, 6.78, 10, 6.22, color=TEAL)
hexbox(ax, 10, 5.88, 6.0, 0.65, "PLT Count?", bg=LTEAL, ec=TEAL, fs=10, tc=TEAL)
# LOW (Thrombocytopenia) ── LEFT
arr(ax, 7.0, 5.88, 4.8, 5.88, color=RED, lw=1.6); tag(ax, 5.9, 6.1, "< 150K", RED, fs=8.2)
pill(ax, 3.0, 5.88, 3.5, 0.52, "THROMBOCYTOPENIA", bg=LRED, ec=RED, fs=8.5, tc=RED, bold=True)
thrombo = [("ITP",PURPLE,LPURP),("Dengue",TEAL,LTEAL),("Chemo",BLUE,LBLUE),("DIC/Sepsis",RED,LRED)]
for i,(lab,ec2,bg2) in enumerate(thrombo):
xi = 1.1 + i * 1.85
arr(ax, 3.0, 5.62, xi, 5.15, color=ec2, lw=1.2)
pill(ax, xi, 4.88, 1.65, 0.44, lab, bg=bg2, ec=ec2, fs=8.2, tc=ec2, lw=1.0)
# HIGH (Thrombocytosis) ── RIGHT
arr(ax, 13.0, 5.88, 15.2, 5.88, color=ORANGE, lw=1.6); tag(ax, 14.0, 6.1, "> 400K", ORANGE, fs=8.2)
pill(ax, 16.8, 5.88, 3.2, 0.52, "THROMBOCYTOSIS\nReactive / ET", bg=LORAN, ec=ORANGE, fs=8.5, tc=ORANGE, bold=True)
# NORMAL
arr(ax, 10, 5.55, 10, 5.02, color=GREEN); tag(ax, 10.4, 5.28, "NORMAL", GREEN, fs=8)
pill(ax, 10, 4.75, 3.5, 0.44, "Normal Platelets", bg=LGREEN, ec=GREEN, fs=8.5, tc=GREEN)
# Bleeding risk legend strip
risk_items = [("> 100K","MINIMAL",GREEN,LGREEN),("50-100K","Minor Risk",AMBER,LAMBER),
("20-50K","MODERATE",ORANGE,LORAN),("< 20K","SPONTANEOUS",RED,LRED)]
rx_start = 2.0
for i,(cnt,risk,ec2,bg2) in enumerate(risk_items):
xi = rx_start + i * 4.5
pill(ax, xi, 3.9, 1.8, 0.42, cnt, bg=DGREY, ec=ec2, fs=9, tc=ec2, lw=1.2)
pill(ax, xi, 3.42, 1.8, 0.42, risk, bg=bg2, ec=ec2, fs=8.5, tc=ec2, bold=True, lw=1.2)
if i < 3:
arr(ax, xi+1.0, 3.66, xi+2.7, 3.66, color=GREY, lw=1.0)
ax.text(10, 4.28, "Platelet Count -> Bleeding Risk Gradient", ha="center",
fontsize=8.5, color=GREY, zorder=4, fontstyle="italic")
# ════════════════════════════════════════════════════════════════════════════
# PANCYTOPENIA WARNING BAR
# ════════════════════════════════════════════════════════════════════════════
pc_rect = FancyBboxPatch((0.3, 2.78), 19.4, 0.62,
boxstyle="round,pad=0.06,rounding_size=0.2",
facecolor=LRED, edgecolor=RED, lw=2.5, zorder=5)
ax.add_patch(pc_rect)
ax.text(10, 3.09,
"!! PANCYTOPENIA: Hgb LOW + WBC LOW + PLT LOW "
"-> Bone Marrow Failure / Aplastic Anemia / Leukemia / MDS -> URGENT referral",
ha="center", va="center", fontsize=9.5, fontweight="bold", color=RED, zorder=6)
# ════════════════════════════════════════════════════════════════════════════
# FOOTER
# ════════════════════════════════════════════════════════════════════════════
foot = FancyBboxPatch((0.3, 0.15), 19.4, 0.55,
boxstyle="round,pad=0.06,rounding_size=0.2",
facecolor=CARD, edgecolor=BORDER, lw=1, zorder=3)
ax.add_patch(foot)
ax.text(10, 0.43,
"For educational reference only. Interpret all CBC results in clinical context with a qualified healthcare provider.",
ha="center", va="center", fontsize=8.5, color=GREY, zorder=4)
# ── Section labels on the left spine ─────────────────────────────────────────
for sy, slbl, sc in [(24.1,"RBC",RED),(18.2,"WBC",BLUE),(11.0,"PLT",TEAL),(5.0,"RISK",TEAL)]:
ax.text(0.62, sy, slbl, fontsize=8, color=sc, fontweight="bold",
rotation=90, ha="center", va="center",
bbox=dict(boxstyle="round,pad=0.3", facecolor=BG, edgecolor=sc, lw=1.2), zorder=5)
# ════════════════════════════════════════════════════════════════════════════
plt.tight_layout(pad=0)
out = "/home/daytona/workspace/cbc-cheatsheet/CBC_Flowchart_DarkTheme.png"
plt.savefig(out, dpi=180, bbox_inches="tight", facecolor=fig.get_facecolor())
print(f"Saved: {out}")
cd /home/daytona/workspace/cbc-cheatsheet && python generate_cbc_flowchart_v2.py…

CBC Flowchart DarkTheme
Image · PNG
| Feature | Previous | This Version |
|---|---|---|
| Background | Light white/blue | Dark GitHub-style (#0D1117) |
| Layout | 3 side-by-side columns | Single vertical spine with branches |
| Nodes | Rectangular boxes | Rounded pill shapes |
| Decisions | Yellow diamonds | Color-coded diamonds per panel |
| WBC section | Summary table below | Inline row-by-row differential cards (HIGH / LOW causes) |
| Platelets | Separate risk table | Inline gradient risk strip |
| Section bands | Column headers | Subtle colored background glow bands |
| Style | Clinical/formal | Dark-mode, modern UI feel |
I am not satisfied with flowchart dark ways
~/cbc-cheatsheet/generate_cbc_v3.py
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, Polygon, FancyArrowPatch
import numpy as np
fig, ax = plt.subplots(figsize=(24, 32))
ax.set_xlim(0, 24)
ax.set_ylim(0, 32)
ax.axis("off")
fig.patch.set_facecolor("#FFFFFF")
# ── Palette (bright, clean) ──────────────────────────────────────────────────
WHITE = "#FFFFFF"
OFFWH = "#F8FAFF"
LGREY = "#F1F3F6"
MGREY = "#DDE1E7"
DGREY = "#5A6475"
BLACK = "#1A1A2E"
RED = "#D32F2F"; LRED = "#FFEBEE"; MRED = "#EF5350"
BLUE = "#1565C0"; LBLUE = "#E3F2FD"; MBLUE = "#1976D2"
GREEN = "#2E7D32"; LGREEN = "#E8F5E9"; MGREEN = "#43A047"
TEAL = "#00695C"; LTEAL = "#E0F2F1"; MTEAL = "#00897B"
PURPLE = "#6A1B9A"; LPURPL = "#F3E5F5"; MPURPL = "#8E24AA"
ORANGE = "#E65100"; LORAN = "#FFF3E0"; MORAN = "#FB8C00"
AMBER = "#F57F17"; LAMBER = "#FFFDE7"
NAVY = "#0D2B6B"
ROSE = "#C2185B"; LROSE = "#FCE4EC"
# ── Helpers ───────────────────────────────────────────────────────────────────
def box(ax, x, y, w, h, txt, bg=WHITE, ec=BLUE, fs=9, tc=BLACK,
bold=False, lw=1.8, rad=0.2, alpha=1.0):
r = FancyBboxPatch((x-w/2, y-h/2), w, h,
boxstyle=f"round,pad=0.05,rounding_size={rad}",
facecolor=bg, edgecolor=ec, linewidth=lw, zorder=4, alpha=alpha)
ax.add_patch(r)
ax.text(x, y, txt, ha="center", va="center", fontsize=fs,
fontweight="bold" if bold else "normal", color=tc, zorder=5,
multialignment="center", linespacing=1.35)
def diam(ax, x, y, w, h, txt, bg=LAMBER, ec=AMBER, fs=9.5, tc=BLACK):
pts = np.array([[x,y+h/2],[x+w/2,y],[x,y-h/2],[x-w/2,y]])
ax.add_patch(Polygon(pts, closed=True, facecolor=bg, edgecolor=ec, lw=2.2, zorder=4))
ax.text(x, y, txt, ha="center", va="center", fontsize=fs,
fontweight="bold", color=tc, zorder=5, multialignment="center", linespacing=1.3)
def arr(ax, x1,y1,x2,y2, color=DGREY, lw=1.8):
ax.annotate("", xy=(x2,y2), xytext=(x1,y1),
arrowprops=dict(arrowstyle="-|>", color=color, lw=lw,
mutation_scale=15), zorder=3)
def line(ax, x1,y1,x2,y2, color=MGREY, lw=1.4, ls="-"):
ax.plot([x1,x2],[y1,y2], color=color, lw=lw, ls=ls, zorder=2)
def lbl(ax, x, y, txt, color=DGREY, fs=8.5, ha="left", bold=True):
ax.text(x, y, txt, fontsize=fs, color=color,
fontweight="bold" if bold else "normal",
ha=ha, va="center", zorder=6)
def badge(ax, x, y, txt, bg, ec, tc=WHITE, fs=8, w=2.2, h=0.38):
box(ax, x, y, w, h, txt, bg=bg, ec=ec, fs=fs, tc=tc, bold=True, lw=1.2, rad=0.15)
def swim_label(ax, x, y, txt, color):
ax.text(x, y, txt, fontsize=9, color=color, fontweight="bold",
rotation=90, ha="center", va="center", zorder=6,
bbox=dict(boxstyle="round,pad=0.4", fc=WHITE, ec=color, lw=1.5))
# ════════════════════════════════════════════════════════════════════════════
# SWIM LANE BACKGROUNDS
# ════════════════════════════════════════════════════════════════════════════
lanes = [
(25.0, 5.8, RED, "RBC"),
(18.5, 6.2, BLUE, "WBC"),
(11.8, 4.8, TEAL, "PLT"),
(5.4, 3.4, PURPLE, "SUMMARY"),
]
for cy, ch, cc, clbl in lanes:
r = FancyBboxPatch((1.1, cy-ch/2), 22.6, ch,
boxstyle="round,pad=0.1,rounding_size=0.3",
facecolor=cc, edgecolor=cc, alpha=0.06,
linewidth=0, zorder=1)
ax.add_patch(r)
# left side tab
tab = FancyBboxPatch((0.2, cy-ch/2), 0.85, ch,
boxstyle="round,pad=0.05,rounding_size=0.15",
facecolor=cc, edgecolor=cc, alpha=0.85, linewidth=0, zorder=2)
ax.add_patch(tab)
ax.text(0.625, cy, clbl, fontsize=8, color=WHITE, fontweight="bold",
rotation=90, ha="center", va="center", zorder=3)
# ════════════════════════════════════════════════════════════════════════════
# TITLE
# ════════════════════════════════════════════════════════════════════════════
title_r = FancyBboxPatch((1.1, 30.55), 22.6, 1.25,
boxstyle="round,pad=0.1,rounding_size=0.35",
facecolor=NAVY, edgecolor=NAVY, lw=0, zorder=4)
ax.add_patch(title_r)
ax.text(12.4, 31.25, "CBC COMPLETE BLOOD COUNT — INTERPRETATION FLOWCHART",
ha="center", va="center", fontsize=18, fontweight="bold", color=WHITE, zorder=5)
ax.text(12.4, 30.82, "Systematic step-by-step guide | RBC • WBC • Platelets • Clinical Patterns",
ha="center", va="center", fontsize=10.5, color="#A8C4E8", zorder=5)
# START
box(ax, 12.4, 30.1, 9, 0.55,
"Receive CBC Report -> Interpret Each Panel Systematically",
bg=LBLUE, ec=BLUE, fs=10.5, tc=NAVY, bold=True, lw=2)
arr(ax, 12.4, 29.82, 12.4, 29.3, color=NAVY, lw=2)
# ════════════════════════════════════════════════════════════════════════════
# PANEL 1 — RBC
# ════════════════════════════════════════════════════════════════════════════
box(ax, 12.4, 29.05, 5.5, 0.48,
"PANEL 1 RED BLOOD CELLS (RBC)",
bg=RED, ec=RED, fs=11, tc=WHITE, bold=True, lw=0, rad=0.22)
arr(ax, 12.4, 28.81, 12.4, 28.28, color=RED, lw=1.8)
box(ax, 12.4, 28.02, 8.5, 0.48,
"Step 1 : Check Hemoglobin (Hgb) Male ≥ 13.5 | Female ≥ 12.0 g/dL",
bg=LRED, ec=RED, fs=9.5, tc=RED, bold=True, lw=1.5)
arr(ax, 12.4, 27.78, 12.4, 27.22, color=RED)
diam(ax, 12.4, 26.85, 6.4, 0.72, "Is Hgb LOW?", bg=LAMBER, ec=AMBER, fs=10.5, tc=BLACK)
# NO (right) ────────────────────────────────────────────────────────────────
arr(ax, 15.6, 26.85, 17.6, 26.85, color=GREEN, lw=1.6)
lbl(ax, 16.0, 27.08, "NO", GREEN)
box(ax, 19.5, 26.85, 3.5, 0.6,
"Hgb NORMAL\nNo Anemia",
bg=LGREEN, ec=GREEN, fs=9, tc=GREEN, bold=True, lw=1.5)
# HIGH (left)
arr(ax, 9.2, 26.85, 7.2, 26.85, color=ORANGE, lw=1.5)
lbl(ax, 7.3, 27.08, "HIGH", ORANGE)
box(ax, 5.4, 26.85, 3.0, 0.6,
"POLYCYTHEMIA\nCheck JAK2 / EPO",
bg=LORAN, ec=ORANGE, fs=8.8, tc=ORANGE, bold=True, lw=1.5)
# YES (down)
arr(ax, 12.4, 26.49, 12.4, 25.95, color=RED)
lbl(ax, 12.65, 26.2, "YES — ANEMIA", RED)
box(ax, 12.4, 25.68, 5.5, 0.48,
"Step 2 : Check MCV (cell size)",
bg=LRED, ec=RED, fs=9.5, tc=RED, bold=True, lw=1.5)
arr(ax, 12.4, 25.44, 12.4, 24.88, color=RED)
diam(ax, 12.4, 24.52, 6.6, 0.72, "MCV Value?", bg=LAMBER, ec=AMBER, fs=10.5, tc=BLACK)
# ── THREE MCV BRANCHES ──────────────────────────────────────────────────────
# LEFT < 80 MICROCYTIC
line(ax, 9.1, 24.52, 5.5, 24.52, color=RED, lw=1.6)
arr(ax, 5.5, 24.52, 5.5, 24.08, color=RED)
lbl(ax, 6.4, 24.72, "< 80 fL Microcytic", RED, fs=8.5)
box(ax, 5.5, 23.78, 4.2, 0.52, "MICROCYTIC ANEMIA\nCheck Serum Ferritin", bg=LRED, ec=RED, fs=9, tc=RED, bold=True)
arr(ax, 5.5, 23.52, 5.5, 22.98, color=RED)
diam(ax, 5.5, 22.65, 4.0, 0.62, "Ferritin?", bg=LAMBER, ec=AMBER, fs=9.5)
# Ferritin LOW
arr(ax, 5.5, 22.34, 5.5, 21.82, color=RED); lbl(ax, 5.72, 22.06, "LOW", RED)
box(ax, 5.5, 21.52, 4.2, 0.62,
"IRON DEFICIENCY\nOral Iron • Find cause\n(GI bleed / menorrhagia)",
bg=LRED, ec=RED, fs=8.5, tc=RED, bold=True)
# Ferritin NORM / HIGH
arr(ax, 7.5, 22.65, 9.5, 22.65, color=ORANGE, lw=1.4); lbl(ax, 7.6, 22.85, "NORM/HIGH", ORANGE, fs=8)
box(ax, 10.85, 22.65, 2.4, 0.62, "Thalassemia\nLead poison.\nHgb electro.", bg=LORAN, ec=ORANGE, fs=8, tc=ORANGE)
# MIDDLE 80-100 NORMOCYTIC
arr(ax, 12.4, 24.16, 12.4, 23.62, color=TEAL); lbl(ax, 12.62, 23.88, "80–100 fL Normocytic", TEAL, fs=8.5)
box(ax, 12.4, 23.35, 4.5, 0.52, "NORMOCYTIC ANEMIA\nCheck Reticulocyte Count", bg=LTEAL, ec=TEAL, fs=9, tc=TEAL, bold=True)
arr(ax, 12.4, 23.09, 12.4, 22.55, color=TEAL)
diam(ax, 12.4, 22.22, 4.2, 0.62, "Reticulocytes?", bg=LTEAL, ec=TEAL, fs=9.5, tc=TEAL)
# Retic HIGH
arr(ax, 14.5, 22.22, 16.5, 22.22, color=ORANGE, lw=1.4); lbl(ax, 14.6, 22.42, "HIGH > 2%", ORANGE, fs=8)
box(ax, 18.3, 22.22, 3.3, 0.65, "Hemolytic Anemia /\nAcute Blood Loss\nLDH, Coombs test", bg=LORAN, ec=ORANGE, fs=8.2, tc=ORANGE)
# Retic LOW
arr(ax, 12.4, 21.91, 12.4, 21.38, color=BLUE); lbl(ax, 12.62, 21.62, "LOW < 0.5%", BLUE, fs=8)
box(ax, 12.4, 21.1, 4.5, 0.52, "CKD / Anemia of chronic\ndisease / Aplastic anemia", bg=LBLUE, ec=BLUE, fs=8.5, tc=BLUE)
# RIGHT >100 MACROCYTIC
line(ax, 15.7, 24.52, 19.5, 24.52, color=PURPLE, lw=1.6)
arr(ax, 19.5, 24.52, 19.5, 24.08, color=PURPLE)
lbl(ax, 16.2, 24.72, "> 100 fL Macrocytic", PURPLE, fs=8.5)
box(ax, 19.5, 23.78, 4.2, 0.52, "MACROCYTIC ANEMIA\nCheck B12 & Folate", bg=LPURPL, ec=PURPLE, fs=9, tc=PURPLE, bold=True)
arr(ax, 19.5, 23.52, 19.5, 22.98, color=PURPLE)
diam(ax, 19.5, 22.65, 4.0, 0.62, "B12 / Folate?", bg=LPURPL, ec=PURPLE, fs=9.5, tc=PURPLE)
arr(ax, 19.5, 22.34, 19.5, 21.82, color=PURPLE); lbl(ax, 19.72, 22.06, "LOW", PURPLE)
box(ax, 19.5, 21.52, 4.2, 0.65,
"MEGALOBLASTIC\nB12: IM injection\nFolate: 5 mg oral",
bg=LPURPL, ec=PURPLE, fs=8.5, tc=PURPLE, bold=True)
arr(ax, 21.5, 22.65, 22.5, 22.65, color=DGREY, lw=1.3); lbl(ax, 21.55, 22.85, "NORM", DGREY, fs=8)
box(ax, 23.2, 22.0, 1.4, 0.9, "Alcohol\nLiver dis.\nHypothyroid\nMeds", bg=LGREY, ec=MGREY, fs=7.5, tc=DGREY)
# RDW note
box(ax, 12.4, 20.42, 10.5, 0.45,
"Also check RDW (Normal 11.5–14.5%) — High RDW = Mixed deficiency or early anemia",
bg=LGREY, ec=MGREY, fs=8.5, tc=DGREY, lw=1.2, rad=0.15)
# ════════════════════════════════════════════════════════════════════════════
# PANEL 2 — WBC
# ════════════════════════════════════════════════════════════════════════════
arr(ax, 12.4, 20.19, 12.4, 19.65, color=BLUE, lw=2)
box(ax, 12.4, 19.4, 5.5, 0.48,
"PANEL 2 WHITE BLOOD CELLS (WBC)",
bg=BLUE, ec=BLUE, fs=11, tc=WHITE, bold=True, lw=0, rad=0.22)
arr(ax, 12.4, 19.16, 12.4, 18.62, color=BLUE)
box(ax, 12.4, 18.38, 8.5, 0.46,
"Step 3 : Check Total WBC Count Normal : 4,000 – 11,000 cells / µL",
bg=LBLUE, ec=BLUE, fs=9.5, tc=BLUE, bold=True, lw=1.5)
arr(ax, 12.4, 18.15, 12.4, 17.62, color=BLUE)
diam(ax, 12.4, 17.25, 6.6, 0.72, "WBC Count?", bg=LBLUE, ec=BLUE, fs=10.5, tc=NAVY)
# LOW (left)
arr(ax, 9.1, 17.25, 6.8, 17.25, color=TEAL, lw=1.6)
lbl(ax, 7.5, 17.48, "< 4K LOW", TEAL)
box(ax, 5.2, 17.25, 3.4, 0.62, "LEUKOPENIA\nViral infx / Chemo\nAutoimmune", bg=LTEAL, ec=TEAL, fs=8.8, tc=TEAL, bold=True)
# HIGH (right)
arr(ax, 15.7, 17.25, 17.8, 17.25, color=RED, lw=1.6)
lbl(ax, 16.0, 17.48, "> 11K HIGH", RED)
box(ax, 19.5, 17.25, 3.6, 0.62, "LEUKOCYTOSIS\nCheck Differential", bg=LRED, ec=RED, fs=8.8, tc=RED, bold=True)
# NORMAL (down)
arr(ax, 12.4, 16.89, 12.4, 16.35, color=GREEN); lbl(ax, 12.62, 16.6, "4–11K NORMAL", GREEN)
box(ax, 12.4, 16.1, 4.8, 0.46, "WBC in Normal Range\nStill review Differential", bg=LGREEN, ec=GREEN, fs=8.8, tc=GREEN)
arr(ax, 12.4, 15.87, 12.4, 15.35, color=BLUE)
# DIFFERENTIAL TABLE
box(ax, 12.4, 15.1, 9.0, 0.45,
"Step 4 : WBC Differential — Which cell type is abnormal?",
bg=BLUE, ec=BLUE, fs=9.5, tc=WHITE, bold=True, lw=0)
# Table header
hdr_y = 14.68
for cx2, cw2, txt2, bg2 in [
(4.0, 3.8, "Cell Type (Normal %)", NAVY),
(8.5, 4.2, "HIGH — Think of ...", RED),
(13.8, 4.2, "LOW — Think of ...", TEAL),
(19.2, 4.2, "Urgent Flag", MORAN),
]:
box(ax, cx2, hdr_y, cw2-0.1, 0.42, txt2, bg=bg2, ec=bg2, fs=8.5, tc=WHITE, bold=True, lw=0)
diff_rows = [
("Neutrophil (50–70%)", "Bacterial infx, steroids, MI", "Viral infx, chemo, aplasia", "Neutropenia < 1.5K = infection risk"),
("Lymphocyte (20–40%)", "Viral infx, EBV, CLL", "HIV, radiation, steroids", "Lymphopenia = immune compromise"),
("Monocyte (2–8%)", "TB, fungal infx, mono leukemia", "Aplasia (uncommon)", "Monocytosis + fever = TB/fungi"),
("Eosinophil (1–4%)", "Allergy, asthma, parasites", "Steroids, Cushing's", "Eosinophilia > 1.5K = investigate"),
("Basophil (0.5–1%)", "CML, hypothyroidism", "Hyperthyroidism, acute infx", "Basophilia + high WBC = CML?"),
]
for ri, (cn, hi, lo, flag) in enumerate(diff_rows):
ry2 = hdr_y - 0.54*(ri+1)
bg2 = WHITE if ri % 2 == 0 else LGREY
box(ax, 4.0, ry2, 3.7, 0.48, cn, bg=bg2, ec=MGREY, fs=8.2, tc=BLACK, lw=0.8, bold=True)
box(ax, 8.5, ry2, 4.1, 0.48, hi, bg=LRED, ec=MRED, fs=8, tc=RED, lw=0.8)
box(ax, 13.8, ry2, 4.1, 0.48, lo, bg=LTEAL, ec=MTEAL, fs=8, tc=TEAL, lw=0.8)
box(ax, 19.2, ry2, 4.1, 0.48, flag, bg=LORAN, ec=MORAN, fs=7.8, tc=ORANGE,lw=0.8)
# BLASTS warning
box(ax, 12.4, 11.7, 16.5, 0.52,
"!! BLASTS on Peripheral Smear -> ACUTE LEUKEMIA -> URGENT Haematology Referral + Bone Marrow Biopsy",
bg=LRED, ec=RED, fs=9.5, tc=RED, bold=True, lw=2.5)
# Left shift
box(ax, 12.4, 11.1, 14.0, 0.44,
"Left Shift (Bands / Immature neutrophils HIGH) -> Severe Bacterial Infection / Sepsis",
bg=LORAN, ec=ORANGE, fs=8.8, tc=ORANGE, lw=1.5)
# ════════════════════════════════════════════════════════════════════════════
# PANEL 3 — PLATELETS
# ════════════════════════════════════════════════════════════════════════════
arr(ax, 12.4, 10.86, 12.4, 10.32, color=TEAL, lw=2)
box(ax, 12.4, 10.08, 5.5, 0.48,
"PANEL 3 PLATELETS",
bg=TEAL, ec=TEAL, fs=11, tc=WHITE, bold=True, lw=0, rad=0.22)
arr(ax, 12.4, 9.84, 12.4, 9.3, color=TEAL)
box(ax, 12.4, 9.06, 8.5, 0.46,
"Step 5 : Check Platelet Count (PLT) Normal : 150,000 – 400,000 / µL",
bg=LTEAL, ec=TEAL, fs=9.5, tc=TEAL, bold=True, lw=1.5)
arr(ax, 12.4, 8.83, 12.4, 8.28, color=TEAL)
diam(ax, 12.4, 7.92, 6.6, 0.72, "PLT Count?", bg=LTEAL, ec=TEAL, fs=10.5, tc=TEAL)
# LOW (left)
arr(ax, 9.1, 7.92, 7.0, 7.92, color=RED, lw=1.6); lbl(ax, 7.1, 8.15, "< 150K LOW", RED)
box(ax, 5.0, 7.92, 3.6, 0.58, "THROMBOCYTOPENIA\nCheck cause", bg=LRED, ec=RED, fs=9, tc=RED, bold=True)
causes = [("ITP",PURPLE,LPURPL),("Dengue",TEAL,LTEAL),("Chemo",BLUE,LBLUE),("DIC/Sepsis",RED,LRED)]
for i,(cl,ce,cbg) in enumerate(causes):
xi = 1.4 + i*2.0
arr(ax, 5.0, 7.63, xi, 7.18, color=ce, lw=1.2)
box(ax, xi, 6.9, 1.8, 0.44, cl, bg=cbg, ec=ce, fs=8.5, tc=ce, bold=True, lw=1.2)
# HIGH (right)
arr(ax, 15.7, 7.92, 17.8, 7.92, color=ORANGE, lw=1.6); lbl(ax, 16.0, 8.15, "> 400K HIGH", ORANGE)
box(ax, 20.0, 7.92, 4.0, 0.65,
"THROMBOCYTOSIS\nReactive: infx, iron def\nPrimary: ET, PV",
bg=LORAN, ec=ORANGE, fs=8.5, tc=ORANGE, bold=True)
# NORMAL
arr(ax, 12.4, 7.56, 12.4, 7.05, color=GREEN); lbl(ax, 12.62, 7.3, "NORMAL", GREEN)
box(ax, 12.4, 6.8, 3.5, 0.42, "Normal Platelets", bg=LGREEN, ec=GREEN, fs=8.5, tc=GREEN)
# MPV row
box(ax, 12.4, 6.28, 10.5, 0.44,
"Check MPV (Normal 7–13 fL) | HIGH MPV = Large platelets (ITP, recovery) LOW MPV = Small platelets (aplasia, chemo)",
bg=LGREY, ec=MGREY, fs=8.5, tc=DGREY, lw=1.2, rad=0.15)
# Bleeding risk strip
risk = [("> 100K","Minimal Risk",GREEN,LGREEN),
("50–100K","Minor Risk",AMBER,LAMBER),
("20–50K","Moderate Risk",ORANGE,LORAN),
("< 20K","SPONTANEOUS\nBLEEDING",RED,LRED)]
box(ax, 12.4, 5.7, 10.5, 0.38, "Platelet Count vs Bleeding Risk", bg=NAVY, ec=NAVY, fs=9, tc=WHITE, bold=True, lw=0)
for i,(cnt,risk_txt,rc,rbg) in enumerate(risk):
xi = 3.6 + i*4.8
box(ax, xi, 5.24, 2.1, 0.38, cnt, bg=rbg, ec=rc, fs=8.5, tc=rc, bold=True, lw=1.2)
box(ax, xi, 4.82, 2.1, 0.38, risk_txt, bg=rbg, ec=rc, fs=8.2, tc=rc, lw=1.2)
if i < 3:
arr(ax, xi+1.1, 5.03, xi+2.7, 5.03, color=MGREY, lw=1.2)
# ════════════════════════════════════════════════════════════════════════════
# SUMMARY — PANCYTOPENIA + PATTERNS
# ════════════════════════════════════════════════════════════════════════════
arr(ax, 12.4, 4.62, 12.4, 4.12, color=PURPLE, lw=2)
# Pancytopenia bar
pc = FancyBboxPatch((1.1, 3.75), 22.6, 0.65,
boxstyle="round,pad=0.06,rounding_size=0.2",
facecolor=LRED, edgecolor=RED, lw=2.5, zorder=5)
ax.add_patch(pc)
ax.text(12.4, 4.08,
"!! PANCYTOPENIA (Hgb LOW + WBC LOW + PLT LOW)"
" -> Bone Marrow Failure / Aplastic Anemia / Leukemia / MDS -> URGENT haematology referral",
ha="center", va="center", fontsize=9.5, fontweight="bold", color=RED, zorder=6)
# Pattern table
box(ax, 12.4, 3.2, 22.6, 0.42,
"Common CBC Patterns -> Diagnosis",
bg=NAVY, ec=NAVY, fs=10, tc=WHITE, bold=True, lw=0)
patterns = [
("Hgb LOW + MCV LOW + Ferritin LOW", "Iron Deficiency Anemia", RED, LRED),
("Hgb LOW + MCV HIGH", "B12 / Folate Deficiency", PURPLE, LPURPL),
("WBC HIGH + Neutrophils HIGH", "Bacterial Infection / Sepsis", MRED, LRED),
("WBC HIGH + Lymphocytes HIGH", "Viral Infection (EBV, CMV)", BLUE, LBLUE),
("PLT LOW only", "ITP / Dengue / Drug-induced", TEAL, LTEAL),
("RBC HIGH + Hgb HIGH + Hct HIGH", "Polycythemia Vera", ORANGE, LORAN),
("WBC HIGH + BLASTS on smear", "ACUTE LEUKEMIA — URGENT", RED, LRED),
]
for i,(pat,dx,pc2,pbg) in enumerate(patterns):
ry2 = 2.84 - i*0.38
bg2 = WHITE if i%2==0 else LGREY
isurg = "URGENT" in dx
box(ax, 7.2, ry2, 10.4, 0.35, pat, bg=LRED if isurg else bg2, ec=pc2, fs=8.2, tc=RED if isurg else BLACK, lw=0.8, bold=isurg)
box(ax, 17.8, ry2, 9.4, 0.35, dx, bg=LRED if isurg else pbg, ec=pc2, fs=8.2, tc=pc2, lw=0.8, bold=True)
# Tips strip (far right column)
tips_r = FancyBboxPatch((20.2, 0.35), 3.55, 2.65,
boxstyle="round,pad=0.08,rounding_size=0.2",
facecolor=LBLUE, edgecolor=BLUE, lw=1.8, zorder=3)
ax.add_patch(tips_r)
ax.text(21.97, 2.82, "Reading Tips", ha="center", fontsize=9.5, fontweight="bold", color=NAVY, zorder=4)
tips = ["Use YOUR lab's reference range",
"Trends > single values",
"All 3 lines low = urgent",
"H/L flags guide, not diagnose",
"CBC directs next test",
"Context: age, sex, meds"]
for i, t in enumerate(tips):
ax.text(20.42, 2.48-i*0.37, f"• {t}", fontsize=8, color=NAVY, zorder=4, va="center")
# ── FOOTER ──────────────────────────────────────────────────────────────────
foot = FancyBboxPatch((1.1, 0.12), 18.8, 0.42,
boxstyle="round,pad=0.05,rounding_size=0.15",
facecolor=LGREY, edgecolor=MGREY, lw=1, zorder=3)
ax.add_patch(foot)
ax.text(10.5, 0.33,
"For educational reference only. All CBC results must be interpreted in full clinical context by a qualified healthcare provider.",
ha="center", va="center", fontsize=8.2, color=DGREY, zorder=4)
plt.tight_layout(pad=0)
out = "/home/daytona/workspace/cbc-cheatsheet/CBC_Flowchart_Bright.png"
plt.savefig(out, dpi=180, bbox_inches="tight", facecolor="white")
print(f"Saved: {out}")
cd /home/daytona/workspace/cbc-cheatsheet && python generate_cbc_v3.py 2>&1

CBC Flowchart Bright
Image · PNG
| Feature | This Version |
|---|---|
| Background | Pure white |
| Swim lanes | Colored tab strips on the left edge (RBC / WBC / PLT / SUMMARY) |
| Layout | Single vertical spine flowing top to bottom |
| MCV branches | Three clear left / center / right branches with color coding |
| WBC differential | Full 5-row color-coded table (High causes / Low causes / Urgent flags) |
| Platelet risk | Horizontal gradient strip (green → amber → orange → red) |
| Pattern table | 7 common CBC patterns at the bottom |
| Tips box | Clean panel at bottom-right |
Plz don't give png pattern another pattern what type you give me
Sample give them I decide
cat << 'PYEOF' > /home/daytona/workspace/cbc-cheatsheet/style1_pastel.py import …
cat << 'PYEOF' > /home/daytona/workspace/cbc-cheatsheet/style2_infographic.py im…
cat << 'PYEOF' > /home/daytona/workspace/cbc-cheatsheet/style3_minimal.py import…
cat << 'PYEOF' > /home/daytona/workspace/cbc-cheatsheet/style4_clinical.py impor…
python3 -c " content = open('/home/daytona/workspace/cbc-cheatsheet/style3_minim…
| # | Style | Best for |
|---|---|---|
| 1 | Pastel / Soft | Easy on the eyes, studying |
| 2 | Bold Infographic | Posters, presentations |
| 3 | Minimal / Clean | Printing, notes |
| 4 | Medical Textbook | Clinical reference, exams |