How to understand cbc report

Reading File
Finding Sources
Searching the Web

how to understand CBC complete blood count report normal values

Finding Sources
Reading File
I now have solid textbook and web material to give a thorough, well-organized answer.

How to Understand a CBC (Complete Blood Count) Report

A CBC is one of the most commonly ordered blood tests. It measures the three main cell lines in your blood and several derived values. Here is a component-by-component guide.

1. Red Blood Cell (RBC) Panel - Oxygen Carrying

Hemoglobin (Hgb / Hb)

The most important red cell value. It measures the protein that carries oxygen.
GroupNormal Range
Adult males13.5 - 17.5 g/dL
Adult females12.0 - 15.5 g/dL
Childrenvaries by age
  • Low = Anemia (fatigue, breathlessness, pallor)
  • High = Polycythemia, dehydration, high altitude

Hematocrit (Hct / PCV)

Percentage of blood volume occupied by red cells.
  • Males: ~41-53% | Females: ~36-46%
  • Moves in the same direction as hemoglobin.

RBC Count

Number of red cells per microliter. Doctors generally rely on Hgb/Hct more than raw RBC count.

Red Cell Indices (the "shape and size" values)

IndexWhat it meansNormalLow meansHigh means
MCV (Mean Corpuscular Volume)Average RBC size80-100 fLMicrocytic (small) - iron deficiency, thalassemiaMacrocytic (large) - B12/folate deficiency
MCH (Mean Corpuscular Hemoglobin)Avg hemoglobin per cell27-33 pgHypochromic - iron deficiency-
MCHC (Mean Corpuscular Hgb Concentration)Hemoglobin concentration in cells32-36 g/dLIron deficiencySpherocytosis
RDW (Red Cell Distribution Width)Variation in RBC size11.5-14.5%(low rarely matters)Mixed deficiency, early iron/B12 deficiency
Key pattern to memorize:
  • Low MCV + low MCH = Iron deficiency anemia (most common) or thalassemia
  • High MCV = B12 or folate deficiency, alcoholism, liver disease, hypothyroidism
  • Normal MCV = Anemia of chronic disease, acute blood loss, kidney disease

2. White Blood Cell (WBC) Panel - Immune System

Total WBC Count

Normal: 4,000 - 11,000 cells/µL (or 4.0 - 11.0 × 10⁹/L)
  • High (Leukocytosis) = Infection (usually bacterial), inflammation, stress, steroids, leukemia
  • Low (Leukopenia) = Viral infections, chemotherapy, autoimmune disease, bone marrow failure

WBC Differential

The differential breaks the total WBC into 5 types. Their normal percentages:
Cell TypeNormal %FunctionElevated inLow in
Neutrophils50-70%Fight bacteriaBacterial infection, stress, steroidsViral infection, chemo
Lymphocytes20-40%Fight viruses, antibodiesViral infections, CLLHIV, steroids
Monocytes2-8%Cleanup, chronic infectionTB, fungal infections, monocytic leukemia-
Eosinophils1-4%Allergies, parasitesAllergies, asthma, parasitic infectionsSteroids
Basophils0.01-0.08 × 10⁹/LInflammation/allergyCML, 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
"Left shift" = Increase in immature neutrophils (bands) = severe bacterial infection or sepsis.

3. Platelet Panel - Clotting

Platelet Count (PLT)

Normal: 150,000 - 400,000/µL (or 150-400 × 10⁹/L)
  • Low (Thrombocytopenia <150,000) = Bleeding risk, ITP, dengue fever, chemotherapy, liver disease, sepsis
  • High (Thrombocytosis >400,000) = Reactive (infection, iron deficiency, inflammation) or essential thrombocythemia
Platelet CountClinical Risk
50,000-100,000Minor bleeding risk
20,000-50,000Moderate bleeding risk
<20,000Spontaneous bleeding risk - urgent

MPV (Mean Platelet Volume)

  • High MPV = Large platelets being produced rapidly (ITP, dengue recovery)
  • Low MPV = Small platelets (chemotherapy-related, aplastic anemia)

