Skip to content

Commit d1d2371

Browse files
update(raincloud-basic): plotly — 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 d1d2371

3 files changed

Lines changed: 68 additions & 74 deletions

File tree

Lines changed: 60 additions & 66 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: plotly 6.5.0 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-25
3+
Library: plotly 6.5.2 | Python 3.14
4+
Quality: /100 | Updated: 2026-02-14
55
"""
66

77
import numpy as np
@@ -14,20 +14,15 @@
1414
conditions = ["Control", "Treatment A", "Treatment B", "Treatment C"]
1515
n_per_group = 80
1616

17-
# Generate realistic reaction time data with different distributions
1817
data = {
1918
"Control": np.random.normal(450, 60, n_per_group),
20-
"Treatment A": np.random.normal(380, 45, n_per_group), # Faster, less variable
19+
"Treatment A": np.random.normal(380, 45, n_per_group),
2120
"Treatment B": np.concatenate(
22-
[ # Bimodal distribution
23-
np.random.normal(350, 30, n_per_group // 2),
24-
np.random.normal(480, 35, n_per_group // 2),
25-
]
21+
[np.random.normal(350, 30, n_per_group // 2), np.random.normal(480, 35, n_per_group // 2)]
2622
),
27-
"Treatment C": np.random.normal(400, 80, n_per_group), # More variable
23+
"Treatment C": np.random.normal(400, 80, n_per_group),
2824
}
2925

30-
# Add some outliers to show box plot whiskers
3126
data["Control"] = np.append(data["Control"], [620, 650, 280])
3227
data["Treatment C"] = np.append(data["Treatment C"], [600, 620, 250])
3328

@@ -37,125 +32,124 @@
3732
fig = go.Figure()
3833

3934
# Positioning parameters
40-
box_width = 0.08
4135
violin_width = 0.4
4236

4337
for i, (condition, values) in enumerate(data.items()):
4438
color = colors[i]
45-
46-
# Calculate statistics for hover info
4739
median_val = np.median(values)
4840
q1 = np.percentile(values, 25)
4941
q3 = np.percentile(values, 75)
5042
mean_val = np.mean(values)
5143
std_val = np.std(values)
5244

53-
# Half-violin (cloud) - positioned on top (positive side) - HORIZONTAL orientation
45+
# Cloud (half-violin) - extends UPWARD (positive y-direction)
5446
fig.add_trace(
5547
go.Violin(
56-
x=values, # Values on x-axis
57-
y=[condition] * len(values), # Categories on y-axis
58-
side="positive", # Cloud on top
48+
x=values,
49+
y=[condition] * len(values),
50+
side="positive",
5951
width=violin_width,
6052
line_color=color,
6153
fillcolor=color,
6254
opacity=0.6,
6355
meanline_visible=False,
6456
box_visible=False,
6557
points=False,
66-
name=f"{condition} (Cloud)",
58+
name=condition,
6759
legendgroup=condition,
6860
showlegend=True,
6961
hoverinfo="x+name",
7062
hoveron="violins",
71-
orientation="h", # Horizontal orientation
63+
orientation="h",
7264
)
7365
)
7466

75-
# Box plot - in the middle with custom hover
67+
# Box plot - centered on category baseline
7668
fig.add_trace(
7769
go.Box(
78-
x=values, # Values on x-axis
79-
y=[condition] * len(values), # Categories on y-axis
80-
width=box_width,
70+
x=values,
71+
y=[condition] * len(values),
72+
width=0.08,
8173
marker_color=color,
8274
line_color="#333333",
8375
fillcolor="white",
8476
boxpoints=False,
85-
name=f"{condition} (Stats)",
77+
name=condition,
8678
legendgroup=condition,
8779
showlegend=False,
88-
orientation="h", # Horizontal orientation
80+
orientation="h",
8981
hovertemplate=(
9082
f"<b>{condition}</b><br>"
9183
f"Median: {median_val:.0f} ms<br>"
9284
f"Q1-Q3: {q1:.0f}-{q3:.0f} ms<br>"
93-
f"Mean: {mean_val:.1f} ± {std_val:.1f} ms"
85+
f"Mean: {mean_val:.1f} \u00b1 {std_val:.1f} ms"
9486
"<extra></extra>"
9587
),
9688
)
9789
)
9890

99-
# Jittered strip points (rain) - on the bottom (negative side)
91+
# Rain (jittered points) - falls DOWNWARD (negative y-direction)
10092
fig.add_trace(
10193
go.Violin(
102-
x=values, # Values on x-axis
103-
y=[condition] * len(values), # Categories on y-axis
104-
side="negative", # Rain below
94+
x=values,
95+
y=[condition] * len(values),
96+
side="negative",
10597
width=0,
10698
points="all",
10799
pointpos=-0.4,
108100
jitter=0.15,
109-
marker=dict(size=8, color=color, opacity=0.6, line=dict(width=0.5, color="#333333")),
101+
marker={"size": 8, "color": color, "opacity": 0.6, "line": {"width": 0.5, "color": "#333333"}},
110102
line_width=0,
111103
fillcolor="rgba(0,0,0,0)",
112-
name=f"{condition} (Rain)",
104+
name=condition,
113105
legendgroup=condition,
114106
showlegend=False,
115-
orientation="h", # Horizontal orientation
107+
orientation="h",
116108
hovertemplate=f"<b>{condition}</b><br>Value: %{{x:.0f}} ms<extra></extra>",
117109
)
118110
)
119111

120-
# Update layout for 4800x2700 output - HORIZONTAL orientation
112+
# Layout - HORIZONTAL orientation with categories on y-axis, values on x-axis
121113
fig.update_layout(
122-
title=dict(
123-
text="raincloud-basic · plotly · pyplots.ai", font=dict(size=32, color="#333333"), x=0.5, xanchor="center"
124-
),
125-
yaxis=dict( # Categories on y-axis
126-
title=dict(text="Experimental Condition", font=dict(size=24)),
127-
tickfont=dict(size=20),
128-
categoryorder="array",
129-
categoryarray=conditions,
130-
showgrid=False,
131-
),
132-
xaxis=dict( # Values on x-axis
133-
title=dict(text="Reaction Time (ms)", font=dict(size=24)),
134-
tickfont=dict(size=20),
135-
gridcolor="rgba(0,0,0,0.1)",
136-
gridwidth=1,
137-
range=[200, 700],
138-
),
114+
title={
115+
"text": "raincloud-basic \u00b7 plotly \u00b7 pyplots.ai",
116+
"font": {"size": 32, "color": "#333333"},
117+
"x": 0.5,
118+
"xanchor": "center",
119+
},
120+
yaxis={
121+
"title": {"text": "Experimental Condition", "font": {"size": 24}},
122+
"tickfont": {"size": 20},
123+
"categoryorder": "array",
124+
"categoryarray": conditions,
125+
"showgrid": False,
126+
},
127+
xaxis={
128+
"title": {"text": "Reaction Time (ms)", "font": {"size": 24}},
129+
"tickfont": {"size": 20},
130+
"gridcolor": "rgba(0,0,0,0.1)",
131+
"gridwidth": 1,
132+
"range": [200, 700],
133+
},
139134
template="plotly_white",
140135
plot_bgcolor="white",
141136
paper_bgcolor="white",
142-
margin=dict(l=180, r=180, t=100, b=100),
137+
margin={"l": 180, "r": 80, "t": 100, "b": 100},
143138
violingap=0,
144139
violinmode="overlay",
145-
legend=dict(
146-
title=dict(text="Components", font=dict(size=18)),
147-
font=dict(size=16),
148-
bgcolor="rgba(255,255,255,0.9)",
149-
bordercolor="rgba(0,0,0,0.2)",
150-
borderwidth=1,
151-
x=1.02,
152-
y=0.98,
153-
xanchor="left",
154-
yanchor="top",
155-
),
156-
hoverlabel=dict(bgcolor="white", bordercolor="#333333", font=dict(size=16, color="#333333")),
140+
legend={
141+
"title": {"text": "Condition", "font": {"size": 18}},
142+
"font": {"size": 16},
143+
"bgcolor": "rgba(255,255,255,0.9)",
144+
"bordercolor": "rgba(0,0,0,0.2)",
145+
"borderwidth": 1,
146+
},
147+
hoverlabel={"bgcolor": "white", "bordercolor": "#333333", "font": {"size": 16, "color": "#333333"}},
157148
)
158149

159-
# Save as PNG (4800x2700) and HTML
150+
# Save
160151
fig.write_image("plot.png", width=1600, height=900, scale=3)
152+
153+
# Add range slider for interactive HTML exploration
154+
fig.update_xaxes(rangeslider={"visible": True, "thickness": 0.05})
161155
fig.write_html("plot.html", include_plotlyjs="cdn")

plots/raincloud-basic/metadata/plotly.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
library: plotly
22
specification_id: raincloud-basic
33
created: '2025-12-25T08:25:05Z'
4-
updated: '2025-12-25T08:27:20Z'
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: 20501864541
77
issue: 0
8-
python_version: 3.13.11
9-
library_version: 6.5.0
8+
python_version: "3.14"
9+
library_version: "6.5.2"
1010
preview_url: https://storage.googleapis.com/pyplots-images/plots/raincloud-basic/plotly/plot.png
1111
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/raincloud-basic/plotly/plot_thumb.png
1212
preview_html: https://storage.googleapis.com/pyplots-images/plots/raincloud-basic/plotly/plot.html
13-
quality_score: 91
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)