Skip to content

Commit 109eed0

Browse files
feat(bokeh): implement spectrum-basic (#2974)
## Implementation: `spectrum-basic` - bokeh Implements the **bokeh** version of `spectrum-basic`. **File:** `plots/spectrum-basic/implementations/bokeh.py` **Parent Issue:** #2926 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20612829247)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 989e9ce commit 109eed0

2 files changed

Lines changed: 134 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: bokeh
2+
specification_id: spectrum-basic
3+
created: '2025-12-31T05:38:40Z'
4+
updated: '2025-12-31T05:53:30Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20612829247
7+
issue: 2926
8+
python_version: 3.13.11
9+
library_version: 3.8.1
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/spectrum-basic/bokeh/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/spectrum-basic/bokeh/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/spectrum-basic/bokeh/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent visual clarity with proper text sizing for the 4800x2700 canvas
17+
- Clear peak highlighting with dashed vertical lines and circle markers at 50 Hz,
18+
150 Hz, and 400 Hz
19+
- Good use of varea for the subtle fill under the curve which aids visual interpretation
20+
- Proper FFT implementation with correct normalization and dB conversion
21+
- Colorblind-safe color scheme with good contrast
22+
weaknesses:
23+
- Legend text appears small relative to the canvas size; could benefit from larger
24+
legend text
25+
- Missing HoverTool which would showcase Bokeh interactivity for showing exact frequency/amplitude
26+
values

0 commit comments

Comments
 (0)