|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | rose-basic: Basic Rose Chart |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-23 |
| 3 | +Library: highcharts unknown | Python 3.13.13 |
| 4 | +Quality: 85/100 | Updated: 2026-04-30 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
7 | 8 | import tempfile |
8 | 9 | import time |
9 | 10 | import urllib.request |
|
16 | 17 | from selenium.webdriver.chrome.options import Options |
17 | 18 |
|
18 | 19 |
|
19 | | -# Data - Monthly rainfall in mm (showing natural 12-month cycle) |
| 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 | +BRAND = "#009E73" # Okabe-Ito position 1 |
| 28 | + |
| 29 | +# Data - Monthly rainfall in mm (UK-like temperate oceanic climate) |
20 | 30 | months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
21 | 31 | rainfall = [78, 52, 65, 45, 38, 25, 18, 22, 42, 68, 85, 92] |
22 | 32 |
|
| 33 | +# Value-proportional color gradient: light mint (low) → brand green (peak) |
| 34 | +# Encodes rainfall intensity visually — wet months appear darker and more saturated |
| 35 | +_lo = (200, 232, 222) # #C8E8DE light mint base |
| 36 | +_hi = (0, 158, 115) # #009E73 brand green |
| 37 | +_mn, _mx = min(rainfall), max(rainfall) |
| 38 | +colors = [ |
| 39 | + "#{:02X}{:02X}{:02X}".format( |
| 40 | + int(_lo[0] + (v - _mn) / (_mx - _mn) * (_hi[0] - _lo[0])), |
| 41 | + int(_lo[1] + (v - _mn) / (_mx - _mn) * (_hi[1] - _lo[1])), |
| 42 | + int(_lo[2] + (v - _mn) / (_mx - _mn) * (_hi[2] - _lo[2])), |
| 43 | + ) |
| 44 | + for v in rainfall |
| 45 | +] |
| 46 | + |
23 | 47 | # Create chart with polar/rose configuration |
24 | 48 | chart = Chart(container="container") |
25 | 49 | chart.options = HighchartsOptions() |
26 | 50 |
|
27 | | -# Chart configuration for polar column (rose chart) |
28 | | -chart.options.chart = {"polar": True, "type": "column", "width": 4800, "height": 2700, "backgroundColor": "#ffffff"} |
| 51 | +# Square canvas — optimal geometry for a circular rose chart |
| 52 | +chart.options.chart = { |
| 53 | + "polar": True, |
| 54 | + "type": "column", |
| 55 | + "width": 3600, |
| 56 | + "height": 3600, |
| 57 | + "backgroundColor": PAGE_BG, |
| 58 | + "style": {"color": INK}, |
| 59 | +} |
29 | 60 |
|
30 | 61 | # Title |
31 | 62 | chart.options.title = { |
32 | | - "text": "rose-basic · highcharts · pyplots.ai", |
33 | | - "style": {"fontSize": "48px", "fontWeight": "bold"}, |
| 63 | + "text": "rose-basic · highcharts · anyplot.ai", |
| 64 | + "style": {"fontSize": "48px", "fontWeight": "bold", "color": INK}, |
34 | 65 | } |
35 | 66 |
|
36 | 67 | # Subtitle for context |
37 | | -chart.options.subtitle = {"text": "Monthly Rainfall (mm)", "style": {"fontSize": "32px"}} |
| 68 | +chart.options.subtitle = {"text": "Monthly Rainfall (mm)", "style": {"fontSize": "32px", "color": INK_SOFT}} |
38 | 69 |
|
39 | 70 | # X-axis (categories around the circle) |
40 | 71 | chart.options.x_axis = { |
41 | 72 | "categories": months, |
42 | 73 | "tickmarkPlacement": "on", |
43 | 74 | "lineWidth": 0, |
44 | | - "labels": {"style": {"fontSize": "28px"}}, |
| 75 | + "labels": {"style": {"fontSize": "28px", "color": INK_SOFT}}, |
| 76 | + "gridLineColor": GRID, |
45 | 77 | } |
46 | 78 |
|
47 | | -# Y-axis (radial - values extend from center) |
| 79 | +# Y-axis (radial — values extend from center) |
48 | 80 | chart.options.y_axis = { |
49 | 81 | "min": 0, |
50 | 82 | "gridLineInterpolation": "polygon", |
51 | 83 | "lineWidth": 0, |
52 | | - "labels": {"style": {"fontSize": "24px"}}, |
53 | | - "title": {"text": "Rainfall (mm)", "style": {"fontSize": "28px"}}, |
| 84 | + "labels": {"format": "{value} mm", "style": {"fontSize": "22px", "color": INK_SOFT}}, |
| 85 | + "title": {"text": "Rainfall (mm)", "style": {"fontSize": "28px", "color": INK}}, |
| 86 | + "gridLineColor": GRID, |
54 | 87 | } |
55 | 88 |
|
| 89 | +# Pane — startAngle 0 places Jan at 12 o'clock; larger pane fills square canvas |
| 90 | +chart.options.pane = {"size": "88%", "startAngle": 0} |
| 91 | + |
56 | 92 | # Plot options for the rose/polar column |
57 | 93 | chart.options.plot_options = { |
58 | | - "column": {"pointPadding": 0, "groupPadding": 0, "borderWidth": 2, "borderColor": "#ffffff"}, |
59 | | - "series": {"dataLabels": {"enabled": True, "format": "{y}", "style": {"fontSize": "20px", "fontWeight": "normal"}}}, |
| 94 | + "column": {"pointPadding": 0, "groupPadding": 0, "borderWidth": 2, "borderColor": PAGE_BG}, |
| 95 | + "series": { |
| 96 | + "dataLabels": { |
| 97 | + "enabled": True, |
| 98 | + "format": "{y}", |
| 99 | + "style": {"fontSize": "26px", "fontWeight": "normal", "color": INK_SOFT}, |
| 100 | + } |
| 101 | + }, |
60 | 102 | } |
61 | 103 |
|
62 | | -# Pane configuration for polar chart |
63 | | -chart.options.pane = {"size": "85%", "startAngle": -15} |
| 104 | +# Rich tooltip using Highcharts pointFormat — highlights the per-point color swatch |
| 105 | +chart.options.tooltip = { |
| 106 | + "headerFormat": "<span style='font-size:24px'><b>{point.key}</b></span><br/>", |
| 107 | + "pointFormat": "<span style='color:{point.color}'>●</span> Rainfall: <b>{point.y} mm</b>", |
| 108 | + "backgroundColor": ELEVATED_BG, |
| 109 | + "style": {"color": INK, "fontSize": "22px"}, |
| 110 | + "borderColor": INK_SOFT, |
| 111 | +} |
64 | 112 |
|
65 | | -# Legend configuration |
66 | | -chart.options.legend = {"enabled": True, "itemStyle": {"fontSize": "28px"}} |
| 113 | +# Disable legend — redundant for a single-series chart |
| 114 | +chart.options.legend = {"enabled": False} |
67 | 115 |
|
68 | | -# Create series with Python Blue color |
| 116 | +# Series with per-point value-proportional colors (colorByPoint via data objects) |
69 | 117 | series = ColumnSeries() |
70 | 118 | series.name = "Rainfall" |
71 | | -series.data = rainfall |
72 | | -series.color = "#306998" |
| 119 | +series.data = [{"y": v, "color": c} for v, c in zip(rainfall, colors, strict=True)] |
73 | 120 |
|
74 | 121 | chart.add_series(series) |
75 | 122 |
|
76 | | -# Download Highcharts JS and Highcharts More (for polar charts) |
77 | | -highcharts_url = "https://code.highcharts.com/highcharts.js" |
78 | | -highcharts_more_url = "https://code.highcharts.com/highcharts-more.js" |
| 123 | +# Download Highcharts JS and Highcharts More (required for polar charts) |
| 124 | +highcharts_url = "https://cdn.jsdelivr.net/npm/highcharts@latest/highcharts.js" |
| 125 | +highcharts_more_url = "https://cdn.jsdelivr.net/npm/highcharts@latest/highcharts-more.js" |
79 | 126 |
|
80 | 127 | with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
81 | 128 | highcharts_js = response.read().decode("utf-8") |
|
92 | 139 | <script>{highcharts_js}</script> |
93 | 140 | <script>{highcharts_more_js}</script> |
94 | 141 | </head> |
95 | | -<body style="margin:0;"> |
96 | | - <div id="container" style="width: 4800px; height: 2700px;"></div> |
| 142 | +<body style="margin:0; background:{PAGE_BG};"> |
| 143 | + <div id="container" style="width: 3600px; height: 3600px;"></div> |
97 | 144 | <script>{html_str}</script> |
98 | 145 | </body> |
99 | 146 | </html>""" |
100 | 147 |
|
101 | | -# Write temp HTML |
102 | | -with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
| 148 | +# Save HTML artifact for the site |
| 149 | +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: |
103 | 150 | f.write(html_content) |
104 | | - temp_path = f.name |
105 | 151 |
|
106 | | -# Save HTML for interactive version |
107 | | -with open("plot.html", "w", encoding="utf-8") as f: |
| 152 | +# Write temp HTML and take screenshot for the PNG artifact |
| 153 | +with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
108 | 154 | f.write(html_content) |
| 155 | + temp_path = f.name |
109 | 156 |
|
110 | | -# Take screenshot with headless Chrome |
111 | 157 | chrome_options = Options() |
112 | 158 | chrome_options.add_argument("--headless") |
113 | 159 | chrome_options.add_argument("--no-sandbox") |
114 | 160 | chrome_options.add_argument("--disable-dev-shm-usage") |
115 | 161 | chrome_options.add_argument("--disable-gpu") |
116 | | -chrome_options.add_argument("--window-size=4800,2700") |
| 162 | +chrome_options.add_argument("--window-size=3600,3600") |
117 | 163 |
|
118 | 164 | driver = webdriver.Chrome(options=chrome_options) |
119 | 165 | driver.get(f"file://{temp_path}") |
120 | 166 | time.sleep(5) |
121 | | -driver.save_screenshot("plot.png") |
| 167 | +driver.save_screenshot(f"plot-{THEME}.png") |
122 | 168 | driver.quit() |
123 | 169 |
|
124 | | -# Clean up temp file |
125 | 170 | Path(temp_path).unlink() |
0 commit comments