|
1 | 1 | """ pyplots.ai |
2 | 2 | raincloud-basic: Basic Raincloud Plot |
3 | 3 | 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 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import altair as alt |
|
30 | 30 | ) |
31 | 31 |
|
32 | 32 | # 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) |
34 | 34 | 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)) |
36 | 36 |
|
37 | 37 | # 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)} |
39 | 40 | data["condition_num"] = data["condition"].map(condition_map) |
40 | 41 | data["jitter_pos"] = data["condition_num"] + data["jitter"] |
41 | 42 |
|
|
56 | 57 | x2="violin_pos:Q", |
57 | 58 | color=alt.Color( |
58 | 59 | "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 | + ), |
61 | 71 | ), |
| 72 | + tooltip=[ |
| 73 | + alt.Tooltip("condition:N", title="Condition"), |
| 74 | + alt.Tooltip("reaction_time:Q", title="Reaction Time (ms)", format=".0f"), |
| 75 | + ], |
62 | 76 | ) |
63 | 77 | ) |
64 | 78 |
|
|
75 | 89 | .encode( |
76 | 90 | x=alt.X("box_pos:Q", axis=None), |
77 | 91 | 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 | + ), |
79 | 95 | ) |
80 | 96 | ) |
81 | 97 |
|
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) |
83 | 99 | strip = ( |
84 | 100 | 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 |
86 | 102 | .encode( |
87 | 103 | x=alt.X("jitter_pos:Q", axis=None), |
88 | 104 | 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 | + ], |
90 | 112 | ) |
91 | 113 | ) |
92 | 114 |
|
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]}) |
95 | 122 |
|
96 | 123 | x_axis_labels = ( |
97 | 124 | 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) |
100 | 128 | ) |
101 | 129 |
|
102 | | -# Combine all layers |
| 130 | +# Combine using vertical concatenation |
103 | 131 | 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")) |
108 | 134 | .configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.3) |
109 | 135 | .configure_view(strokeWidth=0) |
110 | | - .configure_legend(padding=10) # Removed stroke border for cleaner look |
111 | 136 | ) |
112 | 137 |
|
113 | 138 | # Save outputs |
|
0 commit comments