Skip to content

Commit 41be756

Browse files
feat(highcharts): implement sudoku-basic (#5353)
## Implementation: `sudoku-basic` - python/highcharts Implements the **python/highcharts** version of `sudoku-basic`. **File:** `plots/sudoku-basic/implementations/python/highcharts.py` **Parent Issue:** #1311 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24882421168)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 977c5bc commit 41be756

2 files changed

Lines changed: 253 additions & 224 deletions

File tree

Lines changed: 83 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
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
55
"""
66

7+
import os
78
import tempfile
89
import time
910
import urllib.request
@@ -12,12 +13,17 @@
1213
from highcharts_core.chart import Chart
1314
from highcharts_core.options import HighchartsOptions
1415
from highcharts_core.options.series.scatter import ScatterSeries
15-
from PIL import Image
1616
from selenium import webdriver
1717
from selenium.webdriver.chrome.options import Options
1818

1919

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)
2127
grid = [
2228
[5, 3, 0, 0, 7, 0, 0, 0, 0],
2329
[6, 0, 0, 1, 9, 5, 0, 0, 0],
@@ -30,50 +36,73 @@
3036
[0, 0, 0, 0, 8, 0, 0, 7, 9],
3137
]
3238

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
3449
chart = Chart(container="container")
3550
chart.options = HighchartsOptions()
3651

3752
chart.options.chart = {
3853
"width": 3600,
3954
"height": 3600,
40-
"backgroundColor": "#ffffff",
55+
"backgroundColor": PAGE_BG,
56+
"plotBackgroundColor": PAGE_BG,
4157
"plotBorderWidth": 0,
42-
"margin": [120, 100, 100, 100],
58+
"margin": [180, 120, 60, 120],
59+
"style": {"fontFamily": "Arial, sans-serif"},
4360
}
4461

4562
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,
4966
}
5067

51-
# Disable legend
5268
chart.options.legend = {"enabled": False}
69+
chart.options.credits = {"enabled": False}
70+
chart.options.tooltip = {"enabled": False}
5371

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+
]
6487

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+
]
75104

76-
# Prepare data points for numbers
105+
# Numbers rendered as invisible scatter points with data labels centered per cell
77106
data_points = []
78107
for row in range(9):
79108
for col in range(9):
@@ -83,78 +112,53 @@
83112
{
84113
"x": col + 0.5,
85114
"y": row + 0.5,
86-
"name": str(value),
87-
"marker": {"radius": 0},
88115
"dataLabels": {
89116
"enabled": True,
90117
"format": str(value),
118+
"align": "center",
119+
"verticalAlign": "middle",
91120
"style": {
92-
"fontSize": "72px",
121+
"fontSize": "96px",
93122
"fontWeight": "bold",
94123
"fontFamily": "Arial, sans-serif",
95124
"textOutline": "none",
125+
"color": INK,
96126
},
97-
"color": "#000000",
98127
},
99128
}
100129
)
101130

102-
# Create scatter series for numbers
103131
series = ScatterSeries()
104132
series.data = data_points
105133
series.name = "Numbers"
134+
series.marker = {"enabled": False}
106135
series.enable_mouse_tracking = False
107136
chart.add_series(series)
108137

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"
114140
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
115141
highcharts_js = response.read().decode("utf-8")
116142

117-
# Generate HTML with grid lines drawn via CSS/HTML
118143
html_str = chart.to_js_literal()
119144

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-
145145
html_content = f"""<!DOCTYPE html>
146146
<html>
147147
<head>
148148
<meta charset="utf-8">
149149
<script>{highcharts_js}</script>
150150
</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>
154153
<script>{html_str}</script>
155154
</body>
156155
</html>"""
157156

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
158162
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
159163
f.write(html_content)
160164
temp_path = f.name
@@ -164,37 +168,17 @@
164168
chrome_options.add_argument("--no-sandbox")
165169
chrome_options.add_argument("--disable-dev-shm-usage")
166170
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")
168173

169174
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+
)
170179
driver.get(f"file://{temp_path}")
171180
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")
175182
driver.quit()
176183

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-
200184
Path(temp_path).unlink()

0 commit comments

Comments
 (0)