Heatmap dynamic change on callback


I am making a heatmap and the user selects what data to look at via a multiselect tool. They can also change the heatmap start and end. But when the user changes the heatmap high and end, the labels disappear. What am I doing wrong?

from math import pi

import pandas as pd

from bokeh.io import show
from bokeh.models import BasicTicker, ColorBar, LinearColorMapper, PrintfTickFormatter, BooleanFilter, MultiSelect, CDSView, TextInput, ColumnDataSource, CustomJS
from bokeh.plotting import figure
from bokeh.layouts import row, column
from bokeh.sampledata.unemployment1948 import data

data['Year'] = data['Year'].astype(str)
data = data.set_index('Year')
#data.drop('Annual', axis=1, inplace=True)
data.columns.name = 'Month'

years = list(data.index)
months = list(data.columns)

# reshape to 1D array or rates with a month and year for each row.
df = pd.DataFrame(data.stack(), columns=['rate']).reset_index()
source = ColumnDataSource(df)

year_filter= BooleanFilter([True if x == '2000' else False for x in source.data['Year']])
hmLow = TextInput(title = 'Heatmap low:', value = str(df.rate.min()))
hmMax = TextInput(title = 'Heatmap high:', value = str(df.rate.max()))

view = CDSView(source=source, filters=[year_filter])

year_select = MultiSelect(title= 'Year: ', value =['2000'],  width = 200, height = 200,

colors = ["#75968f", "#a5bab7", "#c9d9d3", "#e2e2e2", "#dfccce", "#ddb7b1", "#cc7878", "#933b41", "#550b1d"]
mapper = LinearColorMapper(palette=colors, low=df.rate.min(), high=df.rate.max())

TOOLS = "hover,save,pan,box_zoom,reset,wheel_zoom"

p = figure(title="US Unemployment ({0} - {1})".format(years[0], years[-1]),
           x_range=years, y_range=list(reversed(months)),
           x_axis_location="above", plot_width=900, plot_height=400,
           tools=TOOLS, toolbar_location='below',
           tooltips=[('date', '@Month @Year'), ('rate', '@rate%')])

p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_text_font_size = "14px"
p.axis.major_label_standoff = 0
p.xaxis.major_label_orientation = pi / 3

p.rect(x="Year", y="Month", width=1, height=1,
       view = view,
       fill_color={'field': 'rate', 'transform': mapper},

color_bar = ColorBar(color_mapper=mapper, major_label_text_font_size="7px",
                     label_standoff=6, border_line_color=None, location=(0, 0))

p.add_layout(color_bar, 'right')

year_select.js_on_change('value', CustomJS(args = dict(f = year_filter, 
                                                       source = source),
                                          const val = cb_obj.value;
                                          f.booleans = Array.from(source.data['Year']).map(d =>  val.includes(d != null && d.toString())); 
                                          //source.selected.indices = [...new Set(source.data.index.filter((_, idx) => f.booleans[idx]))];
heatmapchange = CustomJS(args = dict(mapper = mapper, 
                                     hmLow = hmLow,
                                     hmMax = hmMax),

                                          mapper.attributes.low = hmLow.value
                                          mapper.attributes.high = hmMax.value

hmLow.js_on_change('value', heatmapchange)
hmMax.js_on_change('value', heatmapchange)

show(row(p, year_select, column(hmLow, hmMax)))

Replace the body of the callback with just

mapper.low = parseFloat(hmLow.value);
mapper.high = parseFloat(hmMax.value);

Don’t use attributes. And no need for mapper.change.emit() because you assign the new values to the attributes as opposed to mutating the values inside containers.