I am trying to update a ColorBar in Bokeh, and neither the title nor the colors themselves will update. Here is a minimal functional example (Python 3.6.9, Bokeh 2.3.0, Tornado 6.1):
from bokeh.plotting import figure, curdoc
from bokeh.colors import HSL
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, LinearColorMapper, ColorBar, Select
import numpy as np
def colorize(data,reverse=False):
if reverse:
return [HSL(h-180,1.0,0.5).to_rgb() for h in data]
else:
return [HSL(h,1.0,0.5).to_rgb() for h in data]
def update(attr,old,new):
if menu.value == 'reverse':
y = -1 * x
c = colorize(x,reverse=True)
else:
y = 1 * x
c = colorize(x)
source.data = {'x':x,'y':y,'c':c}
#updateColorBar(p.right[0])
updateColorBar(p._property_values['right'][0])
def updateColorBar(cb):
if menu.value == 'reverse':
cb.title = 'reverse'
u = colorize(np.linspace(0,360,101,endpoint=True),
reverse=True)
else:
cb.title = 'normal'
u = colorize(np.linspace(0,360,101,endpoint=True))
newcm = LinearColorMapper(palette=u,low=0.0,high=2*np.pi)
cb.color_mapper.palette = newcm.palette
cb.color_mapper.low = newcm.low
cb.color_mapper.high = newcm.high
# prepare some data
x = np.linspace(0,360,361,endpoint=True)
y = x
c = colorize(x)
source = ColumnDataSource({'x':x,'y':y,'c':c})
# create a new plot with a title and axis labels
p = figure(title="Simple scatter example", x_axis_label="x",
y_axis_label="y",toolbar_location="above")
# create a plot to show the colors
p.scatter('x', 'y',size=2,color='c',source=source)
u = colorize(np.linspace(0,360,101,endpoint=True))
color_mapper = LinearColorMapper(palette=u,low=0.0,high=2*np.pi)
color_bar = ColorBar(color_mapper=color_mapper,
label_standoff=12,
title='normal')
p.add_layout(color_bar, 'right')
menu = Select(title='Order',value='normal',
options=['normal','reverse'])
menu.on_change('value',update)
t = column(children=[menu,p])
# show the results
curdoc().add_root(t)
I am running this example with bokeh serve --show file_name.py.
When I choose 'reverse' from the menu, the plot itself is updated correctly, but the ColorBar is unchanged. I followed the advice here to make the smallest change possible, but perhaps my changes are too small? My guess would be that LinearColorMapper isn't pushing an update to ColorBar, but changing the title on ColorBar doesn't work either.
Edit:
By adding a print(cbar.title) to the end of the update method, I can see that the title of the color bar is being updated. It changes from 'normal' to 'reverse' and back again as I select the two items from the menu. It appears that the ColorBar is simply not being redrawn.
Edit2: If I try to replace the ColorBar entirely, it disappears. Modify the update function and add a new one to create a ColorBar:
def update(attr,old,new):
if new == 'reverse':
y = 360 - 1 * x
c = colorize(x,reverse=True)
else:
y = 1 * x
c = colorize(x)
source.data = {'x':x,'y':y,'c':c}
cbar = p.right[0]
#updateColorBar(cbar,new)
p.right[0] = makeColorBar()
print(p.right[0].visible)
def makeColorBar():
if menu.value == 'reverse':
title = 'reverse'
u = colorize(np.linspace(0,360,101,endpoint=True),True)
else:
title = 'normal'
u = colorize(np.linspace(0,360,101,endpoint=True))
cm = LinearColorMapper(palette=u,low=0.0,high=2*np.pi)
return ColorBar(color_mapper=cm,label_standoff=12,
title=title)
When I run this a different way, using python3 -i file_name.py and ask for the identity of p.right[0] I get ColorBar(id='1042', ...). If I then manually change menu.value = 'reverse' (True is printed to screen) and look at the identity of the ColorBar I get ColorBar(id='1057', ...). It appears that the new color bar isn't being pushed to the display, while the old one is being removed.
Edit3: This has been marked as a bug in Bokeh and is planned to be updated for versions 2.3.2.