Skip to content

Commit d20a360

Browse files
feat(letsplot): implement scatter-animated-controls (#3091)
## Implementation: `scatter-animated-controls` - letsplot Implements the **letsplot** version of `scatter-animated-controls`. **File:** `plots/scatter-animated-controls/implementations/letsplot.py` **Parent Issue:** #3067 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20620306159)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 314a914 commit d20a360

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# ruff: noqa: F405
2+
"""pyplots.ai
3+
scatter-animated-controls: Animated Scatter Plot with Play Controls
4+
Library: letsplot | Python 3.13
5+
Quality: pending | Created: 2025-12-31
6+
"""
7+
8+
import numpy as np
9+
import pandas as pd
10+
from lets_plot import * # noqa: F403
11+
12+
13+
LetsPlot.setup_html()
14+
15+
# Data - Simulated country-level metrics over 20 years (Gapminder-style)
16+
np.random.seed(42)
17+
18+
countries = [
19+
"Northland",
20+
"Eastoria",
21+
"Westopia",
22+
"Southaven",
23+
"Centralia",
24+
"Alpinia",
25+
"Deltania",
26+
"Oceanica",
27+
"Valleysia",
28+
"Highlands",
29+
]
30+
years = list(range(2000, 2020))
31+
32+
data_rows = []
33+
for country in countries:
34+
# Base values with country-specific characteristics
35+
base_gdp = np.random.uniform(5000, 40000)
36+
base_life = np.random.uniform(55, 75)
37+
base_pop = np.random.uniform(5, 200) # millions
38+
39+
# Growth trends
40+
gdp_growth = np.random.uniform(0.02, 0.06)
41+
life_growth = np.random.uniform(0.002, 0.008)
42+
pop_growth = np.random.uniform(0.005, 0.02)
43+
44+
for i, year in enumerate(years):
45+
# Add some noise and trends
46+
gdp = base_gdp * (1 + gdp_growth) ** i * (1 + np.random.normal(0, 0.05))
47+
life_exp = min(85, base_life + life_growth * i * 100 + np.random.normal(0, 0.5))
48+
pop = base_pop * (1 + pop_growth) ** i
49+
50+
data_rows.append(
51+
{"country": country, "year": year, "gdp_per_capita": gdp, "life_expectancy": life_exp, "population": pop}
52+
)
53+
54+
df = pd.DataFrame(data_rows)
55+
56+
# lets-plot does not have built-in animation like Plotly
57+
# Per spec: "Libraries without animation support should implement a static faceted version"
58+
# Create a faceted view showing key time points
59+
60+
# Select key years for faceted display
61+
key_years = [2000, 2005, 2010, 2015, 2019]
62+
df_key = df[df["year"].isin(key_years)].copy()
63+
df_key["year_label"] = df_key["year"].astype(str)
64+
65+
# Create the faceted plot showing temporal evolution
66+
plot = (
67+
ggplot(df_key, aes(x="gdp_per_capita", y="life_expectancy"))
68+
+ geom_point(aes(color="country", size="population"), alpha=0.85)
69+
+ scale_size(range=[6, 20], name="Population (M)")
70+
+ scale_color_brewer(palette="Paired", name="Country")
71+
+ scale_x_log10()
72+
+ facet_wrap("year_label", ncol=5)
73+
+ labs(
74+
title="scatter-animated-controls \u00b7 lets-plot \u00b7 pyplots.ai",
75+
x="GDP per Capita (log scale, USD)",
76+
y="Life Expectancy (years)",
77+
)
78+
+ theme_minimal()
79+
+ theme(
80+
plot_title=element_text(size=28, face="bold"),
81+
axis_title=element_text(size=18),
82+
axis_text=element_text(size=14),
83+
legend_title=element_text(size=16),
84+
legend_text=element_text(size=14),
85+
strip_text=element_text(size=18, face="bold"),
86+
legend_position="right",
87+
)
88+
+ ggsize(1600, 900)
89+
)
90+
91+
# Save as PNG (scale 3x for 4800x2700)
92+
ggsave(plot, "plot.png", path=".", scale=3)
93+
94+
# Save as HTML for interactivity
95+
ggsave(plot, "plot.html", path=".")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
library: letsplot
2+
specification_id: scatter-animated-controls
3+
created: '2025-12-31T13:53:17Z'
4+
updated: '2025-12-31T14:16:05Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20620306159
7+
issue: 3067
8+
python_version: 3.13.11
9+
library_version: 4.8.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/scatter-animated-controls/letsplot/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/scatter-animated-controls/letsplot/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/scatter-animated-controls/letsplot/plot.html
13+
quality_score: 90
14+
review:
15+
strengths:
16+
- Excellent implementation of Gapminder-style visualization with realistic country
17+
data
18+
- Correct fallback approach using faceted view for library without native animation
19+
support
20+
- Well-formatted title following exact spec requirements
21+
- Good use of lets-plot grammar of graphics features (scales, faceting, theming)
22+
- Proper sizing with ggsize(1600, 900) and scale=3 for 4800x2700 output
23+
- Clean, readable code structure following KISS principle
24+
weaknesses:
25+
- Spec suggests large year indicator per panel for Gapminder storytelling feel -
26+
facet strip labels work but larger text could enhance the visualization
27+
- Some point overlap in the 2019 panel where multiple countries converge at higher
28+
GDP/life expectancy values

0 commit comments

Comments
 (0)