Build Animation
In this tutorial, we will create a bar chart race using the Altair plotting library and the Player
widget.
Your browser does not support the video tag.
:::{dropdown} Dependencies
:::
:::{dropdown} Code
import altair as alt
import pandas as pd
import panel as pn
pn.extension("vega")
BY = "t_manu"
VALUE = "p_cap"
@pn.cache()
def get_data():
return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz")
data = get_data()
min_year = int(data.p_year.min())
max_year = int(data.p_year.max())
max_capacity_by_manufacturer = data.groupby(BY)[VALUE].sum().max()
def get_group_sum(year):
return (
data[data.p_year <= year][[BY, VALUE]]
.groupby(BY)
.sum()
.sort_values(by=VALUE, ascending=False)
.reset_index()
.head(10)
)
def get_plot(year):
data = get_group_sum(year)
base = (
alt.Chart(data)
.encode(
x=alt.X(VALUE, scale=alt.Scale(domain=[0, max_capacity_by_manufacturer]), title="Capacity"),
y=alt.Y(f"{BY}:O", sort=[BY], title="Manufacturer"),
text=alt.Text(VALUE, format=",.0f"),
)
.properties(title=str(year), height=500, width="container")
)
return base.mark_bar() + base.mark_text(align="left", dx=2)
year = pn.widgets.Player(
value=max_year,
start=min_year,
end=max_year,
name="Year",
loop_policy="loop",
interval=300,
align="center",
)
def pause_player_at_max_year(value):
if year.value==max_year:
year.pause()
pn.bind(pause_player_at_max_year, year, watch=True)
plot = pn.pane.Vega(pn.bind(get_plot, year))
pn.Column("# Wind Turbine Capacity 1982-2022", plot, year, sizing_mode="stretch_width").servable()
:::
Install the dependencies
Please ensure that Altair and Panel are installed.
::::{tab-set}
:::{tab-item} pip :sync: pip
:::
:::{tab-item} conda :sync: conda
conda install -y -c conda-forge altair panel
:::
::::
Explanation
🚀 Welcome to the Altair & Panel Bar Chart Race Tutorial!
Let's embark on an adventure to visualize the dynamic evolution of wind turbine capacities over the years. 🌬️💨
import altair as alt
import pandas as pd
import panel as pn
pn.extension("vega")
First things first, we need to import our trusty companions: Altair for stunning visualizations, Pandas for data manipulation, and Panel for creating interactive web apps.
📊 Step 1: Data Exploration and Extraction
BY = "t_manu"
VALUE = "p_cap"
@pn.cache()
def get_data():
return pd.read_csv("https://datasets.holoviz.org/windturbines/v1/windturbines.csv.gz")
data = get_data()
min_year = int(data.p_year.min())
max_year = int(data.p_year.max())
max_capacity_by_manufacturer = data.groupby(BY)[VALUE].sum().max()
We begin by defining our data source and understanding its bounds. We extract turbine data and identify the minimum and maximum years present in the dataset, along with the maximum capacity value aggregated by manufacturer.
🔄 Step 2: Data Transformation and Grouping
def get_group_sum(year):
return (
data[data.p_year <= year][[BY, VALUE]]
.groupby(BY)
.sum()
.sort_values(by=VALUE, ascending=False)
.reset_index()
.head(10)
)
We create a function to aggregate the turbine data up to a specified year, grouping by manufacturer and summing the capacity values. We then select the top 10 manufacturers based on capacity.
📈 Step 3: Plotting Function
def get_plot(year):
data = get_group_sum(year)
base = (
alt.Chart(data)
.encode(
x=alt.X(VALUE, scale=alt.Scale(domain=[0, max_capacity_by_manufacturer]), title="Capacity"),
y=alt.Y(f"{BY}:O", sort=[BY], title="Manufacturer"),
text=alt.Text(VALUE, format=",.0f"),
)
.properties(title=str(year), height=500, width="container")
)
return base.mark_bar() + base.mark_text(align="left", dx=2)
Time to bring our visualization to life! This function generates an Altair chart based on the grouped data for a given year, encoding capacity on the x-axis, manufacturer on the y-axis, and displaying capacity as text within the bars.
⏩ Step 4: Adding Interactive Controls
year = pn.widgets.Player(
value=max_year,
start=min_year,
end=max_year,
name="Year",
loop_policy="loop",
interval=300,
align="center",
)
def pause_player_at_max_year(value):
if year.value==max_year:
year.pause()
pn.bind(pause_player_at_max_year, year, watch=True); # semi-colon is only needed when line is run at end of cell
Let's make it interactive! We introduce a Player
widget to loop continuously through the years.
🎨 Step 5: Binding Plot to Widget
plot = pn.pane.Vega(pn.bind(get_plot, year))
Now, let's bind our plot function to the selected year. Whenever the user changes the year, the plot dynamically updates to reflect the selected timeframe.
🖼️ Step 6: Layout and Presentation
pn.Column("# Wind Turbine Capacity 1982-2022", plot, year, sizing_mode="stretch_width").servable()
Lastly, we organize our components into a neat layout. A title sets the stage, followed by our interactive plot and the year selection widget. The layout adjusts to fill the available width.
Voilà! 🎉 You're now ready to run the code and witness the mesmerizing bar chart race showcasing the evolution of wind turbine capacities over the years. Happy exploring! 🌟
:::{hint} You can create many types of animations using the object types that Panel can display. Not just bar chart races using Altair. :::
Serve the App
Now serve the app with:
::::{tab-set}
:::{tab-item} Script :sync: script
:::
:::{tab-item} Notebook :sync: notebook
panel serve app.ipynb --dev
:::
::::
Open http://localhost:5006/app
Press the play button.
It should resemble
Your browser does not support the video tag.
Recap
In this tutorial, we built a bar chart race using the Altair plotting library and the Player
widget.