Skip to content

Commit a3227d2

Browse files
feat(highcharts): implement facet-grid (#6519)
## Implementation: `facet-grid` - python/highcharts Implements the **python/highcharts** version of `facet-grid`. **File:** `plots/facet-grid/implementations/python/highcharts.py` **Parent Issue:** #2696 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25776596236)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 7b2f5bd commit a3227d2

2 files changed

Lines changed: 284 additions & 191 deletions

File tree

plots/facet-grid/implementations/python/highcharts.py

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

7+
import os
78
import tempfile
89
import time
910
import urllib.request
@@ -16,6 +17,17 @@
1617
from selenium.webdriver.chrome.options import Options
1718

1819

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+
1931
# Data
2032
np.random.seed(42)
2133

@@ -45,20 +57,17 @@
4557
# Calculate subplot dimensions with margins
4658
chart_width = 4800
4759
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
5365

5466
plot_area_width = chart_width - margin_left - margin_right
5567
plot_area_height = chart_height - margin_top - margin_bottom
5668
subplot_width = (plot_area_width - spacing * (n_cols - 1)) / n_cols
5769
subplot_height = (plot_area_height - spacing * (n_rows - 1)) / n_rows
5870

59-
# Colors for facets - colorblind-safe palette
60-
colors = ["#306998", "#FFD43B", "#9467BD"]
61-
6271
# Build series data for each facet
6372
series_list = []
6473
x_axes = []
@@ -85,24 +94,23 @@
8594
"height": 0,
8695
"min": 15,
8796
"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},
93102
"title": {
94103
"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,
97106
},
98-
"gridLineWidth": 1,
99-
"gridLineColor": "rgba(0,0,0,0.15)",
100-
"gridLineDashStyle": "Dash",
107+
"gridLineWidth": 2,
108+
"gridLineColor": GRID,
101109
"offset": 0,
102110
}
103111
)
104112

105-
# Create y-axis for this subplot
113+
# Create y-axis for this subplot - fix for consistent label display
106114
y_axis_id = f"y{row_idx * n_cols + col_idx}"
107115
y_axes.append(
108116
{
@@ -113,20 +121,19 @@
113121
"height": subplot_height,
114122
"min": 0,
115123
"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},
121129
"title": {
122130
"text": "Growth (cm)" if col_idx == 0 else None,
123-
"style": {"fontSize": "24px", "color": "#333333"},
131+
"style": {"fontSize": "26px", "color": INK},
124132
"rotation": 270,
125-
"x": -50,
133+
"x": -60,
126134
},
127-
"gridLineWidth": 1,
128-
"gridLineColor": "rgba(0,0,0,0.15)",
129-
"gridLineDashStyle": "Dash",
135+
"gridLineWidth": 2,
136+
"gridLineColor": GRID,
130137
"offset": 0,
131138
}
132139
)
@@ -140,11 +147,11 @@
140147
"xAxis": row_idx * n_cols + col_idx,
141148
"yAxis": row_idx * n_cols + col_idx,
142149
"marker": {
143-
"radius": 10,
150+
"radius": 12,
144151
"symbol": "circle",
145-
"fillColor": colors[row_idx % len(colors)],
152+
"fillColor": OKABE_ITO[row_idx % len(OKABE_ITO)],
146153
"lineWidth": 2,
147-
"lineColor": "#333333",
154+
"lineColor": PAGE_BG,
148155
},
149156
"showInLegend": False,
150157
}
@@ -160,11 +167,11 @@
160167
{
161168
"labels": [
162169
{
163-
"point": {"x": left, "y": margin_top - 60, "xAxis": None, "yAxis": None},
170+
"point": {"x": left, "y": margin_top - 100, "xAxis": None, "yAxis": None},
164171
"text": f"Light: {light}",
165172
"backgroundColor": "transparent",
166173
"borderWidth": 0,
167-
"style": {"fontSize": "28px", "fontWeight": "bold", "color": "#333333"},
174+
"style": {"fontSize": "30px", "fontWeight": "bold", "color": INK},
168175
}
169176
],
170177
"labelOptions": {"useHTML": True},
@@ -178,11 +185,11 @@
178185
{
179186
"labels": [
180187
{
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},
182189
"text": f"Soil: {soil}",
183190
"backgroundColor": "transparent",
184191
"borderWidth": 0,
185-
"style": {"fontSize": "26px", "fontWeight": "bold", "color": "#333333"},
192+
"style": {"fontSize": "28px", "fontWeight": "bold", "color": INK},
186193
"rotation": 90,
187194
}
188195
],
@@ -199,24 +206,24 @@
199206
"type": "scatter",
200207
"width": chart_width,
201208
"height": chart_height,
202-
"backgroundColor": "#ffffff",
203-
"style": {"fontFamily": "Arial, sans-serif"},
209+
"backgroundColor": PAGE_BG,
210+
"style": {"fontFamily": "Arial, sans-serif", "color": INK},
204211
"marginTop": margin_top,
205212
"marginBottom": margin_bottom,
206213
"marginLeft": margin_left,
207214
"marginRight": margin_right,
208215
}
209216

210217
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,
214221
}
215222

216223
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,
220227
}
221228

222229
chart.options.credits = {"enabled": False}
@@ -228,7 +235,7 @@
228235

229236
chart.options.plot_options = {
230237
"scatter": {
231-
"marker": {"radius": 10, "states": {"hover": {"enabled": True, "lineColor": "#333333"}}},
238+
"marker": {"radius": 12, "states": {"hover": {"enabled": True}}},
232239
"states": {"inactive": {"opacity": 1}},
233240
}
234241
}
@@ -242,15 +249,58 @@
242249
# Generate JavaScript
243250
html_str = chart.to_js_literal()
244251

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")
254304

255305
# Generate HTML with inline scripts
256306
html_content = f"""<!DOCTYPE html>
@@ -260,14 +310,14 @@
260310
<script>{highcharts_js}</script>
261311
<script>{annotations_js}</script>
262312
</head>
263-
<body style="margin:0; padding:0;">
313+
<body style="margin:0; padding:0; background:{PAGE_BG};">
264314
<div id="container" style="width: {chart_width}px; height: {chart_height}px;"></div>
265315
<script>{html_str}</script>
266316
</body>
267317
</html>"""
268318

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:
271321
f.write(html_content)
272322

273323
# Create screenshot with Selenium
@@ -280,13 +330,12 @@
280330
chrome_options.add_argument("--no-sandbox")
281331
chrome_options.add_argument("--disable-dev-shm-usage")
282332
chrome_options.add_argument("--disable-gpu")
283-
chrome_options.add_argument("--window-size=4900,2800")
333+
chrome_options.add_argument("--window-size=4800,2700")
284334

285335
driver = webdriver.Chrome(options=chrome_options)
286-
driver.set_window_size(4900, 2800)
287336
driver.get(f"file://{temp_path}")
288337
time.sleep(5)
289-
driver.save_screenshot("plot.png")
338+
driver.save_screenshot(f"plot-{THEME}.png")
290339
driver.quit()
291340

292341
Path(temp_path).unlink()

0 commit comments

Comments
 (0)