Dashboards#

So far in this tutorial, we have seen how to generate plots with .plot or .hvplot, how to compose these plots together into layouts and overlays, how to link selections between these plots, and how to control visualizations with Panel widgets using rx. In this notebook, we will learn how to put all these pieces together to display (and serve) these components in a dashboard using Panel.

Panel pane objects#

So far we have only focused on Panel being used as a source of widgets and reactive pipelines, but you’ve also seen some Pane objects from Panel that can display various types of data (including output from just about any plotting library). To explore those, first let’s import Panel and load the extension:

import pathlib
import pandas as pd
import panel as pn
import xarray as xr
import holoviews as hv

pn.extension('tabulator', template='bootstrap')

import colorcet as cc
import hvplot.xarray # noqa
import hvplot.pandas # noqa

Here, we have enabled some optional functionality from Panel, specifically the tabulator extension, and selected a default template controlling the overall look and feel of the final app. We’ll come back to the idea of a template later. Here, let’s look at a simple pane, e.g. a Markdown pane that displays Markdown-format text:

pn.pane.Markdown('## Earthquake Dashboard')
logo_path = pathlib.Path('../assets/usgs_logo.png')

The PNG pane can display PNG images:

pn.pane.PNG(logo_path, height=130)

Using pn.panel#

Instead of having to select the pane type explicitly, you can use the pn.panel function that tries to guess the appropriate representation given the input. For instance, here we generate the same two panels using pn.panel and grab handles on the resulting objects:

dashboard_title = pn.panel('## Earthquakes')
usgs_logo = pn.panel(logo_path, height=130)

Exercise#

Confirm that these two objects are of type Markdown and PNG respectively by using the type built-in. Explore using different markdown syntax such as italic, bold or adding bullet points. Finally, try displaying your own PNG image with a PNG pane, using either a local filename or URL.

Panel objects#

In addition to pane objects, Panel offers containers of type panel which allow you to position your components into various layouts. For instance, we can put a small version of our title and logo into a Panel Row layout:

header = pn.Row(dashboard_title, pn.layout.HSpacer(), pn.pane.PNG(logo_path, height=40, align='end'))
header

Next let us load the earthquake dataset and make a basic plot of the sort we have seen earlier on in the tutorial:

%%time
df = pd.read_parquet(pathlib.Path('../data/earthquakes-projected.parq'))
df.index = df.index.tz_localize(None)
df = df.reset_index()

small_df = df.sample(frac=.01)
CPU times: user 2.24 s, sys: 336 ms, total: 2.58 s
Wall time: 1.64 s
%%time
ds = xr.open_dataarray(pathlib.Path('../data/raster/gpw_v4_population_density_rev11_2010_2pt5_min.nc'))
cleaned_ds = ds.where(ds.values != ds.nodatavals).sel(band=1)
cleaned_ds.name = 'population'
CPU times: user 102 ms, sys: 60 ms, total: 162 ms
Wall time: 155 ms
sample_points = small_df.hvplot.points(x='longitude', y='latitude', c='mag', cmap=cc.CET_L4, responsive=True)

rasterized_pop = cleaned_ds.hvplot.image(rasterize=True, logz=True, clim=(1, None), responsive=True, min_height=400).opts(bgcolor='black')

earthquake_example = rasterized_pop * sample_points

Now we can combine this plot with our header in a pn.Column:

mini_dashboard = pn.Column(header, earthquake_example)
mini_dashboard

Showing and serving dashboards#

Nowmini_dashboard is a Panel object that can be displayed or served as a simple dashboard. To view the dashboard in a new tab, you can simply call .show():

# mini_dashboard.show()

If instead of .show() you use the .servable() method, you can serve the dashboard from this notebook using the command:

$ panel serve 06_Dashboards.ipynb

We will use this to serve a more sophisticated dashboard built in this notebook.

A visual earthquake filter#

From the last notebook, first subset the data:

WEB_MERCATOR_LIMITS = (-20037508.342789244, 20037508.342789244)

subset_df = df[
    (df['northing']  < WEB_MERCATOR_LIMITS[1]) &
    (df['mag']       > 4) &
    (df['time']     >= '2017-01-01') &
    (df['time']     <= '2018-01-01')
]

Declare the panel widgets:

date_subrange = pn.widgets.DatetimeRangeSlider(
    name='Date', 
    start=subset_df.time.iloc[0], 
    end=subset_df.time.iloc[-1],
    max_width=400
)
mag_subrange = pn.widgets.FloatSlider(name='Magnitude', start=3, end=9, value=3, max_width=400)

Create an interactive DataFrame and use hvplot to generate a visualization of earthquakes plotted on a map and controlled with widgets:

subset = pn.rx(subset_df)

