|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | line-timeseries: Time Series Line Plot |
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: 90/100 | Updated: 2026-05-09 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import math |
| 8 | +import os |
8 | 9 | import random |
9 | 10 | import tempfile |
10 | 11 | import time |
|
19 | 20 | from selenium.webdriver.chrome.options import Options |
20 | 21 |
|
21 | 22 |
|
| 23 | +# Read theme from environment |
| 24 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 25 | + |
| 26 | +# Theme-adaptive color palette |
| 27 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 28 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 29 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 30 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 31 | +GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" |
| 32 | + |
| 33 | +# Okabe-Ito palette (first series must be #009E73) |
| 34 | +BRAND = "#009E73" |
| 35 | + |
22 | 36 | # Data - Daily temperature readings over one year (365 days) |
23 | 37 | random.seed(42) |
24 | 38 | start_date = datetime(2024, 1, 1) |
|
45 | 59 | chart = Chart(container="container") |
46 | 60 | chart.options = HighchartsOptions() |
47 | 61 |
|
48 | | -# Chart configuration |
| 62 | +# Chart configuration - theme-adaptive background |
49 | 63 | chart.options.chart = { |
50 | 64 | "type": "line", |
51 | 65 | "width": 4800, |
52 | 66 | "height": 2700, |
53 | | - "backgroundColor": "#ffffff", |
| 67 | + "backgroundColor": PAGE_BG, |
54 | 68 | "spacingTop": 60, |
55 | 69 | "spacingBottom": 100, |
56 | 70 | "spacingLeft": 80, |
57 | 71 | "spacingRight": 80, |
58 | 72 | } |
59 | 73 |
|
60 | | -# Title |
| 74 | +# Title - theme-adaptive color |
61 | 75 | chart.options.title = { |
62 | 76 | "text": "line-timeseries · highcharts · pyplots.ai", |
63 | | - "style": {"fontSize": "56px", "fontWeight": "bold"}, |
| 77 | + "style": {"fontSize": "56px", "fontWeight": "bold", "color": INK}, |
64 | 78 | "margin": 40, |
65 | 79 | } |
66 | 80 |
|
67 | | -chart.options.subtitle = { |
68 | | - "text": "Daily Temperature Readings - 2024", |
69 | | - "style": {"fontSize": "36px", "color": "#666666"}, |
70 | | -} |
| 81 | +# Subtitle - theme-adaptive color |
| 82 | +chart.options.subtitle = {"text": "Daily Temperature Readings - 2024", "style": {"fontSize": "36px", "color": INK_SOFT}} |
71 | 83 |
|
72 | 84 | # X-axis (datetime) - with monthly tick intervals to prevent label overlap |
73 | 85 | chart.options.x_axis = { |
74 | 86 | "type": "datetime", |
75 | | - "title": {"text": "Date", "style": {"fontSize": "36px"}, "margin": 25}, |
76 | | - "labels": {"style": {"fontSize": "28px"}}, |
| 87 | + "title": {"text": "Date", "style": {"fontSize": "36px", "color": INK}, "margin": 25}, |
| 88 | + "labels": {"style": {"fontSize": "28px", "color": INK_SOFT}}, |
77 | 89 | "gridLineWidth": 1, |
78 | | - "gridLineColor": "rgba(0, 0, 0, 0.1)", |
| 90 | + "gridLineColor": GRID, |
79 | 91 | "tickInterval": 30 * 24 * 3600 * 1000, # Monthly ticks (30 days in ms) |
80 | 92 | "dateTimeLabelFormats": {"month": "%b %Y"}, |
| 93 | + "lineColor": INK_SOFT, |
| 94 | + "tickColor": INK_SOFT, |
81 | 95 | } |
82 | 96 |
|
83 | | -# Y-axis |
| 97 | +# Y-axis - theme-adaptive colors |
84 | 98 | chart.options.y_axis = { |
85 | | - "title": {"text": "Temperature (°C)", "style": {"fontSize": "36px"}, "margin": 25}, |
86 | | - "labels": {"style": {"fontSize": "28px"}}, |
| 99 | + "title": {"text": "Temperature (°C)", "style": {"fontSize": "36px", "color": INK}, "margin": 25}, |
| 100 | + "labels": {"style": {"fontSize": "28px", "color": INK_SOFT}}, |
87 | 101 | "gridLineWidth": 1, |
88 | | - "gridLineColor": "rgba(0, 0, 0, 0.1)", |
| 102 | + "gridLineColor": GRID, |
| 103 | + "lineColor": INK_SOFT, |
| 104 | + "tickColor": INK_SOFT, |
89 | 105 | } |
90 | 106 |
|
91 | | -# Legend |
92 | | -chart.options.legend = {"enabled": True, "itemStyle": {"fontSize": "32px"}, "margin": 30} |
| 107 | +# Legend - theme-adaptive styling with improved visibility |
| 108 | +chart.options.legend = { |
| 109 | + "enabled": True, |
| 110 | + "itemStyle": {"fontSize": "32px", "color": INK_SOFT}, |
| 111 | + "backgroundColor": ELEVATED_BG, |
| 112 | + "borderColor": INK_SOFT, |
| 113 | + "borderWidth": 1, |
| 114 | + "margin": 30, |
| 115 | +} |
93 | 116 |
|
94 | | -# Tooltip |
95 | | -chart.options.tooltip = {"xDateFormat": "%A, %b %d, %Y", "valueSuffix": " °C", "style": {"fontSize": "28px"}} |
| 117 | +# Tooltip - theme-adaptive styling |
| 118 | +chart.options.tooltip = { |
| 119 | + "xDateFormat": "%A, %b %d, %Y", |
| 120 | + "valueSuffix": " °C", |
| 121 | + "style": {"fontSize": "28px", "color": INK}, |
| 122 | + "backgroundColor": ELEVATED_BG, |
| 123 | + "borderColor": INK_SOFT, |
| 124 | + "borderRadius": 4, |
| 125 | +} |
96 | 126 |
|
97 | 127 | # Plot options |
98 | 128 | chart.options.plot_options = { |
99 | 129 | "line": {"lineWidth": 5, "marker": {"enabled": False}, "states": {"hover": {"lineWidth": 6}}} |
100 | 130 | } |
101 | 131 |
|
102 | | -# Add series |
| 132 | +# Add series with Okabe-Ito brand color |
103 | 133 | series = LineSeries() |
104 | 134 | series.name = "Temperature" |
105 | 135 | series.data = data_points |
106 | | -series.color = "#306998" # Python Blue |
| 136 | +series.color = BRAND # #009E73 - Okabe-Ito position 1 |
107 | 137 |
|
108 | 138 | chart.add_series(series) |
109 | 139 |
|
110 | | -# Export to PNG via Selenium |
111 | | -highcharts_url = "https://code.highcharts.com/highcharts.js" |
112 | | -with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
| 140 | +# Download Highcharts JS (required for headless Chrome) |
| 141 | +highcharts_url = "https://unpkg.com/highcharts@11/highcharts.js" |
| 142 | +req = urllib.request.Request( |
| 143 | + highcharts_url, headers={"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"} |
| 144 | +) |
| 145 | +with urllib.request.urlopen(req, timeout=30) as response: |
113 | 146 | highcharts_js = response.read().decode("utf-8") |
114 | 147 |
|
| 148 | +# Generate chart JS |
115 | 149 | html_str = chart.to_js_literal() |
| 150 | + |
| 151 | +# Create HTML content with proper theme-adaptive styling |
116 | 152 | html_content = f"""<!DOCTYPE html> |
117 | 153 | <html> |
118 | 154 | <head> |
119 | 155 | <meta charset="utf-8"> |
120 | 156 | <script>{highcharts_js}</script> |
121 | 157 | </head> |
122 | | -<body style="margin:0;"> |
| 158 | +<body style="margin:0; background:{PAGE_BG};"> |
123 | 159 | <div id="container" style="width: 4800px; height: 2700px;"></div> |
124 | 160 | <script>{html_str}</script> |
125 | 161 | </body> |
126 | 162 | </html>""" |
127 | 163 |
|
128 | | -# Save HTML version |
129 | | -with open("plot.html", "w", encoding="utf-8") as f: |
| 164 | +# Save HTML version with theme suffix |
| 165 | +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: |
130 | 166 | cdn_html = ( |
131 | 167 | """<!DOCTYPE html> |
132 | 168 | <html> |
133 | 169 | <head> |
134 | 170 | <meta charset="utf-8"> |
135 | 171 | <script src="https://code.highcharts.com/highcharts.js"></script> |
136 | 172 | </head> |
137 | | -<body style="margin:0;"> |
| 173 | +<body style="margin:0; background:""" |
| 174 | + + PAGE_BG |
| 175 | + + """;"> |
138 | 176 | <div id="container" style="width: 100%; height: 600px;"></div> |
139 | 177 | <script>""" |
140 | 178 | + html_str |
|
162 | 200 |
|
163 | 201 | # Screenshot the chart container element for exact dimensions |
164 | 202 | container = driver.find_element("id", "container") |
165 | | -container.screenshot("plot.png") |
| 203 | +container.screenshot(f"plot-{THEME}.png") |
166 | 204 | driver.quit() |
167 | 205 |
|
168 | 206 | Path(temp_path).unlink() |
0 commit comments