Skip to content

Commit e595301

Browse files
update(raincloud-basic): altair — fix orientation consistency
Fix cloud/rain orientation: cloud (half-violin) extends upward, rain (jittered points) falls downward. Updated spec to clarify absolute y-direction terms.
1 parent a1bd610 commit e595301

3 files changed

Lines changed: 33 additions & 40 deletions

File tree

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
""" pyplots.ai
1+
"""pyplots.ai
22
raincloud-basic: Basic Raincloud Plot
3-
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 90/100 | Created: 2025-12-25
3+
Library: altair 6.0.0 | Python 3.14
4+
Quality: /100 | Updated: 2026-02-14
55
"""
66

77
import altair as alt
@@ -12,9 +12,8 @@
1212
# Data: Reaction times (ms) for different treatment conditions
1313
np.random.seed(42)
1414

15-
# Create realistic reaction time data with different distributions
1615
control = np.random.normal(450, 60, 80)
17-
treatment_a = np.random.normal(380, 50, 80) # Faster responses
16+
treatment_a = np.random.normal(380, 50, 80)
1817
treatment_b = np.concatenate(
1918
[
2019
np.random.normal(350, 30, 50), # Bimodal distribution
@@ -29,34 +28,34 @@
2928
}
3029
)
3130

32-
# Map conditions to numeric positions - HORIZONTAL: y=categories, x=values
31+
# Map conditions to numeric y positions (HORIZONTAL: categories on y-axis, values on x-axis)
3332
condition_order = ["Control", "Treatment A", "Treatment B"]
3433
condition_map = {c: i for i, c in enumerate(condition_order)}
3534
data["condition_num"] = data["condition"].map(condition_map)
3635

37-
# Create jittered y positions for strip plot (rain BELOW the cloud)
36+
# Jitter positions for rain BELOW the category baseline (negative y offset)
3837
np.random.seed(42)
39-
data["jitter"] = np.random.uniform(-0.35, -0.15, len(data))
38+
data["jitter"] = np.random.uniform(-0.35, -0.12, len(data))
4039
data["jitter_pos"] = data["condition_num"] + data["jitter"]
4140

42-
# Half-violin (cloud) - positioned on TOP (positive y offset from center)
41+
# Color palette
42+
colors = ["#306998", "#FFD43B", "#4CAF50"]
43+
44+
# Half-violin cloud — extends ABOVE baseline (positive y offset)
4345
violin = (
4446
alt.Chart(data)
4547
.transform_density(
4648
"reaction_time", as_=["reaction_time", "density"], groupby=["condition", "condition_num"], extent=[200, 600]
4749
)
48-
.transform_calculate(
49-
# Scale density and offset to create half-violin on TOP (positive y direction)
50-
violin_pos="datum.condition_num + 0.05 + datum.density * 180"
51-
)
50+
.transform_calculate(violin_pos="datum.condition_num + 0.05 + datum.density * 200")
5251
.mark_area(orient="vertical", opacity=0.7)
5352
.encode(
5453
x=alt.X("reaction_time:Q"),
5554
y=alt.Y("condition_num:Q", axis=None).scale(domain=[-0.6, 2.6]),
5655
y2="violin_pos:Q",
5756
color=alt.Color(
5857
"condition:N",
59-
scale=alt.Scale(domain=condition_order, range=["#306998", "#FFD43B", "#4CAF50"]),
58+
scale=alt.Scale(domain=condition_order, range=colors),
6059
legend=alt.Legend(
6160
title="Condition",
6261
titleFontSize=20,
@@ -75,7 +74,7 @@
7574
)
7675
)
7776

78-
# Box plot - HORIZONTAL orientation (values on x, categories on y)
77+
# Box plot — centered on category baseline
7978
boxplot = (
8079
alt.Chart(data)
8180
.transform_calculate(box_pos="datum.condition_num + 0.02")
@@ -84,40 +83,34 @@
8483
orient="horizontal",
8584
median={"color": "white", "strokeWidth": 3},
8685
box={"strokeWidth": 2},
87-
outliers={"opacity": 0}, # Hide outliers, shown as jittered points
86+
outliers={"opacity": 0},
8887
)
8988
.encode(
9089
x=alt.X("reaction_time:Q", title="Reaction Time (ms)", scale=alt.Scale(domain=[200, 600])),
9190
y=alt.Y("box_pos:Q", axis=None),
92-
color=alt.Color(
93-
"condition:N", scale=alt.Scale(domain=condition_order, range=["#306998", "#FFD43B", "#4CAF50"])
94-
),
91+
color=alt.Color("condition:N", scale=alt.Scale(domain=condition_order, range=colors)),
9592
)
9693
)
9794

98-
# Jittered strip plot (rain) - positioned clearly BELOW the center
95+
# Jittered strip rain BELOW baseline
9996
strip = (
10097
alt.Chart(data)
101-
.mark_circle(size=40, opacity=0.6)
98+
.mark_circle(size=55, opacity=0.6)
10299
.encode(
103100
x=alt.X("reaction_time:Q"),
104101
y=alt.Y("jitter_pos:Q", axis=None),
105-
color=alt.Color(
106-
"condition:N", scale=alt.Scale(domain=condition_order, range=["#306998", "#FFD43B", "#4CAF50"])
107-
),
102+
color=alt.Color("condition:N", scale=alt.Scale(domain=condition_order, range=colors)),
108103
tooltip=[
109104
alt.Tooltip("condition:N", title="Condition"),
110105
alt.Tooltip("reaction_time:Q", title="Reaction Time (ms)", format=".1f"),
111106
],
112107
)
113108
)
114109

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-
)
110+
# Compose layers
111+
main_chart = alt.layer(violin, boxplot, strip).properties(width=1600, height=850).interactive()
119112

120-
# Y-axis labels as a separate chart on the left
113+
# Y-axis labels
121114
y_axis_data = pd.DataFrame({"condition": condition_order, "y_pos": [0, 1, 2]})
122115

123116
y_axis_labels = (
@@ -127,14 +120,14 @@
127120
.properties(width=120, height=850)
128121
)
129122

130-
# Combine using horizontal concatenation
123+
# Final chart
131124
chart = (
132125
alt.hconcat(y_axis_labels, main_chart, spacing=5)
133126
.properties(title=alt.Title("raincloud-basic · altair · pyplots.ai", fontSize=28, anchor="middle"))
134-
.configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.3)
127+
.configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.4)
135128
.configure_view(strokeWidth=0)
136129
)
137130

138-
# Save outputs
131+
# Save
139132
chart.save("plot.png", scale_factor=3.0)
140133
chart.save("plot.html")

plots/raincloud-basic/metadata/altair.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
library: altair
22
specification_id: raincloud-basic
33
created: '2025-12-25T08:23:10Z'
4-
updated: '2025-12-25T08:25:15Z'
5-
generated_by: claude-opus-4-5-20251101
4+
updated: 2026-02-14T20:28:11+00:00
5+
generated_by: claude-opus-4-6
66
workflow_run: 20501865366
77
issue: 0
8-
python_version: 3.13.11
9-
library_version: 6.0.0
8+
python_version: "3.14"
9+
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: 90
13+
quality_score: null
1414
impl_tags:
1515
dependencies: []
1616
techniques:

plots/raincloud-basic/specification.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ A raincloud plot combines three visualization elements—a half-violin (the "clo
2020

2121
## Notes
2222

23-
- **Required Orientation**: Use HORIZONTAL orientation with categories on y-axis and values on x-axis
24-
- **Critical Layout**: The "cloud" (half-violin/KDE) must be on TOP (positive y offset), boxplot in MIDDLE, and "rain" (jittered points) BELOW (negative y offset) - like rain falling from a cloud
23+
- **Required Orientation**: Use HORIZONTAL orientation with categories on y-axis and values on x-axis. "Above" and "below" always refer to the y-direction (screen up/down), not the x-direction
24+
- **Critical Layout**: For each category on the y-axis: the "cloud" (half-violin/KDE) must extend ABOVE the category baseline (upward on screen), the boxplot sits centered ON the category baseline, and "rain" (jittered points) must appear BELOW the category baseline (downward on screen) - like rain falling from a cloud
2525
- Clip the violin to show only half (the "cloud" portion), not a full violin
2626
- Use moderate jitter (0.05-0.1) to spread rain points without excessive overlap
2727
- Apply transparency (alpha 0.5-0.7) to jittered rain points for visibility
2828
- Include median and quartile markers in box plot
29-
- The visual metaphor must be clear: cloud above, rain falling below
29+
- The visual metaphor must be clear: cloud rises upward (positive y-direction), rain falls downward (negative y-direction) from each category line

0 commit comments

Comments
 (0)