Skip to content

Commit df1fbba

Browse files
feat(highcharts): implement polar-bar (#6510)
## Implementation: `polar-bar` - python/highcharts Implements the **python/highcharts** version of `polar-bar`. **File:** `plots/polar-bar/implementations/python/highcharts.py` **Parent Issue:** #2693 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25771976624)* --------- 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 8e5f1cb commit df1fbba

2 files changed

Lines changed: 225 additions & 194 deletions

File tree

plots/polar-bar/implementations/python/highcharts.py

Lines changed: 65 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
polar-bar: Polar Bar Chart (Wind Rose)
3-
Library: highcharts unknown | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-30
3+
Library: highcharts unknown | Python 3.13.13
4+
Quality: 92/100 | Updated: 2026-05-13
55
"""
66

7+
import os
78
import tempfile
89
import time
9-
import urllib.request
1010
from pathlib import Path
1111

1212
import numpy as np
13+
import requests
1314
from highcharts_core.chart import Chart
1415
from highcharts_core.options import HighchartsOptions
1516
from selenium import webdriver
1617
from selenium.webdriver.chrome.options import Options
1718

1819

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+
28+
# Okabe-Ito palette (first series is always #009E73)
29+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2", "#CC79A7"]
30+
1931
# Data - Wind direction frequency by speed category
2032
np.random.seed(42)
2133

@@ -29,14 +41,17 @@
2941
moderate = [5, 3, 4, 2, 3, 8, 15, 8] # 10-20 mph
3042
strong = [2, 1, 2, 1, 1, 4, 8, 5] # 20+ mph
3143

32-
# Download Highcharts JS modules
33-
highcharts_url = "https://code.highcharts.com/highcharts.js"
34-
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
35-
highcharts_js = response.read().decode("utf-8")
44+
# Download Highcharts JS modules from jsDelivr CDN
45+
headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"}
46+
highcharts_url = "https://cdn.jsdelivr.net/npm/highcharts@11.4.3/highcharts.min.js"
47+
response = requests.get(highcharts_url, timeout=30, headers=headers)
48+
response.raise_for_status()
49+
highcharts_js = response.text
3650

37-
highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
38-
with urllib.request.urlopen(highcharts_more_url, timeout=30) as response:
39-
highcharts_more_js = response.read().decode("utf-8")
51+
highcharts_more_url = "https://cdn.jsdelivr.net/npm/highcharts@11.4.3/highcharts-more.min.js"
52+
response_more = requests.get(highcharts_more_url, timeout=30, headers=headers)
53+
response_more.raise_for_status()
54+
highcharts_more_js = response_more.text
4055

4156
# Create chart
4257
chart = Chart(container="container")
@@ -46,46 +61,46 @@
4661
chart.options.chart = {
4762
"polar": True,
4863
"type": "column",
49-
"width": 3600,
50-
"height": 3800,
51-
"backgroundColor": "#ffffff",
52-
"marginBottom": 250, # space for legend
64+
"width": 4800,
65+
"height": 2700,
66+
"backgroundColor": PAGE_BG,
67+
"marginBottom": 250,
5368
}
5469

5570
# Title
5671
chart.options.title = {
57-
"text": "polar-bar · highcharts · pyplots.ai",
58-
"style": {"fontSize": "48px", "fontWeight": "bold"},
72+
"text": "polar-bar · highcharts · anyplot.ai",
73+
"style": {"fontSize": "28px", "fontWeight": "bold", "color": INK},
5974
}
6075

61-
chart.options.subtitle = {"text": "Wind Speed Distribution by Direction", "style": {"fontSize": "32px"}}
76+
chart.options.subtitle = {
77+
"text": "Wind Speed Distribution by Direction",
78+
"style": {"fontSize": "22px", "color": INK_SOFT},
79+
}
6280

6381
# Pane configuration for polar chart
64-
chart.options.pane = {
65-
"size": "60%",
66-
"startAngle": 0,
67-
"endAngle": 360,
68-
"center": ["40%", "50%"], # move chart left to make room for legend on right
69-
}
82+
chart.options.pane = {"size": "70%", "startAngle": 0, "endAngle": 360, "center": ["50%", "50%"]}
7083

7184
# X axis (angular - directions)
7285
chart.options.x_axis = {
7386
"categories": directions,
7487
"tickmarkPlacement": "on",
7588
"lineWidth": 0,
76-
"labels": {"style": {"fontSize": "36px", "fontWeight": "bold"}, "distance": 25},
89+
"labels": {"style": {"fontSize": "22px", "color": INK_SOFT}, "distance": 30},
7790
}
7891

7992
# Y axis (radial - frequency)
8093
chart.options.y_axis = {
8194
"min": 0,
8295
"endOnTick": False,
8396
"showLastLabel": True,
84-
"title": {"text": "Frequency (%)", "style": {"fontSize": "28px"}},
85-
"labels": {"style": {"fontSize": "24px"}},
97+
"title": {"text": "Frequency", "style": {"fontSize": "22px", "color": INK}},
98+
"labels": {"style": {"fontSize": "18px", "color": INK_SOFT}},
8699
"reversedStacks": False,
87-
"gridLineColor": "#cccccc",
100+
"gridLineColor": GRID,
88101
"gridLineWidth": 1,
102+
"lineColor": INK_SOFT,
103+
"tickColor": INK_SOFT,
89104
}
90105

91106
# Legend
@@ -94,29 +109,29 @@
94109
"align": "right",
95110
"verticalAlign": "middle",
96111
"layout": "vertical",
97-
"itemStyle": {"fontSize": "32px"},
112+
"itemStyle": {"fontSize": "18px", "color": INK_SOFT},
98113
"symbolRadius": 0,
99-
"symbolHeight": 28,
100-
"symbolWidth": 40,
101-
"itemMarginBottom": 20,
114+
"symbolHeight": 20,
115+
"symbolWidth": 28,
116+
"itemMarginBottom": 15,
102117
"x": -50,
118+
"backgroundColor": ELEVATED_BG,
119+
"borderColor": INK_SOFT,
120+
"borderWidth": 1,
103121
}
104122

105123
# Plot options for stacked column
106124
chart.options.plot_options = {
107125
"series": {"stacking": "normal", "shadow": False, "groupPadding": 0, "pointPlacement": "on"},
108-
"column": {"pointPadding": 0, "groupPadding": 0, "borderWidth": 2, "borderColor": "#ffffff"},
126+
"column": {"pointPadding": 0, "groupPadding": 0, "borderWidth": 1, "borderColor": PAGE_BG},
109127
}
110128

111-
# Colors - colorblind safe palette
112-
colors = ["#306998", "#FFD43B", "#9467BD", "#17BECF"]
113-
114-
# Add series (stacked from bottom to top)
129+
# Add series with Okabe-Ito colors
115130
series_data = [
116-
{"name": "Calm (<1 mph)", "data": calm, "color": colors[0]},
117-
{"name": "Light (1-10 mph)", "data": light, "color": colors[1]},
118-
{"name": "Moderate (10-20 mph)", "data": moderate, "color": colors[2]},
119-
{"name": "Strong (>20 mph)", "data": strong, "color": colors[3]},
131+
{"name": "Calm (<1 mph)", "data": calm, "color": OKABE_ITO[0]},
132+
{"name": "Light (1-10 mph)", "data": light, "color": OKABE_ITO[1]},
133+
{"name": "Moderate (10-20 mph)", "data": moderate, "color": OKABE_ITO[2]},
134+
{"name": "Strong (>20 mph)", "data": strong, "color": OKABE_ITO[3]},
120135
]
121136

122137
chart.options.series = series_data
@@ -130,13 +145,17 @@
130145
<script>{highcharts_js}</script>
131146
<script>{highcharts_more_js}</script>
132147
</head>
133-
<body style="margin:0; background:#ffffff;">
134-
<div id="container" style="width: 3600px; height: 3800px;"></div>
148+
<body style="margin:0; background:{PAGE_BG};">
149+
<div id="container" style="width: 4800px; height: 2700px;"></div>
135150
<script>{html_str}</script>
136151
</body>
137152
</html>"""
138153

139-
# Write temp HTML and take screenshot
154+
# Save theme-suffixed HTML
155+
with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f:
156+
f.write(html_content)
157+
158+
# Write temp HTML and take screenshot for PNG
140159
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
141160
f.write(html_content)
142161
temp_path = f.name
@@ -146,30 +165,12 @@
146165
chrome_options.add_argument("--no-sandbox")
147166
chrome_options.add_argument("--disable-dev-shm-usage")
148167
chrome_options.add_argument("--disable-gpu")
149-
chrome_options.add_argument("--window-size=3600,3800") # extra height for legend
168+
chrome_options.add_argument("--window-size=4800,2700")
150169

151170
driver = webdriver.Chrome(options=chrome_options)
152171
driver.get(f"file://{temp_path}")
153172
time.sleep(5)
154-
driver.save_screenshot("plot.png")
173+
driver.save_screenshot(f"plot-{THEME}.png")
155174
driver.quit()
156175

157176
Path(temp_path).unlink()
158-
159-
# Also save HTML for interactive version
160-
html_export = f"""<!DOCTYPE html>
161-
<html>
162-
<head>
163-
<meta charset="utf-8">
164-
<title>polar-bar · highcharts · pyplots.ai</title>
165-
<script src="https://code.highcharts.com/highcharts.js"></script>
166-
<script src="https://code.highcharts.com/highcharts-more.js"></script>
167-
</head>
168-
<body style="margin:0; background:#ffffff;">
169-
<div id="container" style="width: 100%; height: 100vh;"></div>
170-
<script>{html_str}</script>
171-
</body>
172-
</html>"""
173-
174-
with open("plot.html", "w", encoding="utf-8") as f:
175-
f.write(html_export)

0 commit comments

Comments
 (0)