{"spec_id":"audiogram-clinical","library":"muix","language":"javascript","code":"// anyplot.ai\n// audiogram-clinical: Clinical Audiogram\n// Library: muix 7.29.1 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-15\n//# anyplot-orientation: square\n// anyplot.ai\n// audiogram-clinical: Clinical Audiogram\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-06-15\nimport * as React from \"react\";\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { useDrawingArea, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Clinical audiogram — occupational health screening, noise-induced high-freq notch\nconst FREQUENCIES = [125, 250, 500, 1000, 2000, 4000, 8000];\nconst RIGHT_EAR   = [15, 15, 20, 25, 45, 75, 80];\nconst LEFT_EAR    = [10, 15, 15, 20, 40, 70, 75];\n\n\n// Semantic colors: matte red for right (clinical convention), Imprint blue for left\nconst RIGHT_COLOR = \"#AE3030\";\nconst LEFT_COLOR  = \"#4467A3\";\n\n// ASHA severity bands with Imprint palette fills\nconst BANDS = [\n  { label: \"Normal\",        yMin: -10, yMax:  25, hex: \"#009E73\", alpha: 0.08 },\n  { label: \"Mild\",          yMin:  25, yMax:  40, hex: \"#4467A3\", alpha: 0.08 },\n  { label: \"Moderate\",      yMin:  40, yMax:  55, hex: \"#BD8233\", alpha: 0.09 },\n  { label: \"Mod. Severe\",   yMin:  55, yMax:  70, hex: \"#BD8233\", alpha: 0.14 },\n  { label: \"Severe\",        yMin:  70, yMax:  90, hex: \"#AE3030\", alpha: 0.10 },\n  { label: \"Profound\",      yMin:  90, yMax: 120, hex: \"#AE3030\", alpha: 0.18 },\n];\n\n// Horizontal severity bands drawn behind the data\nfunction SeverityBands() {\n  const { left, top, width, height } = useDrawingArea();\n  const yScale = useYScale(\"dBHL\") as ((v: number) => number) | undefined;\n  if (!yScale) return null;\n  return (\n    <g>\n      {BANDS.map((band) => {\n        const y0   = yScale(band.yMin);\n        const y1   = yScale(band.yMax);\n        const bTop = Math.min(y0, y1);\n        const bH   = Math.abs(y1 - y0);\n        return (\n          <React.Fragment key={band.label}>\n            <rect\n              x={left} y={bTop}\n              width={width} height={Math.max(bH, 1)}\n              fill={band.hex} opacity={band.alpha}\n            />\n            <text\n              x={left + width - 8} y={bTop + bH * 0.5}\n              textAnchor=\"end\" dominantBaseline=\"middle\"\n              fontSize={11} fill={t.inkSoft} opacity={0.85}\n            >\n              {band.label}\n            </text>\n          </React.Fragment>\n        );\n      })}\n    </g>\n  );\n}\n\n// Centered chart title above the drawing area\nfunction ChartTitle() {\n  const { left, top, width } = useDrawingArea();\n  return (\n    <text\n      x={left + width / 2} y={top - 30}\n      textAnchor=\"middle\"\n      fontSize={22} fontWeight=\"500\"\n      fill={t.ink}\n    >\n      {\"audiogram-clinical · javascript · muix · anyplot.ai\"}\n    </text>\n  );\n}\n\n// O mark — open circle for right ear (clinical convention)\nconst RightMark = ({ x = 0, y = 0 }: any) => (\n  <circle cx={x} cy={y} r={10} fill=\"none\" stroke={RIGHT_COLOR} strokeWidth={2.5} />\n);\n\n// X mark — cross for left ear (clinical convention)\nconst LeftMark = ({ x = 0, y = 0 }: any) => {\n  const d = 7;\n  return (\n    <g>\n      <line x1={x - d} y1={y - d} x2={x + d} y2={y + d} stroke={LEFT_COLOR} strokeWidth={2.5} />\n      <line x1={x + d} y1={y - d} x2={x - d} y2={y + d} stroke={LEFT_COLOR} strokeWidth={2.5} />\n    </g>\n  );\n};\n\nconst MarkDispatcher = (props: any) =>\n  props.seriesId === \"right\" ? <RightMark {...props} /> : <LeftMark {...props} />;\n\nexport default function Chart() {\n  return (\n    <LineChart\n      width={window.ANYPLOT_SIZE.width}\n      height={window.ANYPLOT_SIZE.height}\n      skipAnimation\n      xAxis={[{\n        id: \"freq\",\n        scaleType: \"log\",\n        data: FREQUENCIES,\n        tickInterval: FREQUENCIES,\n        valueFormatter: (v: number) => v >= 1000 ? `${v / 1000}k` : `${v}`,\n        label: \"Frequency (Hz)\",\n        labelStyle: { fontSize: 16, fill: t.ink },\n        tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n      }]}\n      yAxis={[{\n        id: \"dBHL\",\n        min: -10,\n        max: 120,\n        reverse: true,\n        tickNumber: 14,\n        label: \"Hearing Level (dB HL)\",\n        labelStyle: { fontSize: 16, fill: t.ink },\n        tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n      }]}\n      series={[\n        {\n          id: \"right\",\n          data: RIGHT_EAR,\n          color: RIGHT_COLOR,\n          label: \"Right Ear (O)\",\n          showMark: true,\n          curve: \"linear\",\n        },\n        {\n          id: \"left\",\n          data: LEFT_EAR,\n          color: LEFT_COLOR,\n          label: \"Left Ear (X)\",\n          showMark: true,\n          curve: \"linear\",\n        },\n      ]}\n      slots={{ mark: MarkDispatcher }}\n      grid={{ horizontal: true, vertical: true }}\n      sx={{\n        \"& .MuiLineElement-series-left\": { strokeDasharray: \"7 4\" },\n        \"& .MuiChartsAxis-line\": { stroke: t.inkSoft, strokeWidth: 1 },\n        \"& .MuiChartsAxis-tick\": { stroke: t.inkSoft },\n        \"& .MuiChartsGrid-line\": { stroke: t.grid },\n      }}\n      margin={{ top: 80, bottom: 90, left: 100, right: 160 }}\n      legend={{ position: { vertical: \"top\", horizontal: \"right\" } }}\n    >\n      <SeverityBands />\n      <ChartTitle />\n    </LineChart>\n  );\n}\n"}