|
| 1 | +""" pyplots.ai |
| 2 | +scatter-size-mapped: Bubble Chart |
| 3 | +Library: pygal 3.1.0 | Python 3.13.11 |
| 4 | +Quality: 91/100 | Created: 2025-12-27 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import pygal |
| 9 | +from pygal.style import Style |
| 10 | + |
| 11 | + |
| 12 | +# Data: Country economic indicators |
| 13 | +np.random.seed(42) |
| 14 | + |
| 15 | +# Generate 40 synthetic countries across different regions |
| 16 | +n_countries = 40 |
| 17 | +regions = ["Europe", "Asia", "Americas", "Africa"] |
| 18 | + |
| 19 | +# Assign regions with some variation |
| 20 | +region_assignments = np.random.choice(regions, n_countries) |
| 21 | + |
| 22 | +# GDP per capita (1,000 - 80,000) |
| 23 | +gdp_per_capita = np.random.uniform(5000, 75000, n_countries) |
| 24 | +# Adjust by region for realism |
| 25 | +for i, region in enumerate(region_assignments): |
| 26 | + if region == "Europe": |
| 27 | + gdp_per_capita[i] = np.random.uniform(25000, 75000) |
| 28 | + elif region == "Asia": |
| 29 | + gdp_per_capita[i] = np.random.uniform(5000, 60000) |
| 30 | + elif region == "Americas": |
| 31 | + gdp_per_capita[i] = np.random.uniform(8000, 70000) |
| 32 | + else: # Africa |
| 33 | + gdp_per_capita[i] = np.random.uniform(1000, 20000) |
| 34 | + |
| 35 | +# Life expectancy (50-85) correlated with GDP |
| 36 | +life_expectancy = 50 + 30 * (gdp_per_capita / 80000) + np.random.uniform(-5, 5, n_countries) |
| 37 | +life_expectancy = np.clip(life_expectancy, 50, 85) |
| 38 | + |
| 39 | +# Population (log scale, 1M - 1.4B) |
| 40 | +population = 10 ** np.random.uniform(6, 9.2, n_countries) |
| 41 | + |
| 42 | +# Create custom style with larger font sizes for better rendering |
| 43 | +custom_style = Style( |
| 44 | + background="white", |
| 45 | + plot_background="white", |
| 46 | + foreground="#333333", |
| 47 | + foreground_strong="#333333", |
| 48 | + foreground_subtle="#666666", |
| 49 | + colors=("#306998", "#FFD43B", "#E74C3C", "#27AE60", "#9B59B6"), |
| 50 | + title_font_size=84, |
| 51 | + label_font_size=56, |
| 52 | + major_label_font_size=48, |
| 53 | + legend_font_size=44, |
| 54 | + value_font_size=40, |
| 55 | + opacity=0.6, |
| 56 | + opacity_hover=0.8, |
| 57 | +) |
| 58 | + |
| 59 | +# Title with size legend information |
| 60 | +title = "scatter-size-mapped · pygal · pyplots.ai\nBubble Size = Population (1M to 1B+)" |
| 61 | + |
| 62 | +# Create XY chart for scatter plot |
| 63 | +chart = pygal.XY( |
| 64 | + width=4800, |
| 65 | + height=2700, |
| 66 | + style=custom_style, |
| 67 | + title=title, |
| 68 | + x_title="GDP per Capita (USD)", |
| 69 | + y_title="Life Expectancy (years)", |
| 70 | + show_legend=True, |
| 71 | + legend_at_bottom=True, |
| 72 | + legend_at_bottom_columns=4, |
| 73 | + dots_size=15, |
| 74 | + show_x_guides=True, |
| 75 | + show_y_guides=True, |
| 76 | + stroke=False, |
| 77 | + explicit_size=True, |
| 78 | + truncate_legend=-1, |
| 79 | +) |
| 80 | + |
| 81 | +# Group data by region and add as series |
| 82 | +# Pygal XY chart uses value dict with 'value' for coordinates |
| 83 | +for region in regions: |
| 84 | + mask = region_assignments == region |
| 85 | + points = [] |
| 86 | + for i in np.where(mask)[0]: |
| 87 | + # Scale population to reasonable dot size (log scale) |
| 88 | + # Population ranges from 1M to 1.4B, scale to 12-70 for better visibility |
| 89 | + # Increased minimum size from 5 to 12 so smaller bubbles are visible |
| 90 | + size = 12 + 58 * (np.log10(population[i]) - 6) / 3.2 |
| 91 | + points.append({"value": (float(gdp_per_capita[i]), float(life_expectancy[i])), "node": {"r": size}}) |
| 92 | + chart.add(region, points) |
| 93 | + |
| 94 | +# Render to files |
| 95 | +chart.render_to_png("plot.png") |
| 96 | +chart.render_to_file("plot.html") |
0 commit comments