Clinical Audiogram — Chart.js

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 Chart.js

JavaScript source (Chart.js)

// anyplot.ai
// audiogram-clinical: Clinical Audiogram
// Library: chartjs 4.4.7 | JavaScript 22.22.3
// Quality: 90/100 | Created: 2026-06-15
//# anyplot-orientation: square

const t = window.ANYPLOT_TOKENS;

// --- Data -------------------------------------------------------------------
// Pure-tone audiometry: high-frequency sensorineural notch (occupational noise)
const frequencies = [125, 250, 500, 1000, 2000, 4000, 8000];
const rightEarThresholds = [15, 20, 25, 30, 50, 75, 80];
const leftEarThresholds  = [10, 15, 20, 25, 40, 65, 70];

// Semantic color exception: red/blue are universal audiogram conventions
const RIGHT_COLOR = "#AE3030"; // matte red — right-ear clinical convention
const LEFT_COLOR  = "#4467A3"; // Imprint blue — left-ear clinical convention

// Severity bands per WHO audiometric classification
const severityBands = [
  { min: -10, max: 25,  fill: "rgba(0,158,115,0.07)",   label: "Normal"      },
  { min: 25,  max: 40,  fill: "rgba(221,204,119,0.13)", label: "Mild"        },
  { min: 40,  max: 55,  fill: "rgba(189,130,51,0.13)",  label: "Moderate"    },
  { min: 55,  max: 70,  fill: "rgba(189,130,51,0.21)",  label: "Mod. Severe" },
  { min: 70,  max: 90,  fill: "rgba(174,48,48,0.13)",   label: "Severe"      },
  { min: 90,  max: 120, fill: "rgba(174,48,48,0.22)",   label: "Profound"    },
];

// --- Plugin: severity band fills (behind grid) and labels (in right margin) -
const severityPlugin = {
  id: "severityPlugin",
  beforeDraw(chart) {
    const { ctx, chartArea, scales } = chart;
    if (!chartArea || !scales.y) return;
    const yScale = scales.y;
    ctx.save();
    ctx.beginPath();
    ctx.rect(chartArea.left, chartArea.top, chartArea.width, chartArea.height);
    ctx.clip();
    severityBands.forEach(({ min, max, fill }) => {
      const y1     = yScale.getPixelForValue(min);
      const y2     = yScale.getPixelForValue(max);
      const top    = Math.min(y1, y2);
      const height = Math.abs(y2 - y1);
      ctx.fillStyle = fill;
      ctx.fillRect(chartArea.left, top, chartArea.width, height);
    });
    ctx.restore();
  },
  afterDatasetsDraw(chart) {
    const { ctx, chartArea, scales } = chart;
    if (!chartArea || !scales.y) return;
    const yScale = scales.y;
    ctx.save();
    ctx.font = "13px system-ui, sans-serif";
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";
    ctx.fillStyle = t.inkSoft;
    severityBands.forEach(({ min, max, label }) => {
      const y1   = yScale.getPixelForValue(min);
      const y2   = yScale.getPixelForValue(max);
      const midY = (y1 + y2) / 2;
      ctx.fillText(label, chartArea.right + 10, midY);
    });
    ctx.restore();
  },
};

// --- Mount ------------------------------------------------------------------
const canvas = document.createElement("canvas");
document.getElementById("container").appendChild(canvas);

// --- Chart ------------------------------------------------------------------
new Chart(canvas, {
  type: "line",
  plugins: [severityPlugin],
  data: {
    datasets: [
      {
        label: "Right Ear (O)",
        data: rightEarThresholds.map((y, i) => ({ x: frequencies[i], y })),
        borderColor: RIGHT_COLOR,
        backgroundColor: RIGHT_COLOR,
        pointStyle: "circle",
        pointRadius: 9,
        pointHoverRadius: 11,
        pointBorderColor: RIGHT_COLOR,
        pointBorderWidth: 2.5,
        pointBackgroundColor: t.pageBg,
        borderWidth: 2.5,
        tension: 0,
      },
      {
        label: "Left Ear (X)",
        data: leftEarThresholds.map((y, i) => ({ x: frequencies[i], y })),
        borderColor: LEFT_COLOR,
        backgroundColor: LEFT_COLOR,
        pointStyle: "crossRot",
        pointRadius: 10,
        pointHoverRadius: 12,
        pointBorderColor: LEFT_COLOR,
        pointBorderWidth: 3,
        pointBackgroundColor: LEFT_COLOR,
        borderWidth: 2.5,
        borderDash: [7, 4],
        tension: 0,
      },
    ],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    layout: {
      padding: { top: 16, right: 120, bottom: 24, left: 10 },
    },
    plugins: {
      title: {
        display: true,
        text: "audiogram-clinical · javascript · chartjs · anyplot.ai",
        color: t.ink,
        font: { size: 22, weight: "500" },
        padding: { top: 8, bottom: 14 },
      },
      legend: {
        display: true,
        position: "top",
        labels: {
          color: t.ink,
          font: { size: 16 },
          usePointStyle: true,
          pointStyleWidth: 16,
          padding: 24,
        },
      },
    },
    scales: {
      x: {
        type: "logarithmic",
        min: 100,
        max: 10000,
        title: {
          display: true,
          text: "Frequency (Hz)",
          color: t.ink,
          font: { size: 16, weight: "500" },
          padding: { top: 10 },
        },
        ticks: {
          color: t.inkSoft,
          font: { size: 14 },
          callback(value) {
            const map = { 125: "125", 250: "250", 500: "500", 1000: "1k", 2000: "2k", 4000: "4k", 8000: "8k" };
            return map[value] !== undefined ? map[value] : null;
          },
        },
        afterBuildTicks(scale) {
          scale.ticks = [125, 250, 500, 1000, 2000, 4000, 8000].map(v => ({ value: v }));
        },
        grid: {
          color: t.grid,
        },
      },
      y: {
        reverse: true,
        min: -10,
        max: 120,
        title: {
          display: true,
          text: "Hearing Level (dB HL)",
          color: t.ink,
          font: { size: 16, weight: "500" },
          padding: { bottom: 10 },
        },
        ticks: {
          color: t.inkSoft,
          font: { size: 14 },
          stepSize: 10,
        },
        grid: {
          color: t.grid,
        },
      },
    },
  },
});

Part of Clinical Audiogram on anyplot.ai.

Other implementations