Skip to content

Commit b14ad88

Browse files
feat(plotly): implement scatter-animated-controls (#3082)
## Implementation: `scatter-animated-controls` - plotly Implements the **plotly** version of `scatter-animated-controls`. **File:** `plots/scatter-animated-controls/implementations/plotly.py` **Parent Issue:** #3067 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20620300949)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 4955809 commit b14ad88

2 files changed

Lines changed: 215 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
library: plotly
2+
specification_id: scatter-animated-controls
3+
created: '2025-12-31T13:51:22Z'
4+
updated: '2025-12-31T13:54:25Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20620300949
7+
issue: 3067
8+
python_version: 3.13.11
9+
library_version: 6.5.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/scatter-animated-controls/plotly/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/scatter-animated-controls/plotly/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/scatter-animated-controls/plotly/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- 'Excellent Gapminder-style visualization with all key elements: play/pause controls,
17+
timeline slider, year watermark'
18+
- Clean code structure with realistic simulated country data showing growth over
19+
20 years
20+
- Proper use of Plotly animation features including smooth transitions (cubic-in-out
21+
easing)
22+
- Good accessibility with distinct colors and proper font sizing throughout
23+
- Both PNG and HTML outputs generated for static and interactive viewing
24+
weaknesses:
25+
- Some bubble overlap in the lower-left cluster makes individual countries harder
26+
to distinguish in year 2000
27+
- Legend positioned inside the plot area could potentially overlap with data points
28+
in some frames
29+
- Year watermark annotation shows static 2000 and does not update with animation
30+
frames

0 commit comments

Comments
 (0)