Building a Dashboard

Right click to download this notebook from GitHub.


In [ ]:
import numpy as np
import panel as pn
import holoviews as hv
from holoviews import opts  # noqa

pn.extension('katex')
hv.extension('bokeh')

Exercise 1

In this exercise you will construct a number of Panel components and then lay them out as a non-interactive Panel dashboard.

The data

Throughout this tutorial we will be working with one core dataset, a collection of earthquakes recorded between 2000-2018 provided by the US Geological Survey (USGS). The data is provided as a Parquet file as part of the tutorial and we will load it using Dask and persist it. We will return to this later; for now we will focus on building a dashboard and you don't know any of the details about the dataset or the Dask or Pandas API.

In [ ]:
import dask.dataframe as dd

df = dd.read_parquet('../../data/earthquakes.parq')

df = df[~df.mag.isna()].persist()

The first component of the dashboard is an image of the US Geological Survey logo. Start by declaring a pane containing the logo and assign it to the logo variable. Also set a width to ensure the logo doesn't appear too big.

In [ ]:
logo_url = '../../assets/usgs_logo.png'

## Define a panel component containing the logo
logo = ...

## Display it
Solution
logo = pn.panel(logo_url, width=300)

Richter scale equation

Next we will create a component to display the equation for the Richter scale definition. Declare the appropriate pane and assign it to the equation variable.

In [ ]:
equation_string = '$M_L = log_{10}A - log_{10} A_0(\delta)$'

## Define a panel component containing the equation (Hint: Use the LaTeX pane)
equation = ...

## Display it
Solution
logo = pn.pane.LaTeX(equation_string)

List the strongest earthquakes

In [ ]:
year = 2000

def strongest_earthquakes_fn(year):
    year_df = df[(df.time.dt.year == year) & (df.mag > 7)].compute()
    return year_df.sort_values('mag', ascending=False).iloc[:5][['time', 'place', 'mag']].reset_index(drop=True)

## Create a panel component by calling the function with a particular year
strongest_earthquakes = ...

## Display it
Solution
strongest_earthquakes = pn.panel(strongest_earthquakes_fn(year))

Display an iframe of a Google Map

Hint
An iframe is an HTML tag.
In [ ]:
def gmap_fn(year):
    yearly_df = df[(df.time.dt.year == year)].compute()
    index = np.argmax(yearly_df.mag.values)
    strongest = yearly_df.iloc[index]
    lon, lat = strongest.longitude, strongest.latitude
    return """
    <iframe width="300" height="300" src="https://maps.google.com/maps?q={lat},{lon}&z=6&output=embed"
    frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>
    """.format(lat=lat, lon=lon)

## Create a panel component by calling the function with a particular year and wrapping it in the appropriate pane
gmap = ...

## Display it

A plot [challenge]

If you are up to it, create a custom plot from the year_df dataframe defined below, create a Panel component, and assign it to the plot variable.

Info
If you are using matplotlib pyplot you can get the figure with ``plot = plt.gcf()`` and the close it with ``plot.close()``
In [ ]:
year_df = df[df.time.dt.year == year].compute()

## Create a plot and assign it to the plot variable
plot = ...

## Display it
Solution

This example solution uses concepts covered in the plotting section of the tutorial:

plot = hv.Violin(year_df, 'type', 'mag').opts(ylim=(0, None), xrotation=90)

Composing the dashboard

Now that we have defined all the different components, it is time to lay them out into the overall dashboard.

Arrange the following components into a dashboard using the Row and Column panels:

  • logo
  • equation
  • strongest_earthquakes
  • gmap
  • plot (optional)
Solution
year = 2000

logo = pn.panel(logo_url, width=200)
equation = pn.pane.LaTeX(equation_string)
strongest_earthquakes = strongest_earthquakes_fn(year)
gmap = pn.pane.HTML(gmap_fn(year), height=300, width=300)
plot = hv.Violin(year_df, 'type', ('mag', 'Magnitude')).opts(ylim=(0, None))

