Holoviz plotting - shrinking the time range#
We’ve attempted to use the Holoviz package to make some interactive figures. The figures need an active python kernel to function, and so can’t be inserted directly into a static webpage.
In this notebook, we show some examples of creating interactive figures to highlight what can be done. At the end of the script, we build a plotting function, and bind it in a Holoviz Panel, which can then be launched as an app. This requires a separately running server to host the python functionality, and highlights the sort of possibilities that these tools can have.
import erddapy
from erddapy import ERDDAP
import numpy as np
import pandas as pd
import xarray as xr
import datetime
import matplotlib
from matplotlib import pyplot as plt
import pathlib
import holoviews as hv
import hvplot.pandas # noqa
import hvplot.xarray
import panel as pn
pn.extension(template='material')
def get_erddap_data(erddap_url, dataset, data_protocol="griddap", variables=None, constraints=None):
"""
Function: get_erddap_data
This function uses the erddapy python library to access data from ERDDAP servers,
and to return it to users in convenient formats for python users.
Data can be pulled from "tabledap" or "griddap" formats, with different
output types, depending on the dap type.
Inputs:
erddap_url - The url address of the erddap server to pull data from
variables - The selected variables within the dataset.
data_protocol - The erddap data protocol for the chosen dataset.
Options include "tabledap" or "griddap"
The default option is given as "griddap"
dataset - The ID for the relevant dataset on the erddap server
If no variables are given, it is assumed that all variables
will be pulled.
constraints - These are set by the user to help restrict the data pull
to only the area and timeframe of interest.
If no constraints are given, all data in a dataset is pulled.
Constraints should be given as a dictionary, where
each entry is a bound and/or selection of a specific axis variable
Exs. {"longitude<=": "min(longitude)+10", "longitude>=": "0"}
{"longitude=": "140", "time>=": "max(time)-30"}
Outputs:
erddap_data - This variable contains the pulled data from the erddap server.
If the data_protocol is "griddap", then erddap_data is an xarray dataset
If the data_protocol is "tabledap", then erddap_data is a pandas dataframe
"""
import erddapy
from erddapy import ERDDAP
import pandas as pd
import xarray
############################################
# Set-up the connection to the ERDDAP server
############################################
# Connect to the erddap server
e = ERDDAP(server=erddap_url, protocol=data_protocol, response='csv')
# Identify the dataset of interest
e.dataset_id = dataset
#########################################
# Pull the data, based upon protocol type
#########################################
# GRIDDAP Protocol
if data_protocol == "griddap":
# Initialize the connection
e.griddap_initialize()
# Update the constraints
if constraints is not None:
e.constraints.update(constraints)
e.griddap_initialize()
# Update the selection of the variables
if variables is not None:
e.variables = variables
erddap_data = e.to_xarray()
# TABLEDAP Protocol
elif data_protocol == "tabledap":
# Update the constraints
if constraints is not None:
e.constraints = constraints
# Update the selection of the variables
if variables is not None:
e.variables = variables
erddap_data = e.to_pandas()
# Invalid protocol given
else:
print('Invalid ERDDAP protocol. Given protocol is: ' + data_protocol)
print('Valid protocols include "griddap" or "tabledap". Please restart and try again with a valid protocol')
erddap_data = None
#############################
return erddap_data
Load in data from the ERDDAP server#
First, we need to get some data. To do this, we can download data from the NANOOS buoy near Hansville, WA. The data is a gridded product of individual CTD casts taken for the last 10+ years on a near-daily basis.
nwem_url = 'http://nwem.apl.washington.edu/erddap'
nwem_dataset = 'orca3_L3_depthgridded_025'
variables = ["sea_water_temperature",
"sea_water_practical_salinity",
"mass_concentration_of_oxygen_in_sea_water"]
constraints = {"cast_start_time>=":datetime.datetime(2016,1,1).strftime('%Y-%m-%dT%H:%M:%SZ')}
#constraints = {"cast_start_time>=": "max(cast_start_time)-365"}
nwem_grid = get_erddap_data(nwem_url, nwem_dataset,
variables=variables,
constraints=constraints,
data_protocol="griddap")
Create a slider to move between individual casts#
Next, we can create an interactive plot, where we can show the sea surface temperature for individual casts.
slider = pn.widgets.IntSlider(name='Dates', start=100, end=nwem_grid.dims['cast_start_time'])
nwem_grid.interactive().isel(cast_start_time=slider).sea_water_temperature.hvplot()
Create a function to create an interactive plotting time range#
Finally, we can try to create a Holoviz Panel app, which can be embedded into a webpage, if there is an active server running.
def plot_timerange(nwem_interactive, date_slider):
#print(date_slider)
#print(date_slider.value[0])
start_date = np.datetime64(date_slider[0])
end_date = np.datetime64(date_slider[1])
nwem_to_plot = nwem_interactive.where(np.logical_and(nwem_interactive.cast_start_time >= start_date,
nwem_interactive.cast_start_time <= end_date),drop=True)
dates_to_plot = nwem_to_plot.cast_start_time.values
depths_to_plot = nwem_to_plot.depth.values
sst_to_plot = nwem_to_plot.sea_water_temperature.values
sss_to_plot = nwem_to_plot.sea_water_practical_salinity.values
fig = plt.Figure(figsize=(6,4))
ax1 = fig.add_subplot(211)
cb = ax1.pcolor(dates_to_plot, depths_to_plot, sst_to_plot.T)
ax1.invert_yaxis()
fig.colorbar(cb,ax=ax1,label='Temperature')
ax1.set_title('Hansville - NANOOS Buoy')
ax1.set_ylabel('Depth (m)')
ax2 = fig.add_subplot(212)
cb = ax2.pcolor(dates_to_plot, depths_to_plot, sss_to_plot.T)
ax2.invert_yaxis()
fig.colorbar(cb,ax=ax2,label='Salinity')
ax2.set_ylabel('Depth (m)')
fig.tight_layout()
return fig
# Create an interactive xarray
nwem_interactive = nwem_grid.interactive()
# Create a date slider widget
date_slider = pn.widgets.DateRangeSlider(name='Earliest Date',
start=datetime.datetime(2016,1,1),
end=datetime.datetime(2023,1,1),
value=(datetime.datetime(2017,1,1),
datetime.datetime(2020,1,1))).servable(target='sidebar')
# Use a matplotlib panel pane to
# plot the time range, using the interactive array
# and the date slider
mpl = pn.pane.Matplotlib(
pn.bind(plot_timerange, nwem_interactive, date_slider)
)
pn.Row(
'Buoy data', mpl
).servable(target='main')
To test out the app, we can click the “Preview with Panel” button in the notebook ribbon at the top (it’s next to the code/markdown dropdown menu)