Skip to content

Commit bf36445

Browse files
feat(highcharts): implement rose-basic (#5597)
## Implementation: `rose-basic` - python/highcharts Implements the **python/highcharts** version of `rose-basic`. **File:** `plots/rose-basic/implementations/python/highcharts.py` **Parent Issue:** #1003 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25152115281)* --------- 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 0122a38 commit bf36445

2 files changed

Lines changed: 256 additions & 166 deletions

File tree

Lines changed: 81 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
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
55
"""
66

7+
import os
78
import tempfile
89
import time
910
import urllib.request
@@ -16,66 +17,112 @@
1617
from selenium.webdriver.chrome.options import Options
1718

1819

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)
2030
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
2131
rainfall = [78, 52, 65, 45, 38, 25, 18, 22, 42, 68, 85, 92]
2232

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+
2347
# Create chart with polar/rose configuration
2448
chart = Chart(container="container")
2549
chart.options = HighchartsOptions()
2650

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+
}
2960

3061
# Title
3162
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},
3465
}
3566

3667
# 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}}
3869

3970
# X-axis (categories around the circle)
4071
chart.options.x_axis = {
4172
"categories": months,
4273
"tickmarkPlacement": "on",
4374
"lineWidth": 0,
44-
"labels": {"style": {"fontSize": "28px"}},
75+
"labels": {"style": {"fontSize": "28px", "color": INK_SOFT}},
76+
"gridLineColor": GRID,
4577
}
4678

47-
# Y-axis (radial - values extend from center)
79+
# Y-axis (radial values extend from center)
4880
chart.options.y_axis = {
4981
"min": 0,
5082
"gridLineInterpolation": "polygon",
5183
"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,
5487
}
5588

89+
# Pane — startAngle 0 places Jan at 12 o'clock; larger pane fills square canvas
90+
chart.options.pane = {"size": "88%", "startAngle": 0}
91+
5692
# Plot options for the rose/polar column
5793
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+
},
60102
}
61103

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+
}
64112

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}
67115

68-
# Create series with Python Blue color
116+
# Series with per-point value-proportional colors (colorByPoint via data objects)
69117
series = ColumnSeries()
70118
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)]
73120

74121
chart.add_series(series)
75122

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"
79126

80127
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
81128
highcharts_js = response.read().decode("utf-8")
@@ -92,34 +139,32 @@
92139
<script>{highcharts_js}</script>
93140
<script>{highcharts_more_js}</script>
94141
</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>
97144
<script>{html_str}</script>
98145
</body>
99146
</html>"""
100147

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:
103150
f.write(html_content)
104-
temp_path = f.name
105151

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:
108154
f.write(html_content)
155+
temp_path = f.name
109156

110-
# Take screenshot with headless Chrome
111157
chrome_options = Options()
112158
chrome_options.add_argument("--headless")
113159
chrome_options.add_argument("--no-sandbox")
114160
chrome_options.add_argument("--disable-dev-shm-usage")
115161
chrome_options.add_argument("--disable-gpu")
116-
chrome_options.add_argument("--window-size=4800,2700")
162+
chrome_options.add_argument("--window-size=3600,3600")
117163

118164
driver = webdriver.Chrome(options=chrome_options)
119165
driver.get(f"file://{temp_path}")
120166
time.sleep(5)
121-
driver.save_screenshot("plot.png")
167+
driver.save_screenshot(f"plot-{THEME}.png")
122168
driver.quit()
123169

124-
# Clean up temp file
125170
Path(temp_path).unlink()

0 commit comments

Comments
 (0)