|
| 1 | +""" pyplots.ai |
| 2 | +spectrum-basic: Frequency Spectrum Plot |
| 3 | +Library: bokeh 3.8.1 | Python 3.13.11 |
| 4 | +Quality: 92/100 | Created: 2025-12-31 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +from bokeh.io import export_png, save |
| 9 | +from bokeh.models import ColumnDataSource |
| 10 | +from bokeh.plotting import figure |
| 11 | + |
| 12 | + |
| 13 | +# Data: Generate synthetic signal with multiple frequency components |
| 14 | +np.random.seed(42) |
| 15 | +sample_rate = 8192 # Hz |
| 16 | +duration = 1.0 # seconds |
| 17 | +n_samples = int(sample_rate * duration) |
| 18 | +t = np.linspace(0, duration, n_samples, endpoint=False) |
| 19 | + |
| 20 | +# Create composite signal: 50 Hz base, 150 Hz harmonic, 400 Hz component, plus noise |
| 21 | +signal = ( |
| 22 | + 1.0 * np.sin(2 * np.pi * 50 * t) # Fundamental at 50 Hz |
| 23 | + + 0.5 * np.sin(2 * np.pi * 150 * t) # Harmonic at 150 Hz |
| 24 | + + 0.3 * np.sin(2 * np.pi * 400 * t) # Component at 400 Hz |
| 25 | + + 0.1 * np.random.randn(n_samples) # Noise |
| 26 | +) |
| 27 | + |
| 28 | +# Compute FFT |
| 29 | +fft_result = np.fft.rfft(signal) |
| 30 | +frequencies = np.fft.rfftfreq(n_samples, 1 / sample_rate) |
| 31 | +amplitude = np.abs(fft_result) / n_samples # Normalize amplitude |
| 32 | + |
| 33 | +# Convert to dB scale (with floor to avoid log(0)) |
| 34 | +amplitude_db = 20 * np.log10(np.maximum(amplitude, 1e-10)) |
| 35 | + |
| 36 | +# Limit to 500 Hz for better visualization |
| 37 | +mask = frequencies <= 500 |
| 38 | +frequencies = frequencies[mask] |
| 39 | +amplitude_db = amplitude_db[mask] |
| 40 | + |
| 41 | +# Create data source |
| 42 | +source = ColumnDataSource(data={"frequency": frequencies, "amplitude": amplitude_db}) |
| 43 | + |
| 44 | +# Create figure (4800 x 2700 px for 16:9) |
| 45 | +p = figure( |
| 46 | + width=4800, |
| 47 | + height=2700, |
| 48 | + title="spectrum-basic · bokeh · pyplots.ai", |
| 49 | + x_axis_label="Frequency (Hz)", |
| 50 | + y_axis_label="Amplitude (dB)", |
| 51 | + tools="pan,wheel_zoom,box_zoom,reset,save", |
| 52 | +) |
| 53 | + |
| 54 | +# Plot spectrum as line with area fill |
| 55 | +p.line(x="frequency", y="amplitude", source=source, line_width=4, line_color="#306998", legend_label="Signal Spectrum") |
| 56 | + |
| 57 | +# Add subtle fill under the curve |
| 58 | +p.varea(x="frequency", y1="amplitude", y2=-80, source=source, fill_color="#306998", fill_alpha=0.2) |
| 59 | + |
| 60 | +# Mark peak frequencies with vertical lines and circles |
| 61 | +peak_freqs = [50, 150, 400] |
| 62 | +peak_labels = ["50 Hz", "150 Hz", "400 Hz"] |
| 63 | +for freq, label in zip(peak_freqs, peak_labels, strict=True): |
| 64 | + idx = np.argmin(np.abs(frequencies - freq)) |
| 65 | + peak_amp = amplitude_db[idx] |
| 66 | + |
| 67 | + # Vertical line at peak |
| 68 | + p.line(x=[freq, freq], y=[-80, peak_amp], line_width=3, line_dash="dashed", line_color="#FFD43B", line_alpha=0.8) |
| 69 | + |
| 70 | + # Circle marker at peak |
| 71 | + p.scatter( |
| 72 | + x=[freq], |
| 73 | + y=[peak_amp], |
| 74 | + size=25, |
| 75 | + color="#FFD43B", |
| 76 | + line_color="#306998", |
| 77 | + line_width=3, |
| 78 | + legend_label=f"Peak: {label}", |
| 79 | + ) |
| 80 | + |
| 81 | +# Styling - text sizes for large canvas (4800x2700) |
| 82 | +p.title.text_font_size = "36pt" |
| 83 | +p.title.text_color = "#333333" |
| 84 | +p.xaxis.axis_label_text_font_size = "28pt" |
| 85 | +p.yaxis.axis_label_text_font_size = "28pt" |
| 86 | +p.xaxis.major_label_text_font_size = "20pt" |
| 87 | +p.yaxis.major_label_text_font_size = "20pt" |
| 88 | + |
| 89 | +# Grid styling |
| 90 | +p.xgrid.grid_line_alpha = 0.3 |
| 91 | +p.ygrid.grid_line_alpha = 0.3 |
| 92 | +p.xgrid.grid_line_dash = "dashed" |
| 93 | +p.ygrid.grid_line_dash = "dashed" |
| 94 | + |
| 95 | +# Legend styling |
| 96 | +p.legend.label_text_font_size = "20pt" |
| 97 | +p.legend.location = "top_right" |
| 98 | +p.legend.background_fill_alpha = 0.8 |
| 99 | +p.legend.border_line_color = "#cccccc" |
| 100 | +p.legend.padding = 10 |
| 101 | +p.legend.spacing = 5 |
| 102 | + |
| 103 | +# Background |
| 104 | +p.background_fill_color = "#fafafa" |
| 105 | + |
| 106 | +# Save as PNG and HTML |
| 107 | +export_png(p, filename="plot.png") |
| 108 | +save(p, filename="plot.html", title="spectrum-basic · bokeh · pyplots.ai") |
0 commit comments