|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | raincloud-basic: Basic Raincloud Plot |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-25 |
| 3 | +Library: highcharts 1.10.3 | Python 3.14 |
| 4 | +Quality: /100 | Updated: 2026-02-14 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import tempfile |
|
61 | 61 | chart.options = HighchartsOptions() |
62 | 62 |
|
63 | 63 | # Chart configuration - HORIZONTAL orientation |
| 64 | +# inverted=True swaps axes: x-axis (categories) on left, y-axis (values) on bottom |
| 65 | +# In inverted mode, x-axis increases DOWNWARD, so: |
| 66 | +# - negative x offset = UPWARD on screen (for clouds) |
| 67 | +# - positive x offset = DOWNWARD on screen (for rain) |
64 | 68 | chart.options.chart = { |
65 | 69 | "type": "boxplot", |
66 | | - "inverted": True, # Swap axes for horizontal orientation |
| 70 | + "inverted": True, |
67 | 71 | "width": 4800, |
68 | 72 | "height": 2700, |
69 | 73 | "backgroundColor": "#ffffff", |
|
75 | 79 |
|
76 | 80 | # Title |
77 | 81 | chart.options.title = { |
78 | | - "text": "raincloud-basic · highcharts · pyplots.ai", |
| 82 | + "text": "raincloud-basic \u00b7 highcharts \u00b7 pyplots.ai", |
79 | 83 | "style": {"fontSize": "56px", "fontWeight": "bold"}, |
80 | 84 | } |
81 | 85 |
|
82 | | -# X-axis (categories - but shown on left due to inverted) |
| 86 | +# X-axis (categories - shown on left due to inverted) |
83 | 87 | chart.options.x_axis = { |
84 | 88 | "title": {"text": "Experimental Condition", "style": {"fontSize": "44px"}}, |
85 | 89 | "labels": {"style": {"fontSize": "36px"}}, |
|
93 | 97 | "title": {"text": "Reaction Time (ms)", "style": {"fontSize": "44px"}}, |
94 | 98 | "labels": {"style": {"fontSize": "36px"}}, |
95 | 99 | "gridLineWidth": 1, |
96 | | - "gridLineColor": "rgba(0, 0, 0, 0.15)", |
97 | | - "gridLineDashStyle": "Dash", |
| 100 | + "gridLineColor": "rgba(0, 0, 0, 0.08)", |
| 101 | + "gridLineDashStyle": "Dot", |
98 | 102 | "tickInterval": 50, |
99 | 103 | "min": 250, |
100 | 104 | "max": 650, |
101 | 105 | } |
102 | 106 |
|
103 | | -# Legend |
| 107 | +# Legend - consolidated to show only condition names |
104 | 108 | chart.options.legend = { |
105 | 109 | "enabled": True, |
106 | 110 | "itemStyle": {"fontSize": "36px"}, |
|
135 | 139 | "polygon": {"fillOpacity": 0.6, "lineWidth": 2}, |
136 | 140 | } |
137 | 141 |
|
138 | | -# Create polygon data for half-violin (the "cloud") on TOP |
139 | | -# With inverted=True, "positive" x offset becomes TOP visually |
| 142 | +# Create polygon data for half-violin (the "cloud") ABOVE category baseline |
| 143 | +# In inverted chart, x-axis goes downward, so NEGATIVE x offset = UPWARD on screen |
140 | 144 | for i, data in enumerate(all_data): |
141 | 145 | # Inline KDE computation (Gaussian kernel) |
142 | 146 | data_arr = np.array(data) |
|
151 | 155 | density = density / (n * bandwidth * np.sqrt(2 * np.pi)) |
152 | 156 | density = density / density.max() * 0.35 |
153 | 157 |
|
154 | | - # Create polygon points for filled half-violin on TOP |
| 158 | + # Cloud extends UPWARD (negative x offset in inverted chart) |
155 | 159 | polygon_points = [] |
156 | | - # With inverted chart: x=category position, y=value |
157 | | - # Cloud extends in positive x direction from category center |
158 | 160 | for y, d in zip(y_range, density, strict=True): |
159 | | - polygon_points.append([float(i + d + 0.05), float(y)]) |
160 | | - # Close polygon by going back along the baseline |
| 161 | + polygon_points.append([float(i - d - 0.05), float(y)]) |
| 162 | + # Close polygon along baseline |
161 | 163 | for y in reversed(y_range): |
162 | | - polygon_points.append([float(i + 0.05), float(y)]) |
| 164 | + polygon_points.append([float(i - 0.05), float(y)]) |
163 | 165 |
|
164 | 166 | series = PolygonSeries() |
165 | 167 | series.data = polygon_points |
166 | | - series.name = f"{categories[i]} (Cloud)" |
| 168 | + series.name = categories[i] |
167 | 169 | series.color = colors[i] |
168 | 170 | series.fill_color = colors[i] |
169 | 171 | series.fill_opacity = 0.6 |
|
191 | 193 | box_series.name = "Box Plot" |
192 | 194 | box_series.color = "#1a1a1a" |
193 | 195 | box_series.color_by_point = True |
| 196 | +box_series.show_in_legend = False |
194 | 197 | chart.add_series(box_series) |
195 | 198 |
|
196 | | -# Create jittered scatter data (the "rain") BELOW |
| 199 | +# Create jittered scatter data (the "rain") BELOW category baseline |
| 200 | +# In inverted chart, POSITIVE x offset = DOWNWARD on screen |
197 | 201 | for i, data in enumerate(all_data): |
198 | 202 | scatter_points = [] |
199 | 203 | for val in data: |
200 | 204 | jitter = np.random.uniform(-0.08, 0.08) |
201 | | - # Rain on negative side (BELOW with inverted chart) |
202 | | - scatter_points.append([float(i - 0.25 + jitter), float(val)]) |
| 205 | + # Rain falls downward (positive x offset in inverted chart) |
| 206 | + scatter_points.append([float(i + 0.25 + jitter), float(val)]) |
203 | 207 |
|
204 | 208 | scatter_series = ScatterSeries() |
205 | 209 | scatter_series.data = scatter_points |
206 | | - scatter_series.name = f"{categories[i]} (Points)" |
| 210 | + scatter_series.name = categories[i] |
207 | 211 | scatter_series.color = colors[i] |
208 | 212 | scatter_series.opacity = 0.65 |
209 | 213 | scatter_series.marker = {"radius": 16, "lineWidth": 2, "lineColor": "rgba(0,0,0,0.4)", "fillColor": colors[i]} |
| 214 | + scatter_series.show_in_legend = False |
210 | 215 | chart.add_series(scatter_series) |
211 | 216 |
|
212 | 217 | # Download Highcharts JS and required modules |
|
238 | 243 | f.write(html_content) |
239 | 244 | temp_path = f.name |
240 | 245 |
|
241 | | -# Save HTML for interactive viewing |
242 | | -with open("plot.html", "w", encoding="utf-8") as f: |
243 | | - standalone_html = f"""<!DOCTYPE html> |
244 | | -<html> |
245 | | -<head> |
246 | | - <meta charset="utf-8"> |
247 | | - <script src="https://code.highcharts.com/highcharts.js"></script> |
248 | | - <script src="https://code.highcharts.com/highcharts-more.js"></script> |
249 | | -</head> |
250 | | -<body style="margin:0;"> |
251 | | - <div id="container" style="width: 100%; height: 100vh;"></div> |
252 | | - <script>{html_str}</script> |
253 | | -</body> |
254 | | -</html>""" |
255 | | - f.write(standalone_html) |
256 | | - |
257 | 246 | # Setup Chrome for screenshot |
258 | 247 | chrome_options = Options() |
259 | 248 | chrome_options.add_argument("--headless") |
|
0 commit comments