filtered_subrange = subset[
    (subset['mag']   > mag_subrange) &
    (subset['time'] >= date_subrange.param.value_start) &
    (subset['time'] <= date_subrange.param.value_end)
]

geo = filtered_subrange.hvplot(
    'easting', 'northing', color='mag', kind='points', framewise=False,
    xaxis=None, yaxis=None, responsive=True, min_height=500, tiles='ESRI')

This geo object works in Panel layouts, which means we can now add a header to it:

pn.Column(header, pn.Row(pn.param.ReactiveExpr(geo).widgets, hv.DynamicMap(geo)))

As before, we now have a functional dashboard that we can show with .show() or serve with .servable().

Final Dashboard#

We can now put all the concepts we have learned together to make a more sophisticated, interactive dashboard supporting linked selections:

pn.state.template.sidebar_width = 250
pn.state.template.title = 'Earthquake Interactive Demo'

ls = hv.link_selections.instance(unselected_alpha=0.02)

# Table is not yet dynamically linked to the linked selection
filtered_table = filtered_subrange.pipe(ls.filter, selection_expr=ls.param.selection_expr)[['time', 'place', 'mag', 'depth']]
table = pn.widgets.Tabulator(
    filtered_table, pagination='remote', page_size=10, show_index=False
)

mag_hist = filtered_subrange.hvplot(
    y='mag', kind='hist', responsive=True, min_height=300, max_height=600, grid=True)

depth_hist = filtered_subrange.hvplot(
    y='depth', kind='hist', responsive=True, min_height=300, max_height=600, grid=True)

geo = filtered_subrange.hvplot(
    'easting', 'northing', color='mag', kind='points',
    xaxis=None, yaxis=None, responsive=True, min_height=500,
    data_aspect=1, framewise=False, clim=(4, 10), line_color='black'
)

column = pn.Column(
    pn.Row(
        hv.element.tiles.EsriImagery() * ls(hv.DynamicMap(geo)),
    ),
    pn.Row(
        table,
        ls(hv.DynamicMap(depth_hist)),
        ls(hv.DynamicMap(mag_hist)),
    )
)
pn.param.ReactiveExpr(filtered_table).widgets.servable(area='sidebar')
column.servable(title='Earthquake Interactive Demo')

This dashboard should work in the notebook interface (with widgets shown separately from the plots above) for debugging, but a complex layout like that is meant to be served separately, e.g. using panel serve --port 5067 06_Dashboards.ipynb run in this directory. If working with the anaconda-project version of this tutorial, you can run:

anaconda-project run dashboard

Note that the code above can instead be pasted into a Python text file and run with panel serve file.py; none of the machinery depends on Jupyter or on being stored in the notebook format, and apps can be developed fully in text editors if preferred.

Sharing#

Once you are happy with the dashboard you have built, it’s time to share it! You can find on Panel’s website more details about how to:

  • configure the server

  • deploy the application on various platforms (e.g.Anaconda Notebooks, Microsoft Azure, Google Cloud Platform, Hugging Face)

  • save a dashboard as a static HTML file

  • deploy a dashboard leveraging a new technology called WebAssembly thanks to which (and to Pyodide or PyScript) a Python application can run entirely in the browser, making it a lot easier to deploy a dashboard as you no longer need to bother setting up a server

Lumen#

Panel is a highly flexible library to build apps and dashboards that provides all the required building blocks for you to assemble as you like. The Lumen library is a relatively new addition to the HoloViz ecosystem that is much more opinionated about what a data app is, handling many of the tasks involved automatically. Building on top of Intake Panel, Lumen can take care of ingesting, filtering, transforming and viewing data, all through a declarative specification that can be stored in YAML files.

Conclusion#

The techniques above and in the previous notebooks should enable you to build complex, deeply interlinked layouts and dashboards that help you understand your data and uncover hidden relationships, with custom interactivity for exploring any parameters of interest. You can explore further at the websites for any of the tools mentioned here, starting at hvplot.holoviz.org and moving on from there as needed.

In the tutorials so far, we have focused almost exclusively on the highest-level APIs provided by HoloViz, namely .hvplot() and pn.rx(). These interfaces let you focus on the data you are trying to work with, without getting bogged down in writing dozens or hundreds of lines of plotting or callback code. Of course, they don’t cover every possible type of plot or interactivity, and if you want full control, you’ll need to learn the lower-level APIs provided by HoloViz tools (HoloViews and Panel), or sometimes the even lower-level tools, like Bokeh. The remaining tutorials are entirely optional, but they introduce some of those lower-level APIs so that you can see how to do things more manually when needed. These later tutorials are particularly useful for understanding the examples of interesting apps at examples.holoviz.org, most of which were written before the simple .rx() interface was developed.

This web page was generated from a Jupyter notebook and not all interactivity will work on this website. Right click to download and run locally for full Python-backed interactivity.

Right click to download this notebook from GitHub.