Gapminders#

Hans Rosling’s gapminder example rendered with four plotting libraries, Matplotlib, Plotly, Altair and hvPlot, all driven by the same widgets.

import altair as alt
import hvplot.pandas  # noqa: F401
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import panel as pn
import plotly.graph_objs as go

pn.extension('vega', 'plotly', defer_load=True)

Configuration#

Let us start by configuring some high-level variables:

XLABEL = 'GDP per capita (2000 dollars)'
YLABEL = 'Life expectancy (years)'
YLIM = (20, 90)
ACCENT = "#00A170"

PERIOD = 1000 # milliseconds

Extract the dataset#

First, we’ll get the data into a Pandas dataframe. We use the built in cache to speed up the app.

@pn.cache
def get_dataset():
    url = 'https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv'
    return pd.read_csv(url)

dataset = get_dataset()

YEARS = [int(year) for year in dataset.year.unique()]

dataset.sample(10)
country year pop continent lifeExp gdpPercap
2 Afghanistan 1962 10267083.0 Asia 31.997 853.100710
80 Austria 1992 7914969.0 Europe 76.040 27042.018680
1369 Slovak Republic 1957 3844277.0 Europe 67.450 6093.262980
1595 Uganda 2007 29170398.0 Africa 51.542 1056.380121
779 Italy 2007 58147733.0 Europe 80.546 28569.719700
805 Jordan 1957 746559.0 Asia 45.669 1886.080591
164 Botswana 1992 1342614.0 Africa 62.745 7954.111645
191 Bulgaria 2007 7322858.0 Europe 73.005 10680.792820
1461 Swaziland 1997 1054486.0 Africa 54.289 3876.768460
129 Benin 1997 6066080.0 Africa 54.777 1232.975292

Set up widgets and description#

A DiscretePlayer steps through the years and loops on its own, so no periodic callback is needed:

year = pn.ui.DiscretePlayer(
    value=YEARS[-1], options=YEARS, label="Year", interval=PERIOD, loop_policy="loop", show_value=True
)
show_legend = pn.ui.Checkbox(value=True, label="Show Legend")

desc = pn.ui.Markdown("""## 🎓 Info

The [Panel](https://panel.holoviz.org) library from [HoloViz](https://holoviz.org)
lets you make widget-controlled apps and dashboards from a wide variety of
plotting libraries and data types. Here you can try out four different plotting libraries
controlled by a couple of widgets, for Hans Rosling's
[gapminder](https://demo.bokeh.org/gapminder) example.

Source: [holoviz-topics - gapminder](https://github.com/holoviz-topics/examples/blob/main/gapminders/gapminders.ipynb)
""")

settings = pn.ui.Column(
    pn.ui.Markdown("## ⚙️ Settings"), year, show_legend, desc,
    sizing_mode='stretch_width'
)

settings

Define plotting functions#

Now let’s define helper functions and functions to plot this dataset with Matplotlib, Plotly, Altair, and hvPlot (using HoloViews and Bokeh). Only the data extraction is cached; the plot functions return fresh, mutable figures for each session.

@pn.cache
def get_data(year):
    df = dataset[(dataset.year==year) & (dataset.gdpPercap < 10000)].copy()
    df['size'] = np.sqrt(df['pop']*2.666051223553066e-05)
    df['size_hvplot'] = df['size']*6
    return df

def get_title(library, year):
    return f"{library}: Life expectancy vs. GDP, {year}"

def get_xlim(data):
    return (data['gdpPercap'].min()-100, data['gdpPercap'].max()+1000)

