Skip to content

Commit 174ca99

Browse files
feat(pygal): implement bar-race-animated (#3684)
## Implementation: `bar-race-animated` - pygal Implements the **pygal** version of `bar-race-animated`. **File:** `plots/bar-race-animated/implementations/pygal.py` **Parent Issue:** #3653 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20886563904)* --------- 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 6393d95 commit 174ca99

File tree

2 files changed

+425
-0
lines changed

2 files changed

+425
-0
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
""" pyplots.ai
2+
bar-race-animated: Animated Bar Chart Race
3+
Library: pygal 3.1.0 | Python 3.13.11
4+
Quality: 87/100 | Created: 2026-01-11
5+
"""
6+
7+
from io import BytesIO
8+
9+
import cairosvg
10+
import numpy as np
11+
import pygal
12+
from PIL import Image, ImageDraw, ImageFont
13+
from pygal.style import Style
14+
15+
16+
# Data - Technology companies market cap evolution (2019-2024)
17+
np.random.seed(42)
18+
19+
companies = ["Apple", "Microsoft", "Alphabet", "Amazon", "Meta", "Tesla", "NVIDIA", "Samsung"]
20+
years = [2019, 2020, 2021, 2022, 2023, 2024]
21+
22+
# Generate realistic market cap data (in billions USD)
23+
base_values = {
24+
"Apple": 1200,
25+
"Microsoft": 900,
26+
"Alphabet": 800,
27+
"Amazon": 700,
28+
"Meta": 400,
29+
"Tesla": 100,
30+
"NVIDIA": 150,
31+
"Samsung": 300,
32+
}
33+
34+
growth_rates = {
35+
"Apple": [1.0, 1.3, 1.8, 1.5, 2.0, 2.8],
36+
"Microsoft": [1.0, 1.4, 2.0, 1.6, 2.2, 2.9],
37+
"Alphabet": [1.0, 1.2, 1.6, 1.2, 1.5, 2.0],
38+
"Amazon": [1.0, 1.5, 1.6, 1.0, 1.3, 1.8],
39+
"Meta": [1.0, 1.5, 1.8, 0.6, 1.2, 1.8],
40+
"Tesla": [1.0, 5.0, 8.0, 4.0, 6.0, 7.0],
41+
"NVIDIA": [1.0, 2.0, 4.0, 2.5, 6.0, 14.0],
42+
"Samsung": [1.0, 1.1, 1.3, 0.9, 1.1, 1.4],
43+
}
44+
45+
# Calculate market cap for each year
46+
data = {}
47+
for company in companies:
48+
data[company] = [int(base_values[company] * growth_rates[company][i]) for i in range(len(years))]
49+
50+
# Colors for consistent entity tracking - distinct hues to avoid confusion
51+
company_colors = {
52+
"Apple": "#306998", # Python Blue (primary)
53+
"Microsoft": "#FFD43B", # Python Yellow (primary)
54+
"Alphabet": "#34A853", # Google Green (distinct from blues)
55+
"Amazon": "#FF9900", # Amazon Orange
56+
"Meta": "#E040FB", # Purple (distinct from blue tones)
57+
"Tesla": "#CC0000", # Tesla Red
58+
"NVIDIA": "#76B900", # NVIDIA Green
59+
"Samsung": "#795548", # Brown (distinct from all other colors)
60+
}
61+
62+
# Create individual charts for each year
63+
charts = []
64+
for year_idx, year in enumerate(years):
65+
# Get values for this year and sort by value (descending)
66+
year_data = [(company, data[company][year_idx]) for company in companies]
67+
year_data.sort(key=lambda x: x[1], reverse=True)
68+
69+
# Create style for this chart
70+
year_style = Style(
71+
background="white",
72+
plot_background="white",
73+
foreground="#333333",
74+
foreground_strong="#333333",
75+
foreground_subtle="#666666",
76+
title_font_size=52,
77+
label_font_size=36,
78+
major_label_font_size=32,
79+
legend_font_size=32,
80+
value_font_size=32,
81+
tooltip_font_size=28,
82+
)
83+
84+
chart = pygal.HorizontalBar(
85+
width=1500,
86+
height=950,
87+
style=year_style,
88+
show_legend=False, # Disable legend on individual charts - use global legend only
89+
title=str(year),
90+
x_title="Market Cap ($B)",
91+
print_values=True,
92+
print_values_position="middle",
93+
value_formatter=lambda x: f"${x:,.0f}B",
94+
margin=40,
95+
spacing=12,
96+
truncate_label=-1,
97+
show_x_labels=True,
98+
x_label_rotation=0,
99+
show_minor_x_labels=False,
100+
)
101+
102+
# Set x_labels for company names (shown on y-axis for horizontal bar)
103+
chart.x_labels = [company for company, _ in year_data]
104+
105+
# Add each company as a separate series with its value at the correct position
106+
# Use None placeholders for other positions to avoid stacking
107+
num_companies = len(year_data)
108+
for idx, (company, value) in enumerate(year_data):
109+
values = [None] * num_companies
110+
values[idx] = {"value": value, "color": company_colors[company]}
111+
chart.add(company, values)
112+
113+
charts.append(chart)
114+
115+
# Render each chart to PNG and combine into grid
116+
chart_images = []
117+
for chart in charts:
118+
svg_data = chart.render()
119+
png_data = cairosvg.svg2png(bytestring=svg_data, output_width=1500, output_height=950)
120+
img = Image.open(BytesIO(png_data))
121+
chart_images.append(img)
122+
123+
# Create 3x2 grid layout (4800 x 2700 final size)
124+
grid_width = 4800
125+
grid_height = 2700
126+
title_height = 160
127+
legend_height = 120
128+
content_height = grid_height - title_height - legend_height
129+
cell_width = grid_width // 3
130+
cell_height = content_height // 2
131+
132+
combined = Image.new("RGB", (grid_width, grid_height), "white")
133+
draw = ImageDraw.Draw(combined)
134+
135+
# Load fonts
136+
try:
137+
title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 72)
138+
legend_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 36)
139+
except OSError:
140+
title_font = ImageFont.load_default()
141+
legend_font = ImageFont.load_default()
142+
143+
# Add main title
144+
title_text = "bar-race-animated · pygal · pyplots.ai"
145+
bbox = draw.textbbox((0, 0), title_text, font=title_font)
146+
title_width = bbox[2] - bbox[0]
147+
draw.text(((grid_width - title_width) // 2, 40), title_text, fill="#333333", font=title_font)
148+
149+
# Paste charts into grid
150+
positions = [
151+
(0, 0),
152+
(1, 0),
153+
(2, 0), # Top row: 2019, 2020, 2021
154+
(0, 1),
155+
(1, 1),
156+
(2, 1), # Bottom row: 2022, 2023, 2024
157+
]
158+
159+
for idx, (col, row) in enumerate(positions):
160+
if idx < len(chart_images):
161+
img = chart_images[idx].resize((cell_width, cell_height), Image.Resampling.LANCZOS)
162+
x = col * cell_width
163+
y = title_height + row * cell_height
164+
combined.paste(img, (x, y))
165+
166+
# Add legend at bottom - larger boxes and centered layout
167+
legend_y = grid_height - legend_height + 20
168+
box_size = 40
169+
spacing_between = grid_width // len(companies)
170+
legend_x_start = spacing_between // 2 - 80 # Center the legend items
171+
172+
for i, company in enumerate(companies):
173+
x_pos = legend_x_start + i * spacing_between
174+
# Draw color box
175+
draw.rectangle([x_pos, legend_y, x_pos + box_size, legend_y + box_size], fill=company_colors[company])
176+
# Draw company name
177+
draw.text((x_pos + box_size + 12, legend_y - 2), company, fill="#333333", font=legend_font)
178+
179+
# Save as PNG
180+
combined.save("plot.png", dpi=(300, 300))
181+
182+
# Save as HTML (interactive SVG version showing 2024 final state)
183+
html_style = Style(
184+
background="white",
185+
plot_background="white",
186+
foreground="#333333",
187+
foreground_strong="#333333",
188+
foreground_subtle="#666666",
189+
title_font_size=36,
190+
label_font_size=20,
191+
major_label_font_size=18,
192+
legend_font_size=18,
193+
value_font_size=16,
194+
tooltip_font_size=16,
195+
)
196+
197+
html_chart = pygal.HorizontalBar(
198+
width=1200,
199+
height=800,
200+
style=html_style,
201+
show_legend=True,
202+
title="Tech Company Market Cap 2024 · bar-race-animated · pygal · pyplots.ai",
203+
x_title="Market Cap (Billion USD)",
204+
print_values=True,
205+
value_formatter=lambda x: f"${x:,.0f}B",
206+
)
207+
208+
final_data = [(company, data[company][-1]) for company in companies]
209+
final_data.sort(key=lambda x: x[1], reverse=True)
210+
211+
for company, value in final_data:
212+
html_chart.add(company, [{"value": value, "color": company_colors[company]}])
213+
214+
html_chart.render_to_file("plot.html")

0 commit comments

Comments
 (0)