{"spec_id":"audiogram-clinical","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// audiogram-clinical: Clinical Audiogram\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 90/100 | Created: 2026-06-15\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data -------------------------------------------------------------------\n// Pure-tone audiometry: high-frequency sensorineural notch (occupational noise)\nconst frequencies = [125, 250, 500, 1000, 2000, 4000, 8000];\nconst rightEarThresholds = [15, 20, 25, 30, 50, 75, 80];\nconst leftEarThresholds  = [10, 15, 20, 25, 40, 65, 70];\n\n// Semantic color exception: red/blue are universal audiogram conventions\nconst RIGHT_COLOR = \"#AE3030\"; // matte red — right-ear clinical convention\nconst LEFT_COLOR  = \"#4467A3\"; // Imprint blue — left-ear clinical convention\n\n// Severity bands per WHO audiometric classification\nconst severityBands = [\n  { min: -10, max: 25,  fill: \"rgba(0,158,115,0.07)\",   label: \"Normal\"      },\n  { min: 25,  max: 40,  fill: \"rgba(221,204,119,0.13)\", label: \"Mild\"        },\n  { min: 40,  max: 55,  fill: \"rgba(189,130,51,0.13)\",  label: \"Moderate\"    },\n  { min: 55,  max: 70,  fill: \"rgba(189,130,51,0.21)\",  label: \"Mod. Severe\" },\n  { min: 70,  max: 90,  fill: \"rgba(174,48,48,0.13)\",   label: \"Severe\"      },\n  { min: 90,  max: 120, fill: \"rgba(174,48,48,0.22)\",   label: \"Profound\"    },\n];\n\n// --- Plugin: severity band fills (behind grid) and labels (in right margin) -\nconst severityPlugin = {\n  id: \"severityPlugin\",\n  beforeDraw(chart) {\n    const { ctx, chartArea, scales } = chart;\n    if (!chartArea || !scales.y) return;\n    const yScale = scales.y;\n    ctx.save();\n    ctx.beginPath();\n    ctx.rect(chartArea.left, chartArea.top, chartArea.width, chartArea.height);\n    ctx.clip();\n    severityBands.forEach(({ min, max, fill }) => {\n      const y1     = yScale.getPixelForValue(min);\n      const y2     = yScale.getPixelForValue(max);\n      const top    = Math.min(y1, y2);\n      const height = Math.abs(y2 - y1);\n      ctx.fillStyle = fill;\n      ctx.fillRect(chartArea.left, top, chartArea.width, height);\n    });\n    ctx.restore();\n  },\n  afterDatasetsDraw(chart) {\n    const { ctx, chartArea, scales } = chart;\n    if (!chartArea || !scales.y) return;\n    const yScale = scales.y;\n    ctx.save();\n    ctx.font = \"13px system-ui, sans-serif\";\n    ctx.textAlign = \"left\";\n    ctx.textBaseline = \"middle\";\n    ctx.fillStyle = t.inkSoft;\n    severityBands.forEach(({ min, max, label }) => {\n      const y1   = yScale.getPixelForValue(min);\n      const y2   = yScale.getPixelForValue(max);\n      const midY = (y1 + y2) / 2;\n      ctx.fillText(label, chartArea.right + 10, midY);\n    });\n    ctx.restore();\n  },\n};\n\n// --- Mount ------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  plugins: [severityPlugin],\n  data: {\n    datasets: [\n      {\n        label: \"Right Ear (O)\",\n        data: rightEarThresholds.map((y, i) => ({ x: frequencies[i], y })),\n        borderColor: RIGHT_COLOR,\n        backgroundColor: RIGHT_COLOR,\n        pointStyle: \"circle\",\n        pointRadius: 9,\n        pointHoverRadius: 11,\n        pointBorderColor: RIGHT_COLOR,\n        pointBorderWidth: 2.5,\n        pointBackgroundColor: t.pageBg,\n        borderWidth: 2.5,\n        tension: 0,\n      },\n      {\n        label: \"Left Ear (X)\",\n        data: leftEarThresholds.map((y, i) => ({ x: frequencies[i], y })),\n        borderColor: LEFT_COLOR,\n        backgroundColor: LEFT_COLOR,\n        pointStyle: \"crossRot\",\n        pointRadius: 10,\n        pointHoverRadius: 12,\n        pointBorderColor: LEFT_COLOR,\n        pointBorderWidth: 3,\n        pointBackgroundColor: LEFT_COLOR,\n        borderWidth: 2.5,\n        borderDash: [7, 4],\n        tension: 0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: {\n      padding: { top: 16, right: 120, bottom: 24, left: 10 },\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: \"audiogram-clinical · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { top: 8, bottom: 14 },\n      },\n      legend: {\n        display: true,\n        position: \"top\",\n        labels: {\n          color: t.ink,\n          font: { size: 16 },\n          usePointStyle: true,\n          pointStyleWidth: 16,\n          padding: 24,\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: \"logarithmic\",\n        min: 100,\n        max: 10000,\n        title: {\n          display: true,\n          text: \"Frequency (Hz)\",\n          color: t.ink,\n          font: { size: 16, weight: \"500\" },\n          padding: { top: 10 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback(value) {\n            const map = { 125: \"125\", 250: \"250\", 500: \"500\", 1000: \"1k\", 2000: \"2k\", 4000: \"4k\", 8000: \"8k\" };\n            return map[value] !== undefined ? map[value] : null;\n          },\n        },\n        afterBuildTicks(scale) {\n          scale.ticks = [125, 250, 500, 1000, 2000, 4000, 8000].map(v => ({ value: v }));\n        },\n        grid: {\n          color: t.grid,\n        },\n      },\n      y: {\n        reverse: true,\n        min: -10,\n        max: 120,\n        title: {\n          display: true,\n          text: \"Hearing Level (dB HL)\",\n          color: t.ink,\n          font: { size: 16, weight: \"500\" },\n          padding: { bottom: 10 },\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: 10,\n        },\n        grid: {\n          color: t.grid,\n        },\n      },\n    },\n  },\n});\n"}