|
| 1 | +""" pyplots.ai |
| 2 | +scatter-animated-controls: Animated Scatter Plot with Play Controls |
| 3 | +Library: plotly 6.5.0 | Python 3.13.11 |
| 4 | +Quality: 92/100 | Created: 2025-12-31 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import pandas as pd |
| 9 | +import plotly.express as px |
| 10 | + |
| 11 | + |
| 12 | +# Data: Simulated country data (GDP, life expectancy, population) over 20 years |
| 13 | +np.random.seed(42) |
| 14 | + |
| 15 | +countries = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta"] |
| 16 | +regions = ["North", "North", "South", "South", "East", "East", "West", "West"] |
| 17 | +years = list(range(2000, 2021)) |
| 18 | + |
| 19 | +# Base values for each country |
| 20 | +base_gdp = np.array([8000, 12000, 3000, 5000, 15000, 7000, 4000, 20000]) |
| 21 | +base_life_exp = np.array([65, 72, 58, 62, 75, 68, 60, 78]) |
| 22 | +base_pop = np.array([50, 30, 120, 80, 25, 45, 90, 20]) |
| 23 | + |
| 24 | +# Build dataset with growth trends |
| 25 | +data = [] |
| 26 | +for i, country in enumerate(countries): |
| 27 | + for year_idx, year in enumerate(years): |
| 28 | + # GDP growth with some variation (1-5% annual growth) |
| 29 | + growth_factor = 1 + np.random.uniform(0.01, 0.05) |
| 30 | + gdp = base_gdp[i] * (growth_factor**year_idx) + np.random.normal(0, 500) |
| 31 | + gdp = max(1000, gdp) |
| 32 | + |
| 33 | + # Life expectancy slowly increases (0.1-0.3 years per year) |
| 34 | + life_exp = base_life_exp[i] + year_idx * np.random.uniform(0.1, 0.3) + np.random.normal(0, 0.5) |
| 35 | + life_exp = min(max(50, life_exp), 85) |
| 36 | + |
| 37 | + # Population changes (0.5-2% annual change) |
| 38 | + pop_factor = 1 + np.random.uniform(0.005, 0.02) |
| 39 | + pop = base_pop[i] * (pop_factor**year_idx) + np.random.normal(0, 2) |
| 40 | + pop = max(10, pop) |
| 41 | + |
| 42 | + data.append( |
| 43 | + { |
| 44 | + "Country": country, |
| 45 | + "Region": regions[i], |
| 46 | + "Year": year, |
| 47 | + "GDP per Capita ($)": gdp, |
| 48 | + "Life Expectancy (years)": life_exp, |
| 49 | + "Population (millions)": pop, |
| 50 | + } |
| 51 | + ) |
| 52 | + |
| 53 | +df = pd.DataFrame(data) |
| 54 | + |
| 55 | +# Create animated scatter plot with Gapminder-style visualization |
| 56 | +fig = px.scatter( |
| 57 | + df, |
| 58 | + x="GDP per Capita ($)", |
| 59 | + y="Life Expectancy (years)", |
| 60 | + size="Population (millions)", |
| 61 | + color="Region", |
| 62 | + hover_name="Country", |
| 63 | + animation_frame="Year", |
| 64 | + animation_group="Country", |
| 65 | + size_max=80, |
| 66 | + range_x=[0, df["GDP per Capita ($)"].max() * 1.1], |
| 67 | + range_y=[50, 88], |
| 68 | + color_discrete_sequence=["#306998", "#FFD43B", "#E74C3C", "#2ECC71"], |
| 69 | +) |
| 70 | + |
| 71 | +# Update layout for large canvas |
| 72 | +fig.update_layout( |
| 73 | + title=dict( |
| 74 | + text="scatter-animated-controls · plotly · pyplots.ai", |
| 75 | + font=dict(size=32, color="#333333"), |
| 76 | + x=0.5, |
| 77 | + xanchor="center", |
| 78 | + ), |
| 79 | + xaxis=dict( |
| 80 | + title=dict(text="GDP per Capita ($)", font=dict(size=24)), |
| 81 | + tickfont=dict(size=18), |
| 82 | + gridcolor="rgba(0,0,0,0.1)", |
| 83 | + showgrid=True, |
| 84 | + ), |
| 85 | + yaxis=dict( |
| 86 | + title=dict(text="Life Expectancy (years)", font=dict(size=24)), |
| 87 | + tickfont=dict(size=18), |
| 88 | + gridcolor="rgba(0,0,0,0.1)", |
| 89 | + showgrid=True, |
| 90 | + ), |
| 91 | + legend=dict( |
| 92 | + title=dict(text="Region", font=dict(size=20)), |
| 93 | + font=dict(size=18), |
| 94 | + x=0.02, |
| 95 | + y=0.98, |
| 96 | + bgcolor="rgba(255,255,255,0.8)", |
| 97 | + ), |
| 98 | + template="plotly_white", |
| 99 | + paper_bgcolor="white", |
| 100 | + plot_bgcolor="white", |
| 101 | + margin=dict(l=100, r=100, t=120, b=150), |
| 102 | +) |
| 103 | + |
| 104 | +# Update marker styling |
| 105 | +fig.update_traces(marker=dict(line=dict(width=2, color="white"), opacity=0.85)) |
| 106 | + |
| 107 | +# Update animation settings for smooth playback |
| 108 | +fig.update_layout( |
| 109 | + updatemenus=[ |
| 110 | + dict( |
| 111 | + type="buttons", |
| 112 | + showactive=False, |
| 113 | + y=-0.08, |
| 114 | + x=0.05, |
| 115 | + xanchor="left", |
| 116 | + buttons=[ |
| 117 | + dict( |
| 118 | + label="▶ Play", |
| 119 | + method="animate", |
| 120 | + args=[ |
| 121 | + None, |
| 122 | + dict( |
| 123 | + frame=dict(duration=500, redraw=True), |
| 124 | + fromcurrent=True, |
| 125 | + transition=dict(duration=300, easing="cubic-in-out"), |
| 126 | + ), |
| 127 | + ], |
| 128 | + ), |
| 129 | + dict( |
| 130 | + label="⏸ Pause", |
| 131 | + method="animate", |
| 132 | + args=[ |
| 133 | + [None], |
| 134 | + dict(frame=dict(duration=0, redraw=False), mode="immediate", transition=dict(duration=0)), |
| 135 | + ], |
| 136 | + ), |
| 137 | + ], |
| 138 | + font=dict(size=16), |
| 139 | + ) |
| 140 | + ], |
| 141 | + sliders=[ |
| 142 | + dict( |
| 143 | + active=0, |
| 144 | + yanchor="top", |
| 145 | + xanchor="left", |
| 146 | + currentvalue=dict(font=dict(size=24), prefix="Year: ", visible=True, xanchor="center"), |
| 147 | + transition=dict(duration=300, easing="cubic-in-out"), |
| 148 | + pad=dict(b=10, t=50), |
| 149 | + len=0.85, |
| 150 | + x=0.1, |
| 151 | + y=-0.02, |
| 152 | + steps=[ |
| 153 | + dict( |
| 154 | + args=[ |
| 155 | + [str(year)], |
| 156 | + dict(frame=dict(duration=300, redraw=True), mode="immediate", transition=dict(duration=300)), |
| 157 | + ], |
| 158 | + label=str(year), |
| 159 | + method="animate", |
| 160 | + ) |
| 161 | + for year in years |
| 162 | + ], |
| 163 | + font=dict(size=14), |
| 164 | + ) |
| 165 | + ], |
| 166 | +) |
| 167 | + |
| 168 | +# Add year annotation in background for emphasis |
| 169 | +fig.add_annotation( |
| 170 | + x=0.98, |
| 171 | + y=0.95, |
| 172 | + xref="paper", |
| 173 | + yref="paper", |
| 174 | + text="2000", |
| 175 | + showarrow=False, |
| 176 | + font=dict(size=72, color="rgba(0,0,0,0.08)"), |
| 177 | + xanchor="right", |
| 178 | + yanchor="top", |
| 179 | +) |
| 180 | + |
| 181 | +# Save static PNG (showing first frame) |
| 182 | +fig.write_image("plot.png", width=1600, height=900, scale=3) |
| 183 | + |
| 184 | +# Save interactive HTML |
| 185 | +fig.write_html("plot.html", include_plotlyjs=True, full_html=True) |
0 commit comments