HoloViz logos

Exercises 1-2: Building a Dashboard

import numpy as np
import panel as pn
import holoviews as hv
from holoviews import opts  # noqa

pn.extension("katex")
hv.extension("bokeh")
# Run this if you haven't already to fetch earthquake and population data files
from fetch_data import *

get_earthquake_data()
get_population_data()
Earthquakes dataset present, skipping download

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.

import dask.dataframe as dd

df = dd.read_parquet("../../data/earthquakes.parq", index='index')
df.time = df.time.astype("datetime64[ns]")
df = df[~df.mag.isna()].persist()

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.

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
equation = pn.pane.LaTeX(equation_string)

List the strongest earthquakes#

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.

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()

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:

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)