{"spec_id":"audiogram-clinical","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\naudiogram-clinical: Clinical Audiogram\nLibrary: plotnine 0.15.7 | Python 3.13.13\nQuality: 89/100 | Created: 2026-06-15\n\"\"\"\n\nimport sys\n\n\nsys.path.pop(0)  # prevent this file (plotnine.py) from shadowing the plotnine library\n\nimport os\n\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_point,\n    geom_rect,\n    geom_text,\n    ggplot,\n    guides,\n    labs,\n    scale_color_manual,\n    scale_fill_manual,\n    scale_linetype_manual,\n    scale_shape_manual,\n    scale_x_log10,\n    scale_y_reverse,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Audiogram uses strict clinical color conventions (semantic exception)\nRIGHT_COLOR = \"#AE3030\"  # Imprint matte red — right ear standard\nLEFT_COLOR = \"#4467A3\"  # Imprint blue — left ear standard\nANYPLOT_AMBER = \"#DDCC77\"  # speech frequency range indicator\n\n# Data: noise-induced high-frequency sensorineural hearing loss pattern\nfrequencies = [125, 250, 500, 1000, 2000, 4000, 8000]\nright_thresh = [10, 10, 15, 20, 30, 55, 65]\nleft_thresh = [15, 15, 20, 25, 40, 60, 75]\n\ndf = pd.concat(\n    [\n        pd.DataFrame({\"frequency\": frequencies, \"threshold\": right_thresh, \"ear\": \"Right Ear (O)\"}),\n        pd.DataFrame({\"frequency\": frequencies, \"threshold\": left_thresh, \"ear\": \"Left Ear (X)\"}),\n    ],\n    ignore_index=True,\n)\n\n# Severity bands — contiguous boundaries for clean shading\nbands = pd.DataFrame(\n    {\n        \"xmin\": [100] * 6,\n        \"xmax\": [10000] * 6,\n        \"ymin\": [-10, 25, 40, 55, 70, 90],\n        \"ymax\": [25, 40, 55, 70, 90, 120],\n        \"severity\": [\"Normal\", \"Mild\", \"Moderate\", \"Mod. Severe\", \"Severe\", \"Profound\"],\n    }\n)\n\n# Subtle severity band fills per theme\nif THEME == \"light\":\n    band_colors = {\n        \"Normal\": \"#DFF2EC\",\n        \"Mild\": \"#ECF4D9\",\n        \"Moderate\": \"#F5EDD6\",\n        \"Mod. Severe\": \"#F2E3CE\",\n        \"Severe\": \"#F0D7D7\",\n        \"Profound\": \"#EDD7EC\",\n    }\nelse:\n    band_colors = {\n        \"Normal\": \"#14291E\",\n        \"Mild\": \"#1C2414\",\n        \"Moderate\": \"#242012\",\n        \"Mod. Severe\": \"#241A12\",\n        \"Severe\": \"#241212\",\n        \"Profound\": \"#20121E\",\n    }\n\n# Severity band labels: midpoint y positions, placed near right edge\nband_labels = pd.DataFrame(\n    {\n        \"x\": [9400] * 6,\n        \"y\": [7.5, 32.5, 47.5, 62.5, 80.0, 105.0],\n        \"label\": [\"Normal\", \"Mild\", \"Moderate\", \"Mod. Severe\", \"Severe\", \"Profound\"],\n    }\n)\n\n# Speech frequency reference band (500–4000 Hz is clinically critical for speech intelligibility)\nspeech_ref = pd.DataFrame({\"xmin\": [500], \"xmax\": [4000], \"ymin\": [-10], \"ymax\": [120]})\nspeech_label = pd.DataFrame({\"x\": [1414], \"y\": [-7.0], \"label\": [\"Speech range\"]})\n\n# Title font scaling\ntitle = \"audiogram-clinical · python · plotnine · anyplot.ai\"\nn = len(title)\nratio = 67 / n if n > 67 else 1.0\ntitle_fontsize = max(8, round(12 * ratio))\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"frequency\", y=\"threshold\"))\n    # Severity shading (drawn first so data sits on top)\n    + geom_rect(\n        data=bands, mapping=aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"severity\"), inherit_aes=False\n    )\n    + scale_fill_manual(values=band_colors)\n    + guides(fill=False)\n    # Speech frequency range: subtle amber vertical band highlighting clinically important region\n    + geom_rect(\n        data=speech_ref,\n        mapping=aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\"),\n        inherit_aes=False,\n        fill=ANYPLOT_AMBER,\n        alpha=0.12,\n    )\n    # Connecting lines per ear\n    + geom_line(aes(color=\"ear\", linetype=\"ear\"), size=1.0)\n    # Threshold markers: O for right, X for left\n    + geom_point(aes(color=\"ear\", shape=\"ear\"), size=3.5, stroke=0.8)\n    # Severity band labels inside plot near right edge\n    + geom_text(\n        data=band_labels,\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        inherit_aes=False,\n        size=3.5,\n        color=INK_MUTED,\n        ha=\"right\",\n    )\n    # Speech range label near top of band (y=-7 is near the top of the inverted axis)\n    + geom_text(\n        data=speech_label,\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        inherit_aes=False,\n        size=2.5,\n        color=INK_MUTED,\n        ha=\"center\",\n    )\n    # Color: right=red, left=blue (clinical convention); same name merges legend entries\n    + scale_color_manual(values={\"Right Ear (O)\": RIGHT_COLOR, \"Left Ear (X)\": LEFT_COLOR}, name=\" \")\n    + scale_shape_manual(values={\"Right Ear (O)\": \"o\", \"Left Ear (X)\": \"x\"}, name=\" \")\n    + scale_linetype_manual(values={\"Right Ear (O)\": \"solid\", \"Left Ear (X)\": \"dashed\"}, name=\" \")\n    # Logarithmic frequency axis with standard audiometric ticks\n    + scale_x_log10(\n        breaks=[125, 250, 500, 1000, 2000, 4000, 8000],\n        labels=[\"125\", \"250\", \"500\", \"1k\", \"2k\", \"4k\", \"8k\"],\n        limits=[100, 10000],\n    )\n    # Inverted hearing level axis: 0 dB at top, loss increases downward\n    + scale_y_reverse(limits=(-10, 120), breaks=list(range(-10, 130, 10)))\n    + labs(x=\"Frequency (Hz)\", y=\"Hearing Level (dB HL)\", title=title)\n    + theme_minimal()\n    + theme(\n        figure_size=(6, 6),\n        text=element_text(size=7),\n        plot_title=element_text(size=title_fontsize, color=INK, weight=\"bold\"),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_title=element_text(size=9, color=INK),\n        legend_background=element_rect(fill=ELEVATED_BG, color=None),\n        legend_key=element_blank(),\n        panel_background=element_rect(fill=PAGE_BG, color=None),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_grid_major=element_line(color=INK_SOFT, size=0.3, alpha=0.5),\n        panel_grid_minor=element_blank(),\n        panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5),\n        legend_position=\"right\",\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\")\n"}