Frequency Spectrum Plot — Pygal

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 Pygal

Python source (Pygal)

""" anyplot.ai
spectrum-basic: Frequency Spectrum Plot
Library: pygal 3.1.0 | Python 3.13.13
Quality: 88/100 | Updated: 2026-05-14
"""

import os
import sys

import numpy as np


sys.path.pop(0)
import pygal
from pygal.style import Style


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
BRAND = "#009E73"  # Okabe-Ito position 1

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

# Create a synthetic time-domain signal with known frequency components
fs = 1000  # Sampling frequency (Hz)
t = np.linspace(0, 1, fs)  # 1 second of data

# Signal with 3 frequency components: 50 Hz (strong), 120 Hz (medium), 200 Hz (weak)
signal = (
    3.0 * np.sin(2 * np.pi * 50 * t)  # 50 Hz - dominant frequency
    + 1.5 * np.sin(2 * np.pi * 120 * t)  # 120 Hz - secondary
    + 0.8 * np.sin(2 * np.pi * 200 * t)  # 200 Hz - tertiary
    + 0.3 * np.random.randn(len(t))  # Noise floor
)

# Compute FFT
n = len(signal)
fft_result = np.fft.fft(signal)
frequencies = np.fft.fftfreq(n, 1 / fs)

# Take only positive frequencies
positive_mask = frequencies >= 0
frequencies = frequencies[positive_mask]
amplitude = np.abs(fft_result[positive_mask]) / n * 2  # Normalize

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

# Limit to 0-300 Hz for clarity (where our signal components are)
freq_limit_mask = frequencies <= 300
frequencies = frequencies[freq_limit_mask]
amplitude_db = amplitude_db[freq_limit_mask]

# Downsample for pygal (it works better with fewer points)
step = 2
frequencies = frequencies[::step]
amplitude_db = amplitude_db[::step]

# Create custom style for large canvas with theme-adaptive colors
custom_style = Style(
    background=PAGE_BG,
    plot_background=PAGE_BG,
    foreground=INK,
    foreground_strong=INK,
    foreground_subtle=INK_MUTED,
    colors=(BRAND,),
    title_font_size=28,
    label_font_size=22,
    major_label_font_size=18,
    legend_font_size=16,
    value_font_size=14,
    stroke_width=3,
    opacity=0.85,
    opacity_hover=1.0,
)

# Create XY chart (line chart with custom x values)
chart = pygal.XY(
    width=4800,
    height=2700,
    style=custom_style,
    title="spectrum-basic · pygal · anyplot.ai",
    x_title="Frequency (Hz)",
    y_title="Amplitude (dB)",
    show_dots=False,
    fill=True,
    stroke_style={"width": 3},
    show_x_guides=True,
    show_y_guides=True,
    x_label_rotation=0,
    show_legend=False,
    range=(-60, 20),  # dB range
    dots_size=0,
    margin=80,
    spacing=40,
)

# Add data as XY points
xy_data = [(float(f), float(a)) for f, a in zip(frequencies, amplitude_db, strict=True)]
chart.add("Amplitude", xy_data)

# Save as PNG and HTML with theme suffix
chart.render_to_png(f"plot-{THEME}.png")
with open(f"plot-{THEME}.html", "wb") as f:
    f.write(chart.render())

Part of Frequency Spectrum Plot on anyplot.ai.

Other implementations