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 968 ms, sys: 139 ms, total: 1.11 s
Wall time: 625 ms
%%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 87.8 ms, sys: 55.9 ms, total: 144 ms
Wall time: 163 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.
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.