{"spec_id":"spectrogram-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nspectrogram-basic: Spectrogram Time-Frequency Heatmap\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 95/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\nfrom scipy import signal\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Generate chirp signal (frequency increases over time)\nnp.random.seed(42)\nsample_rate = 1000  # Hz\nduration = 2.0  # seconds\nt = np.linspace(0, duration, int(sample_rate * duration))\n\n# Chirp signal: frequency sweeps from 10 Hz to 200 Hz\nf0, f1 = 10, 200\nchirp_signal = signal.chirp(t, f0=f0, f1=f1, t1=duration, method=\"linear\")\nchirp_signal += 0.1 * np.random.randn(len(t))  # Add noise\n\n# Compute spectrogram using scipy\nnperseg = 128\nnoverlap = 96\nfrequencies, times, Sxx = signal.spectrogram(chirp_signal, fs=sample_rate, nperseg=nperseg, noverlap=noverlap)\n\n# Convert to dB scale for better visualization\nSxx_db = 10 * np.log10(Sxx + 1e-10)\n\n# Create mesh data for heatmap\ntime_grid, freq_grid = np.meshgrid(times, frequencies)\ndf = pd.DataFrame({\"time\": time_grid.flatten(), \"frequency\": freq_grid.flatten(), \"power\": Sxx_db.flatten()})\n\n# Filter to relevant frequency range (0-250 Hz) to avoid wasted space\ndf = df[df[\"frequency\"] <= 250]\n\n# Create spectrogram using geom_tile with theme-adaptive styling\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_SOFT, size=0.3),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(size=20, color=INK),\n    axis_text=element_text(size=16, color=INK_SOFT),\n    axis_line=element_line(color=INK_SOFT, size=0.5),\n    plot_title=element_text(size=24, color=INK),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_title=element_text(size=18, color=INK),\n    legend_text=element_text(size=16, color=INK_SOFT),\n)\n\nplot = (\n    ggplot(df, aes(x=\"time\", y=\"frequency\", fill=\"power\"))\n    + geom_tile()\n    + scale_fill_viridis(name=\"Power (dB)\")\n    + labs(x=\"Time (seconds)\", y=\"Frequency (Hz)\", title=\"spectrogram-basic · letsplot · anyplot.ai\")\n    + theme_minimal()\n    + anyplot_theme\n    + ggsize(1600, 900)\n)\n\n# Save as PNG (scale 3x for 4800x2700) and HTML with theme suffix\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}