Updating Image Glyphs with 2D numpy arrays in Bokeh 3.2.1

Attempted to reproduce the code in this previous post: https://discourse.bokeh.org/t/dynamically-updating-multiple-2d-numpy-arrays-in-image-glyph/8799

Expected Result: Switching between two images with a slider

Actual Result: Initial plot is produced, but slider callback seems to be failing. Looking at the console logs, the following error is produced:

 bokeh-3.2.1.min.js:521 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'dimension')
    at p._set_data (bokeh-3.2.1.min.js:521:1833)
    at p.set_data (bokeh-3.2.1.min.js:376:4575)
    at S.set_data (bokeh-3.2.1.min.js:372:4161)
    at S.update_data (bokeh-3.2.1.min.js:372:4006)
    at S.t (bokeh-3.2.1.min.js:372:2250)
    at S.i (bokeh-3.2.1.min.js:219:229)
    at c.emit (bokeh-3.2.1.min.js:180:757)
    at c.emit (bokeh-3.2.1.min.js:180:842)
    at d.eval (eval at _compile_function (bokeh-3.2.1.min.js:462:890), <anonymous>:7:26)
    at d.<anonymous> (bokeh-3.2.1.min.js:462:961)

Was there a change in bokeh caused this example to no longer work?

Hello Gingtastical and welcome to Bokeh discourse.

The input argument imdict={0:d1,1:d2} of CustomJS seems to make problems with newer Bokeh versions.
I changed it to a list:

cb = CustomJS(args=dict(src=src,imlist=[d1,d2],sl=sl),
              code='''
              src.data['im'] = [imlist[sl.value]]
              src.change.emit()
              ''')

and for me, it works.

Here the complete code:

import numpy as np

from bokeh.plotting import figure, show
from bokeh.layouts import layout
from bokeh.models import Image, ColumnDataSource, Slider, CustomJS

#dummy data taken from https://docs.bokeh.org/en/2.4.0/docs/gallery/image.html
N = 500
x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)
#d1 = first image
d1 = np.sin(xx)*np.cos(yy)
#make a second image
d2 = np.sin(xx**2)*np.cos(yy**2)

p = figure(tooltips=[("x", "$x"), ("y", "$y"), ("value", "@im")])
p.x_range.range_padding = p.y_range.range_padding = 0

#initialize a column datasource and assign first image into it
src = ColumnDataSource(data={'x':[0],'y':[0],'dw':[10],'dh':[10],'im':[d1]})
#create the image randerer pointing to the field names in src, and the source itself
im_rend = p.image(image='im', x='x', y='y', dw='dw', dh='dh', palette="Spectral11", level="image",source=src)

p.grid.grid_line_width = 0.5

#a widget to put a callback on
sl = Slider(start=0,end=1,value=0,step=1,width=100)

#the key here is to pass a dictionary to the callback all the information you need to UPDATE the columndatasource that's driving the renderer
# imdict is basically this --> if slider value is 0, i want to get d1, if slider value is 1, i want to get d2
cb = CustomJS(args=dict(src=src,imlist=[d1,d2],sl=sl),
              code='''
              src.data['im'] = [imlist[sl.value]]
              src.change.emit()
              ''')
sl.js_on_change('value',cb)
lo = layout([p,sl])
show(lo)

I don’t think that is expected, so a GitHub Issue would be appropriate.

@Bryan You are right, working with dictionaries in CustomJS should also work.
The use of a dictionary in the CustomJS arguments actually works, the dictionary then becomes a Map object in Javascript.
But calling a value of the map (former dictionary) with dict[key] in Javascript doesn’t seem to work anymore.
Instead, we can use dict.get(key).

So the initial Javascript code with dictionary will also work with the line:
src.data['im'] = [imdict.get(sl.value)]
instead of:
src.data['im'] = [imdict[sl.value]]

Ah, right, I forgot about the recent move to more defined types on the BokehJS side of things.