|
1 | 1 | """ pyplots.ai |
2 | 2 | pie-basic: Basic Pie Chart |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: highcharts 1.10.3 | Python 3.14.0 |
| 4 | +Quality: 90/100 | Created: 2025-12-23 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import tempfile |
|
12 | 12 | from highcharts_core.chart import Chart |
13 | 13 | from highcharts_core.options import HighchartsOptions |
14 | 14 | from highcharts_core.options.series.pie import PieSeries |
15 | | -from PIL import Image |
16 | 15 | from selenium import webdriver |
17 | 16 | from selenium.webdriver.chrome.options import Options |
18 | 17 |
|
19 | 18 |
|
20 | | -# Data - Market share distribution (5 categories, realistic business context) |
21 | | -categories = ["Product A", "Product B", "Product C", "Product D", "Product E"] |
22 | | -values = [35, 25, 20, 12, 8] |
| 19 | +# Data — Cloud infrastructure market share (5 categories, realistic business context) |
| 20 | +categories = ["AWS", "Azure", "Google Cloud", "Alibaba", "Others"] |
| 21 | +values = [31, 25, 11, 4, 29] |
23 | 22 |
|
24 | | -# Colorblind-safe colors (Python Blue first, then complementary) |
25 | | -colors = ["#306998", "#FFD43B", "#9467BD", "#17BECF", "#8C564B"] |
| 23 | +# Colorblind-safe palette (Python Blue first, then complementary) |
| 24 | +# Replaced cyan (#17BECF) with softer teal (#2CA089) for better palette harmony |
| 25 | +colors = ["#306998", "#FFD43B", "#E07B54", "#2CA089", "#9467BD"] |
26 | 26 |
|
27 | | -# Create chart with container specified |
| 27 | +# Compute top-3 share for subtitle storytelling |
| 28 | +top3_share = sum(values[:3]) |
| 29 | + |
| 30 | +# Chart |
28 | 31 | chart = Chart(container="container") |
29 | 32 | chart.options = HighchartsOptions() |
30 | 33 |
|
31 | | -# Chart configuration for 3600x3600 square (ideal for pie charts) |
32 | 34 | chart.options.chart = { |
33 | 35 | "type": "pie", |
34 | | - "width": 3600, |
35 | | - "height": 3600, |
| 36 | + "width": 4800, |
| 37 | + "height": 2700, |
36 | 38 | "backgroundColor": "#ffffff", |
37 | | - "spacingTop": 80, |
38 | | - "spacingBottom": 80, |
39 | | - "spacingLeft": 80, |
40 | | - "spacingRight": 80, |
| 39 | + "spacingTop": 30, |
| 40 | + "spacingBottom": 25, |
| 41 | + "spacingLeft": 60, |
| 42 | + "spacingRight": 60, |
| 43 | + "style": {"fontFamily": "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif"}, |
41 | 44 | } |
42 | 45 |
|
43 | 46 | # Title |
44 | 47 | chart.options.title = { |
45 | | - "text": "pie-basic · highcharts · pyplots.ai", |
46 | | - "style": {"fontSize": "48px", "fontWeight": "bold"}, |
47 | | - "margin": 40, |
| 48 | + "text": "Cloud Infrastructure Market Share · pie-basic · highcharts · pyplots.ai", |
| 49 | + "style": {"fontSize": "50px", "fontWeight": "bold", "color": "#1a1a2e"}, |
| 50 | + "margin": 10, |
| 51 | +} |
| 52 | + |
| 53 | +# Subtitle with storytelling context and insight callout |
| 54 | +chart.options.subtitle = { |
| 55 | + "text": ( |
| 56 | + f"Global cloud spending by provider, 2024 \u2014 Top 3 providers control {top3_share}% of the market" |
| 57 | + '<br><span style="font-style: italic; color: #1a1a2e; font-weight: 600;">' |
| 58 | + "AWS leads with nearly \u2153 of global cloud revenue</span>" |
| 59 | + ), |
| 60 | + "useHTML": True, |
| 61 | + "style": {"fontSize": "34px", "color": "#555555", "fontWeight": "normal", "textAlign": "center"}, |
48 | 62 | } |
49 | 63 |
|
50 | 64 | # Colors |
51 | 65 | chart.options.colors = colors |
52 | 66 |
|
53 | | -# Plot options for pie with percentage labels and legend |
| 67 | +# Credits |
| 68 | +chart.options.credits = {"enabled": False} |
| 69 | + |
| 70 | +# Plot options with enhanced visual refinement |
54 | 71 | chart.options.plot_options = { |
55 | 72 | "pie": { |
56 | 73 | "allowPointSelect": True, |
57 | 74 | "cursor": "pointer", |
| 75 | + "borderWidth": 2, |
| 76 | + "borderColor": "#ffffff", |
| 77 | + "shadow": {"color": "rgba(0,0,0,0.12)", "offsetX": 3, "offsetY": 3, "width": 8}, |
58 | 78 | "dataLabels": { |
59 | 79 | "enabled": True, |
60 | | - "format": "<b>{point.name}</b>: {point.percentage:.1f}%", |
61 | | - "style": {"fontSize": "32px", "textOutline": "none"}, |
62 | | - "distance": 40, |
| 80 | + "format": "<b>{point.name}</b><br>{point.percentage:.1f}%", |
| 81 | + "style": {"fontSize": "38px", "textOutline": "none", "fontWeight": "normal", "color": "#333333"}, |
| 82 | + "distance": 55, |
63 | 83 | "connectorWidth": 2, |
| 84 | + "connectorColor": "#999999", |
| 85 | + "softConnector": True, |
| 86 | + "connectorShape": "crookedLine", |
64 | 87 | }, |
65 | 88 | "showInLegend": True, |
66 | | - "slicedOffset": 25, |
67 | | - "size": "70%", |
68 | | - "center": ["40%", "50%"], |
| 89 | + "slicedOffset": 40, |
| 90 | + "size": "75%", |
| 91 | + "center": ["50%", "55%"], |
| 92 | + "startAngle": -45, |
| 93 | + "innerSize": "0%", |
| 94 | + "states": {"hover": {"halo": {"size": 15, "opacity": 0.25}}, "inactive": {"opacity": 0.5}}, |
69 | 95 | } |
70 | 96 | } |
71 | 97 |
|
72 | | -# Legend on the right side |
| 98 | +# Legend — bottom horizontal |
73 | 99 | chart.options.legend = { |
74 | 100 | "enabled": True, |
75 | | - "align": "right", |
76 | | - "verticalAlign": "middle", |
77 | | - "layout": "vertical", |
78 | | - "itemStyle": {"fontSize": "36px", "fontWeight": "normal"}, |
79 | | - "itemMarginTop": 20, |
80 | | - "itemMarginBottom": 20, |
81 | | - "symbolRadius": 10, |
| 101 | + "align": "center", |
| 102 | + "verticalAlign": "bottom", |
| 103 | + "layout": "horizontal", |
| 104 | + "itemStyle": {"fontSize": "36px", "fontWeight": "normal", "color": "#444444"}, |
| 105 | + "itemHoverStyle": {"color": "#1a1a2e"}, |
| 106 | + "symbolRadius": 8, |
82 | 107 | "symbolHeight": 20, |
83 | 108 | "symbolWidth": 20, |
84 | | - "x": -80, |
| 109 | + "margin": 8, |
| 110 | + "padding": 8, |
| 111 | +} |
| 112 | + |
| 113 | +# Tooltip |
| 114 | +chart.options.tooltip = { |
| 115 | + "pointFormat": "<b>{point.percentage:.1f}%</b> market share", |
| 116 | + "style": {"fontSize": "28px"}, |
| 117 | + "backgroundColor": "rgba(255,255,255,0.95)", |
| 118 | + "borderColor": "#cccccc", |
| 119 | + "borderRadius": 8, |
| 120 | + "shadow": {"color": "rgba(0,0,0,0.08)", "offsetX": 1, "offsetY": 1, "width": 3}, |
85 | 121 | } |
86 | 122 |
|
87 | | -# Create pie series with data - first slice (largest) is exploded for emphasis |
| 123 | +# Series — largest slice (AWS) exploded for emphasis |
88 | 124 | series = PieSeries() |
89 | 125 | series.name = "Market Share" |
90 | | -series.data = [ |
91 | | - {"name": cat, "y": val, "sliced": i == 0, "selected": i == 0} |
92 | | - for i, (cat, val) in enumerate(zip(categories, values, strict=True)) |
93 | | -] |
94 | 126 |
|
| 127 | +series_data = [] |
| 128 | +for i, (cat, val) in enumerate(zip(categories, values, strict=True)): |
| 129 | + point = {"name": cat, "y": val, "sliced": i == 0, "selected": i == 0} |
| 130 | + if i == 0: |
| 131 | + point["borderWidth"] = 3 |
| 132 | + point["borderColor"] = "#1e4060" |
| 133 | + series_data.append(point) |
| 134 | + |
| 135 | +series.data = series_data |
95 | 136 | chart.add_series(series) |
96 | 137 |
|
97 | | -# Download Highcharts JS for inline embedding (required for headless Chrome) |
| 138 | +# Download Highcharts JS for inline embedding |
98 | 139 | highcharts_url = "https://code.highcharts.com/highcharts.js" |
99 | 140 | with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
100 | 141 | highcharts_js = response.read().decode("utf-8") |
|
108 | 149 | <script>{highcharts_js}</script> |
109 | 150 | </head> |
110 | 151 | <body style="margin:0;"> |
111 | | - <div id="container" style="width: 3600px; height: 3600px;"></div> |
| 152 | + <div id="container" style="width: 4800px; height: 2700px;"></div> |
112 | 153 | <script>{html_str}</script> |
113 | 154 | </body> |
114 | 155 | </html>""" |
115 | 156 |
|
116 | | -# Write temp HTML and take screenshot |
| 157 | +# Write temp HTML and save interactive version |
117 | 158 | with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
118 | 159 | f.write(html_content) |
119 | 160 | temp_path = f.name |
120 | 161 |
|
121 | | -# Also save the HTML for interactive version |
122 | 162 | with open("plot.html", "w", encoding="utf-8") as f: |
123 | 163 | f.write(html_content) |
124 | 164 |
|
125 | | -# Setup Chrome for screenshot |
| 165 | +# Screenshot via headless Chrome |
126 | 166 | chrome_options = Options() |
127 | 167 | chrome_options.add_argument("--headless") |
128 | 168 | chrome_options.add_argument("--no-sandbox") |
129 | 169 | chrome_options.add_argument("--disable-dev-shm-usage") |
130 | 170 | chrome_options.add_argument("--disable-gpu") |
131 | | -chrome_options.add_argument("--window-size=3600,3800") |
| 171 | +chrome_options.add_argument("--window-size=4800,2700") |
132 | 172 |
|
133 | 173 | driver = webdriver.Chrome(options=chrome_options) |
| 174 | + |
| 175 | +# Adjust window to get exact 4800x2700 viewport (compensate for browser chrome) |
| 176 | +inner_h = driver.execute_script("return window.innerHeight") |
| 177 | +outer_h = driver.get_window_size()["height"] |
| 178 | +driver.set_window_size(4800, 2700 + (outer_h - inner_h)) |
| 179 | + |
134 | 180 | driver.get(f"file://{temp_path}") |
135 | | -time.sleep(5) # Wait for chart to render |
136 | | -driver.save_screenshot("plot_raw.png") |
| 181 | +time.sleep(5) |
| 182 | +driver.save_screenshot("plot.png") |
137 | 183 | driver.quit() |
138 | 184 |
|
139 | | -# Crop to exact 3600x3600 dimensions |
140 | | -img = Image.open("plot_raw.png") |
141 | | -img_cropped = img.crop((0, 0, 3600, 3600)) |
142 | | -img_cropped.save("plot.png") |
143 | | -Path("plot_raw.png").unlink() |
144 | | - |
145 | | -Path(temp_path).unlink() # Clean up temp file |
| 185 | +Path(temp_path).unlink() |
0 commit comments