How-To: Use JSLink for updating plot properties

Hi, there have been attempts to change the colormap and other attributes of a plot.

The solution in this forum so far has been to create a new plot object with the new cmap.
However, sometimes this is not desired or feasible (large plots or keeping existing zoom level).

So, I pieced together a small example of how jslink can be used to update a heatmap’s colorbar range and colormap via JS, instead of re-creating the plot from scratch every time.

Result:

Code:

cmaps = matplotlib.pyplot.colormaps()
cmap_dict = {}

for cmap in cmaps:
    _cmap = matplotlib.cm.get_cmap(str(cmap), 12)
    cmap_dict[str(cmap)] = [matplotlib.colors.rgb2hex(_cmap(i)[:3]) for i in range(_cmap.N)]

def get_blank_plot():
    #p1 = hv.Curve([],kdims=["Date"],vdims=["Price [BTC]"]).opts(responsive=True)
    ls = np.linspace(0, 10, 200)
    xx, yy = np.meshgrid(ls, ls)
    
    p1 = hv.Image(np.sin(xx)*np.cos(yy),kdims=['x','y'],vdims=['z']).opts(responsive=False,cmap='viridis',colorbar=True,colorbar_position='left',yaxis='right',colorbar_opts={'title':'z.'},xaxis=None,show_grid=True)
    return p1

class Example(param.Parameterized):
    EXAMPLE_PLOT = get_blank_plot().opts(width=500,height=400)
    
    def __init__(self, **params):
        super().__init__(**params)
        
        self.plot_pane = pn.pane.HoloViews(self.EXAMPLE_PLOT)
        self.cmap = pn.widgets.Select(options=list(cmap_dict.keys()))
        self.colormap_slider = pn.widgets.RangeSlider(end=1,start=0,step=0.1,name='Colorbar Slider')

        # Change colorbar range
        self.colormap_slider.jslink(self.plot_pane, code={'value':'''
            color_mapper.low = source.value[0];
            color_mapper.high = source.value[1];
            '''})
        # Change cmap
        self.cmap.jslink(self.plot_pane, code={'value':'''
            console.log('Cmap is', source.value)
            color_mapper.palette = colors[source.value];
            '''},args={'colors': cmap_dict})
        
    def panel(self):
        return pn.Column(pn.Row(self.colormap_slider,self.cmap),self.plot_pane)
      
app = Example(name='')
app.panel()

Essentially, you access bokeh’s glyph object via the jslink to change some properties of an already existing plot. It took me some time to figure out which glyph parameters corresponded to which plot properties and how to change them. I imagine others have been struggling with that too. So, I thought I’d share.

The resources on Panel’s documentation, as well as @jsignell’s PyData talk helped a lot!

Unfortunately, it requires writing a bit of JS, which I have no experience with.
Also, the palette currently only takes an array of str colors or hex colors. Would be nice to just pass the colormap str directly (i.e. ‘viridis’).

@Marc @mycarta @ahuang11

4 Likes

Thanks for sharing. It helps us all.

1 Like

Super awesome work!

I was wondering if there’s a way to also jslink the number of contour levels too. One way would be to have multiple colormaps with varying color levels i.e. matplotlib.cm.get_cmap(str(cmap), 10), matplotlib.cm.get_cmap(str(cmap), 20), etc. but I wonder if there’s a better way?

E.g.

for cmap_str in cmap_strs:
    cmap_nlv = [9, 10, 11, 19, 20, 21, 255]
    for nlv in cmap_nlv:
        cmap_obj = plt.get_cmap(cmap_str, nlv)
        cmaps[f'{cmap_str}_{nlv}'] = [
            rgb2hex(cmap_obj(i)[:3]) for i in range(nlv)]
``
1 Like

Not sure if I should create a new topic, or document everything related to updating plot properties using jslink here, but slightly modified version of the following for updating xlim/ylim:
https://anaconda.org/philippjfr/linkwidgets/notebook?version=2019.01.11.0344

range_widget = pn.widgets.RangeSlider(value=(0, 1000), start=0, end=1000, name='x-axis range')
curve = hv.Curve(np.random.randn(1000).cumsum()).options(width=500)

code = """
x_range.start = source.value[0]
x_range.end = source.value[1]
"""
range_widget.jslink(curve, code={'value': code})

pn.Row(curve, range_widget)

3 Likes

Just to keep everything in one place: how to update fontsize

1 Like