{"spec_id":"spectrum-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// spectrum-basic: Frequency Spectrum Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-09\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: synthetic FFT spectrum of a plucked-string note (A4 = 440 Hz) ---\n// Fixed-seed LCG (browser has no seeded Math.random)\nlet lcgState = 42;\nfunction rand() {\n  lcgState = (lcgState * 1664525 + 1013904223) % 4294967296;\n  return lcgState / 4294967296;\n}\n\nconst F_MIN = 20;\nconst F_MAX = 20000;\nconst N_BINS = 512;\nconst FUNDAMENTAL = 440;\nconst HARMONICS = [\n  { multiple: 1, amplitudeDb: 0 },\n  { multiple: 2, amplitudeDb: -8 },\n  { multiple: 3, amplitudeDb: -14 },\n  { multiple: 4, amplitudeDb: -19 },\n  { multiple: 5, amplitudeDb: -24 },\n  { multiple: 6, amplitudeDb: -29 },\n  { multiple: 7, amplitudeDb: -34 },\n].map((h) => ({ frequency: h.multiple * FUNDAMENTAL, amplitudeDb: h.amplitudeDb }));\n\nfunction noiseFloorDb(frequency) {\n  return -72 - 6 * Math.log10(frequency / 100) + (rand() - 0.5) * 3;\n}\n\nfunction harmonicPeakDb(frequency, peak, widthOctaves) {\n  const octaves = Math.log2(frequency / peak.frequency);\n  const falloffDb = 55 * (octaves / widthOctaves) ** 2;\n  return peak.amplitudeDb - falloffDb;\n}\n\nconst logStep = Math.log10(F_MAX / F_MIN) / (N_BINS - 1);\nconst spectrum = Array.from({ length: N_BINS }, (_, i) => {\n  const frequency = F_MIN * 10 ** (i * logStep);\n  const peakContribution = HARMONICS.reduce(\n    (acc, h) => Math.max(acc, harmonicPeakDb(frequency, h, 0.05)),\n    -Infinity,\n  );\n  const amplitude = Math.max(noiseFloorDb(frequency), peakContribution);\n  return { x: frequency, y: amplitude };\n});\n\n// --- Mount -------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Distinctive feature: custom plugin calling out the fundamental ----\nconst Y_MIN = -90;\nconst annotateFundamental = {\n  id: \"annotateFundamental\",\n  afterDatasetsDraw(chart) {\n    const { ctx, scales } = chart;\n    const xPixel = scales.x.getPixelForValue(FUNDAMENTAL);\n    const yPixel = scales.y.getPixelForValue(HARMONICS[0].amplitudeDb);\n    const bottomPixel = scales.y.getPixelForValue(Y_MIN);\n\n    ctx.save();\n    ctx.strokeStyle = t.inkSoft;\n    ctx.setLineDash([4, 4]);\n    ctx.beginPath();\n    ctx.moveTo(xPixel, yPixel);\n    ctx.lineTo(xPixel, bottomPixel);\n    ctx.stroke();\n\n    ctx.setLineDash([]);\n    ctx.fillStyle = t.ink;\n    ctx.font = \"14px sans-serif\";\n    ctx.textAlign = \"center\";\n    ctx.fillText(\"440 Hz — A4 fundamental\", xPixel, yPixel - 12);\n    ctx.restore();\n  },\n};\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  plugins: [annotateFundamental],\n  data: {\n    datasets: [\n      {\n        label: \"Amplitude\",\n        data: spectrum,\n        borderColor: t.palette[0],\n        backgroundColor: t.palette[0] + \"26\",\n        borderWidth: 2.5,\n        pointRadius: 0,\n        fill: \"start\",\n        tension: 0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"spectrum-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n        padding: { bottom: 20 },\n      },\n      legend: { display: false },\n    },\n    scales: {\n      x: {\n        type: \"logarithmic\",\n        min: F_MIN,\n        max: F_MAX,\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback: (value) => {\n            const digits = Math.log10(value);\n            if (!Number.isInteger(digits) && value !== 20 && value !== 50 && value !== 200 && value !== 500 && value !== 2000 && value !== 5000) {\n              return null;\n            }\n            return value >= 1000 ? `${value / 1000}k` : `${value}`;\n          },\n        },\n        grid: { display: false },\n        title: { display: true, text: \"Frequency (Hz)\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        min: Y_MIN,\n        suggestedMax: 8,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Amplitude (dB)\", color: t.ink, font: { size: 16 } },\n      },\n    },\n  },\n});\n"}