{"spec_id":"audiogram-clinical","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\naudiogram-clinical: Clinical Audiogram\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 90/100 | Created: 2026-06-15\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import *\n\n\nLetsPlot.setup_html()\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# Imprint palette — clinical semantic assignment: red=right ear, blue=left ear\nRIGHT_COLOR = \"#AE3030\"  # Imprint matte red (position 5)\nLEFT_COLOR = \"#4467A3\"  # Imprint blue (position 3)\n\n# Data: noise-induced high-frequency sensorineural loss (occupational audiometry)\nfrequencies = [125, 250, 500, 1000, 2000, 4000, 8000]\nthreshold_right = [15, 20, 20, 25, 40, 70, 75]\nthreshold_left = [10, 15, 20, 30, 50, 65, 70]\n\ndf_ears = pd.DataFrame(\n    {\n        \"frequency\": frequencies * 2,\n        \"threshold\": threshold_right + threshold_left,\n        \"ear\": pd.Categorical([\"Right Ear\"] * 7 + [\"Left Ear\"] * 7, categories=[\"Right Ear\", \"Left Ear\"], ordered=True),\n    }\n)\n\n# Severity band rectangles (ymin/ymax in dB HL data coordinates)\n# Fill colors from Imprint palette at low alpha — structural background, not data series\nbands_df = pd.DataFrame(\n    {\n        \"xmin\": [100.0] * 6,\n        \"xmax\": [10000.0] * 6,\n        \"ymin\": [-10.0, 25.0, 40.0, 55.0, 70.0, 90.0],\n        \"ymax\": [25.0, 40.0, 55.0, 70.0, 90.0, 120.0],\n        \"fill_col\": [\"#009E73\", \"#99B314\", \"#BD8233\", \"#BD8233\", \"#AE3030\", \"#AE3030\"],\n        \"alpha_val\": [0.08, 0.10, 0.12, 0.16, 0.12, 0.22],\n    }\n)\n\nband_labels_df = pd.DataFrame(\n    {\n        \"x\": [9500.0] * 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\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major=element_line(color=INK_SOFT, size=0.3),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(color=INK, size=12),\n    axis_text=element_text(color=INK_SOFT, size=10),\n    axis_line=element_line(color=INK_SOFT),\n    plot_title=element_text(color=INK, size=16),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=10),\n    legend_title=element_blank(),\n    legend_position=\"right\",\n    panel_border=element_rect(color=INK_SOFT),\n)\n\nplot = (\n    ggplot()\n    # Severity bands (background layer)\n    + geom_rect(\n        data=bands_df,\n        mapping=aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"fill_col\", alpha=\"alpha_val\"),\n        color=None,\n        show_legend=False,\n    )\n    + scale_fill_identity()\n    + scale_alpha_identity()\n    # Severity band labels (right edge)\n    + geom_text(data=band_labels_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK_MUTED, size=3.0, hjust=1)\n    # Connecting lines per ear\n    + geom_line(data=df_ears, mapping=aes(x=\"frequency\", y=\"threshold\", color=\"ear\"), size=1.0)\n    # Threshold markers: O (shape=1) for right ear, X (shape=4) for left ear\n    + geom_point(\n        data=df_ears, mapping=aes(x=\"frequency\", y=\"threshold\", color=\"ear\", shape=\"ear\"), size=4.5, stroke=1.5\n    )\n    + scale_color_manual(values=[RIGHT_COLOR, LEFT_COLOR], name=\"\")\n    + scale_shape_manual(values=[1, 4], name=\"\")\n    # Log x-axis with standard audiometric frequency labels\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 y-axis: 0 dB HL (best hearing) at top, loss increases downward\n    + scale_y_reverse(breaks=list(range(-10, 121, 10)), limits=[-10, 120])\n    + labs(x=\"Frequency (Hz)\", y=\"Hearing Level (dB HL)\", title=\"audiogram-clinical · python · letsplot · anyplot.ai\")\n    + theme_bw()\n    + anyplot_theme\n    + ggsize(600, 600)\n)\n\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}