Skip to content

Commit 125cc2b

Browse files
feat(highcharts): implement choropleth-basic (#3136)
## Implementation: `choropleth-basic` - highcharts Implements the **highcharts** version of `choropleth-basic`. **File:** `plots/choropleth-basic/implementations/highcharts.py` **Parent Issue:** #3069 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20627497556)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 5ef4b50 commit 125cc2b

2 files changed

Lines changed: 214 additions & 0 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
""" pyplots.ai
2+
choropleth-basic: Choropleth Map with Regional Coloring
3+
Library: highcharts unknown | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
import json
8+
import tempfile
9+
import time
10+
import urllib.request
11+
from pathlib import Path
12+
13+
import numpy as np
14+
from selenium import webdriver
15+
from selenium.webdriver.chrome.options import Options
16+
17+
18+
# Data - Population density by European country (synthetic data)
19+
np.random.seed(42)
20+
countries = [
21+
("de", "Germany", 234),
22+
("fr", "France", 119),
23+
("gb", "United Kingdom", 275),
24+
("it", "Italy", 206),
25+
("es", "Spain", 94),
26+
("pl", "Poland", 124),
27+
("nl", "Netherlands", 508),
28+
("be", "Belgium", 376),
29+
("se", "Sweden", 25),
30+
("at", "Austria", 107),
31+
("ch", "Switzerland", 215),
32+
("pt", "Portugal", 112),
33+
("cz", "Czech Republic", 137),
34+
("dk", "Denmark", 137),
35+
("no", "Norway", 15),
36+
("ie", "Ireland", 72),
37+
("fi", "Finland", 18),
38+
("gr", "Greece", 82),
39+
("hu", "Hungary", 107),
40+
("ro", "Romania", 84),
41+
]
42+
43+
# Format data for Highcharts map series
44+
map_data = [{"code": code.upper(), "name": name, "value": value} for code, name, value in countries]
45+
46+
# Create JavaScript configuration for Highmaps
47+
chart_config = {
48+
"chart": {
49+
"map": None, # Will be set by topology
50+
"width": 4800,
51+
"height": 2700,
52+
"backgroundColor": "#ffffff",
53+
"spacing": [80, 100, 80, 80],
54+
},
55+
"title": {
56+
"text": "choropleth-basic \u00b7 highcharts \u00b7 pyplots.ai",
57+
"style": {"fontSize": "72px", "fontWeight": "bold"},
58+
"y": 50,
59+
},
60+
"subtitle": {
61+
"text": "Population Density (people per km\u00b2)",
62+
"style": {"fontSize": "48px", "color": "#666666"},
63+
"y": 110,
64+
},
65+
"mapNavigation": {"enabled": False},
66+
"colorAxis": {
67+
"min": 0,
68+
"max": 550,
69+
"stops": [
70+
[0, "#f7fbff"],
71+
[0.2, "#c6dbef"],
72+
[0.4, "#6baed6"],
73+
[0.6, "#306998"],
74+
[0.8, "#2171b5"],
75+
[1, "#08306b"],
76+
],
77+
"labels": {"style": {"fontSize": "36px"}},
78+
},
79+
"legend": {
80+
"layout": "vertical",
81+
"align": "right",
82+
"verticalAlign": "middle",
83+
"floating": False,
84+
"backgroundColor": "rgba(255, 255, 255, 0.95)",
85+
"padding": 30,
86+
"symbolHeight": 700,
87+
"symbolWidth": 50,
88+
"itemStyle": {"fontSize": "36px"},
89+
"title": {"text": "Density<br/>(per km\u00b2)", "style": {"fontSize": "40px", "fontWeight": "bold"}},
90+
},
91+
"tooltip": {
92+
"style": {"fontSize": "36px"},
93+
"headerFormat": "",
94+
"pointFormat": "<b>{point.name}</b><br/>Density: {point.value} per km\u00b2",
95+
},
96+
"series": [
97+
{
98+
"type": "map",
99+
"name": "Population Density",
100+
"data": map_data,
101+
"joinBy": ["iso-a2", "code"],
102+
"states": {"hover": {"color": "#FFD43B"}},
103+
"dataLabels": {
104+
"enabled": True,
105+
"format": "{point.name}",
106+
"style": {"fontSize": "24px", "fontWeight": "normal", "textOutline": "3px white"},
107+
},
108+
"borderColor": "#ffffff",
109+
"borderWidth": 3,
110+
"nullColor": "#e0e0e0",
111+
}
112+
],
113+
}
114+
115+
# Convert to JSON for JavaScript
116+
chart_json = json.dumps(chart_config)
117+
118+
# Download required JavaScript files
119+
highcharts_url = "https://code.highcharts.com/maps/highmaps.js"
120+
europe_url = "https://code.highcharts.com/mapdata/custom/europe.topo.json"
121+
122+
with urllib.request.urlopen(highcharts_url, timeout=60) as response:
123+
highmaps_js = response.read().decode("utf-8")
124+
125+
with urllib.request.urlopen(europe_url, timeout=60) as response:
126+
europe_topo = response.read().decode("utf-8")
127+
128+
# Generate HTML with inline scripts
129+
html_content = f"""<!DOCTYPE html>
130+
<html>
131+
<head>
132+
<meta charset="utf-8">
133+
<script>{highmaps_js}</script>
134+
</head>
135+
<body style="margin:0;">
136+
<div id="container" style="width: 4800px; height: 2700px;"></div>
137+
<script>
138+
var topology = {europe_topo};
139+
var chartConfig = {chart_json};
140+
chartConfig.chart.map = topology;
141+
Highcharts.mapChart('container', chartConfig);
142+
</script>
143+
</body>
144+
</html>"""
145+
146+
# Save HTML for interactive version
147+
with open("plot.html", "w", encoding="utf-8") as f:
148+
standalone_html = f"""<!DOCTYPE html>
149+
<html>
150+
<head>
151+
<meta charset="utf-8">
152+
<script src="https://code.highcharts.com/maps/highmaps.js"></script>
153+
<script src="https://code.highcharts.com/mapdata/custom/europe.topo.json"></script>
154+
</head>
155+
<body style="margin:0;">
156+
<div id="container" style="width: 100%; height: 100vh;"></div>
157+
<script>
158+
fetch('https://code.highcharts.com/mapdata/custom/europe.topo.json')
159+
.then(response => response.json())
160+
.then(topology => {{
161+
var chartConfig = {chart_json};
162+
chartConfig.chart.map = topology;
163+
Highcharts.mapChart('container', chartConfig);
164+
}});
165+
</script>
166+
</body>
167+
</html>"""
168+
f.write(standalone_html)
169+
170+
# Write temp HTML and take screenshot
171+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
172+
f.write(html_content)
173+
temp_path = f.name
174+
175+
chrome_options = Options()
176+
chrome_options.add_argument("--headless")
177+
chrome_options.add_argument("--no-sandbox")
178+
chrome_options.add_argument("--disable-dev-shm-usage")
179+
chrome_options.add_argument("--disable-gpu")
180+
chrome_options.add_argument("--window-size=4800,2700")
181+
182+
driver = webdriver.Chrome(options=chrome_options)
183+
driver.get(f"file://{temp_path}")
184+
time.sleep(6) # Wait for chart to render (maps need more time)
185+
driver.save_screenshot("plot.png")
186+
driver.quit()
187+
188+
Path(temp_path).unlink() # Clean up temp file
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: highcharts
2+
specification_id: choropleth-basic
3+
created: '2025-12-31T21:33:15Z'
4+
updated: '2025-12-31T21:35:26Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20627497556
7+
issue: 3069
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/choropleth-basic/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/choropleth-basic/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/choropleth-basic/highcharts/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent use of Highmaps TopoJSON topology for Europe map
17+
- Clean sequential blue color palette that is colorblind-accessible
18+
- Good data labels showing country names directly on the map
19+
- Proper handling of missing data with gray coloring (nullColor)
20+
- Well-implemented vertical color legend with title and scale
21+
- Realistic population density data for European countries
22+
- Both interactive HTML and static PNG outputs generated
23+
weaknesses:
24+
- Some small country labels may be difficult to read (e.g., Monaco, Andorra, Luxembourg)
25+
- Legend could have more refined styling/spacing
26+
- Could benefit from hover tooltips being more prominent in static image context

0 commit comments

Comments
 (0)