|
| 1 | +""" pyplots.ai |
| 2 | +scatter-animated-controls: Animated Scatter Plot with Play Controls |
| 3 | +Library: plotnine 0.15.2 | Python 3.13.11 |
| 4 | +Quality: 90/100 | Created: 2025-12-31 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import pandas as pd |
| 9 | +from plotnine import ( |
| 10 | + aes, |
| 11 | + element_blank, |
| 12 | + element_line, |
| 13 | + element_rect, |
| 14 | + element_text, |
| 15 | + facet_wrap, |
| 16 | + geom_point, |
| 17 | + ggplot, |
| 18 | + labs, |
| 19 | + scale_color_brewer, |
| 20 | + scale_size_continuous, |
| 21 | + scale_x_continuous, |
| 22 | + scale_y_continuous, |
| 23 | + theme, |
| 24 | + theme_minimal, |
| 25 | +) |
| 26 | + |
| 27 | + |
| 28 | +# Data - Simulated country metrics over time (Gapminder-style) |
| 29 | +np.random.seed(42) |
| 30 | + |
| 31 | +countries = ["Nordland", "Australis", "Eastoria", "Pacifica", "Centralia", "Westmark"] |
| 32 | +regions = ["Europe", "Americas", "Asia", "Asia", "Africa", "Europe"] |
| 33 | +years = list(range(2000, 2021, 2)) # 11 time points: 2000-2020 every 2 years |
| 34 | +n_countries = len(countries) |
| 35 | +n_years = len(years) |
| 36 | + |
| 37 | +# Generate base values for each country with distinct trajectories |
| 38 | +base_gdp = np.array([25000, 12000, 8000, 3000, 1500, 35000]) |
| 39 | +base_life_exp = np.array([76, 72, 65, 58, 52, 79]) |
| 40 | +base_population = np.array([10, 45, 200, 130, 90, 25]) # in millions |
| 41 | + |
| 42 | +data = [] |
| 43 | +for i, country in enumerate(countries): |
| 44 | + for j, year in enumerate(years): |
| 45 | + # Different growth patterns per region |
| 46 | + if regions[i] == "Asia": |
| 47 | + gdp_growth = 1 + 0.08 * j + np.random.uniform(-0.02, 0.02) # Fast growth |
| 48 | + elif regions[i] == "Africa": |
| 49 | + gdp_growth = 1 + 0.04 * j + np.random.uniform(-0.03, 0.03) # Moderate with volatility |
| 50 | + else: |
| 51 | + gdp_growth = 1 + 0.025 * j + np.random.uniform(-0.01, 0.01) # Steady growth |
| 52 | + |
| 53 | + gdp = base_gdp[i] * gdp_growth |
| 54 | + |
| 55 | + # Life expectancy increases at different rates |
| 56 | + life_gain = 0.4 if regions[i] == "Africa" else 0.25 |
| 57 | + life_exp = base_life_exp[i] + life_gain * j + np.random.uniform(-0.3, 0.3) |
| 58 | + |
| 59 | + # Population growth varies |
| 60 | + pop_rate = 0.025 if regions[i] in ["Asia", "Africa"] else 0.008 |
| 61 | + pop_growth = 1 + pop_rate * j + np.random.uniform(-0.005, 0.005) |
| 62 | + population = base_population[i] * pop_growth |
| 63 | + |
| 64 | + data.append( |
| 65 | + { |
| 66 | + "country": country, |
| 67 | + "region": regions[i], |
| 68 | + "year": year, |
| 69 | + "gdp_per_capita": gdp, |
| 70 | + "life_expectancy": life_exp, |
| 71 | + "population": population, |
| 72 | + } |
| 73 | + ) |
| 74 | + |
| 75 | +df = pd.DataFrame(data) |
| 76 | + |
| 77 | +# Create faceted scatter plot showing key time points |
| 78 | +# Select 6 key time points for facets (fills 2x3 grid perfectly) |
| 79 | +key_years = [2000, 2004, 2008, 2012, 2016, 2020] |
| 80 | +df_faceted = df[df["year"].isin(key_years)].copy() |
| 81 | + |
| 82 | +plot = ( |
| 83 | + ggplot(df_faceted, aes(x="gdp_per_capita", y="life_expectancy", color="region", size="population")) |
| 84 | + + geom_point(alpha=0.85) |
| 85 | + + facet_wrap("~year", ncol=3) |
| 86 | + + labs( |
| 87 | + x="GDP per Capita (USD)", |
| 88 | + y="Life Expectancy (years)", |
| 89 | + title="scatter-animated-controls · plotnine · pyplots.ai", |
| 90 | + color="Region", |
| 91 | + size="Population (M)", |
| 92 | + ) |
| 93 | + + scale_color_brewer(type="qual", palette="Dark2") |
| 94 | + + scale_size_continuous(range=(4, 18), breaks=[25, 100, 200]) |
| 95 | + + scale_x_continuous(breaks=[0, 30000, 60000], labels=["0", "30k", "60k"], limits=(-2000, 65000)) |
| 96 | + + scale_y_continuous(breaks=[55, 65, 75, 85], limits=(50, 88)) |
| 97 | + + theme_minimal() |
| 98 | + + theme( |
| 99 | + figure_size=(16, 9), |
| 100 | + text=element_text(size=12), |
| 101 | + axis_title=element_text(size=18), |
| 102 | + axis_text=element_text(size=10), |
| 103 | + axis_text_x=element_text(size=9), |
| 104 | + plot_title=element_text(size=22, ha="center", weight="bold"), |
| 105 | + legend_text=element_text(size=12), |
| 106 | + legend_title=element_text(size=14, weight="bold"), |
| 107 | + legend_position="right", |
| 108 | + legend_box_spacing=0.4, |
| 109 | + strip_text=element_text(size=16, weight="bold"), |
| 110 | + strip_background=element_rect(fill="#e8e8e8", color="#cccccc", size=0.5), |
| 111 | + panel_spacing_x=0.15, |
| 112 | + panel_spacing_y=0.15, |
| 113 | + panel_grid_major=element_line(color="#dddddd", size=0.4), |
| 114 | + panel_grid_minor=element_blank(), |
| 115 | + panel_background=element_rect(fill="#fafafa"), |
| 116 | + ) |
| 117 | +) |
| 118 | + |
| 119 | +# Save plot |
| 120 | +plot.save("plot.png", dpi=300, verbose=False) |
0 commit comments