{"spec_id":"spectrum-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# spectrum-basic: Frequency Spectrum Plot\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 86/100 | Created: 2026-09-09\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens -----------------------------------------------------------\nTHEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nPAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nINK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nINK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nIMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\n\n# --- Data --------------------------------------------------------------------\n# Synthetic vibration signal from a rotating machine: shaft rotation (42 Hz),\n# a gear-mesh harmonic (126 Hz), a bearing-fault tone (310 Hz), plus noise.\nsample_rate = 2048.0\nn_samples = 2048\nt = (0:(n_samples - 1)) ./ sample_rate\n\nshaft_hz = 42.0\ngearmesh_hz = 126.0\nbearing_hz = 310.0\n\nsignal = 1.0 .* sin.(2π * shaft_hz .* t) .+\n         0.5 .* sin.(2π * gearmesh_hz .* t) .+\n         0.25 .* sin.(2π * bearing_hz .* t) .+\n         0.05 .* randn(n_samples)\n\n# Discrete Fourier transform (positive-frequency half), vectorized as a\n# matrix-vector product since FFTW is not available in this runtime.\nbin_index = collect(0:(n_samples ÷ 2))\nsample_index = collect(0:(n_samples - 1))\nangle_matrix = (-2π / n_samples) .* (bin_index * sample_index')\nspectrum = sqrt.((cos.(angle_matrix) * signal) .^ 2 .+ (sin.(angle_matrix) * signal) .^ 2) ./ n_samples\n\nfrequency = bin_index .* (sample_rate / n_samples)\namplitude_db = 20 .* log10.(spectrum .+ 1e-6)\n\n# Keep only the audible/mechanical band of interest (skip the DC bin for log scale)\nmask = frequency .>= 1.0\nfrequency = frequency[mask]\namplitude_db = amplitude_db[mask]\n\n# Locate the actual peak bin nearest each named harmonic, so the annotation\n# sits exactly on the rendered curve rather than the theoretical frequency.\nfunction nearest_peak_index(target_hz, window_hz = 8.0)\n    candidates = findall(f -> abs(f - target_hz) <= window_hz, frequency)\n    candidates[argmax(amplitude_db[candidates])]\nend\n\npeak_names = [\"shaft\", \"gear mesh\", \"bearing fault\"]\npeak_targets = [shaft_hz, gearmesh_hz, bearing_hz]\npeak_indices = [nearest_peak_index(f) for f in peak_targets]\npeak_freqs = frequency[peak_indices]\npeak_amps = amplitude_db[peak_indices]\npeak_labels = [\"$(round(Int, f)) Hz · $name\" for (f, name) in zip(peak_freqs, peak_names)]\n\n# --- Plot ---------------------------------------------------------------------\ntitle_str = \"spectrum-basic · julia · makie · anyplot.ai\"\n\nfig = Figure(\n    resolution = (1600, 900),\n    fontsize = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title = title_str,\n    titlesize = 20,\n    titlecolor = INK,\n    xlabel = \"Frequency (Hz)\",\n    ylabel = \"Amplitude (dB)\",\n    xlabelsize = 14,\n    ylabelsize = 14,\n    xlabelcolor = INK,\n    ylabelcolor = INK,\n    xticklabelsize = 12,\n    yticklabelsize = 12,\n    xticklabelcolor = INK_SOFT,\n    yticklabelcolor = INK_SOFT,\n    xtickcolor = INK_SOFT,\n    ytickcolor = INK_SOFT,\n    xscale = log10,\n    backgroundcolor = PAGE_BG,\n    topspinevisible = false,\n    rightspinevisible = false,\n    leftspinecolor = INK_SOFT,\n    bottomspinecolor = INK_SOFT,\n    ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    xgridvisible = false,\n    yminorgridvisible = false,\n)\n\nlines!(ax, frequency, amplitude_db; color = IMPRINT_PALETTE[1], linewidth = 2.5)\n\n# Highlight the three dominant harmonics with markers + labels, giving the\n# viewer a guided read of the shaft/gear-mesh/bearing-fault components.\nscatter!(\n    ax, peak_freqs, peak_amps;\n    color = IMPRINT_PALETTE[1], markersize = 14,\n    strokewidth = 2, strokecolor = PAGE_BG,\n)\ntext!(\n    ax, peak_freqs, peak_amps;\n    text = peak_labels, color = INK, fontsize = 13,\n    align = (:center, :bottom), offset = (0, 10),\n)\n\n# --- Save -----------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}