title = pn.panel('# Strongest Earthquakes in the Year %d' % year, width=400)
header = pn.Row(title, pn.layout.HSpacer(), logo)

body = pn.Row(
    pn.Column('### Strongest Earthquakes', strongest_earthquakes),
    pn.Column('### Description', gmap),
    pn.Column('### Magnitude Plot', plot)
)

pn.Column(header, body)

Exercise 2

Having learned about how to create interactive components we can now make the formerly static dashboard interactive by adding a widget to control the year.

The widget

Declare an IntSlider widget with a start value of 2000, end value of 2018, and current value of 2000.

Solution
year_slider = pn.widgets.IntSlider(name='Year', start=2000, end=2018, value=2000)
year_slider

The title

Write a function with dependencies which returns a title for the given year, e.g. "Strongest Earthquakes in the Year 2000":

Solution
@pn.depends(year_slider.param.value)
def title_fn(year):
    return '## Strongest Earthquakes in the Year {year}'.format(year=year)

The table and map

Add dependencies to the functions below so the output updates whenever the slider value changes:

In [ ]:
def strongest_earthquakes_fn(year): # noqa: redefined on purpose
    year_df = df[df.time.dt.year == year].compute()
    return year_df.sort_values('mag', ascending=False).iloc[:5][['time', 'place', 'mag']].reset_index(drop=True)


def gmap_fn(year): # noqa: redefined on purpose
    yearly_df = df[(df.time.dt.year == year)].compute()
    index = np.argmax(yearly_df.mag.values)
    strongest = yearly_df.iloc[index]
    lon, lat = strongest.longitude, strongest.latitude
    return pn.pane.HTML("""
    <iframe width="300" height="300" src="https://maps.google.com/maps?q={lat},{lon}&z=6&output=embed"
    frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>
    """.format(lat=lat, lon=lon), height=300, width=300)

The Plot [challenge]

In case you defined a plot above make the plot dynamic by wrapping it in a function which depends on the year.

Solution
@pn.depends(year_slider.param.value)
def plot_fn(year)
    year_df = df[df.time.dt.year == year].compute()
    return hv.Violin(year_df, 'type', ('mag', 'Magnitude')).opts(xrotation=90)

Composing the dashboard

Now that we have created new dynamic components let us lay them out once again to create a fully interactive dashboard. Ensure that you include the widget so you can actually control the year.

Solution
year_slider = pn.widgets.IntSlider(name='Year', start=2000, end=2018, value=2000)

@pn.depends(year_slider.param.value)
def title_fn(year):
    return '## Strongest Earthquakes in the Year {year}'.format(year=year)

@pn.depends(year_slider.param.value)
def strongest_earthquakes_fn(year):
    year_df = df[df.time.dt.year == year].compute()
    return year_df.sort_values('mag', ascending=False).iloc[:5][['time', 'place', 'mag']].reset_index(drop=True)

@pn.depends(year_slider.param.value)
def gmap_fn(year):
    yearly_df = df[(df.time.dt.year == year)].compute()
    index = np.argmax(yearly_df.mag.values)
    strongest = yearly_df.iloc[index]
    lon, lat = strongest.longitude, strongest.latitude
    return pn.pane.HTML("""
    <iframe width="300" height="300" src="https://maps.google.com/maps?q={lat},{lon}&z=6&output=embed"
    frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>
    """.format(lat=lat, lon=lon), height=300, width=300)

@pn.depends(year_slider.param.value)
def plot_fn(year):
    year_df = df[df.time.dt.year == year].compute()
    return hv.Violin(year_df, 'type', ('mag', 'Magnitude'))

logo = pn.panel(logo_url, width=200)
equation = pn.pane.LaTeX(equation_string)

header = pn.Row(title_fn, pn.layout.HSpacer(), logo)

body = pn.Row(

    pn.Column('### Strongest Earthquakes', strongest_earthquakes_fn),
    pn.Column('### Map', gmap_fn),
    pn.Column('### Magnitude Plot', plot_fn)
)

pn.Column(header, year_slider, body)

Right click to download this notebook from GitHub.