Skip to content

Commit 6f8cd46

Browse files
feat(highcharts): implement learning-curve-basic (#2328)
## Implementation: `learning-curve-basic` - highcharts Implements the **highcharts** version of `learning-curve-basic`. **File:** `plots/learning-curve-basic/implementations/highcharts.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20527871964)* --------- 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 9fa35c9 commit 6f8cd46

2 files changed

Lines changed: 235 additions & 0 deletions

File tree

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
""" pyplots.ai
2+
learning-curve-basic: Model Learning Curve
3+
Library: highcharts unknown | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-26
5+
"""
6+
7+
import json
8+
import tempfile
9+
import time
10+
import urllib.request
11+
from pathlib import Path
12+
13+
import numpy as np
14+
from PIL import Image
15+
from selenium import webdriver
16+
from selenium.webdriver.chrome.options import Options
17+
18+
19+
# Data - Simulated learning curve from a classification model
20+
np.random.seed(42)
21+
22+
train_sizes = [50, 100, 200, 400, 600, 800, 1000, 1200, 1400, 1600]
23+
24+
# Training scores: starts high, remains high (slight decrease with more data due to harder fitting)
25+
train_scores_mean = [0.99, 0.98, 0.97, 0.96, 0.955, 0.95, 0.948, 0.946, 0.944, 0.943]
26+
train_scores_std = [0.01, 0.012, 0.01, 0.008, 0.007, 0.006, 0.005, 0.005, 0.004, 0.004]
27+
28+
# Validation scores: starts low, increases and converges toward training
29+
validation_scores_mean = [0.72, 0.78, 0.83, 0.87, 0.89, 0.905, 0.915, 0.922, 0.928, 0.932]
30+
validation_scores_std = [0.06, 0.05, 0.04, 0.035, 0.03, 0.025, 0.022, 0.02, 0.018, 0.016]
31+
32+
# Calculate bounds for shaded regions (±1 std)
33+
train_upper = [m + s for m, s in zip(train_scores_mean, train_scores_std, strict=True)]
34+
train_lower = [m - s for m, s in zip(train_scores_mean, train_scores_std, strict=True)]
35+
val_upper = [m + s for m, s in zip(validation_scores_mean, validation_scores_std, strict=True)]
36+
val_lower = [m - s for m, s in zip(validation_scores_mean, validation_scores_std, strict=True)]
37+
38+
# Prepare data for Highcharts
39+
# arearange series expects [[x, low, high], ...]
40+
train_band_data = [[x, lo, hi] for x, lo, hi in zip(train_sizes, train_lower, train_upper, strict=True)]
41+
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], ...]
43+
train_line_data = [[x, y] for x, y in zip(train_sizes, train_scores_mean, strict=True)]
44+
val_line_data = [[x, y] for x, y in zip(train_sizes, validation_scores_mean, strict=True)]
45+
46+
# Chart options
47+
chart_options = {
48+
"chart": {
49+
"width": 4800,
50+
"height": 2700,
51+
"backgroundColor": "#ffffff",
52+
"marginBottom": 180,
53+
"marginLeft": 200,
54+
"marginRight": 120,
55+
"marginTop": 150,
56+
"style": {"fontFamily": "Arial, sans-serif"},
57+
},
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"}},
63+
"xAxis": {
64+
"title": {"text": "Training Set Size (samples)", "style": {"fontSize": "48px"}, "margin": 20},
65+
"labels": {"style": {"fontSize": "36px"}},
66+
"gridLineWidth": 1,
67+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
68+
"gridLineDashStyle": "Dash",
69+
"min": 0,
70+
"max": 1700,
71+
},
72+
"yAxis": {
73+
"title": {"text": "Accuracy Score", "style": {"fontSize": "48px"}, "margin": 20},
74+
"labels": {"style": {"fontSize": "36px"}, "format": "{value:.2f}"},
75+
"gridLineWidth": 1,
76+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
77+
"gridLineDashStyle": "Dash",
78+
"min": 0.6,
79+
"max": 1.02,
80+
},
81+
"legend": {
82+
"enabled": True,
83+
"align": "right",
84+
"verticalAlign": "top",
85+
"layout": "vertical",
86+
"x": -50,
87+
"y": 120,
88+
"itemStyle": {"fontSize": "36px"},
89+
"itemMarginBottom": 15,
90+
"backgroundColor": "rgba(255, 255, 255, 0.9)",
91+
"borderWidth": 1,
92+
"borderColor": "#cccccc",
93+
"padding": 15,
94+
},
95+
"plotOptions": {
96+
"arearange": {"fillOpacity": 0.25, "lineWidth": 0, "marker": {"enabled": False}},
97+
"line": {"lineWidth": 6, "marker": {"enabled": True, "radius": 12, "lineWidth": 3, "lineColor": "#ffffff"}},
98+
},
99+
"series": [
100+
# Training confidence band
101+
{
102+
"name": "Training ±1 std",
103+
"type": "arearange",
104+
"data": train_band_data,
105+
"color": "#306998",
106+
"fillOpacity": 0.25,
107+
"zIndex": 0,
108+
"showInLegend": False,
109+
"enableMouseTracking": False,
110+
},
111+
# Validation confidence band
112+
{
113+
"name": "Validation ±1 std",
114+
"type": "arearange",
115+
"data": val_band_data,
116+
"color": "#FFD43B",
117+
"fillOpacity": 0.35,
118+
"zIndex": 0,
119+
"showInLegend": False,
120+
"enableMouseTracking": False,
121+
},
122+
# Training score line
123+
{
124+
"name": "Training Score",
125+
"type": "line",
126+
"data": train_line_data,
127+
"color": "#306998",
128+
"lineWidth": 6,
129+
"zIndex": 1,
130+
"marker": {"fillColor": "#306998", "radius": 12, "lineWidth": 3, "lineColor": "#ffffff"},
131+
},
132+
# Validation score line
133+
{
134+
"name": "Validation Score",
135+
"type": "line",
136+
"data": val_line_data,
137+
"color": "#FFD43B",
138+
"lineWidth": 6,
139+
"zIndex": 1,
140+
"marker": {"fillColor": "#FFD43B", "radius": 12, "lineWidth": 3, "lineColor": "#ffffff"},
141+
},
142+
],
143+
}
144+
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"
148+
149+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
150+
highcharts_js = response.read().decode("utf-8")
151+
with urllib.request.urlopen(highcharts_more_url, timeout=30) as response:
152+
highcharts_more_js = response.read().decode("utf-8")
153+
154+
# Generate HTML with inline scripts
155+
chart_options_json = json.dumps(chart_options)
156+
html_content = f"""<!DOCTYPE html>
157+
<html>
158+
<head>
159+
<meta charset="utf-8">
160+
<script>{highcharts_js}</script>
161+
<script>{highcharts_more_js}</script>
162+
</head>
163+
<body style="margin:0;">
164+
<div id="container" style="width: 4800px; height: 2700px;"></div>
165+
<script>
166+
document.addEventListener('DOMContentLoaded', function() {{
167+
Highcharts.chart('container', {chart_options_json});
168+
}});
169+
</script>
170+
</body>
171+
</html>"""
172+
173+
# Write temp HTML file
174+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
175+
f.write(html_content)
176+
temp_path = f.name
177+
178+
# Also save the HTML for interactive viewing
179+
with open("plot.html", "w", encoding="utf-8") as f:
180+
f.write(html_content)
181+
182+
# Take screenshot with headless Chrome
183+
chrome_options = Options()
184+
chrome_options.add_argument("--headless=new")
185+
chrome_options.add_argument("--no-sandbox")
186+
chrome_options.add_argument("--disable-dev-shm-usage")
187+
chrome_options.add_argument("--disable-gpu")
188+
chrome_options.add_argument("--force-device-scale-factor=1")
189+
190+
driver = webdriver.Chrome(options=chrome_options)
191+
driver.set_window_size(4900, 2900)
192+
driver.get(f"file://{temp_path}")
193+
time.sleep(5)
194+
195+
# Take screenshot
196+
driver.save_screenshot("plot_raw.png")
197+
driver.quit()
198+
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+
205+
# Clean up
206+
Path("plot_raw.png").unlink()
207+
Path(temp_path).unlink()
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
library: highcharts
2+
specification_id: learning-curve-basic
3+
created: '2025-12-26T19:13:41Z'
4+
updated: '2025-12-26T19:16:01Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20527871964
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/learning-curve-basic/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/learning-curve-basic/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/learning-curve-basic/highcharts/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent use of Highcharts arearange series for confidence bands with proper
17+
layering (bands behind lines)
18+
- Colorblind-safe palette using blue (#306998) and yellow (#FFD43B)
19+
- Professional appearance with appropriate font sizes for 4800x2700 canvas
20+
- Realistic learning curve data showing typical ML training behavior (overfitting
21+
gap that decreases with more data)
22+
- Proper title format following spec-id · library · pyplots.ai convention
23+
- Clean separation of training and validation curves with distinct markers (squares
24+
vs triangles)
25+
weaknesses:
26+
- Legend does not include entries for the confidence bands (±1 std) - only shows
27+
the line series
28+
- Y-axis could start at 0.65 instead of 0.60 to reduce empty space at the bottom

0 commit comments

Comments
 (0)