|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
|
14 | 14 | conditions = ["Control", "Treatment A", "Treatment B", "Treatment C"] |
15 | 15 | n_per_group = 80 |
16 | 16 |
|
17 | | -# Generate realistic reaction time data with different distributions |
18 | 17 | data = { |
19 | 18 | "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), |
21 | 20 | "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)] |
26 | 22 | ), |
27 | | - "Treatment C": np.random.normal(400, 80, n_per_group), # More variable |
| 23 | + "Treatment C": np.random.normal(400, 80, n_per_group), |
28 | 24 | } |
29 | 25 |
|
30 | | -# Add some outliers to show box plot whiskers |
31 | 26 | data["Control"] = np.append(data["Control"], [620, 650, 280]) |
32 | 27 | data["Treatment C"] = np.append(data["Treatment C"], [600, 620, 250]) |
33 | 28 |
|
|
37 | 32 | fig = go.Figure() |
38 | 33 |
|
39 | 34 | # Positioning parameters |
40 | | -box_width = 0.08 |
41 | 35 | violin_width = 0.4 |
42 | 36 |
|
43 | 37 | for i, (condition, values) in enumerate(data.items()): |
44 | 38 | color = colors[i] |
45 | | - |
46 | | - # Calculate statistics for hover info |
47 | 39 | median_val = np.median(values) |
48 | 40 | q1 = np.percentile(values, 25) |
49 | 41 | q3 = np.percentile(values, 75) |
50 | 42 | mean_val = np.mean(values) |
51 | 43 | std_val = np.std(values) |
52 | 44 |
|
53 | | - # Half-violin (cloud) - positioned on top (positive side) - HORIZONTAL orientation |
| 45 | + # Cloud (half-violin) - extends UPWARD (positive y-direction) |
54 | 46 | fig.add_trace( |
55 | 47 | 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", |
59 | 51 | width=violin_width, |
60 | 52 | line_color=color, |
61 | 53 | fillcolor=color, |
62 | 54 | opacity=0.6, |
63 | 55 | meanline_visible=False, |
64 | 56 | box_visible=False, |
65 | 57 | points=False, |
66 | | - name=f"{condition} (Cloud)", |
| 58 | + name=condition, |
67 | 59 | legendgroup=condition, |
68 | 60 | showlegend=True, |
69 | 61 | hoverinfo="x+name", |
70 | 62 | hoveron="violins", |
71 | | - orientation="h", # Horizontal orientation |
| 63 | + orientation="h", |
72 | 64 | ) |
73 | 65 | ) |
74 | 66 |
|
75 | | - # Box plot - in the middle with custom hover |
| 67 | + # Box plot - centered on category baseline |
76 | 68 | fig.add_trace( |
77 | 69 | 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, |
81 | 73 | marker_color=color, |
82 | 74 | line_color="#333333", |
83 | 75 | fillcolor="white", |
84 | 76 | boxpoints=False, |
85 | | - name=f"{condition} (Stats)", |
| 77 | + name=condition, |
86 | 78 | legendgroup=condition, |
87 | 79 | showlegend=False, |
88 | | - orientation="h", # Horizontal orientation |
| 80 | + orientation="h", |
89 | 81 | hovertemplate=( |
90 | 82 | f"<b>{condition}</b><br>" |
91 | 83 | f"Median: {median_val:.0f} ms<br>" |
92 | 84 | 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" |
94 | 86 | "<extra></extra>" |
95 | 87 | ), |
96 | 88 | ) |
97 | 89 | ) |
98 | 90 |
|
99 | | - # Jittered strip points (rain) - on the bottom (negative side) |
| 91 | + # Rain (jittered points) - falls DOWNWARD (negative y-direction) |
100 | 92 | fig.add_trace( |
101 | 93 | 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", |
105 | 97 | width=0, |
106 | 98 | points="all", |
107 | 99 | pointpos=-0.4, |
108 | 100 | 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"}}, |
110 | 102 | line_width=0, |
111 | 103 | fillcolor="rgba(0,0,0,0)", |
112 | | - name=f"{condition} (Rain)", |
| 104 | + name=condition, |
113 | 105 | legendgroup=condition, |
114 | 106 | showlegend=False, |
115 | | - orientation="h", # Horizontal orientation |
| 107 | + orientation="h", |
116 | 108 | hovertemplate=f"<b>{condition}</b><br>Value: %{{x:.0f}} ms<extra></extra>", |
117 | 109 | ) |
118 | 110 | ) |
119 | 111 |
|
120 | | -# Update layout for 4800x2700 output - HORIZONTAL orientation |
| 112 | +# Layout - HORIZONTAL orientation with categories on y-axis, values on x-axis |
121 | 113 | 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 | + }, |
139 | 134 | template="plotly_white", |
140 | 135 | plot_bgcolor="white", |
141 | 136 | paper_bgcolor="white", |
142 | | - margin=dict(l=180, r=180, t=100, b=100), |
| 137 | + margin={"l": 180, "r": 80, "t": 100, "b": 100}, |
143 | 138 | violingap=0, |
144 | 139 | 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"}}, |
157 | 148 | ) |
158 | 149 |
|
159 | | -# Save as PNG (4800x2700) and HTML |
| 150 | +# Save |
160 | 151 | 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}) |
161 | 155 | fig.write_html("plot.html", include_plotlyjs="cdn") |
0 commit comments