Clinical Audiogram — Matplotlib

A standardized clinical audiogram that displays hearing test results, plotting hearing threshold (dB HL) against test frequency for each ear. The y-axis is inverted so that 0 dB HL (best hearing) sits at the top and increasing hearing loss extends downward, while the x-axis is logarithmic spanning the standard audiometric frequencies (125 Hz to 8 kHz). Thresholds are marked with the conventional symbols — a circle (O) for the right ear in red and a cross (X) for the left ear in blue — and connected per ear, with shaded horizontal bands indicating severity of hearing loss (normal, mild, moderate, severe, profound).

Clinical Audiogram rendered with Matplotlib

Python source (Matplotlib)

""" anyplot.ai
audiogram-clinical: Clinical Audiogram
Library: matplotlib 3.11.0 | Python 3.13.13
Quality: 91/100 | Created: 2026-06-14
"""

import sys


# Prevent this file from shadowing the installed matplotlib package when run
# from its own directory (sys.path[0] would otherwise resolve to this file).
sys.path.pop(0)

import os

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from matplotlib.transforms import blended_transform_factory


# Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome")
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Ear colors — semantic exception from Imprint palette (red=right, blue=left per audiological convention)
RIGHT_EAR = "#AE3030"  # Imprint matte red — right-ear convention
LEFT_EAR = "#4467A3"  # Imprint blue — left-ear convention

# Data — noise-induced high-frequency sensorineural hearing loss (occupational pattern)
frequencies = [125, 250, 500, 1000, 2000, 4000, 8000]
threshold_right = [10, 10, 15, 15, 25, 55, 65]  # right ear (dB HL)
threshold_left = [15, 15, 20, 20, 30, 65, 75]  # left ear (dB HL)

# Severity bands: (y_start, y_end, fill_color, alpha, label)
severity_bands = [
    (-10, 25, "#009E73", 0.07, "Normal"),
    (25, 40, "#DDCC77", 0.10, "Mild"),
    (40, 55, "#BD8233", 0.10, "Moderate"),
    (55, 70, "#AE3030", 0.09, "Mod. Severe"),
    (70, 90, "#AE3030", 0.17, "Severe"),
    (90, 120, "#AE3030", 0.27, "Profound"),
]

# Plot — square canvas for standardized audiogram layout
fig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)

# Severity band shading
for y_start, y_end, color, alpha, _ in severity_bands:
    ax.axhspan(y_start, y_end, alpha=alpha, color=color, zorder=0, lw=0)

# Band labels on right margin outside axes
band_trans = blended_transform_factory(ax.transAxes, ax.transData)
for y_start, y_end, _, _, label in severity_bands:
    mid = (y_start + y_end) / 2
    ax.text(
        1.025, mid, label, transform=band_trans, fontsize=7.5, color=INK_MUTED, va="center", ha="left", clip_on=False
    )

# Right ear — open circle (O) markers with solid connecting line
ax.plot(
    frequencies,
    threshold_right,
    marker="o",
    linestyle="-",
    color=RIGHT_EAR,
    linewidth=2.2,
    markersize=10,
    markerfacecolor="none",
    markeredgewidth=2.2,
    markeredgecolor=RIGHT_EAR,
    label="Right Ear (O)",
    zorder=3,
)

# Left ear — cross (X) markers with solid connecting line
ax.plot(
    frequencies,
    threshold_left,
    marker="x",
    linestyle="-",
    color=LEFT_EAR,
    linewidth=2.2,
    markersize=11,
    markeredgewidth=2.5,
    label="Left Ear (X)",
    zorder=3,
)

# X-axis — logarithmic, standard audiometric frequencies
ax.set_xscale("log")
ax.set_xlim(88, 9500)
ax.set_xticks(frequencies)
ax.set_xticklabels(["125", "250", "500", "1k", "2k", "4k", "8k"])
ax.xaxis.set_minor_locator(ticker.NullLocator())

# Y-axis — inverted (0 dB HL at top), range -10 to 120, gridlines every 10 dB
ax.set_ylim(120, -10)
ax.set_yticks(range(-10, 130, 10))

# Grid — every 10 dB and at each standard frequency
ax.grid(True, which="major", alpha=0.18, linewidth=0.8, color=INK, zorder=1)

# Title with scaled fontsize
title = "audiogram-clinical · python · matplotlib · anyplot.ai"
n = len(title)
title_fs = max(8, round(12 * 67 / n)) if n > 67 else 12

# Chrome styling
ax.set_title(title, fontsize=title_fs, fontweight="medium", color=INK, pad=10)
ax.set_xlabel("Frequency (Hz)", fontsize=10, color=INK, labelpad=6)
ax.set_ylabel("Hearing Level (dB HL)", fontsize=10, color=INK, labelpad=6)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT, length=0)

for spine in ax.spines.values():
    spine.set_color(INK_SOFT)
    spine.set_linewidth(0.8)

# Legend — placed in lower-left (high dB HL region, no data)
leg = ax.legend(fontsize=8, loc="lower left")
leg.get_frame().set_facecolor(ELEVATED_BG)
leg.get_frame().set_edgecolor(INK_SOFT)
plt.setp(leg.get_texts(), color=INK_SOFT)

# Layout — explicit margins so severity labels on the right are not clipped
fig.subplots_adjust(left=0.13, right=0.82, top=0.93, bottom=0.12)

# Save
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)
plt.close()

Part of Clinical Audiogram on anyplot.ai.

Other implementations