Skip to content

Commit f540440

Browse files
feat(altair): implement raincloud-basic (#1957)
## Implementation: `raincloud-basic` - altair Implements the **altair** version of `raincloud-basic`. **File:** `plots/raincloud-basic/implementations/altair.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20501865366)* --------- 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 6b79a25 commit f540440

2 files changed

Lines changed: 60 additions & 39 deletions

File tree

plots/raincloud-basic/implementations/altair.py

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" pyplots.ai
22
raincloud-basic: Basic Raincloud Plot
33
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-25
4+
Quality: 90/100 | Created: 2025-12-25
55
"""
66

77
import altair as alt
@@ -30,12 +30,13 @@
3030
)
3131

3232
# Create jittered x positions for strip plot (rain below the cloud)
33-
# For vertical layout: rain should be on LEFT (lower x values), cloud on RIGHT (higher x values)
33+
# For vertical layout: rain on LEFT (lower x values), cloud on RIGHT (higher x values)
3434
np.random.seed(42)
35-
data["jitter"] = np.random.uniform(-0.35, -0.15, len(data)) # Wider range, more offset left
35+
data["jitter"] = np.random.uniform(-0.35, -0.15, len(data))
3636

3737
# Map conditions to numeric positions for layering
38-
condition_map = {"Control": 0, "Treatment A": 1, "Treatment B": 2}
38+
condition_order = ["Control", "Treatment A", "Treatment B"]
39+
condition_map = {c: i for i, c in enumerate(condition_order)}
3940
data["condition_num"] = data["condition"].map(condition_map)
4041
data["jitter_pos"] = data["condition_num"] + data["jitter"]
4142

@@ -56,9 +57,22 @@
5657
x2="violin_pos:Q",
5758
color=alt.Color(
5859
"condition:N",
59-
scale=alt.Scale(range=["#306998", "#FFD43B", "#4CAF50"]),
60-
legend=alt.Legend(title="Condition", titleFontSize=20, labelFontSize=18, orient="right"),
60+
scale=alt.Scale(domain=condition_order, range=["#306998", "#FFD43B", "#4CAF50"]),
61+
legend=alt.Legend(
62+
title="Condition",
63+
titleFontSize=20,
64+
labelFontSize=18,
65+
orient="right",
66+
fillColor="white",
67+
strokeColor="#cccccc",
68+
padding=12,
69+
cornerRadius=4,
70+
),
6171
),
72+
tooltip=[
73+
alt.Tooltip("condition:N", title="Condition"),
74+
alt.Tooltip("reaction_time:Q", title="Reaction Time (ms)", format=".0f"),
75+
],
6276
)
6377
)
6478

@@ -75,39 +89,50 @@
7589
.encode(
7690
x=alt.X("box_pos:Q", axis=None),
7791
y=alt.Y("reaction_time:Q", title="Reaction Time (ms)", scale=alt.Scale(domain=[200, 600])),
78-
color=alt.Color("condition:N", scale=alt.Scale(range=["#306998", "#FFD43B", "#4CAF50"])),
92+
color=alt.Color(
93+
"condition:N", scale=alt.Scale(domain=condition_order, range=["#306998", "#FFD43B", "#4CAF50"])
94+
),
7995
)
8096
)
8197

82-
# Jittered strip plot (rain) - positioned clearly to the LEFT (below/before the cloud)
98+
# Jittered strip plot (rain) - positioned clearly to the LEFT (below the cloud)
8399
strip = (
84100
alt.Chart(data)
85-
.mark_circle(size=50, opacity=0.6) # Smaller point size to reduce overlap
101+
.mark_circle(size=40, opacity=0.6) # Smaller size to reduce overlap
86102
.encode(
87103
x=alt.X("jitter_pos:Q", axis=None),
88104
y=alt.Y("reaction_time:Q"),
89-
color=alt.Color("condition:N", scale=alt.Scale(range=["#306998", "#FFD43B", "#4CAF50"])),
105+
color=alt.Color(
106+
"condition:N", scale=alt.Scale(domain=condition_order, range=["#306998", "#FFD43B", "#4CAF50"])
107+
),
108+
tooltip=[
109+
alt.Tooltip("condition:N", title="Condition"),
110+
alt.Tooltip("reaction_time:Q", title="Reaction Time (ms)", format=".1f"),
111+
],
90112
)
91113
)
92114

93-
# Create X-axis using rule marks at condition positions with text labels
94-
x_axis_data = pd.DataFrame({"condition": ["Control", "Treatment A", "Treatment B"], "x_pos": [0, 1, 2]})
115+
# Main chart layer with raincloud elements
116+
main_chart = (
117+
alt.layer(violin, boxplot, strip).properties(width=1600, height=850).interactive() # Enable zoom and pan
118+
)
119+
120+
# X-axis labels as a separate chart below the main chart
121+
x_axis_data = pd.DataFrame({"condition": condition_order, "x_pos": [0, 1, 2]})
95122

96123
x_axis_labels = (
97124
alt.Chart(x_axis_data)
98-
.mark_text(fontSize=20, fontWeight="bold", baseline="top", dy=15)
99-
.encode(x=alt.X("x_pos:Q", scale=alt.Scale(domain=[-0.6, 2.6])), y=alt.value(900), text="condition:N")
125+
.mark_text(fontSize=20, fontWeight="bold", baseline="middle")
126+
.encode(x=alt.X("x_pos:Q", scale=alt.Scale(domain=[-0.6, 2.6]), axis=None), text="condition:N")
127+
.properties(width=1600, height=50)
100128
)
101129

102-
# Combine all layers
130+
# Combine using vertical concatenation
103131
chart = (
104-
alt.layer(violin, boxplot, strip, x_axis_labels)
105-
.properties(
106-
width=1600, height=900, title=alt.Title("raincloud-basic · altair · pyplots.ai", fontSize=28, anchor="middle")
107-
)
132+
alt.vconcat(main_chart, x_axis_labels, spacing=5)
133+
.properties(title=alt.Title("raincloud-basic · altair · pyplots.ai", fontSize=28, anchor="middle"))
108134
.configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.3)
109135
.configure_view(strokeWidth=0)
110-
.configure_legend(padding=10) # Removed stroke border for cleaner look
111136
)
112137

113138
# Save outputs
Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
library: altair
22
specification_id: raincloud-basic
3-
created: '2025-12-25T07:15:03Z'
4-
updated: '2025-12-25T07:23:22Z'
3+
created: '2025-12-25T08:23:10Z'
4+
updated: '2025-12-25T08:25:15Z'
55
generated_by: claude-opus-4-5-20251101
6-
workflow_run: 20500985898
6+
workflow_run: 20501865366
77
issue: 0
88
python_version: 3.13.11
99
library_version: 6.0.0
1010
preview_url: https://storage.googleapis.com/pyplots-images/plots/raincloud-basic/altair/plot.png
1111
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/raincloud-basic/altair/plot_thumb.png
1212
preview_html: https://storage.googleapis.com/pyplots-images/plots/raincloud-basic/altair/plot.html
13-
quality_score: 91
13+
quality_score: 90
1414
review:
1515
strengths:
16-
- Excellent implementation of the raincloud metaphor with correct spatial arrangement
17-
(cloud on right, rain on left for vertical orientation)
18-
- Good use of Altair's layered approach combining violin, boxplot, and strip plot
19-
layers
20-
- Bimodal distribution in Treatment B effectively demonstrates the value of raincloud
21-
plots over simple boxplots
22-
- Clean, readable code structure with clear comments explaining each layer
23-
- Appropriate color scheme with good accessibility
24-
- Proper title format and axis labeling with units
16+
- Excellent raincloud visualization with all three components (half-violin, boxplot,
17+
jittered points) correctly positioned
18+
- 'Outstanding data quality: includes bimodal distribution (Treatment B) that demonstrates
19+
the key advantage of raincloud plots over simple boxplots'
20+
- Colorblind-safe color palette (blue, yellow, green)
21+
- Correct title format following the specification
22+
- Clean, reproducible code with proper random seed
23+
- Interactive features enabled for HTML output
24+
- Realistic reaction time data that tells a coherent scientific story
2525
weaknesses:
26-
- Legend lacks a background/border, making it slightly blend with the plot area
27-
- X-axis labels implemented via manual text marks rather than a proper axis could
28-
be fragile
29-
- No tooltips or interactivity despite Altair being an interactive library
30-
- Minor point overlap in dense regions could benefit from slightly smaller point
31-
sizes
26+
- Grid lines are quite subtle (alpha 0.3) making them barely visible
27+
- Jittered points could be slightly larger for better visibility at full resolution

0 commit comments

Comments
 (0)