|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | sudoku-basic: Basic Sudoku Grid |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-23 |
| 3 | +Library: highcharts unknown | Python 3.14.4 |
| 4 | +Quality: 89/100 | Updated: 2026-04-24 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
7 | 8 | import tempfile |
8 | 9 | import time |
9 | 10 | import urllib.request |
|
12 | 13 | from highcharts_core.chart import Chart |
13 | 14 | from highcharts_core.options import HighchartsOptions |
14 | 15 | from highcharts_core.options.series.scatter import ScatterSeries |
15 | | -from PIL import Image |
16 | 16 | from selenium import webdriver |
17 | 17 | from selenium.webdriver.chrome.options import Options |
18 | 18 |
|
19 | 19 |
|
20 | | -# Example Sudoku puzzle with starting numbers (0 = empty) |
| 20 | +# Theme tokens |
| 21 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 22 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 23 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 24 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 25 | + |
| 26 | +# Data: partially filled Sudoku puzzle (0 = empty cell) |
21 | 27 | grid = [ |
22 | 28 | [5, 3, 0, 0, 7, 0, 0, 0, 0], |
23 | 29 | [6, 0, 0, 1, 9, 5, 0, 0, 0], |
|
30 | 36 | [0, 0, 0, 0, 8, 0, 0, 7, 9], |
31 | 37 | ] |
32 | 38 |
|
33 | | -# Create chart |
| 39 | +# Build grid lines as native highcharts plotLines: |
| 40 | +# thin lines for cell boundaries, thick lines for 3x3 box boundaries |
| 41 | +thin_positions = [1, 2, 4, 5, 7, 8] |
| 42 | +thick_positions = [0, 3, 6, 9] |
| 43 | + |
| 44 | +axis_plot_lines = [{"value": v, "width": 2, "color": INK_SOFT, "zIndex": 3} for v in thin_positions] + [ |
| 45 | + {"value": v, "width": 8, "color": INK, "zIndex": 5} for v in thick_positions |
| 46 | +] |
| 47 | + |
| 48 | +# Chart |
34 | 49 | chart = Chart(container="container") |
35 | 50 | chart.options = HighchartsOptions() |
36 | 51 |
|
37 | 52 | chart.options.chart = { |
38 | 53 | "width": 3600, |
39 | 54 | "height": 3600, |
40 | | - "backgroundColor": "#ffffff", |
| 55 | + "backgroundColor": PAGE_BG, |
| 56 | + "plotBackgroundColor": PAGE_BG, |
41 | 57 | "plotBorderWidth": 0, |
42 | | - "margin": [120, 100, 100, 100], |
| 58 | + "margin": [180, 120, 60, 120], |
| 59 | + "style": {"fontFamily": "Arial, sans-serif"}, |
43 | 60 | } |
44 | 61 |
|
45 | 62 | chart.options.title = { |
46 | | - "text": "sudoku-basic · highcharts · pyplots.ai", |
47 | | - "style": {"fontSize": "56px", "fontWeight": "bold", "fontFamily": "Arial, sans-serif"}, |
48 | | - "y": 60, |
| 63 | + "text": "sudoku-basic · highcharts · anyplot.ai", |
| 64 | + "style": {"fontSize": "56px", "fontWeight": "bold", "color": INK, "fontFamily": "Arial, sans-serif"}, |
| 65 | + "margin": 60, |
49 | 66 | } |
50 | 67 |
|
51 | | -# Disable legend |
52 | 68 | chart.options.legend = {"enabled": False} |
| 69 | +chart.options.credits = {"enabled": False} |
| 70 | +chart.options.tooltip = {"enabled": False} |
53 | 71 |
|
54 | | -# Configure axes for 9x9 grid |
55 | | -chart.options.x_axis = { |
56 | | - "min": 0, |
57 | | - "max": 9, |
58 | | - "tickInterval": 1, |
59 | | - "gridLineWidth": 0, |
60 | | - "lineWidth": 0, |
61 | | - "labels": {"enabled": False}, |
62 | | - "title": {"text": None}, |
63 | | -} |
| 72 | +chart.options.x_axis = [ |
| 73 | + { |
| 74 | + "min": 0, |
| 75 | + "max": 9, |
| 76 | + "tickInterval": 1, |
| 77 | + "gridLineWidth": 0, |
| 78 | + "lineWidth": 0, |
| 79 | + "tickLength": 0, |
| 80 | + "labels": {"enabled": False}, |
| 81 | + "title": {"text": ""}, |
| 82 | + "plotLines": axis_plot_lines, |
| 83 | + "startOnTick": True, |
| 84 | + "endOnTick": True, |
| 85 | + } |
| 86 | +] |
64 | 87 |
|
65 | | -chart.options.y_axis = { |
66 | | - "min": 0, |
67 | | - "max": 9, |
68 | | - "tickInterval": 1, |
69 | | - "gridLineWidth": 0, |
70 | | - "lineWidth": 0, |
71 | | - "labels": {"enabled": False}, |
72 | | - "title": {"text": None}, |
73 | | - "reversed": True, |
74 | | -} |
| 88 | +chart.options.y_axis = [ |
| 89 | + { |
| 90 | + "min": 0, |
| 91 | + "max": 9, |
| 92 | + "tickInterval": 1, |
| 93 | + "gridLineWidth": 0, |
| 94 | + "lineWidth": 0, |
| 95 | + "tickLength": 0, |
| 96 | + "labels": {"enabled": False}, |
| 97 | + "title": {"text": ""}, |
| 98 | + "plotLines": axis_plot_lines, |
| 99 | + "reversed": True, |
| 100 | + "startOnTick": True, |
| 101 | + "endOnTick": True, |
| 102 | + } |
| 103 | +] |
75 | 104 |
|
76 | | -# Prepare data points for numbers |
| 105 | +# Numbers rendered as invisible scatter points with data labels centered per cell |
77 | 106 | data_points = [] |
78 | 107 | for row in range(9): |
79 | 108 | for col in range(9): |
|
83 | 112 | { |
84 | 113 | "x": col + 0.5, |
85 | 114 | "y": row + 0.5, |
86 | | - "name": str(value), |
87 | | - "marker": {"radius": 0}, |
88 | 115 | "dataLabels": { |
89 | 116 | "enabled": True, |
90 | 117 | "format": str(value), |
| 118 | + "align": "center", |
| 119 | + "verticalAlign": "middle", |
91 | 120 | "style": { |
92 | | - "fontSize": "72px", |
| 121 | + "fontSize": "96px", |
93 | 122 | "fontWeight": "bold", |
94 | 123 | "fontFamily": "Arial, sans-serif", |
95 | 124 | "textOutline": "none", |
| 125 | + "color": INK, |
96 | 126 | }, |
97 | | - "color": "#000000", |
98 | 127 | }, |
99 | 128 | } |
100 | 129 | ) |
101 | 130 |
|
102 | | -# Create scatter series for numbers |
103 | 131 | series = ScatterSeries() |
104 | 132 | series.data = data_points |
105 | 133 | series.name = "Numbers" |
| 134 | +series.marker = {"enabled": False} |
106 | 135 | series.enable_mouse_tracking = False |
107 | 136 | chart.add_series(series) |
108 | 137 |
|
109 | | -# Disable tooltip |
110 | | -chart.options.tooltip = {"enabled": False} |
111 | | - |
112 | | -# Export to PNG via Selenium |
113 | | -highcharts_url = "https://code.highcharts.com/highcharts.js" |
| 138 | +# Download Highcharts JS (embed inline for headless Chrome) |
| 139 | +highcharts_url = "https://cdnjs.cloudflare.com/ajax/libs/highcharts/11.4.8/highcharts.js" |
114 | 140 | with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
115 | 141 | highcharts_js = response.read().decode("utf-8") |
116 | 142 |
|
117 | | -# Generate HTML with grid lines drawn via CSS/HTML |
118 | 143 | html_str = chart.to_js_literal() |
119 | 144 |
|
120 | | -# Build grid lines HTML - thin lines for cells, thick lines for 3x3 boxes |
121 | | -cell_size = 378 # (3600 - 200) / 9 ~ 378px per cell (accounting for margins) |
122 | | -offset_x = 100 # Left margin |
123 | | -offset_y = 120 # Top margin |
124 | | - |
125 | | -grid_lines_html = "" |
126 | | - |
127 | | -# Thin lines for all cells (light gray) |
128 | | -for i in range(10): |
129 | | - # Vertical lines |
130 | | - x = offset_x + i * cell_size |
131 | | - grid_lines_html += f'<div style="position:absolute; left:{x}px; top:{offset_y}px; width:2px; height:{cell_size * 9}px; background:#999999;"></div>' |
132 | | - # Horizontal lines |
133 | | - y = offset_y + i * cell_size |
134 | | - grid_lines_html += f'<div style="position:absolute; left:{offset_x}px; top:{y}px; width:{cell_size * 9}px; height:2px; background:#999999;"></div>' |
135 | | - |
136 | | -# Thick lines for 3x3 boxes (black) |
137 | | -for i in range(4): |
138 | | - # Vertical lines |
139 | | - x = offset_x + i * 3 * cell_size |
140 | | - grid_lines_html += f'<div style="position:absolute; left:{x - 2}px; top:{offset_y}px; width:6px; height:{cell_size * 9}px; background:#000000;"></div>' |
141 | | - # Horizontal lines |
142 | | - y = offset_y + i * 3 * cell_size |
143 | | - grid_lines_html += f'<div style="position:absolute; left:{offset_x}px; top:{y - 2}px; width:{cell_size * 9}px; height:6px; background:#000000;"></div>' |
144 | | - |
145 | 145 | html_content = f"""<!DOCTYPE html> |
146 | 146 | <html> |
147 | 147 | <head> |
148 | 148 | <meta charset="utf-8"> |
149 | 149 | <script>{highcharts_js}</script> |
150 | 150 | </head> |
151 | | -<body style="margin:0; background:#ffffff;"> |
152 | | - <div id="container" style="width: 3600px; height: 3600px; position: relative;"></div> |
153 | | - {grid_lines_html} |
| 151 | +<body style="margin:0; padding:0; background:{PAGE_BG}; overflow:hidden;"> |
| 152 | + <div id="container" style="width: 3600px; height: 3600px;"></div> |
154 | 153 | <script>{html_str}</script> |
155 | 154 | </body> |
156 | 155 | </html>""" |
157 | 156 |
|
| 157 | +# Save HTML artifact |
| 158 | +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: |
| 159 | + f.write(html_content) |
| 160 | + |
| 161 | +# Screenshot via headless Chrome |
158 | 162 | with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
159 | 163 | f.write(html_content) |
160 | 164 | temp_path = f.name |
|
164 | 168 | chrome_options.add_argument("--no-sandbox") |
165 | 169 | chrome_options.add_argument("--disable-dev-shm-usage") |
166 | 170 | chrome_options.add_argument("--disable-gpu") |
167 | | -chrome_options.add_argument("--window-size=3700,3700") |
| 171 | +chrome_options.add_argument("--hide-scrollbars") |
| 172 | +chrome_options.add_argument("--window-size=3600,3600") |
168 | 173 |
|
169 | 174 | driver = webdriver.Chrome(options=chrome_options) |
| 175 | +# Force exact viewport dimensions (headless outer vs inner can differ) |
| 176 | +driver.execute_cdp_cmd( |
| 177 | + "Emulation.setDeviceMetricsOverride", {"width": 3600, "height": 3600, "deviceScaleFactor": 1, "mobile": False} |
| 178 | +) |
170 | 179 | driver.get(f"file://{temp_path}") |
171 | 180 | time.sleep(5) |
172 | | - |
173 | | -# Take screenshot and crop to exact 3600x3600 |
174 | | -driver.save_screenshot("plot_temp.png") |
| 181 | +driver.save_screenshot(f"plot-{THEME}.png") |
175 | 182 | driver.quit() |
176 | 183 |
|
177 | | -# Crop to exact dimensions using PIL |
178 | | -img = Image.open("plot_temp.png") |
179 | | -img_cropped = img.crop((0, 0, 3600, 3600)) |
180 | | -img_cropped.save("plot.png") |
181 | | -Path("plot_temp.png").unlink() |
182 | | - |
183 | | -# Also save HTML for interactive version |
184 | | -with open("plot.html", "w", encoding="utf-8") as f: |
185 | | - # For the HTML version, use CDN links (works in browser) |
186 | | - html_interactive = f"""<!DOCTYPE html> |
187 | | -<html> |
188 | | -<head> |
189 | | - <meta charset="utf-8"> |
190 | | - <script src="https://code.highcharts.com/highcharts.js"></script> |
191 | | -</head> |
192 | | -<body style="margin:0; background:#ffffff;"> |
193 | | - <div id="container" style="width: 3600px; height: 3600px; position: relative;"></div> |
194 | | - {grid_lines_html} |
195 | | - <script>{html_str}</script> |
196 | | -</body> |
197 | | -</html>""" |
198 | | - f.write(html_interactive) |
199 | | - |
200 | 184 | Path(temp_path).unlink() |
0 commit comments