holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.69k stars 403 forks source link

Using .redim.defaults overrides other Dimension attributes #3561

Closed rsignell-usgs closed 5 years ago

rsignell-usgs commented 5 years ago

As discussed on StackOverflow, it would be nice to be able to specify the default values for widgets created automatically by hvplot.

I've got a 4D dataset and hvplot is nicely generating widgets for the dimensions I've specified in the groupby:

import xarray as xr

ds = xr.open_dataset('http://gamone.whoi.edu/thredds/dodsC/coawst_4/use/fmrc/coawst_4_use_best.ncd')

rho_vars = ['salt','temp']

from cartopy import crs as ccrs
import hvplot.xarray
import holoviews as hv
from geoviews import tile_sources as gvts
import panel as pn

var_select = pn.widgets.Select(name='Variables:', options=rho_vars, 
                               value='temp')

crs = ccrs.PlateCarree()

def plot(var=None):
    base_map = gvts.OSM
    var = var or var_select.value
    mesh = ds[var][-24:,:,:].hvplot.quadmesh(x='lon_rho', y='lat_rho', rasterize=True, title=var,
                                    width=600, height=400, crs=crs,
                                    groupby=list(ds[var].dims[:-2]), cmap='jet')
    overlay = (base_map * mesh.opts(alpha=0.7)).opts(active_tools=['wheel_zoom', 'pan'])
    widgets = {dim: pn.widgets.Select for dim in ds[var].dims[:-2]}
    return pn.holoviews.HoloViews(overlay, widgets=widgets).layout

def on_var_select(event):
    var = event.obj.value
    dashboard[-1] = plot(var=var)

var_select.param.watch(on_var_select, parameter_names=['value']);

dashboard = pn.Column(var_select, plot(var_select.value))

which produces: one

However, while hvplot defaults to the first value of the options in the OrderedDict, I would like to default to the last value.

If I look at the dashboard elements:

    print(dashboard)

    Column
        [0] Select(name='Variables:', options=OrderedDict([('salt', ...]), value='temp')
        [1] Row
            [0] HoloViews(DynamicMap, widgets={'time': <class 'panel.wid...})
            [1] Column
                [0] Select(name='Forecast time f..., options=OrderedDict([('2019-03-09 ...]), value=numpy.datetime64('2019-03-...)
                [1] Select(name='S-coordinate a..., options=OrderedDict([('-0.96875', ...]), value=-0.96875)

I see that I can specify the Select widget value post facto like:

dashboard[1][1][1].value = next(reversed(dashboard[1][1][1].options.values())) 

and that indeed works: two

I would like to make a stand alone dashboard, however, that defaults to a last value when the user selects a new variable. With the current code, when a new variable is selected, the default value is unfortunately again set to the first value.

Is there a way to embed my post facto code to select the last value into my plot function, or is there another better approach to accomplish this?

The full notebook is here: https://nbviewer.jupyter.org/gist/rsignell-usgs/edb9eb572058c4fdee6708359d24539d

philippjfr commented 5 years ago

Still thinking about a good solution for this issue, passing in widget instances rather than classes works but is not very convenient. The easiest approach for now may be to set the dimension default on the HoloViews object using .redim.default, i.e. something likes this:

mesh = mesh.redim.default(**{d: ds[d].max() for d in ds[var].dims[:-2]})

In the longer term I'd like to continue thinking about ways to improve the way this can be specified in panel.

rsignell-usgs commented 5 years ago

@philippjfr, I'm still not sure how to use this. I tried add that line just after the mesh creation in my plot function, but when I try to render I get:

SkipRendering: DynamicMap cannot be displayed without explicit indexing as 'time', 's_rho' dimension(s) are unbounded. 
Set dimensions bounds with the DynamicMap redim.range or redim.values methods.
rsignell-usgs commented 5 years ago

From my reading of the Holoviews Live Data docs it would seem somehow the dimension values are getting lost during the redim.default assignment.

import xarray as xr
import hvplot.xarray
import panel as pn

ds = xr.tutorial.open_dataset('air_temperature').load()
var = 'air'
atemp = ds[var][:10,:,:]
mesh = atemp.hvplot(groupby='time')

2019-03-15_11-30-42

mesh = mesh.redim.default(**{d: ds[d].max() for d in ds[var].dims[:-2]})

2019-03-15_11-32-01

philippjfr commented 5 years ago

Yeah this is starting to sound like a HoloViews bug. Separately though I think I've decided that the widgets dictionary argument to panel should accept dictionaries with partial overrides, i.e. I would like something like this to work:

widgets = {dim: {'value': ds[dim].max()} for dim in ds[var].dims[:-2]}
return pn.holoviews.HoloViews(overlay, widgets=widgets).layout
philippjfr commented 5 years ago

I'm going to transfer this issue over to holoviews since the HoloViews pane now support something like this:

widgets = {dim: {'type: pn.widget.Select, 'value': ds[dim].max()} for dim in ds[var].dims[:-2]}
return pn.holoviews.HoloViews(overlay, widgets=widgets).layout
philippjfr commented 5 years ago

Once #3580 is merged this will work:

import xarray as xr
import hvplot.xarray
import panel as pn

ds = xr.tutorial.open_dataset('air_temperature').load()
var = 'air'
atemp = ds[var][:10,:,:]
mesh = atemp.hvplot(groupby='time')

mesh.redim.default(**{d: atemp[d].values.max() for d in ds[var].dims[:-2]})
rsignell-usgs commented 5 years ago

@philippjfr , the example above works, but I can't get my real example to work: https://nbviewer.jupyter.org/gist/rsignell-usgs/bca07fbcee937cda0c62ac490e647461

I'm probably doing something simple wrong?

You should be able to run that notebook without modification, as it loads data via OPeNDAP.
I used this environment.

rsignell-usgs commented 5 years ago

@philippjfr seems like value is expecting a string only?

rsignell-usgs commented 5 years ago

Post anacondacon ping. 😊

philippjfr commented 5 years ago

Taking a look shortly.

philippjfr commented 5 years ago

@philippjfr seems like value is expecting a string only?

Could you clarify which value you are referring to here? I just tried your notebook and am not quite clear which part isn't working. So far everything I've tried worked okay.

philippjfr commented 5 years ago

Also, separate quick tip, I'd separate the base map selection from the plot redraw since that can be done in a DynamicMap without a full rerender. For now I'd use something like this:

base_map = hv.DynamicMap(lambda value: value(), streams=[hv.streams.Params(base_map_select, ['value'])])

but I'm going to make a PR to improve that spelling to at least allow:

base_map = hv.DynamicMap(lambda value: value(), streams=[base_map_select.param.value])
rsignell-usgs commented 5 years ago

Indeed, @philippjfr , I rebuilt the environment and it seems to be working fine now! Sorry for the distraction. This is awesome!