Skip to content

Commit 3fad2ba

Browse files
feat(highcharts): implement learning-curve-basic (#6218)
## Implementation: `learning-curve-basic` - python/highcharts Implements the **python/highcharts** version of `learning-curve-basic`. **File:** `plots/learning-curve-basic/implementations/python/highcharts.py` **Parent Issue:** #2275 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25618803570)* --------- 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 3bb3696 commit 3fad2ba

2 files changed

Lines changed: 200 additions & 191 deletions

File tree

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
learning-curve-basic: Model Learning Curve
3-
Library: highcharts unknown | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-26
3+
Library: highcharts unknown | Python 3.13.13
4+
Quality: 86/100 | Updated: 2026-05-10
55
"""
66

77
import json
8+
import os
89
import tempfile
910
import time
1011
import urllib.request
1112
from pathlib import Path
1213

1314
import numpy as np
14-
from PIL import Image
1515
from selenium import webdriver
1616
from selenium.webdriver.chrome.options import Options
1717

1818

19+
# Theme tokens
20+
THEME = os.getenv("ANYPLOT_THEME", "light")
21+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
22+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
23+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
24+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
25+
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
26+
27+
# Okabe-Ito palette
28+
BRAND = "#009E73" # Position 1 - first series
29+
ORANGE = "#D55E00" # Position 2
30+
1931
# Data - Simulated learning curve from a classification model
2032
np.random.seed(42)
2133

2234
train_sizes = [50, 100, 200, 400, 600, 800, 1000, 1200, 1400, 1600]
2335

24-
# Training scores: starts high, remains high (slight decrease with more data due to harder fitting)
36+
# Training scores: starts high, remains high (slight decrease with more data)
2537
train_scores_mean = [0.99, 0.98, 0.97, 0.96, 0.955, 0.95, 0.948, 0.946, 0.944, 0.943]
2638
train_scores_std = [0.01, 0.012, 0.01, 0.008, 0.007, 0.006, 0.005, 0.005, 0.004, 0.004]
2739

@@ -36,10 +48,8 @@
3648
val_lower = [m - s for m, s in zip(validation_scores_mean, validation_scores_std, strict=True)]
3749

3850
# Prepare data for Highcharts
39-
# arearange series expects [[x, low, high], ...]
4051
train_band_data = [[x, lo, hi] for x, lo, hi in zip(train_sizes, train_lower, train_upper, strict=True)]
4152
val_band_data = [[x, lo, hi] for x, lo, hi in zip(train_sizes, val_lower, val_upper, strict=True)]
42-
# line series expects [[x, y], ...]
4353
train_line_data = [[x, y] for x, y in zip(train_sizes, train_scores_mean, strict=True)]
4454
val_line_data = [[x, y] for x, y in zip(train_sizes, validation_scores_mean, strict=True)]
4555

@@ -48,33 +58,32 @@
4858
"chart": {
4959
"width": 4800,
5060
"height": 2700,
51-
"backgroundColor": "#ffffff",
61+
"backgroundColor": PAGE_BG,
5262
"marginBottom": 180,
5363
"marginLeft": 200,
5464
"marginRight": 120,
5565
"marginTop": 150,
56-
"style": {"fontFamily": "Arial, sans-serif"},
66+
"style": {"fontFamily": "Arial, sans-serif", "color": INK},
5767
},
58-
"title": {
59-
"text": "learning-curve-basic · highcharts · pyplots.ai",
60-
"style": {"fontSize": "64px", "fontWeight": "bold"},
61-
},
62-
"subtitle": {"text": "Model Performance vs Training Set Size", "style": {"fontSize": "38px", "color": "#666666"}},
68+
"title": {"text": "learning-curve-basic · highcharts · anyplot.ai", "style": {"fontSize": "28px", "color": INK}},
69+
"subtitle": {"text": "Model Performance vs Training Set Size", "style": {"fontSize": "22px", "color": INK_SOFT}},
6370
"xAxis": {
64-
"title": {"text": "Training Set Size (samples)", "style": {"fontSize": "48px"}, "margin": 20},
65-
"labels": {"style": {"fontSize": "36px"}},
71+
"title": {"text": "Training Set Size (samples)", "style": {"fontSize": "22px", "color": INK}},
72+
"labels": {"style": {"fontSize": "18px", "color": INK_SOFT}},
73+
"lineColor": INK_SOFT,
74+
"tickColor": INK_SOFT,
6675
"gridLineWidth": 1,
67-
"gridLineColor": "rgba(0, 0, 0, 0.1)",
68-
"gridLineDashStyle": "Dash",
76+
"gridLineColor": GRID,
6977
"min": 0,
7078
"max": 1700,
7179
},
7280
"yAxis": {
73-
"title": {"text": "Accuracy Score", "style": {"fontSize": "48px"}, "margin": 20},
74-
"labels": {"style": {"fontSize": "36px"}, "format": "{value:.2f}"},
81+
"title": {"text": "Accuracy Score", "style": {"fontSize": "22px", "color": INK}},
82+
"labels": {"style": {"fontSize": "18px", "color": INK_SOFT}, "format": "{value:.2f}"},
83+
"lineColor": INK_SOFT,
84+
"tickColor": INK_SOFT,
7585
"gridLineWidth": 1,
76-
"gridLineColor": "rgba(0, 0, 0, 0.1)",
77-
"gridLineDashStyle": "Dash",
86+
"gridLineColor": GRID,
7887
"min": 0.6,
7988
"max": 1.02,
8089
},
@@ -85,24 +94,24 @@
8594
"layout": "vertical",
8695
"x": -50,
8796
"y": 120,
88-
"itemStyle": {"fontSize": "36px"},
97+
"itemStyle": {"fontSize": "18px", "color": INK_SOFT},
8998
"itemMarginBottom": 15,
90-
"backgroundColor": "rgba(255, 255, 255, 0.9)",
99+
"backgroundColor": ELEVATED_BG,
91100
"borderWidth": 1,
92-
"borderColor": "#cccccc",
101+
"borderColor": INK_SOFT,
93102
"padding": 15,
94103
},
95104
"plotOptions": {
96105
"arearange": {"fillOpacity": 0.25, "lineWidth": 0, "marker": {"enabled": False}},
97-
"line": {"lineWidth": 6, "marker": {"enabled": True, "radius": 12, "lineWidth": 3, "lineColor": "#ffffff"}},
106+
"line": {"lineWidth": 6, "marker": {"enabled": True, "radius": 8, "lineWidth": 2, "lineColor": PAGE_BG}},
98107
},
99108
"series": [
100109
# Training confidence band
101110
{
102111
"name": "Training ±1 std",
103112
"type": "arearange",
104113
"data": train_band_data,
105-
"color": "#306998",
114+
"color": BRAND,
106115
"fillOpacity": 0.25,
107116
"zIndex": 0,
108117
"showInLegend": False,
@@ -113,7 +122,7 @@
113122
"name": "Validation ±1 std",
114123
"type": "arearange",
115124
"data": val_band_data,
116-
"color": "#FFD43B",
125+
"color": ORANGE,
117126
"fillOpacity": 0.35,
118127
"zIndex": 0,
119128
"showInLegend": False,
@@ -124,27 +133,27 @@
124133
"name": "Training Score",
125134
"type": "line",
126135
"data": train_line_data,
127-
"color": "#306998",
136+
"color": BRAND,
128137
"lineWidth": 6,
129138
"zIndex": 1,
130-
"marker": {"fillColor": "#306998", "radius": 12, "lineWidth": 3, "lineColor": "#ffffff"},
139+
"marker": {"fillColor": BRAND, "radius": 8, "lineWidth": 2, "lineColor": PAGE_BG},
131140
},
132141
# Validation score line
133142
{
134143
"name": "Validation Score",
135144
"type": "line",
136145
"data": val_line_data,
137-
"color": "#FFD43B",
146+
"color": ORANGE,
138147
"lineWidth": 6,
139148
"zIndex": 1,
140-
"marker": {"fillColor": "#FFD43B", "radius": 12, "lineWidth": 3, "lineColor": "#ffffff"},
149+
"marker": {"fillColor": ORANGE, "radius": 8, "lineWidth": 2, "lineColor": PAGE_BG},
141150
},
142151
],
143152
}
144153

145-
# Download Highcharts JS and highcharts-more (needed for arearange)
146-
highcharts_url = "https://code.highcharts.com/highcharts.js"
147-
highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
154+
# Download Highcharts JS and highcharts-more from jsDelivr CDN
155+
highcharts_url = "https://cdn.jsdelivr.net/npm/highcharts/highcharts.js"
156+
highcharts_more_url = "https://cdn.jsdelivr.net/npm/highcharts/highcharts-more.js"
148157

149158
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
150159
highcharts_js = response.read().decode("utf-8")
@@ -160,7 +169,7 @@
160169
<script>{highcharts_js}</script>
161170
<script>{highcharts_more_js}</script>
162171
</head>
163-
<body style="margin:0;">
172+
<body style="margin:0; background:{PAGE_BG};">
164173
<div id="container" style="width: 4800px; height: 2700px;"></div>
165174
<script>
166175
document.addEventListener('DOMContentLoaded', function() {{
@@ -170,38 +179,29 @@
170179
</body>
171180
</html>"""
172181

173-
# Write temp HTML file
174-
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
182+
# Save HTML artifact
183+
with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f:
175184
f.write(html_content)
176-
temp_path = f.name
177185

178-
# Also save the HTML for interactive viewing
179-
with open("plot.html", "w", encoding="utf-8") as f:
186+
# Write temp HTML for screenshot
187+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
180188
f.write(html_content)
189+
temp_path = f.name
181190

182191
# Take screenshot with headless Chrome
183192
chrome_options = Options()
184-
chrome_options.add_argument("--headless=new")
193+
chrome_options.add_argument("--headless")
185194
chrome_options.add_argument("--no-sandbox")
186195
chrome_options.add_argument("--disable-dev-shm-usage")
187196
chrome_options.add_argument("--disable-gpu")
188-
chrome_options.add_argument("--force-device-scale-factor=1")
197+
chrome_options.add_argument("--window-size=4800,2700")
189198

190199
driver = webdriver.Chrome(options=chrome_options)
191-
driver.set_window_size(4900, 2900)
192200
driver.get(f"file://{temp_path}")
193201
time.sleep(5)
194202

195-
# Take screenshot
196-
driver.save_screenshot("plot_raw.png")
203+
driver.save_screenshot(f"plot-{THEME}.png")
197204
driver.quit()
198205

199-
# Crop/resize to exact 4800x2700 using PIL
200-
img = Image.open("plot_raw.png")
201-
final_img = Image.new("RGB", (4800, 2700), (255, 255, 255))
202-
final_img.paste(img.crop((0, 0, min(img.width, 4800), min(img.height, 2700))), (0, 0))
203-
final_img.save("plot.png")
204-
205206
# Clean up
206-
Path("plot_raw.png").unlink()
207207
Path(temp_path).unlink()

0 commit comments

Comments
 (0)