HoloViews/Panel - TypeError: unsupported operand type(s) for *: 'function' and 'Points'

196 views Asked by At

I'm trying to create the parameter px on the holoviews.operation.datashader.spread operation interactively changeable together with an additional overlay.

The diagram with an IntSlider and the function returned by pn.bind(get_spreaded, px=px_slider) is working as expected when executing with pn.Column(px_slider, interactive)`.

But with an additional overlay, the line pn.Column(px_slider, interactive * other) reports TypeError: unsupported operand type(s) for *: 'function' and 'Points'.

How can I use the * operator with the function returned from pn.bind(...)?

Or is this the wrong way doing this? Is there a better and easier solution?

I ran the following code in jupyter lab:

import holoviews as hv
import panel as pn
import numpy as np
from holoviews.operation.datashader import rasterize, spread
import colorcet
import pandas as pd

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

hv.opts.defaults(
    hv.opts.Path(width=800, height=400),
    hv.opts.Image(width=800, height=400)
)

def random_walk(n, f=200):
    """Random walk in a 2D space, smoothed with a filter of length f"""
    xs = np.convolve(np.random.normal(0, 0.1, size=n), np.ones(f)/f).cumsum()
    ys = np.convolve(np.random.normal(0, 0.1, size=n), np.ones(f)/f).cumsum()
    xs += 0.1*np.sin(0.1*np.array(range(n-1+f))) # add wobble on x axis
    xs += np.random.normal(0, 0.005, size=n-1+f) # add measurement noise
    ys += np.random.normal(0, 0.005, size=n-1+f)
    return np.column_stack([xs, ys])

# create a path and plot it
path = hv.Path([random_walk(10000, 30)])
path

# rasterize and show the plot
rasterized = rasterize(path).opts(colorbar=True, cmap=colorcet.fire, cnorm='log')
rasterized

# the callback for getting the spreaded plot
def get_spreaded(px=3, shape='circle'):
    return spread(rasterized, px=px, shape=shape)

# show the plot returned from the callback
get_spreaded()

# create the slider for interactively changing the px value
px_slider = pn.widgets.IntSlider(name='Number of pixels to spread on all sides', start=0, end=10, value=3, step=1)

# bind the slider to the callback method
interactive = pn.bind(get_spreaded, px=px_slider)

# show only one plot without any overlay
pn.Column(px_slider, interactive)

# create data for an overlay
df = pd.DataFrame(data={'c1': [1, 2, 3, 4, 5], 'c2': [3, 4, 5, 6, 7]})
other = hv.Points(data=df)
other

# show both plots
pn.Column(px_slider, interactive * other)

The last line results in the following Error message:

# 

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[159], line 1
----> 1 pn.Column(px_slider, interactive * other)

TypeError: unsupported operand type(s) for *: 'function' and 'Points'

I would expect, that there is some way to wrap the function and makes it possible to use the * operator. But I couldn't find a way yet.

1

There are 1 answers

0
James A. Bednar On

Although in this particular case the return value of the function is something that HoloViews could (in principle) overlay with the other plot, HoloViews doesn't know that; the HoloViews * operator only knows how to handle HoloViews objects (Elements, HoloMaps, Layouts, and DynamicMaps), not bound Panel functions.

You could use a DynamicMap much like you used pn.bind, but here HoloViews operations already understand what to do with Panel widgets, so you can simply supply the widget to the spread operation (or any other operation's parameters):

import panel as pn, numpy as np, holoviews as hv, colorcet, pandas as pd
from holoviews.operation.datashader import rasterize, spread

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

hv.opts.defaults(
    hv.opts.Path(width=800, height=400),
    hv.opts.Image(width=800, height=400)
)

def random_walk(n, f=200):
    """Random walk in a 2D space, smoothed with a filter of length f"""
    xs = np.convolve(np.random.normal(0, 0.1, size=n), np.ones(f)/f).cumsum()
    ys = np.convolve(np.random.normal(0, 0.1, size=n), np.ones(f)/f).cumsum()
    xs += 0.1*np.sin(0.1*np.array(range(n-1+f))) # add wobble on x axis
    xs += np.random.normal(0, 0.005, size=n-1+f) # add measurement noise
    ys += np.random.normal(0, 0.005, size=n-1+f)
    return np.column_stack([xs, ys])

# create plot with interactively controlled spreading
px_slider = pn.widgets.IntSlider(name='Number of pixels to spread on all sides', 
                                 start=0, end=10, value=3, step=1)

path = hv.Path([random_walk(10000, 30)])
rasterized = rasterize(path).opts(colorbar=True, cmap=colorcet.fire, cnorm='log')
spreaded= spread(rasterized, px=px_slider, shape='circle')

# create data for an overlay
df = pd.DataFrame(data={'c1': [1, 2, 3, 4, 5], 'c2': [3, 4, 5, 6, 7]})
other = hv.Points(data=df)

# show both plots
pn.Column(px_slider, spreaded * other)

screenshot