4. Quick Clinical Patterns to Recognize

PatternWhat to Think
Low Hgb + Low MCV + Low MCHIron deficiency anemia
Low Hgb + High MCVB12 or folate deficiency
High WBC + high neutrophilsBacterial infection
High WBC + high lymphocytesViral infection
Low WBC + low platelets + low Hgb (pancytopenia)Bone marrow failure, leukemia, aplastic anemia - urgent
Low platelets onlyITP, dengue, drug-induced
High RBC + high Hgb + high HctPolycythemia vera or secondary polycythemia

5. Important Points When Reading Your Report

  1. Reference ranges vary by lab - always compare to the specific ranges printed on your report, not just memorized numbers.
  2. Context matters more than a single value - a hemoglobin of 11 g/dL is very different in a 70-year-old man vs. a pregnant woman.
  3. Trends matter - a declining platelet count over several tests is more concerning than a single mildly low value.
  4. Flags - labs mark values with "H" (high) or "L" (low). Not all flagged values need treatment; your doctor interprets them in clinical context.
  5. A CBC alone rarely gives a final diagnosis - it points the doctor toward the right direction for further testing.

Source: Robbins & Kumar Basic Pathology, Laboratory Tests reference section; Cleveland Clinic CBC guide; WebMD CBC overview
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

Reading File
Running Command

mkdir -p /home/daytona/workspace/cbc-cheatsheet && ls /home/daytona/workspace/cb…

Writing File

~/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}")
Running Command

cd /home/daytona/workspace/cbc-cheatsheet && python generate_cbc_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/cbc-cheatsheet/CBC_Cheat_Sheet.pdf

Your CBC cheat sheet PDF is ready to download and print:

CBC Cheat Sheet

PDF Document · PDF


Here's what's included on the single A4 page:
SectionWhat's in it
RBC PanelHgb, Hct, RBC count, MCV, MCH, MCHC, RDW, Reticulocytes - normal ranges + causes of high/low
Anemia ClassificationMicrocytic / Normocytic / Macrocytic with key causes and next tests
WBC PanelTotal WBC count + full differential (Neutrophils, Lymphocytes, Monocytes, Eosinophils, Basophils)
Platelet PanelPLT count, MPV + a bleeding risk table by platelet level
Clinical Patterns9 common CBC patterns mapped to diagnoses (urgent ones highlighted in red)
Tips box6 practical reminders for interpreting any CBC
It is designed for A4 paper, prints cleanly in color or black-and-white, and fits on a single page. Just open and print at actual size (100%, no scaling).

Flowchart give me

Asking for Preferences
Writing File

~/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}")
Running Command

cd /home/daytona/workspace/cbc-cheatsheet && python generate_anemia_flowchart.py…

Running Command

ls -lh /home/daytona/workspace/cbc-cheatsheet/Anemia_Workup_Flowchart.png

Here's your Anemia Workup Flowchart:
Anemia Workup Flowchart

Anemia Workup Flowchart

Image · PNG


The flowchart walks through the full diagnostic pathway:
  1. Start - Suspected anemia symptoms
  2. Check Hgb - Is it low? (with gender-specific cutoffs)
  3. MCV split - Three branches:
    • < 80 fL (Microcytic) - Ferritin check → Iron deficiency vs. Thalassemia
    • 80-100 fL (Normocytic) - Reticulocyte count → Hemolytic / Blood loss / CKD / Aplastic anemia
    • > 100 fL (Macrocytic) - B12/Folate → Megaloblastic anemia vs. Alcohol/Liver/Hypothyroid
  4. Severity table at the bottom (Mild / Moderate / Severe / Very Severe)
  5. Legend explaining all shapes and colors
Right-click the image to save it, or use the download link above to get the full 661 KB high-res PNG.

Cbc interapertion flowchart

Writing File

~/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}")
Running Command

cd /home/daytona/workspace/cbc-cheatsheet && python generate_cbc_flowchart.py 2>…

