Skip to content

Commit 2fe0b38

Browse files
feat(bokeh): implement polar-line (#2755)
## Implementation: `polar-line` - bokeh Implements the **bokeh** version of `polar-line`. **File:** `plots/polar-line/implementations/bokeh.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20601057155)* --------- 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 86e8a1b commit 2fe0b38

2 files changed

Lines changed: 153 additions & 0 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
""" pyplots.ai
2+
polar-line: Polar Line Plot
3+
Library: bokeh 3.8.1 | Python 3.13.11
4+
Quality: 91/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
10+
from bokeh.plotting import figure, output_file, save
11+
12+
13+
# Data: Wind speed pattern over 24 hours (cyclical)
14+
np.random.seed(42)
15+
16+
# Hours of day (cyclical, 0-24)
17+
hours = np.linspace(0, 24, 25)
18+
theta = hours * (2 * np.pi / 24) # Convert to radians
19+
20+
# Wind speed pattern: lower at night, higher during afternoon
21+
base_pattern = 5 + 3 * np.sin(theta - np.pi / 2) + 2 * np.cos(2 * theta)
22+
wind_speed_day1 = base_pattern + np.random.normal(0, 0.5, len(theta))
23+
wind_speed_day2 = base_pattern * 0.8 + np.random.normal(0, 0.4, len(theta))
24+
25+
# Ensure positive values
26+
wind_speed_day1 = np.maximum(wind_speed_day1, 0.5)
27+
wind_speed_day2 = np.maximum(wind_speed_day2, 0.5)
28+
29+
# Close the loop by connecting last point to first
30+
theta = np.append(theta, theta[0])
31+
wind_speed_day1 = np.append(wind_speed_day1, wind_speed_day1[0])
32+
wind_speed_day2 = np.append(wind_speed_day2, wind_speed_day2[0])
33+
34+
# Convert polar to Cartesian for Bokeh (which doesn't have native polar support)
35+
x1 = wind_speed_day1 * np.cos(theta)
36+
y1 = wind_speed_day1 * np.sin(theta)
37+
x2 = wind_speed_day2 * np.cos(theta)
38+
y2 = wind_speed_day2 * np.sin(theta)
39+
40+
# Create figure (square for polar plot)
41+
p = figure(
42+
width=3600,
43+
height=3600,
44+
title="polar-line · bokeh · pyplots.ai",
45+
x_axis_label="Wind Speed (m/s)",
46+
y_axis_label="Wind Speed (m/s)",
47+
x_range=(-12, 12),
48+
y_range=(-12, 12),
49+
tools="",
50+
toolbar_location=None,
51+
)
52+
53+
# Draw concentric circles for radial grid
54+
for r in [2, 4, 6, 8, 10]:
55+
circle_theta = np.linspace(0, 2 * np.pi, 100)
56+
cx = r * np.cos(circle_theta)
57+
cy = r * np.sin(circle_theta)
58+
p.line(cx, cy, line_color="#cccccc", line_width=1.5, line_alpha=0.5)
59+
60+
# Draw radial lines for angular grid (every 30 degrees = 2 hours)
61+
for angle in np.linspace(0, 2 * np.pi, 12, endpoint=False):
62+
p.line([0, 11 * np.cos(angle)], [0, 11 * np.sin(angle)], line_color="#cccccc", line_width=1.5, line_alpha=0.5)
63+
64+
# Add hour labels around the circle
65+
hour_labels = ["0h", "2h", "4h", "6h", "8h", "10h", "12h", "14h", "16h", "18h", "20h", "22h"]
66+
label_radius = 11.5
67+
for i, label in enumerate(hour_labels):
68+
angle = i * (2 * np.pi / 12)
69+
lx = label_radius * np.cos(angle)
70+
ly = label_radius * np.sin(angle)
71+
p.text(
72+
[lx],
73+
[ly],
74+
text=[label],
75+
text_align="center",
76+
text_baseline="middle",
77+
text_font_size="18pt",
78+
text_color="#444444",
79+
)
80+
81+
# Add radius labels
82+
for r in [2, 4, 6, 8, 10]:
83+
p.text([r + 0.3], [0.5], text=[f"{r}"], text_font_size="14pt", text_color="#666666")
84+
85+
# Create data sources
86+
source1 = ColumnDataSource(data={"x": x1, "y": y1})
87+
source2 = ColumnDataSource(data={"x": x2, "y": y2})
88+
89+
# Plot the polar lines
90+
p.line("x", "y", source=source1, line_color="#306998", line_width=4, legend_label="Day 1", line_alpha=0.9)
91+
p.scatter("x", "y", source=source1, color="#306998", size=12, alpha=0.9)
92+
93+
p.line("x", "y", source=source2, line_color="#FFD43B", line_width=4, legend_label="Day 2", line_alpha=0.9)
94+
p.scatter("x", "y", source=source2, color="#FFD43B", size=12, alpha=0.9)
95+
96+
# Style the plot
97+
p.title.text_font_size = "32pt"
98+
p.title.align = "center"
99+
100+
p.xaxis.axis_label_text_font_size = "22pt"
101+
p.yaxis.axis_label_text_font_size = "22pt"
102+
p.xaxis.major_label_text_font_size = "18pt"
103+
p.yaxis.major_label_text_font_size = "18pt"
104+
105+
# Hide the default axes for cleaner polar appearance
106+
p.xaxis.visible = False
107+
p.yaxis.visible = False
108+
p.xgrid.visible = False
109+
p.ygrid.visible = False
110+
111+
# Style legend
112+
p.legend.location = "top_right"
113+
p.legend.label_text_font_size = "18pt"
114+
p.legend.background_fill_alpha = 0.8
115+
p.legend.border_line_color = "#cccccc"
116+
p.legend.padding = 15
117+
p.legend.spacing = 10
118+
119+
# Background
120+
p.background_fill_color = "#fafafa"
121+
p.border_fill_color = "#ffffff"
122+
p.outline_line_color = "#dddddd"
123+
124+
# Save PNG and HTML
125+
export_png(p, filename="plot.png")
126+
output_file("plot.html", title="polar-line · bokeh · pyplots.ai")
127+
save(p)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: bokeh
2+
specification_id: polar-line
3+
created: '2025-12-30T16:30:25Z'
4+
updated: '2025-12-30T16:37:54Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20601057155
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-line/bokeh/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/polar-line/bokeh/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/polar-line/bokeh/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent simulation of polar coordinates in Bokeh (which lacks native polar support)
17+
using manual Cartesian conversion
18+
- Clean, readable hour labels around the perimeter with good spacing
19+
- Appropriate data scenario (wind speed pattern) that naturally fits polar/cyclical
20+
visualization
21+
- Good color contrast between the two series (blue/yellow)
22+
- Properly closed loops connecting back to starting point
23+
weaknesses:
24+
- Missing axis label for radial dimension (could add Wind Speed m/s text annotation)
25+
- Radius labels (2, 4, 6, 8, 10) positioned only on positive X-axis could be more
26+
prominent

0 commit comments

Comments
 (0)