Frequency Spectrum Plot — lets-plot

A frequency spectrum plot displays signal amplitude or power across a range of frequencies, showing the frequency domain representation of time-series data. This visualization reveals the frequency components present in a signal, making it essential for identifying dominant frequencies, harmonics, and noise characteristics. It is fundamental in signal processing, audio engineering, and vibration analysis.

Frequency Spectrum Plot rendered with lets-plot

Python source (lets-plot)

""" anyplot.ai
spectrum-basic: Frequency Spectrum Plot
Library: letsplot 4.9.0 | Python 3.13.13
Quality: 94/100 | Updated: 2026-05-14
"""

import os
import shutil

import numpy as np
import pandas as pd
from lets_plot import *


LetsPlot.setup_html()

# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
BRAND = "#009E73"  # Okabe-Ito position 1

# Grid color (theme-adaptive)
GRID_COLOR = "rgba(26, 26, 23, 0.08)" if THEME == "light" else "rgba(240, 239, 232, 0.08)"

# Data: Generate a synthetic signal with multiple frequency components
np.random.seed(42)

# Sampling parameters
sample_rate = 1024  # Hz
duration = 1.0  # seconds
n_samples = int(sample_rate * duration)
t = np.linspace(0, duration, n_samples, endpoint=False)

# Create a signal with multiple frequency components
# 50 Hz fundamental, 120 Hz harmonic, 200 Hz component, plus noise
signal = (
    1.0 * np.sin(2 * np.pi * 50 * t)  # 50 Hz fundamental
    + 0.5 * np.sin(2 * np.pi * 120 * t)  # 120 Hz harmonic
    + 0.3 * np.sin(2 * np.pi * 200 * t)  # 200 Hz component
    + 0.1 * np.random.randn(n_samples)  # Noise
)

# Compute FFT
fft_result = np.fft.fft(signal)
frequencies = np.fft.fftfreq(n_samples, 1 / sample_rate)

# Take only positive frequencies
positive_mask = frequencies >= 0
frequencies = frequencies[positive_mask]
amplitude = np.abs(fft_result[positive_mask])

# Convert to dB scale (power spectrum)
amplitude_db = 20 * np.log10(amplitude + 1e-10)

# Create DataFrame for lets-plot
df = pd.DataFrame({"frequency": frequencies, "amplitude": amplitude_db})

# Filter to show meaningful frequency range (0-300 Hz)
df = df[df["frequency"] <= 300]

# Create plot with theme-adaptive styling
plot = (
    ggplot(df, aes(x="frequency", y="amplitude"))
    + geom_line(color=BRAND, size=1.2, alpha=0.9)
    + geom_area(fill=BRAND, alpha=0.2)
    + labs(x="Frequency (Hz)", y="Amplitude (dB)", title="spectrum-basic · letsplot · anyplot.ai")
    + theme_minimal()
    + theme(
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_grid_major=element_line(color=GRID_COLOR, size=0.3),
        panel_grid_minor=element_blank(),
        axis_title=element_text(size=20, color=INK),
        axis_text=element_text(size=16, color=INK_SOFT),
        plot_title=element_text(size=24, color=INK, face="bold"),
        axis_line=element_line(color=INK_SOFT, size=0.3),
    )
    + ggsize(1600, 900)  # Will be scaled 3x to 4800x2700
)

# Save as PNG and HTML (ggsave saves to lets-plot-images/ subdirectory)
ggsave(plot, f"plot-{THEME}.png", scale=3)
ggsave(plot, f"plot-{THEME}.html")

# Move files to current directory
if os.path.exists(f"lets-plot-images/plot-{THEME}.png"):
    shutil.move(f"lets-plot-images/plot-{THEME}.png", f"plot-{THEME}.png")
if os.path.exists(f"lets-plot-images/plot-{THEME}.html"):
    shutil.move(f"lets-plot-images/plot-{THEME}.html", f"plot-{THEME}.html")

# Clean up empty subdirectory if all files are moved
try:
    if not os.listdir("lets-plot-images"):
        os.rmdir("lets-plot-images")
except OSError:
    pass

Part of Frequency Spectrum Plot on anyplot.ai.

Other implementations