Running Command

cd /home/daytona/workspace/cbc-cheatsheet && sed -i 's/🩸 PANEL 1:/[RBC] PANEL…

Running Command

python3 -c " content = open('/home/daytona/workspace/cbc-cheatsheet/generate_cbc…

Clean save, no warnings. Here's your full CBC Interpretation Flowchart:
CBC Interpretation Flowchart

CBC Interpretation Flowchart

Image · PNG


The flowchart covers all 3 CBC panels in one diagram:
PanelWhat it shows
[RBC] Panel 1Hgb → Is it low? → MCV branch (Microcytic/Normocytic/Macrocytic) → each leads to specific diagnosis + treatment
[WBC] Panel 2WBC count → High/Low/Normal → Differential (which cell?) → Neutrophilia / Lymphocytosis / Eosinophilia + WBC differential quick-reference mini table
[PLT] Panel 3PLT count → High/Low → Causes of thrombocytopenia + Bleeding risk table by platelet count
Bottom sectionPancytopenia warning bar, Anemia severity table, 7 Common CBC patterns, 6 clinical tips

Flowchart in different pattern plz

Writing File

~/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}")
Running Command

cd /home/daytona/workspace/cbc-cheatsheet && python generate_cbc_flowchart_v2.py…

Here's the new CBC flowchart in a completely different style:
CBC Flowchart - Dark Theme

CBC Flowchart DarkTheme

Image · PNG


Here's what's different from the previous version:
FeaturePreviousThis Version
BackgroundLight white/blueDark GitHub-style (#0D1117)
Layout3 side-by-side columnsSingle vertical spine with branches
NodesRectangular boxesRounded pill shapes
DecisionsYellow diamondsColor-coded diamonds per panel
WBC sectionSummary table belowInline row-by-row differential cards (HIGH / LOW causes)
PlateletsSeparate risk tableInline gradient risk strip
Section bandsColumn headersSubtle colored background glow bands
StyleClinical/formalDark-mode, modern UI feel

I am not satisfied with flowchart dark ways

Writing File

~/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}")
Running Command

cd /home/daytona/workspace/cbc-cheatsheet && python generate_cbc_v3.py 2>&1

Here's your new bright, clean flowchart:
CBC Flowchart - Bright Theme

CBC Flowchart Bright

Image · PNG


What's different this time:
FeatureThis Version
BackgroundPure white
Swim lanesColored tab strips on the left edge (RBC / WBC / PLT / SUMMARY)
LayoutSingle vertical spine flowing top to bottom
MCV branchesThree clear left / center / right branches with color coding
WBC differentialFull 5-row color-coded table (High causes / Low causes / Urgent flags)
Platelet riskHorizontal gradient strip (green → amber → orange → red)
Pattern table7 common CBC patterns at the bottom
Tips boxClean panel at bottom-right

Plz don't give png pattern another pattern what type you give me

Asking for Preferences

Sample give them I decide

Running Command

cat << 'PYEOF' > /home/daytona/workspace/cbc-cheatsheet/style1_pastel.py import …

Running Command

cat << 'PYEOF' > /home/daytona/workspace/cbc-cheatsheet/style2_infographic.py im…

Running Command

cat << 'PYEOF' > /home/daytona/workspace/cbc-cheatsheet/style3_minimal.py import…

Running Command

cat << 'PYEOF' > /home/daytona/workspace/cbc-cheatsheet/style4_clinical.py impor…

Running Command

python3 -c " content = open('/home/daytona/workspace/cbc-cheatsheet/style3_minim…

Which one do you like?
#StyleBest for
1Pastel / SoftEasy on the eyes, studying
2Bold InfographicPosters, presentations
3Minimal / CleanPrinting, notes
4Medical TextbookClinical reference, exams
Just say "Style 1", "Style 2", "Style 3", or "Style 4" and I'll build the full detailed version in that style!
This is a shared conversation. Sign in to Orris to start your own chat.