{"spec_id":"spectrogram-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nspectrogram-basic: Spectrogram Time-Frequency Heatmap\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent current directory from shadowing the plotnine package\nsys.path = [p for p in sys.path if not p.endswith(\"implementations\") and not p.endswith(\"python\")]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_line,\n    element_rect,\n    element_text,\n    geom_tile,\n    ggplot,\n    labs,\n    scale_fill_cmap,\n    theme,\n    theme_minimal,\n)\nfrom scipy import signal\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Data - Generate a chirp signal with increasing frequency\nnp.random.seed(42)\nsample_rate = 1000  # Hz\nduration = 2.0  # seconds\nt = np.linspace(0, duration, int(sample_rate * duration))\n\n# Create chirp signal: frequency increases from 50 Hz to 200 Hz\nf0, f1 = 50, 200\nchirp_signal = signal.chirp(t, f0=f0, f1=f1, t1=duration, method=\"linear\")\nchirp_signal += 0.3 * np.random.randn(len(chirp_signal))  # Add noise\n\n# Compute spectrogram using Short-Time Fourier Transform\nnperseg = 128\nnoverlap = nperseg // 2\nfrequencies, times, Sxx = signal.spectrogram(chirp_signal, fs=sample_rate, nperseg=nperseg, noverlap=noverlap)\n\n# Convert power to dB scale\nSxx_db = 10 * np.log10(Sxx + 1e-10)\n\n# Create DataFrame for plotnine (convert 2D grid to long format)\ntime_grid, freq_grid = np.meshgrid(times, frequencies)\ndf = pd.DataFrame({\"Time\": time_grid.ravel(), \"Frequency\": freq_grid.ravel(), \"Power\": Sxx_db.ravel()})\n\n# Filter to relevant frequency range (tighter bound for better visualization)\ndf = df[df[\"Frequency\"] <= 250]\n\n# Create spectrogram plot using geom_tile (heatmap) with viridis colormap\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major=element_line(color=INK, size=0.3, alpha=0.12),\n    panel_grid_minor=element_line(color=INK, size=0.15, alpha=0.06),\n    panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5),\n    axis_title=element_text(color=INK, size=20, weight=\"bold\"),\n    axis_text=element_text(color=INK_SOFT, size=16),\n    axis_line=element_line(color=INK_SOFT, size=0.4),\n    plot_title=element_text(color=INK, size=24, weight=\"bold\"),\n    legend_text=element_text(color=INK_SOFT, size=14),\n    legend_title=element_text(color=INK, size=16, weight=\"bold\"),\n    figure_size=(16, 9),\n    text=element_text(size=14),\n)\n\nplot = (\n    ggplot(df, aes(x=\"Time\", y=\"Frequency\", fill=\"Power\"))\n    + geom_tile()\n    + scale_fill_cmap(cmap_name=\"viridis\", name=\"Power (dB)\")\n    + labs(\n        x=\"Time (s)\",\n        y=\"Frequency (Hz)\",\n        title=\"spectrogram-basic · plotnine · anyplot.ai\",\n        caption=\"Chirp signal: linear frequency sweep from 50 Hz to 200 Hz over 2 seconds\",\n    )\n    + theme_minimal()\n    + anyplot_theme\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}