|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | facet-grid: Faceted Grid Plot |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 90/100 | Created: 2025-12-30 |
| 3 | +Library: highcharts unknown | Python 3.13.13 |
| 4 | +Quality: 86/100 | Updated: 2026-05-13 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
7 | 8 | import tempfile |
8 | 9 | import time |
9 | 10 | import urllib.request |
|
16 | 17 | from selenium.webdriver.chrome.options import Options |
17 | 18 |
|
18 | 19 |
|
| 20 | +# Theme tokens |
| 21 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 22 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 23 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 24 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 25 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 26 | +GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" |
| 27 | + |
| 28 | +# Okabe-Ito palette for soil types |
| 29 | +OKABE_ITO = ["#009E73", "#D55E00", "#0072B2"] |
| 30 | + |
19 | 31 | # Data |
20 | 32 | np.random.seed(42) |
21 | 33 |
|
|
45 | 57 | # Calculate subplot dimensions with margins |
46 | 58 | chart_width = 4800 |
47 | 59 | chart_height = 2700 |
48 | | -margin_top = 200 |
49 | | -margin_bottom = 140 |
50 | | -margin_left = 200 |
51 | | -margin_right = 120 |
52 | | -spacing = 80 |
| 60 | +margin_top = 240 |
| 61 | +margin_bottom = 180 |
| 62 | +margin_left = 240 |
| 63 | +margin_right = 200 |
| 64 | +spacing = 100 |
53 | 65 |
|
54 | 66 | plot_area_width = chart_width - margin_left - margin_right |
55 | 67 | plot_area_height = chart_height - margin_top - margin_bottom |
56 | 68 | subplot_width = (plot_area_width - spacing * (n_cols - 1)) / n_cols |
57 | 69 | subplot_height = (plot_area_height - spacing * (n_rows - 1)) / n_rows |
58 | 70 |
|
59 | | -# Colors for facets - colorblind-safe palette |
60 | | -colors = ["#306998", "#FFD43B", "#9467BD"] |
61 | | - |
62 | 71 | # Build series data for each facet |
63 | 72 | series_list = [] |
64 | 73 | x_axes = [] |
|
85 | 94 | "height": 0, |
86 | 95 | "min": 15, |
87 | 96 | "max": 105, |
88 | | - "lineWidth": 2, |
89 | | - "lineColor": "#333333", |
90 | | - "tickWidth": 2, |
91 | | - "tickLength": 10, |
92 | | - "labels": {"style": {"fontSize": "20px", "color": "#333333"}, "y": 30}, |
| 97 | + "lineWidth": 3, |
| 98 | + "lineColor": INK_SOFT, |
| 99 | + "tickWidth": 3, |
| 100 | + "tickLength": 12, |
| 101 | + "labels": {"style": {"fontSize": "22px", "color": INK_SOFT}, "y": 40}, |
93 | 102 | "title": { |
94 | 103 | "text": "Water (mm)" if row_idx == n_rows - 1 else None, |
95 | | - "style": {"fontSize": "24px", "color": "#333333"}, |
96 | | - "y": 55, |
| 104 | + "style": {"fontSize": "26px", "color": INK}, |
| 105 | + "y": 70, |
97 | 106 | }, |
98 | | - "gridLineWidth": 1, |
99 | | - "gridLineColor": "rgba(0,0,0,0.15)", |
100 | | - "gridLineDashStyle": "Dash", |
| 107 | + "gridLineWidth": 2, |
| 108 | + "gridLineColor": GRID, |
101 | 109 | "offset": 0, |
102 | 110 | } |
103 | 111 | ) |
104 | 112 |
|
105 | | - # Create y-axis for this subplot |
| 113 | + # Create y-axis for this subplot - fix for consistent label display |
106 | 114 | y_axis_id = f"y{row_idx * n_cols + col_idx}" |
107 | 115 | y_axes.append( |
108 | 116 | { |
|
113 | 121 | "height": subplot_height, |
114 | 122 | "min": 0, |
115 | 123 | "max": 50, |
116 | | - "lineWidth": 2, |
117 | | - "lineColor": "#333333", |
118 | | - "tickWidth": 2, |
119 | | - "tickLength": 10, |
120 | | - "labels": {"style": {"fontSize": "20px", "color": "#333333"}, "x": -15}, |
| 124 | + "lineWidth": 3, |
| 125 | + "lineColor": INK_SOFT, |
| 126 | + "tickWidth": 3, |
| 127 | + "tickLength": 12, |
| 128 | + "labels": {"style": {"fontSize": "22px", "color": INK_SOFT}, "x": -20}, |
121 | 129 | "title": { |
122 | 130 | "text": "Growth (cm)" if col_idx == 0 else None, |
123 | | - "style": {"fontSize": "24px", "color": "#333333"}, |
| 131 | + "style": {"fontSize": "26px", "color": INK}, |
124 | 132 | "rotation": 270, |
125 | | - "x": -50, |
| 133 | + "x": -60, |
126 | 134 | }, |
127 | | - "gridLineWidth": 1, |
128 | | - "gridLineColor": "rgba(0,0,0,0.15)", |
129 | | - "gridLineDashStyle": "Dash", |
| 135 | + "gridLineWidth": 2, |
| 136 | + "gridLineColor": GRID, |
130 | 137 | "offset": 0, |
131 | 138 | } |
132 | 139 | ) |
|
140 | 147 | "xAxis": row_idx * n_cols + col_idx, |
141 | 148 | "yAxis": row_idx * n_cols + col_idx, |
142 | 149 | "marker": { |
143 | | - "radius": 10, |
| 150 | + "radius": 12, |
144 | 151 | "symbol": "circle", |
145 | | - "fillColor": colors[row_idx % len(colors)], |
| 152 | + "fillColor": OKABE_ITO[row_idx % len(OKABE_ITO)], |
146 | 153 | "lineWidth": 2, |
147 | | - "lineColor": "#333333", |
| 154 | + "lineColor": PAGE_BG, |
148 | 155 | }, |
149 | 156 | "showInLegend": False, |
150 | 157 | } |
|
160 | 167 | { |
161 | 168 | "labels": [ |
162 | 169 | { |
163 | | - "point": {"x": left, "y": margin_top - 60, "xAxis": None, "yAxis": None}, |
| 170 | + "point": {"x": left, "y": margin_top - 100, "xAxis": None, "yAxis": None}, |
164 | 171 | "text": f"Light: {light}", |
165 | 172 | "backgroundColor": "transparent", |
166 | 173 | "borderWidth": 0, |
167 | | - "style": {"fontSize": "28px", "fontWeight": "bold", "color": "#333333"}, |
| 174 | + "style": {"fontSize": "30px", "fontWeight": "bold", "color": INK}, |
168 | 175 | } |
169 | 176 | ], |
170 | 177 | "labelOptions": {"useHTML": True}, |
|
178 | 185 | { |
179 | 186 | "labels": [ |
180 | 187 | { |
181 | | - "point": {"x": chart_width - margin_right + 50, "y": top, "xAxis": None, "yAxis": None}, |
| 188 | + "point": {"x": chart_width - margin_right + 100, "y": top, "xAxis": None, "yAxis": None}, |
182 | 189 | "text": f"Soil: {soil}", |
183 | 190 | "backgroundColor": "transparent", |
184 | 191 | "borderWidth": 0, |
185 | | - "style": {"fontSize": "26px", "fontWeight": "bold", "color": "#333333"}, |
| 192 | + "style": {"fontSize": "28px", "fontWeight": "bold", "color": INK}, |
186 | 193 | "rotation": 90, |
187 | 194 | } |
188 | 195 | ], |
|
199 | 206 | "type": "scatter", |
200 | 207 | "width": chart_width, |
201 | 208 | "height": chart_height, |
202 | | - "backgroundColor": "#ffffff", |
203 | | - "style": {"fontFamily": "Arial, sans-serif"}, |
| 209 | + "backgroundColor": PAGE_BG, |
| 210 | + "style": {"fontFamily": "Arial, sans-serif", "color": INK}, |
204 | 211 | "marginTop": margin_top, |
205 | 212 | "marginBottom": margin_bottom, |
206 | 213 | "marginLeft": margin_left, |
207 | 214 | "marginRight": margin_right, |
208 | 215 | } |
209 | 216 |
|
210 | 217 | chart.options.title = { |
211 | | - "text": "Plant Growth Study · facet-grid · highcharts · pyplots.ai", |
212 | | - "style": {"fontSize": "36px", "fontWeight": "bold", "color": "#333333"}, |
213 | | - "y": 55, |
| 218 | + "text": "facet-grid · highcharts · anyplot.ai", |
| 219 | + "style": {"fontSize": "34px", "fontWeight": "bold", "color": INK}, |
| 220 | + "y": 60, |
214 | 221 | } |
215 | 222 |
|
216 | 223 | chart.options.subtitle = { |
217 | | - "text": "Growth by Soil Type (rows) and Light Condition (columns)", |
218 | | - "style": {"fontSize": "26px", "color": "#666666"}, |
219 | | - "y": 110, |
| 224 | + "text": "Plant Growth by Soil Type (rows) and Light Condition (columns)", |
| 225 | + "style": {"fontSize": "24px", "color": INK_SOFT}, |
| 226 | + "y": 120, |
220 | 227 | } |
221 | 228 |
|
222 | 229 | chart.options.credits = {"enabled": False} |
|
228 | 235 |
|
229 | 236 | chart.options.plot_options = { |
230 | 237 | "scatter": { |
231 | | - "marker": {"radius": 10, "states": {"hover": {"enabled": True, "lineColor": "#333333"}}}, |
| 238 | + "marker": {"radius": 12, "states": {"hover": {"enabled": True}}}, |
232 | 239 | "states": {"inactive": {"opacity": 1}}, |
233 | 240 | } |
234 | 241 | } |
|
242 | 249 | # Generate JavaScript |
243 | 250 | html_str = chart.to_js_literal() |
244 | 251 |
|
245 | | -# Download Highcharts JS |
246 | | -highcharts_url = "https://code.highcharts.com/highcharts.js" |
247 | | -with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
248 | | - highcharts_js = response.read().decode("utf-8") |
249 | | - |
250 | | -# Download annotations module |
251 | | -annotations_url = "https://code.highcharts.com/modules/annotations.js" |
252 | | -with urllib.request.urlopen(annotations_url, timeout=30) as response: |
253 | | - annotations_js = response.read().decode("utf-8") |
| 252 | +# Download Highcharts JS with fallback |
| 253 | +highcharts_js = None |
| 254 | +for url in ["https://code.highcharts.com/highcharts.js", "https://cdn.jsdelivr.net/npm/highcharts@11/highcharts.js"]: |
| 255 | + try: |
| 256 | + with urllib.request.urlopen(url, timeout=30) as response: |
| 257 | + highcharts_js = response.read().decode("utf-8") |
| 258 | + break |
| 259 | + except Exception: |
| 260 | + continue |
| 261 | + |
| 262 | +if highcharts_js is None: |
| 263 | + local_paths = [ |
| 264 | + Path(__file__).resolve().parent.parent.parent.parent / "node_modules" / "highcharts" / "highcharts.js", |
| 265 | + Path("node_modules/highcharts/highcharts.js"), |
| 266 | + ] |
| 267 | + for p in local_paths: |
| 268 | + if p.exists(): |
| 269 | + highcharts_js = p.read_text(encoding="utf-8") |
| 270 | + break |
| 271 | + |
| 272 | +if highcharts_js is None: |
| 273 | + raise RuntimeError("Failed to download Highcharts JS from CDN or find local copy") |
| 274 | + |
| 275 | +# Download annotations module with fallback |
| 276 | +annotations_js = None |
| 277 | +for url in [ |
| 278 | + "https://code.highcharts.com/modules/annotations.js", |
| 279 | + "https://cdn.jsdelivr.net/npm/highcharts@11/modules/annotations.js", |
| 280 | +]: |
| 281 | + try: |
| 282 | + with urllib.request.urlopen(url, timeout=30) as response: |
| 283 | + annotations_js = response.read().decode("utf-8") |
| 284 | + break |
| 285 | + except Exception: |
| 286 | + continue |
| 287 | + |
| 288 | +if annotations_js is None: |
| 289 | + local_paths = [ |
| 290 | + Path(__file__).resolve().parent.parent.parent.parent |
| 291 | + / "node_modules" |
| 292 | + / "highcharts" |
| 293 | + / "modules" |
| 294 | + / "annotations.js", |
| 295 | + Path("node_modules/highcharts/modules/annotations.js"), |
| 296 | + ] |
| 297 | + for p in local_paths: |
| 298 | + if p.exists(): |
| 299 | + annotations_js = p.read_text(encoding="utf-8") |
| 300 | + break |
| 301 | + |
| 302 | +if annotations_js is None: |
| 303 | + raise RuntimeError("Failed to download annotations module from CDN or find local copy") |
254 | 304 |
|
255 | 305 | # Generate HTML with inline scripts |
256 | 306 | html_content = f"""<!DOCTYPE html> |
|
260 | 310 | <script>{highcharts_js}</script> |
261 | 311 | <script>{annotations_js}</script> |
262 | 312 | </head> |
263 | | -<body style="margin:0; padding:0;"> |
| 313 | +<body style="margin:0; padding:0; background:{PAGE_BG};"> |
264 | 314 | <div id="container" style="width: {chart_width}px; height: {chart_height}px;"></div> |
265 | 315 | <script>{html_str}</script> |
266 | 316 | </body> |
267 | 317 | </html>""" |
268 | 318 |
|
269 | | -# Save HTML |
270 | | -with open("plot.html", "w", encoding="utf-8") as f: |
| 319 | +# Save HTML with theme-suffixed filename |
| 320 | +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: |
271 | 321 | f.write(html_content) |
272 | 322 |
|
273 | 323 | # Create screenshot with Selenium |
|
280 | 330 | chrome_options.add_argument("--no-sandbox") |
281 | 331 | chrome_options.add_argument("--disable-dev-shm-usage") |
282 | 332 | chrome_options.add_argument("--disable-gpu") |
283 | | -chrome_options.add_argument("--window-size=4900,2800") |
| 333 | +chrome_options.add_argument("--window-size=4800,2700") |
284 | 334 |
|
285 | 335 | driver = webdriver.Chrome(options=chrome_options) |
286 | | -driver.set_window_size(4900, 2800) |
287 | 336 | driver.get(f"file://{temp_path}") |
288 | 337 | time.sleep(5) |
289 | | -driver.save_screenshot("plot.png") |
| 338 | +driver.save_screenshot(f"plot-{THEME}.png") |
290 | 339 | driver.quit() |
291 | 340 |
|
292 | 341 | Path(temp_path).unlink() |
0 commit comments