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)