Redim behaves differently in different contexts

I’m doing some experiments in a notebook to work out some data flow for a larger application. I have the following code:

# cell1
import holoviews as hv
import numpy as np
import pandas as pd
import panel as pn

hv.extension('bokeh')
pn.extension()

# cell2
df = pd.DataFrame({k: np.random.default_rng().random(100) + i for i, k in enumerate('abcd')})
pipe = hv.streams.Pipe()
dm = hv.DynamicMap(hv.Dataset, streams=[pipe])

# cell3
pipe.send(df)

# cell4
def plot(elem, x_col, y_col):
    range_x = {x_col: (df[x_col].min(), df[x_col].max())}
    range_y = {y_col: (df[y_col].min(), df[y_col].max())}
    range = {**range_x, **range_y}
    values = {**{x_col: df[x_col], y_col: df[y_col]}}
    im = hv.Points(elem, kdims=[x_col, y_col]).redim.values(**values).redim.range(**range)
    return im

# cell5
x_sel = pn.widgets.Select(name='x_col', options=list(df.columns), value=list(df.columns)[0])
y_sel = pn.widgets.Select(name='y_col', options=list(df.columns), value=list(df.columns)[0])

pn.Column(x_sel, y_sel)  # this puts two selectors in the notebook

# cell6
dm.apply(plot, x_col=x_sel, y_col=y_sel)  # executing this cell creates the plot

This looks a bit artificial with the pipe and the inner wrapper of the DynamicMap.apply, but there’s a reason for this: this code gets used in an application where the plotting logic is accessing a DynamicMap constructed in this manner, and then calling .apply(hv.Points) (or other hv element function) on the result. That works just fine to generate the plot and now I’m trying to wire up various additional pieces of reactivity into it (for example, if someone changes the columns, I want the plot to be updated automatically).

After executing these cells, the output is a couple of selector widgets and a plot. The different dimensions have different ranges and what I want is that when I change the values in the selector, the plot should be updated with the appropriate axes. Now, the weird thing is that this works correctly in the sense that the plot values are themselves updated, so if I change a to b in the x_sel selector, the actual points get updated. However, the axis range is not updated, despite calling redim.range. On the other hand, if I simply do the following:

dm.apply(plot, x_col='c', y_col='d')  # executing this cell creates the plot

this does exactly what I want, i.e. it generates the correct plot with the correct ranges on the axes. It seems to me that these should be equivalent, and when I put print statements in the plot function, they show identical values for the dictionaries being passed to redim, but the effects are different. Why?

Actually the problem seems to reproduce in the following simpler scenario as well:

def plot2(x_col, y_col):
    return hv.Points(df2, kdims=[x_col, y_col])

dm2 = hv.DynamicMap(plot2, streams={'x_col': x_sel, 'y_col': y_sel})

Changing the selectors changes the points, but does not set the correct axes, even if I call redim inside of plot2. Is there a way to achieve the desired effect?

I think I have answered my own question: you have to use framewise=True like so:

def plot2(x_col, y_col):
    return hv.Points(df2, kdims=[x_col, y_col]).opts(framewise=True)

dm2 = hv.DynamicMap(plot2, streams={'x_col': x_sel, 'y_col': y_sel})

And of course mutatis mutandis for my initial example above. I discovered this option while crawling various threads on here and I think it would be great if this was more directly explained since I’m sure I’m not the only one who has encountered this weirdness.

Yes having it documented somewhere would be great; added this to the list of best practices Personal opinions about best practices for Panel + HoloViews - #28 by ahuang11