Skip to content

Commit 0877a7f

Browse files
feat(letsplot): implement polar-line (#2771)
## Implementation: `polar-line` - letsplot Implements the **letsplot** version of `polar-line`. **File:** `plots/polar-line/implementations/letsplot.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20601061174)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 244a6d8 commit 0877a7f

2 files changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
""" pyplots.ai
2+
polar-line: Polar Line Plot
3+
Library: letsplot 4.8.2 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-30
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from lets_plot import (
10+
LetsPlot,
11+
aes,
12+
coord_fixed,
13+
element_text,
14+
geom_path,
15+
geom_point,
16+
geom_text,
17+
ggplot,
18+
ggsize,
19+
labs,
20+
scale_color_manual,
21+
theme,
22+
theme_void,
23+
)
24+
from lets_plot.export import ggsave
25+
26+
27+
LetsPlot.setup_html()
28+
29+
# Data - Monthly average temperature pattern (cyclical)
30+
np.random.seed(42)
31+
months = np.arange(0, 360, 30) # 12 months as angles (0, 30, 60, ... 330)
32+
month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
33+
34+
# Simulated temperature pattern (warm summer, cold winter)
35+
temp_city_a = np.array([2, 4, 10, 15, 20, 25, 28, 27, 22, 14, 7, 3])
36+
temp_city_b = np.array([5, 7, 12, 17, 22, 27, 30, 29, 24, 16, 10, 6])
37+
38+
# Close the loop by adding the first point at the end
39+
angles_closed = np.append(months, 360)
40+
temp_a_closed = np.append(temp_city_a, temp_city_a[0])
41+
temp_b_closed = np.append(temp_city_b, temp_city_b[0])
42+
43+
# Convert to radians for polar coordinates
44+
theta_a = np.radians(angles_closed)
45+
theta_b = np.radians(angles_closed)
46+
47+
# Create x, y coordinates from polar (for lets-plot which uses Cartesian)
48+
x_a = temp_a_closed * np.cos(theta_a)
49+
y_a = temp_a_closed * np.sin(theta_a)
50+
x_b = temp_b_closed * np.cos(theta_b)
51+
y_b = temp_b_closed * np.sin(theta_b)
52+
53+
# Create DataFrame
54+
df = pd.DataFrame(
55+
{
56+
"x": np.concatenate([x_a, x_b]),
57+
"y": np.concatenate([y_a, y_b]),
58+
"radius": np.concatenate([temp_a_closed, temp_b_closed]),
59+
"angle": np.concatenate([angles_closed, angles_closed]),
60+
"city": ["City A"] * len(x_a) + ["City B"] * len(x_b),
61+
}
62+
)
63+
64+
# Create concentric circles for polar grid
65+
grid_radii = [10, 20, 30]
66+
circle_points = 100
67+
grid_circles = []
68+
for r in grid_radii:
69+
theta_grid = np.linspace(0, 2 * np.pi, circle_points)
70+
grid_circles.append(pd.DataFrame({"x": r * np.cos(theta_grid), "y": r * np.sin(theta_grid), "radius": r}))
71+
grid_df = pd.concat(grid_circles, ignore_index=True)
72+
73+
# Create radial lines for grid (every 30 degrees = each month)
74+
radial_lines = []
75+
for angle_deg in range(0, 360, 30):
76+
angle_rad = np.radians(angle_deg)
77+
radial_lines.append(
78+
pd.DataFrame({"x": [0, 35 * np.cos(angle_rad)], "y": [0, 35 * np.sin(angle_rad)], "angle": angle_deg})
79+
)
80+
radial_df = pd.concat(radial_lines, ignore_index=True)
81+
82+
# Month labels positions
83+
label_radius = 34
84+
month_labels_df = pd.DataFrame(
85+
{
86+
"x": [label_radius * np.cos(np.radians(a)) for a in months],
87+
"y": [label_radius * np.sin(np.radians(a)) for a in months],
88+
"label": month_names,
89+
}
90+
)
91+
92+
# Plot
93+
plot = (
94+
ggplot()
95+
# Concentric grid circles
96+
+ geom_path(aes(x="x", y="y", group="radius"), data=grid_df, color="#CCCCCC", size=0.5, alpha=0.6)
97+
# Radial grid lines
98+
+ geom_path(aes(x="x", y="y", group="angle"), data=radial_df, color="#CCCCCC", size=0.5, alpha=0.6)
99+
# Data lines
100+
+ geom_path(aes(x="x", y="y", color="city"), data=df, size=2)
101+
# Data points
102+
+ geom_point(aes(x="x", y="y", color="city"), data=df, size=5)
103+
# Month labels
104+
+ geom_text(aes(x="x", y="y", label="label"), data=month_labels_df, size=14, color="#333333")
105+
# Colors
106+
+ scale_color_manual(values=["#306998", "#FFD43B"])
107+
# Theme and labels
108+
+ labs(title="polar-line · letsplot · pyplots.ai", color="Location")
109+
+ theme_void()
110+
+ theme(
111+
plot_title=element_text(size=24, hjust=0.5),
112+
legend_position="right",
113+
legend_title=element_text(size=18),
114+
legend_text=element_text(size=16),
115+
)
116+
+ coord_fixed()
117+
+ ggsize(1600, 900)
118+
)
119+
120+
# Save
121+
ggsave(plot, "plot.png", path=".", scale=3)
122+
123+
# Also save HTML for interactive version
124+
ggsave(plot, "plot.html", path=".")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: letsplot
2+
specification_id: polar-line
3+
created: '2025-12-30T16:34:34Z'
4+
updated: '2025-12-30T16:44:24Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20601061174
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 4.8.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/polar-line/letsplot/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/polar-line/letsplot/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/polar-line/letsplot/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent polar coordinate implementation using manual Cartesian transformation
17+
when native coord_polar is unavailable
18+
- Clean visual design with appropriate colors, grid lines, and month labels
19+
- Good use of multiple series to demonstrate comparison capability
20+
- Realistic temperature data with clear seasonal patterns
21+
- Proper closed-loop polar lines connecting first and last points
22+
- Well-balanced layout with legend positioned cleanly
23+
weaknesses:
24+
- Missing temperature scale labels (no indication of what the concentric circles
25+
represent in °C)
26+
- 'Minor: LetsPlot.setup_html() call is unnecessary for PNG-only output'

0 commit comments

Comments
 (0)