Skip to content

Commit ef74f6a

Browse files
feat(highcharts): implement circos-basic (#3063)
## Implementation: `circos-basic` - highcharts Implements the **highcharts** version of `circos-basic`. **File:** `plots/circos-basic/implementations/highcharts.py` **Parent Issue:** #3005 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617728725)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 3fc1350 commit ef74f6a

2 files changed

Lines changed: 256 additions & 0 deletions

File tree

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
""" pyplots.ai
2+
circos-basic: Circos Plot
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 - Simulating trade flows between 8 regions
19+
np.random.seed(42)
20+
21+
regions = ["N.America", "S.America", "Europe", "Africa", "MidEast", "S.Asia", "E.Asia", "Oceania"]
22+
23+
# Generate flow matrix - inter-regional trade flows
24+
n_regions = len(regions)
25+
flow_matrix = np.zeros((n_regions, n_regions))
26+
27+
# Create realistic flow patterns with some regions having stronger connections
28+
for i in range(n_regions):
29+
for j in range(n_regions):
30+
if i != j:
31+
base_flow = np.random.exponential(50)
32+
# Some regional pairs have stronger trade relationships
33+
if (i == 0 and j == 6) or (i == 6 and j == 0): # North America - East Asia
34+
base_flow *= 3
35+
elif (i == 2 and j == 6) or (i == 6 and j == 2): # Europe - East Asia
36+
base_flow *= 2.5
37+
elif (i == 0 and j == 2) or (i == 2 and j == 0): # North America - Europe
38+
base_flow *= 2
39+
flow_matrix[i, j] = base_flow
40+
41+
# Prepare data for Highcharts dependency wheel (circular chord-like visualization)
42+
# Format: [from, to, weight] arrays
43+
connections = []
44+
for i in range(n_regions):
45+
for j in range(n_regions):
46+
if i != j and flow_matrix[i, j] > 20: # Filter small connections
47+
connections.append([regions[i], regions[j], float(round(flow_matrix[i, j], 1))])
48+
49+
# Colorblind-safe colors for each region
50+
colors = [
51+
"#306998", # Python Blue - N. America
52+
"#FFD43B", # Python Yellow - S. America
53+
"#9467BD", # Purple - Europe
54+
"#17BECF", # Cyan - Africa
55+
"#8C564B", # Brown - Mid. East
56+
"#E377C2", # Pink - S. Asia
57+
"#2CA02C", # Green - E. Asia
58+
"#BCBD22", # Olive - Oceania
59+
]
60+
61+
# Full region names for legend
62+
region_full_names = [
63+
"North America",
64+
"South America",
65+
"Europe",
66+
"Africa",
67+
"Middle East",
68+
"South Asia",
69+
"East Asia",
70+
"Oceania",
71+
]
72+
73+
# Generate inner track data (regional GDP representation as percentage)
74+
gdp_data = [26.0, 4.5, 22.0, 3.5, 5.0, 8.0, 28.0, 3.0] # Approximate world GDP share %
75+
76+
# Create nodes with colors
77+
nodes = [{"id": regions[i], "color": colors[i]} for i in range(n_regions)]
78+
79+
# Create inner track pie chart data (GDP representation)
80+
inner_track_data = [{"name": region_full_names[i], "y": gdp_data[i], "color": colors[i]} for i in range(n_regions)]
81+
82+
# Create Highcharts configuration directly
83+
chart_config = {
84+
"chart": {
85+
"type": "dependencywheel",
86+
"width": 3600,
87+
"height": 3600,
88+
"backgroundColor": "#ffffff",
89+
"marginRight": 420,
90+
},
91+
"title": {"text": "circos-basic · highcharts · pyplots.ai", "style": {"fontSize": "52px", "fontWeight": "bold"}},
92+
"subtitle": {"text": "Global Trade Flows Between Regions (with GDP Inner Track)", "style": {"fontSize": "36px"}},
93+
"accessibility": {"enabled": False},
94+
"credits": {"enabled": False},
95+
"tooltip": {"style": {"fontSize": "28px"}},
96+
"legend": {
97+
"enabled": True,
98+
"align": "right",
99+
"verticalAlign": "middle",
100+
"layout": "vertical",
101+
"itemStyle": {"fontSize": "32px", "fontWeight": "normal"},
102+
"symbolHeight": 28,
103+
"symbolWidth": 28,
104+
"symbolRadius": 14,
105+
"itemMarginTop": 18,
106+
"itemMarginBottom": 18,
107+
"x": -30,
108+
"title": {"text": "Regions", "style": {"fontSize": "36px", "fontWeight": "bold"}},
109+
},
110+
"plotOptions": {"dependencywheel": {"showInLegend": False}, "pie": {"showInLegend": False}},
111+
"series": [
112+
{
113+
"type": "dependencywheel",
114+
"name": "Trade Flow",
115+
"keys": ["from", "to", "weight"],
116+
"data": connections,
117+
"nodes": nodes,
118+
"size": "78%",
119+
"center": ["42%", "50%"],
120+
"dataLabels": {
121+
"enabled": True,
122+
"style": {"fontSize": "40px", "fontWeight": "bold", "textOutline": "5px white"},
123+
"distance": 50,
124+
"rotationMode": "circular",
125+
"padding": 8,
126+
},
127+
"nodeWidth": 45,
128+
},
129+
{
130+
"type": "pie",
131+
"name": "GDP Share",
132+
"data": inner_track_data,
133+
"size": "28%",
134+
"innerSize": "20%",
135+
"center": ["42%", "50%"],
136+
"showInLegend": False,
137+
"dataLabels": {
138+
"enabled": True,
139+
"format": "{point.percentage:.0f}%",
140+
"distance": -25,
141+
"style": {"fontSize": "22px", "fontWeight": "bold", "color": "#ffffff", "textOutline": "2px #333333"},
142+
},
143+
"tooltip": {"pointFormat": "<b>{point.name}</b>: {point.y}% of World GDP"},
144+
},
145+
]
146+
+ [
147+
{
148+
"type": "pie",
149+
"name": region_full_names[i],
150+
"data": [{"name": region_full_names[i], "y": 1, "color": colors[i]}],
151+
"size": 0,
152+
"showInLegend": True,
153+
"dataLabels": {"enabled": False},
154+
}
155+
for i in range(n_regions)
156+
],
157+
}
158+
159+
# Download Highcharts JS and sankey module (dependency wheel requires sankey)
160+
highcharts_url = "https://code.highcharts.com/highcharts.js"
161+
sankey_url = "https://code.highcharts.com/modules/sankey.js"
162+
dependency_wheel_url = "https://code.highcharts.com/modules/dependency-wheel.js"
163+
164+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
165+
highcharts_js = response.read().decode("utf-8")
166+
167+
with urllib.request.urlopen(sankey_url, timeout=30) as response:
168+
sankey_js = response.read().decode("utf-8")
169+
170+
with urllib.request.urlopen(dependency_wheel_url, timeout=30) as response:
171+
dependency_wheel_js = response.read().decode("utf-8")
172+
173+
# Generate HTML with inline scripts
174+
chart_config_json = json.dumps(chart_config)
175+
html_content = f"""<!DOCTYPE html>
176+
<html>
177+
<head>
178+
<meta charset="utf-8">
179+
<script>{highcharts_js}</script>
180+
<script>{sankey_js}</script>
181+
<script>{dependency_wheel_js}</script>
182+
</head>
183+
<body style="margin:0; background-color: #ffffff;">
184+
<div id="container" style="width: 3600px; height: 3600px;"></div>
185+
<script>
186+
Highcharts.chart('container', {chart_config_json});
187+
</script>
188+
</body>
189+
</html>"""
190+
191+
# Write temp HTML and take screenshot
192+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
193+
f.write(html_content)
194+
temp_path = f.name
195+
196+
# Also save HTML for interactive version
197+
interactive_html = f"""<!DOCTYPE html>
198+
<html>
199+
<head>
200+
<meta charset="utf-8">
201+
<title>circos-basic · highcharts · pyplots.ai</title>
202+
<script src="https://code.highcharts.com/highcharts.js"></script>
203+
<script src="https://code.highcharts.com/modules/sankey.js"></script>
204+
<script src="https://code.highcharts.com/modules/dependency-wheel.js"></script>
205+
</head>
206+
<body style="margin:0; background-color: #ffffff;">
207+
<div id="container" style="width: 100%; height: 100vh;"></div>
208+
<script>
209+
Highcharts.chart('container', {chart_config_json});
210+
</script>
211+
</body>
212+
</html>"""
213+
with open("plot.html", "w", encoding="utf-8") as f:
214+
f.write(interactive_html)
215+
216+
# Setup Chrome for screenshot
217+
chrome_options = Options()
218+
chrome_options.add_argument("--headless")
219+
chrome_options.add_argument("--no-sandbox")
220+
chrome_options.add_argument("--disable-dev-shm-usage")
221+
chrome_options.add_argument("--disable-gpu")
222+
chrome_options.add_argument("--window-size=3600,3600")
223+
224+
driver = webdriver.Chrome(options=chrome_options)
225+
driver.get(f"file://{temp_path}")
226+
time.sleep(5) # Wait for chart to render
227+
driver.save_screenshot("plot.png")
228+
driver.quit()
229+
230+
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: circos-basic
3+
created: '2025-12-31T11:15:18Z'
4+
updated: '2025-12-31T11:41:11Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617728725
7+
issue: 3005
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/circos-basic/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/circos-basic/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/circos-basic/highcharts/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Creative and effective implementation of Circos-style visualization using Highcharts
17+
dependency wheel combined with inner pie chart for additional data track
18+
- Excellent colorblind-safe palette with 8 distinct, easily distinguishable colors
19+
- Realistic trade flow scenario with appropriate data showing varied connection
20+
strengths between regions
21+
- Well-balanced layout on 3600x3600 canvas with properly positioned legend
22+
- Clean, reproducible code with fixed random seed
23+
weaknesses:
24+
- Minor label overlap at N.America segment where multiple ribbons converge
25+
- Inner pie chart percentage labels are somewhat small and could be more prominent
26+
- Missing explicit gaps between outer ring segments as mentioned in specification

0 commit comments

Comments
 (0)