|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | bar-race-animated: Animated Bar Chart Race |
3 | | -Library: altair 6.0.0 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2026-01-11 |
| 3 | +Library: altair 6.1.0 | Python 3.13.13 |
| 4 | +Quality: 89/100 | Updated: 2026-05-19 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import altair as alt |
8 | 10 | import numpy as np |
9 | 11 | import pandas as pd |
10 | 12 |
|
11 | 13 |
|
| 14 | +# Theme tokens |
| 15 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 16 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 17 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 18 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 19 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 20 | + |
| 21 | +# Okabe-Ito palette (first series always #009E73) + accessible extensions for 10 entities |
| 22 | +COLORS = [ |
| 23 | + "#009E73", # Okabe-Ito pos 1 — brand green, always first |
| 24 | + "#D55E00", # Okabe-Ito pos 2 — vermillion |
| 25 | + "#0072B2", # Okabe-Ito pos 3 — blue |
| 26 | + "#CC79A7", # Okabe-Ito pos 4 — reddish purple |
| 27 | + "#E69F00", # Okabe-Ito pos 5 — orange |
| 28 | + "#56B4E9", # Okabe-Ito pos 6 — sky blue |
| 29 | + "#F0E442", # Okabe-Ito pos 7 — yellow |
| 30 | + "#332288", # accessible extension — deep indigo |
| 31 | + "#117733", # accessible extension — deep green |
| 32 | + "#882255", # accessible extension — deep crimson |
| 33 | +] |
| 34 | + |
12 | 35 | # Data: Simulated streaming platform subscribers (millions) over time |
13 | 36 | np.random.seed(42) |
14 | 37 |
|
|
26 | 49 | ] |
27 | 50 | years = list(range(2015, 2025)) |
28 | 51 |
|
29 | | -# Generate evolving subscriber counts with different growth trajectories |
30 | | -data = [] |
31 | 52 | base_values = { |
32 | 53 | "StreamFlix": 40, |
33 | 54 | "ViewMax": 35, |
|
53 | 74 | "CloudTV": 1.06, |
54 | 75 | } |
55 | 76 |
|
| 77 | +data = [] |
56 | 78 | for platform in platforms: |
57 | 79 | value = base_values[platform] |
58 | 80 | for year in years: |
|
65 | 87 | # Select key years for small multiples |
66 | 88 | key_years = [2015, 2018, 2021, 2024] |
67 | 89 | df_key = df[df["Year"].isin(key_years)].copy() |
68 | | - |
69 | | -# Add rank for each year (used for sorting within each facet) |
70 | 90 | df_key["Rank"] = df_key.groupby("Year")["Subscribers"].rank(ascending=True, method="first").astype(int) |
71 | 91 |
|
72 | | -# Color palette with Python Blue and Yellow first |
73 | | -colors = ["#306998", "#FFD43B", "#4A90D9", "#2ECC71", "#E74C3C", "#9B59B6", "#F39C12", "#1ABC9C", "#34495E", "#E67E22"] |
74 | | - |
75 | | -# Create single bar chart that will be faceted |
| 92 | +# Bar chart layer |
76 | 93 | bar_chart = ( |
77 | 94 | alt.Chart(df_key) |
78 | 95 | .mark_bar(cornerRadiusEnd=6, height=45) |
79 | 96 | .encode( |
80 | 97 | x=alt.X( |
81 | 98 | "Subscribers:Q", |
82 | 99 | title="Subscribers (millions)", |
83 | | - axis=alt.Axis(labelFontSize=16, titleFontSize=20, grid=True, gridOpacity=0.3), |
| 100 | + axis=alt.Axis(labelFontSize=16, titleFontSize=20, grid=True, gridOpacity=0.25), |
84 | 101 | ), |
85 | 102 | y=alt.Y("Rank:O", title=None, axis=None, sort="descending"), |
86 | | - color=alt.Color("Platform:N", scale=alt.Scale(domain=platforms, range=colors), legend=None), |
| 103 | + color=alt.Color( |
| 104 | + "Platform:N", |
| 105 | + scale=alt.Scale(domain=platforms, range=COLORS), |
| 106 | + legend=alt.Legend( |
| 107 | + title="Platform", labelFontSize=16, titleFontSize=18, symbolSize=160, padding=14, cornerRadius=4 |
| 108 | + ), |
| 109 | + ), |
87 | 110 | tooltip=[ |
88 | 111 | alt.Tooltip("Platform:N", title="Platform"), |
89 | 112 | alt.Tooltip("Subscribers:Q", format=".1f", title="Subscribers (M)"), |
|
92 | 115 | ) |
93 | 116 | ) |
94 | 117 |
|
95 | | -# Add text labels on bars |
| 118 | +# Platform name labels outside bars |
96 | 119 | text_labels = ( |
97 | 120 | alt.Chart(df_key) |
98 | 121 | .mark_text(align="left", dx=8, fontSize=14, fontWeight="bold") |
99 | 122 | .encode( |
100 | 123 | x=alt.X("Subscribers:Q"), |
101 | 124 | y=alt.Y("Rank:O", sort="descending"), |
102 | 125 | text=alt.Text("Platform:N"), |
103 | | - color=alt.value("#333333"), |
| 126 | + color=alt.value(INK), |
104 | 127 | ) |
105 | 128 | ) |
106 | 129 |
|
107 | | -# Value labels at end of bars |
| 130 | +# Value labels inside bars |
108 | 131 | value_labels = ( |
109 | 132 | alt.Chart(df_key) |
110 | | - .mark_text(align="right", dx=-8, fontSize=13, fontWeight="normal") |
| 133 | + .mark_text(align="right", dx=-8, fontSize=16, fontWeight="normal") |
111 | 134 | .encode( |
112 | 135 | x=alt.X("Subscribers:Q"), |
113 | 136 | y=alt.Y("Rank:O", sort="descending"), |
|
116 | 139 | ) |
117 | 140 | ) |
118 | 141 |
|
119 | | -# Combine layers - target 4800x2700 at scale 3x = 1600x900 base |
| 142 | +# Combine layers and facet by year |
120 | 143 | combined = (bar_chart + text_labels + value_labels).properties(width=340, height=750) |
121 | 144 |
|
122 | | -# Facet by year |
123 | 145 | chart = ( |
124 | 146 | combined.facet( |
125 | | - column=alt.Column("Year:O", header=alt.Header(labelFontSize=26, title=None, labelPadding=15)), spacing=20 |
| 147 | + column=alt.Column("Year:O", header=alt.Header(labelFontSize=26, title=None, labelPadding=15, labelColor=INK)), |
| 148 | + spacing=20, |
126 | 149 | ) |
127 | | - .configure_view(strokeWidth=0) |
128 | | - .configure_axis(labelFontSize=18, titleFontSize=22) |
129 | 150 | .properties( |
130 | 151 | title=alt.Title( |
131 | | - "bar-race-animated · altair · pyplots.ai", |
| 152 | + "bar-race-animated · python · altair · anyplot.ai", |
132 | 153 | fontSize=34, |
133 | 154 | anchor="middle", |
134 | 155 | subtitle="Streaming Platform Subscribers Over Time (millions)", |
135 | 156 | subtitleFontSize=22, |
136 | 157 | dy=-10, |
137 | | - ) |
| 158 | + ), |
| 159 | + background=PAGE_BG, |
| 160 | + ) |
| 161 | + .configure_view(strokeWidth=0, fill=PAGE_BG) |
| 162 | + .configure_axis( |
| 163 | + labelFontSize=18, |
| 164 | + titleFontSize=22, |
| 165 | + domainColor=INK_SOFT, |
| 166 | + tickColor=INK_SOFT, |
| 167 | + gridColor=INK_SOFT, |
| 168 | + gridOpacity=0.20, |
| 169 | + labelColor=INK_SOFT, |
| 170 | + titleColor=INK, |
138 | 171 | ) |
| 172 | + .configure_title(color=INK, subtitleColor=INK_SOFT) |
| 173 | + .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) |
139 | 174 | .resolve_scale(x="independent") |
140 | 175 | ) |
141 | 176 |
|
142 | | -# Save outputs |
143 | | -chart.save("plot.png", scale_factor=3.0) |
144 | | -chart.save("plot.html") |
| 177 | +# Save |
| 178 | +chart.save(f"plot-{THEME}.png", scale_factor=3.0) |
| 179 | +chart.save(f"plot-{THEME}.html") |
0 commit comments