Skip to content

Commit 7287c52

Browse files
feat(highcharts): implement bubble-packed (#1088)
## Implementation: `bubble-packed` - highcharts Implements the **highcharts** version of `bubble-packed`. **File:** `plots/bubble-packed/implementations/highcharts.py` **Parent Issue:** #992 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20279594591)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 222fe73 commit 7287c52

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
"""
2+
bubble-packed: Basic Packed Bubble Chart
3+
Library: highcharts
4+
"""
5+
6+
import tempfile
7+
import time
8+
import urllib.request
9+
from pathlib import Path
10+
11+
from highcharts_core.chart import Chart
12+
from highcharts_core.options import HighchartsOptions
13+
from PIL import Image
14+
from selenium import webdriver
15+
from selenium.webdriver.chrome.options import Options
16+
17+
18+
# Data - Company market share by sector
19+
# Packed bubbles group by sector with size representing market value
20+
data = [
21+
# Technology sector
22+
{
23+
"name": "Technology",
24+
"data": [
25+
{"name": "Software", "value": 850},
26+
{"name": "Hardware", "value": 420},
27+
{"name": "Cloud Services", "value": 680},
28+
{"name": "Semiconductors", "value": 390},
29+
{"name": "Cybersecurity", "value": 280},
30+
],
31+
},
32+
# Finance sector
33+
{
34+
"name": "Finance",
35+
"data": [
36+
{"name": "Banking", "value": 720},
37+
{"name": "Insurance", "value": 480},
38+
{"name": "Asset Management", "value": 350},
39+
{"name": "Fintech", "value": 260},
40+
],
41+
},
42+
# Healthcare sector
43+
{
44+
"name": "Healthcare",
45+
"data": [
46+
{"name": "Pharmaceuticals", "value": 580},
47+
{"name": "Medical Devices", "value": 320},
48+
{"name": "Biotech", "value": 420},
49+
{"name": "Healthcare Services", "value": 240},
50+
],
51+
},
52+
# Energy sector
53+
{
54+
"name": "Energy",
55+
"data": [
56+
{"name": "Oil & Gas", "value": 550},
57+
{"name": "Renewables", "value": 380},
58+
{"name": "Utilities", "value": 290},
59+
],
60+
},
61+
# Consumer sector
62+
{
63+
"name": "Consumer",
64+
"data": [
65+
{"name": "Retail", "value": 460},
66+
{"name": "Food & Beverage", "value": 340},
67+
{"name": "Automotive", "value": 510},
68+
{"name": "Entertainment", "value": 270},
69+
],
70+
},
71+
]
72+
73+
# Colorblind-safe palette for sectors
74+
colors = ["#306998", "#FFD43B", "#9467BD", "#17BECF", "#8C564B"]
75+
76+
# Create chart
77+
chart = Chart(container="container")
78+
chart.options = HighchartsOptions()
79+
80+
# Chart configuration
81+
chart.options.chart = {"type": "packedbubble", "width": 4800, "height": 2700, "backgroundColor": "#ffffff"}
82+
83+
# Title
84+
chart.options.title = {
85+
"text": "Market Sectors · bubble-packed · highcharts · pyplots.ai",
86+
"style": {"fontSize": "64px", "fontWeight": "bold"},
87+
}
88+
89+
# Subtitle
90+
chart.options.subtitle = {"text": "Circle size represents market value ($B)", "style": {"fontSize": "36px"}}
91+
92+
# Tooltip
93+
chart.options.tooltip = {
94+
"useHTML": True,
95+
"style": {"fontSize": "28px"},
96+
"pointFormat": "<b>{point.name}</b>: ${point.value}B",
97+
}
98+
99+
# Legend
100+
chart.options.legend = {"enabled": True, "itemStyle": {"fontSize": "36px"}, "symbolHeight": 24, "symbolWidth": 24}
101+
102+
# Plot options for packed bubble
103+
chart.options.plot_options = {
104+
"packedbubble": {
105+
"minSize": "20%",
106+
"maxSize": "100%",
107+
"zMin": 0,
108+
"zMax": 1000,
109+
"layoutAlgorithm": {
110+
"gravitationalConstant": 0.02,
111+
"splitSeries": True,
112+
"seriesInteraction": False,
113+
"dragBetweenSeries": False,
114+
"parentNodeLimit": True,
115+
"parentNodeOptions": {"bubblePadding": 20},
116+
},
117+
"dataLabels": {
118+
"enabled": True,
119+
"format": "{point.name}",
120+
"filter": {"property": "y", "operator": ">", "value": 300},
121+
"style": {"fontSize": "24px", "fontWeight": "bold", "color": "white", "textOutline": "2px contrast"},
122+
},
123+
}
124+
}
125+
126+
# Add series with colors
127+
series_list = []
128+
for i, sector in enumerate(data):
129+
series_config = {
130+
"type": "packedbubble",
131+
"name": sector["name"],
132+
"data": sector["data"],
133+
"color": colors[i % len(colors)],
134+
}
135+
series_list.append(series_config)
136+
137+
chart.options.series = series_list
138+
139+
# Download Highcharts JS and highcharts-more.js for packed bubble support
140+
highcharts_url = "https://code.highcharts.com/highcharts.js"
141+
highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
142+
143+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
144+
highcharts_js = response.read().decode("utf-8")
145+
146+
with urllib.request.urlopen(highcharts_more_url, timeout=30) as response:
147+
highcharts_more_js = response.read().decode("utf-8")
148+
149+
# Generate HTML with inline scripts
150+
html_str = chart.to_js_literal()
151+
html_content = f"""<!DOCTYPE html>
152+
<html>
153+
<head>
154+
<meta charset="utf-8">
155+
<script>{highcharts_js}</script>
156+
<script>{highcharts_more_js}</script>
157+
</head>
158+
<body style="margin:0;">
159+
<div id="container" style="width: 4800px; height: 2700px;"></div>
160+
<script>{html_str}</script>
161+
</body>
162+
</html>"""
163+
164+
# Save HTML for interactive version
165+
with open("plot.html", "w", encoding="utf-8") as f:
166+
standalone_html = f"""<!DOCTYPE html>
167+
<html>
168+
<head>
169+
<meta charset="utf-8">
170+
<script src="https://code.highcharts.com/highcharts.js"></script>
171+
<script src="https://code.highcharts.com/highcharts-more.js"></script>
172+
</head>
173+
<body style="margin:0;">
174+
<div id="container" style="width: 100%; height: 100vh;"></div>
175+
<script>{html_str}</script>
176+
</body>
177+
</html>"""
178+
f.write(standalone_html)
179+
180+
# Write temp HTML and take screenshot
181+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
182+
f.write(html_content)
183+
temp_path = f.name
184+
185+
chrome_options = Options()
186+
chrome_options.add_argument("--headless")
187+
chrome_options.add_argument("--no-sandbox")
188+
chrome_options.add_argument("--disable-dev-shm-usage")
189+
chrome_options.add_argument("--disable-gpu")
190+
chrome_options.add_argument("--window-size=4800,2900")
191+
192+
driver = webdriver.Chrome(options=chrome_options)
193+
driver.get(f"file://{temp_path}")
194+
time.sleep(5) # Wait for chart to render
195+
driver.save_screenshot("plot_raw.png")
196+
driver.quit()
197+
198+
# Crop to exact 4800x2700 dimensions
199+
img = Image.open("plot_raw.png")
200+
img_cropped = img.crop((0, 0, 4800, 2700))
201+
img_cropped.save("plot.png")
202+
Path("plot_raw.png").unlink()
203+
204+
Path(temp_path).unlink() # Clean up temp file
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Per-library metadata for highcharts implementation of bubble-packed
2+
# Auto-generated by impl-generate.yml
3+
4+
library: highcharts
5+
specification_id: bubble-packed
6+
7+
# Preview URLs (filled by workflow)
8+
preview_url: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/highcharts/plot.png
9+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/highcharts/plot_thumb.png
10+
preview_html: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/highcharts/plot.html
11+
12+
current:
13+
version: 0
14+
generated_at: 2025-12-16T19:54:43Z
15+
generated_by: claude-opus-4-5-20251101
16+
workflow_run: 20279594591
17+
issue: 992
18+
quality_score: 94
19+
# Version info (filled by workflow)
20+
python_version: "3.13.11"
21+
library_version: "unknown"
22+
23+
history: []

0 commit comments

Comments
 (0)