Skip to content

Commit 8d641d6

Browse files
feat(bokeh): implement polar-bar (#2759)
## Implementation: `polar-bar` - bokeh Implements the **bokeh** version of `polar-bar`. **File:** `plots/polar-bar/implementations/bokeh.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20601057120)* --------- 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 4d92a03 commit 8d641d6

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
""" pyplots.ai
2+
polar-bar: Polar Bar Chart (Wind Rose)
3+
Library: bokeh 3.8.1 | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-30
5+
"""
6+
7+
import numpy as np
8+
from bokeh.io import export_png
9+
from bokeh.models import ColumnDataSource, Label, Title
10+
from bokeh.palettes import Blues8
11+
from bokeh.plotting import figure
12+
13+
14+
# Data - Wind frequency by direction (8 compass points)
15+
np.random.seed(42)
16+
directions = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]
17+
# Simulating wind pattern: prevailing winds from SW and W
18+
frequencies = np.array([12, 8, 10, 6, 9, 18, 22, 14])
19+
20+
# Convert directions to angles (0° = North, clockwise)
21+
# In Bokeh, angles are counterclockwise from East, so we need to convert
22+
n_dirs = len(directions)
23+
angles = np.linspace(0, 2 * np.pi, n_dirs, endpoint=False)
24+
# Bokeh: 0 = East, pi/2 = North, counterclockwise
25+
start_angles = np.pi / 2 - angles - np.pi / n_dirs
26+
end_angles = np.pi / 2 - angles + np.pi / n_dirs
27+
28+
# Normalize frequencies for bar length
29+
max_freq = frequencies.max()
30+
radii = frequencies / max_freq * 0.75 # Scale to 75% of plot radius
31+
32+
# Colors - Python Blue palette
33+
colors = [Blues8[min(7, max(0, 7 - int((f / max_freq) * 7)))] for f in frequencies]
34+
35+
# Create figure (square for polar plot)
36+
p = figure(width=3600, height=3600, x_range=(-1.05, 1.05), y_range=(-1.05, 1.05), tools="", toolbar_location=None)
37+
38+
# Remove axes and grid for polar appearance
39+
p.axis.visible = False
40+
p.grid.visible = False
41+
p.outline_line_color = None
42+
p.background_fill_color = "#fafafa"
43+
44+
# Title styling
45+
p.title = Title(text="polar-bar · bokeh · pyplots.ai", text_font_size="42pt", text_color="#306998", align="center")
46+
47+
# Draw concentric reference circles
48+
circle_radii = [0.25, 0.50, 0.75]
49+
for r in circle_radii:
50+
p.circle(x=0, y=0, radius=r, fill_color=None, line_color="#aaaaaa", line_width=2, line_dash="dashed")
51+
52+
# Draw outer circle
53+
p.circle(x=0, y=0, radius=0.85, fill_color=None, line_color="#888888", line_width=2)
54+
55+
# Draw radial lines for each direction
56+
for i in range(n_dirs):
57+
angle = np.pi / 2 - angles[i]
58+
x_end = np.cos(angle) * 0.85
59+
y_end = np.sin(angle) * 0.85
60+
p.line([0, x_end], [0, y_end], line_color="#cccccc", line_width=1.5)
61+
62+
# Draw wedges (polar bars)
63+
source = ColumnDataSource(
64+
data={
65+
"x": [0] * n_dirs,
66+
"y": [0] * n_dirs,
67+
"radius": radii.tolist(),
68+
"start_angle": start_angles.tolist(),
69+
"end_angle": end_angles.tolist(),
70+
"color": colors,
71+
"direction": directions,
72+
"frequency": frequencies.tolist(),
73+
}
74+
)
75+
76+
p.wedge(
77+
x="x",
78+
y="y",
79+
radius="radius",
80+
start_angle="start_angle",
81+
end_angle="end_angle",
82+
fill_color="color",
83+
fill_alpha=0.9,
84+
line_color="#306998",
85+
line_width=3,
86+
source=source,
87+
)
88+
89+
# Add direction labels around the plot
90+
label_radius = 0.93
91+
for i, direction in enumerate(directions):
92+
angle = np.pi / 2 - angles[i]
93+
x_label = np.cos(angle) * label_radius
94+
y_label = np.sin(angle) * label_radius
95+
96+
label = Label(
97+
x=x_label,
98+
y=y_label,
99+
text=direction,
100+
text_font_size="32pt",
101+
text_font_style="bold",
102+
text_color="#306998",
103+
text_align="center",
104+
text_baseline="middle",
105+
)
106+
p.add_layout(label)
107+
108+
# Add frequency scale labels at reference circles
109+
for r in circle_radii:
110+
freq_val = int(r / 0.75 * max_freq)
111+
label = Label(
112+
x=0.03,
113+
y=r + 0.015,
114+
text=f"{freq_val}%",
115+
text_font_size="20pt",
116+
text_color="#666666",
117+
text_align="left",
118+
text_baseline="bottom",
119+
)
120+
p.add_layout(label)
121+
122+
# Save as PNG
123+
export_png(p, filename="plot.png")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
library: bokeh
2+
specification_id: polar-bar
3+
created: '2025-12-30T16:30:56Z'
4+
updated: '2025-12-30T16:39:50Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20601057120
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 3.8.1
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/polar-bar/bokeh/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/polar-bar/bokeh/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/polar-bar/bokeh/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent use of Bokeh wedge glyph to create authentic polar bars
17+
- Clean mathematical transformation from compass directions to Bokeh angle system
18+
- Professional styling with Python blue (#306998) color scheme throughout
19+
- Good use of ColumnDataSource for data organization
20+
- Reference circles and radial lines provide clear visual guides
21+
- Color intensity mapping to frequency adds extra visual dimension
22+
weaknesses:
23+
- Scale labels show percent symbol but data represents raw frequency counts, which
24+
is semantically inconsistent
25+
- No legend or annotation explaining what the colors and values represent

0 commit comments

Comments
 (0)