def mpl_view(year=1952, show_legend=True):
    data = get_data(year)

    plot = plt.figure(figsize=(10, 6), facecolor=(0, 0, 0, 0))
    ax = plot.add_subplot(111)
    ax.set_xscale("log")
    ax.set_title(get_title("Matplotlib", year))
    ax.set_xlabel(XLABEL)
    ax.set_ylabel(YLABEL)
    ax.set_ylim(YLIM)
    ax.set_xlim(get_xlim(data))

    for continent, df in data.groupby('continent'):
        ax.scatter(df.gdpPercap, y=df.lifeExp, s=df['size']*5,
                   edgecolor='black', label=continent)

    if show_legend:
        ax.legend(loc=4)

    plt.close(plot)
    return plot

def plotly_view(year=1952, show_legend=True):
    data = get_data(year)

    traces = []
    for continent, df in data.groupby('continent'):
        marker = dict(symbol='circle', sizemode='area', sizeref=0.1, size=df['size'], line=dict(width=2))
        traces.append(go.Scatter(x=df.gdpPercap, y=df.lifeExp, mode='markers', marker=marker, name=continent, text=df.country))

    axis_opts = dict(zerolinewidth=1, ticklen=5, gridwidth=2)
    layout = go.Layout(
        title=get_title("Plotly", year), showlegend=show_legend,
        xaxis=dict(title=XLABEL, type='log', **axis_opts),
        yaxis=dict(title=YLABEL, **axis_opts),
        autosize=True,
    )
    return go.Figure(data=traces, layout=layout)

def altair_view(year=1952, show_legend=True):
    data = get_data(year)
    legend = {} if show_legend else {'legend': None}
    return (
        alt.Chart(data)
            .mark_circle().encode(
                alt.X('gdpPercap:Q', scale=alt.Scale(type='log'), axis=alt.Axis(title=XLABEL)),
                alt.Y('lifeExp:Q', scale=alt.Scale(zero=False, domain=YLIM), axis=alt.Axis(title=YLABEL)),
                size=alt.Size('pop:Q', scale=alt.Scale(type="log"), legend=None),
                color=alt.Color('continent', scale=alt.Scale(scheme="category10"), **legend),
                tooltip=['continent', 'country'])
            .configure_axis(grid=False)
            .properties(title=get_title("Altair/ Vega", year), height="container", width="container", background='rgba(0,0,0,0)')
            .interactive()
    )

def hvplot_view(year=1952, show_legend=True):
    data = get_data(year)
    return data.hvplot.scatter(
        'gdpPercap', 'lifeExp', by='continent', s='size_hvplot', alpha=0.8,
        logx=True, title=get_title("hvPlot/ Bokeh", year), responsive=True,
        legend='bottom_right', hover_cols=['country'], ylim=YLIM, xlim=get_xlim(data),
        ylabel=YLABEL, xlabel=XLABEL
    ).opts(show_legend=show_legend)

Bind the plot functions to the widgets#

Each pane is created once and binds its plot function to the widgets, so only the figure is replaced when the year changes.

def card(pane):
    return pn.ui.Grid(
        pn.ui.Paper(pane, variant="outlined", sizing_mode="stretch_both", min_height=420),
        size={"xs": 12, "lg": 6},
    )

plots = pn.ui.Grid(
    card(pn.ui.HoloViews(pn.bind(hvplot_view, year=year, show_legend=show_legend), sizing_mode='stretch_both')),
    card(pn.ui.Plotly(pn.bind(plotly_view, year=year, show_legend=show_legend), sizing_mode='stretch_both')),
    card(pn.ui.Matplotlib(pn.bind(mpl_view, year=year, show_legend=show_legend), format='png', sizing_mode='stretch_both', tight=True)),
    card(pn.ui.Vega(pn.bind(altair_view, year=year, show_legend=show_legend), sizing_mode='stretch_both')),
    container=True, spacing=2,
)

page = pn.ui.Page(
    site_url="https://panel.holoviz.org",
    title="Hans Rosling's Gapminder",
    main=[plots],
    sidebar=[settings],
    theme_config={"palette": {"primary": {"main": ACCENT}}}
)

page

Served App#

if pn.state.served:
    page.servable()