|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | band-basic: Basic Band Plot |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: highcharts 1.10.3 | Python 3.14 |
| 4 | +Quality: /100 | Updated: 2026-02-23 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import json |
|
16 | 16 | from selenium.webdriver.chrome.options import Options |
17 | 17 |
|
18 | 18 |
|
19 | | -# Data - Time series with 95% confidence interval |
| 19 | +# Data - Daily temperature forecast with 95% prediction interval |
20 | 20 | np.random.seed(42) |
21 | | -x = np.linspace(0, 10, 50) |
22 | | -# Central trend with sinusoidal pattern |
23 | | -y_center = 50 + 20 * np.sin(x) + x * 2 |
24 | | -# Uncertainty increases with x (heteroscedastic) |
25 | | -uncertainty = 3 + 0.5 * x |
26 | | -# Upper and lower bounds |
27 | | -y_lower = y_center - 1.96 * uncertainty |
28 | | -y_upper = y_center + 1.96 * uncertainty |
| 21 | +days = np.arange(1, 31) |
| 22 | +# Central forecast: warming trend with daily variation |
| 23 | +temp_center = 12 + 0.3 * days + 4 * np.sin(days * 0.4) |
| 24 | +# Prediction uncertainty widens over the forecast horizon |
| 25 | +uncertainty = 1.5 + 0.08 * days |
| 26 | +temp_lower = temp_center - 1.96 * uncertainty |
| 27 | +temp_upper = temp_center + 1.96 * uncertainty |
29 | 28 |
|
30 | 29 | # Prepare data for Highcharts |
31 | 30 | # arearange series expects [[x, low, high], ...] |
32 | | -band_data = [[float(xi), float(lo), float(hi)] for xi, lo, hi in zip(x, y_lower, y_upper, strict=True)] |
| 31 | +band_data = [ |
| 32 | + [int(d), round(float(lo), 1), round(float(hi), 1)] for d, lo, hi in zip(days, temp_lower, temp_upper, strict=True) |
| 33 | +] |
33 | 34 | # line series expects [[x, y], ...] |
34 | | -line_data = [[float(xi), float(yi)] for xi, yi in zip(x, y_center, strict=True)] |
| 35 | +line_data = [[int(d), round(float(t), 1)] for d, t in zip(days, temp_center, strict=True)] |
| 36 | + |
| 37 | +# Font stack |
| 38 | +font_family = "'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" |
35 | 39 |
|
36 | 40 | # Chart options using arearange for band and line for center |
37 | 41 | chart_options = { |
|
40 | 44 | "height": 2700, |
41 | 45 | "backgroundColor": "#ffffff", |
42 | 46 | "marginBottom": 180, |
43 | | - "marginLeft": 200, |
| 47 | + "marginLeft": 220, |
44 | 48 | "marginRight": 100, |
45 | | - "style": {"fontFamily": "Arial, sans-serif"}, |
| 49 | + "spacing": [40, 40, 40, 40], |
| 50 | + "style": {"fontFamily": font_family}, |
| 51 | + }, |
| 52 | + "title": { |
| 53 | + "text": "30-Day Temperature Forecast \u00b7 band-basic \u00b7 highcharts \u00b7 pyplots.ai", |
| 54 | + "style": {"fontSize": "48px", "fontWeight": "bold", "fontFamily": font_family}, |
| 55 | + }, |
| 56 | + "subtitle": { |
| 57 | + "text": "Daily forecast with 95% prediction interval", |
| 58 | + "style": {"fontSize": "30px", "color": "#555555", "fontFamily": font_family}, |
46 | 59 | }, |
47 | | - "title": {"text": "band-basic · highcharts · pyplots.ai", "style": {"fontSize": "64px", "fontWeight": "bold"}}, |
48 | | - "subtitle": {"text": "Time series with 95% confidence interval", "style": {"fontSize": "38px", "color": "#666666"}}, |
49 | 60 | "xAxis": { |
50 | | - "title": {"text": "Time", "style": {"fontSize": "48px"}, "margin": 20}, |
51 | | - "labels": {"style": {"fontSize": "36px"}}, |
52 | | - "gridLineWidth": 1, |
53 | | - "gridLineColor": "rgba(0, 0, 0, 0.1)", |
54 | | - "gridLineDashStyle": "Dash", |
55 | | - "tickInterval": 1, |
| 61 | + "title": {"text": "Forecast Day", "style": {"fontSize": "36px", "fontFamily": font_family}, "margin": 20}, |
| 62 | + "labels": {"style": {"fontSize": "28px", "fontFamily": font_family}}, |
| 63 | + "gridLineWidth": 0, |
| 64 | + "tickInterval": 5, |
| 65 | + "lineColor": "#cccccc", |
| 66 | + "tickColor": "#cccccc", |
56 | 67 | }, |
57 | 68 | "yAxis": { |
58 | | - "title": {"text": "Value", "style": {"fontSize": "48px"}, "margin": 20}, |
59 | | - "labels": {"style": {"fontSize": "36px"}}, |
| 69 | + "title": { |
| 70 | + "text": "Temperature (\u00b0C)", |
| 71 | + "style": {"fontSize": "36px", "fontFamily": font_family}, |
| 72 | + "margin": 20, |
| 73 | + }, |
| 74 | + "labels": {"format": "{value}\u00b0", "style": {"fontSize": "28px", "fontFamily": font_family}}, |
60 | 75 | "gridLineWidth": 1, |
61 | | - "gridLineColor": "rgba(0, 0, 0, 0.1)", |
62 | | - "gridLineDashStyle": "Dash", |
| 76 | + "gridLineColor": "rgba(0, 0, 0, 0.08)", |
| 77 | + "gridLineDashStyle": "Dot", |
| 78 | + "lineColor": "#cccccc", |
| 79 | + "lineWidth": 1, |
63 | 80 | }, |
64 | 81 | "legend": { |
65 | 82 | "enabled": True, |
66 | 83 | "align": "right", |
67 | 84 | "verticalAlign": "top", |
68 | 85 | "layout": "vertical", |
69 | | - "x": -50, |
70 | | - "y": 100, |
71 | | - "itemStyle": {"fontSize": "36px"}, |
| 86 | + "x": -40, |
| 87 | + "y": 80, |
| 88 | + "itemStyle": {"fontSize": "28px", "fontFamily": font_family}, |
72 | 89 | }, |
73 | 90 | "plotOptions": { |
74 | | - "arearange": {"fillOpacity": 0.3, "lineWidth": 0, "marker": {"enabled": False}}, |
75 | | - "line": {"lineWidth": 6, "marker": {"enabled": False}}, |
| 91 | + "arearange": {"fillOpacity": 0.25, "lineWidth": 0, "marker": {"enabled": False}}, |
| 92 | + "line": {"lineWidth": 5, "marker": {"enabled": False}}, |
76 | 93 | }, |
77 | 94 | "series": [ |
78 | 95 | { |
79 | | - "name": "95% Confidence Interval", |
| 96 | + "name": "95% Prediction Interval", |
80 | 97 | "type": "arearange", |
81 | 98 | "data": band_data, |
82 | 99 | "color": "#306998", |
83 | | - "fillOpacity": 0.3, |
| 100 | + "fillOpacity": 0.25, |
84 | 101 | "zIndex": 0, |
85 | 102 | }, |
86 | | - {"name": "Mean Value", "type": "line", "data": line_data, "color": "#FFD43B", "lineWidth": 6, "zIndex": 1}, |
| 103 | + {"name": "Forecast", "type": "line", "data": line_data, "color": "#FFD43B", "lineWidth": 5, "zIndex": 1}, |
87 | 104 | ], |
| 105 | + "credits": {"enabled": False}, |
88 | 106 | } |
89 | 107 |
|
90 | | -# Download Highcharts JS and highcharts-more (needed for arearange) |
91 | | -highcharts_url = "https://code.highcharts.com/highcharts.js" |
92 | | -highcharts_more_url = "https://code.highcharts.com/highcharts-more.js" |
93 | | - |
94 | | -with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
95 | | - highcharts_js = response.read().decode("utf-8") |
96 | | -with urllib.request.urlopen(highcharts_more_url, timeout=30) as response: |
97 | | - highcharts_more_js = response.read().decode("utf-8") |
| 108 | +# Download Highcharts JS files (jsDelivr CDN with version pin) |
| 109 | +cdn_base = "https://cdn.jsdelivr.net/npm/highcharts@11.4" |
| 110 | +js_urls = {"highcharts": f"{cdn_base}/highcharts.js", "highcharts_more": f"{cdn_base}/highcharts-more.js"} |
| 111 | +js_modules = {} |
| 112 | +for name, url in js_urls.items(): |
| 113 | + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) |
| 114 | + with urllib.request.urlopen(req, timeout=30) as response: |
| 115 | + js_modules[name] = response.read().decode("utf-8") |
98 | 116 |
|
99 | 117 | # Generate HTML with inline scripts |
100 | 118 | chart_options_json = json.dumps(chart_options) |
101 | 119 | html_content = f"""<!DOCTYPE html> |
102 | 120 | <html> |
103 | 121 | <head> |
104 | 122 | <meta charset="utf-8"> |
105 | | - <script>{highcharts_js}</script> |
106 | | - <script>{highcharts_more_js}</script> |
| 123 | + <script>{js_modules["highcharts"]}</script> |
| 124 | + <script>{js_modules["highcharts_more"]}</script> |
107 | 125 | </head> |
108 | 126 | <body style="margin:0;"> |
109 | 127 | <div id="container" style="width: 4800px; height: 2700px;"></div> |
|
120 | 138 | f.write(html_content) |
121 | 139 | temp_path = f.name |
122 | 140 |
|
123 | | -# Also save the HTML for interactive viewing |
| 141 | +# Save HTML for interactive viewing |
124 | 142 | with open("plot.html", "w", encoding="utf-8") as f: |
125 | 143 | f.write(html_content) |
126 | 144 |
|
127 | 145 | # Take screenshot with headless Chrome |
128 | 146 | chrome_options = Options() |
129 | | -chrome_options.add_argument("--headless=new") |
| 147 | +chrome_options.add_argument("--headless") |
130 | 148 | chrome_options.add_argument("--no-sandbox") |
131 | 149 | chrome_options.add_argument("--disable-dev-shm-usage") |
132 | 150 | chrome_options.add_argument("--disable-gpu") |
133 | | -chrome_options.add_argument("--force-device-scale-factor=1") |
| 151 | +chrome_options.add_argument("--window-size=4800,2900") |
134 | 152 |
|
135 | 153 | driver = webdriver.Chrome(options=chrome_options) |
136 | | -driver.set_window_size(4900, 2900) |
137 | 154 | driver.get(f"file://{temp_path}") |
138 | 155 | time.sleep(5) |
139 | | - |
140 | | -# Take screenshot |
141 | 156 | driver.save_screenshot("plot_raw.png") |
142 | 157 | driver.quit() |
143 | 158 |
|
144 | | -# Crop/resize to exact 4800x2700 using PIL |
| 159 | +# Crop to exact 4800x2700 dimensions |
145 | 160 | img = Image.open("plot_raw.png") |
146 | | -final_img = Image.new("RGB", (4800, 2700), (255, 255, 255)) |
147 | | -final_img.paste(img.crop((0, 0, min(img.width, 4800), min(img.height, 2700))), (0, 0)) |
148 | | -final_img.save("plot.png") |
149 | | - |
150 | | -# Clean up |
| 161 | +img_cropped = img.crop((0, 0, 4800, 2700)) |
| 162 | +img_cropped.save("plot.png") |
151 | 163 | Path("plot_raw.png").unlink() |
| 164 | + |
152 | 165 | Path(temp_path).unlink() |
0 commit comments