How to update x_range and y_range with CustomJSFilter? Categorical Heatmap and Multiple Selection

Dear Community. I am working in categorical heatmaps with 2 or 3 categories per axis. All works fine in Bokeh Server, but I decide to build the graphic using CustomJS because unsolved problems during embedding the graph with frameworks. Below is the code that include data so you can reproduce it. Additional comments:
… it is clear that CustomJSFilter and CDSView filter that data since the beginning and data is updated. It means, initial graph depends on the initial values of the widget, which is fine, and data is filtered and changes according to change in multiple selection, which is expected
… the problem is how to synchronize x_range and y_range with data filters, at the beginning and then when multiple selection change.
I am a novice user and expect your kind help.

Rodrigo


import pandas as pd
import numpy as np
import colorcet as cc

from bokeh.plotting import figure, output_notebook, show
from bokeh.layouts import column, layout, row
from bokeh.models import ColumnDataSource, HoverTool, MultiChoice, CustomJSFilter, CDSView, CustomJS, FactorRange, LinearColorMapper, ColorBar, BasicTicker, Title, Div
from bokeh.palettes import Spectral5
from bokeh.palettes import palettes
from bokeh.transform import linear_cmap, factor_cmap, transform

data_array = {‘WASH’: [‘01_01’, ‘01_01’, ‘01_01’, ‘01_01’, ‘01_01’, ‘01_01’, ‘01_01’, ‘01_01’],
‘PROCESS’: [‘02.MACHINE’, ‘02.MACHINE’, ‘02.MACHINE’, ‘02.MACHINE’, ‘02.MACHINE’, ‘02.MACHINE’, ‘02.MACHINE’, ‘02.MACHINE’],
‘PRODUCT’: [‘06_662’, ‘06_662’, ‘06_662’, ‘06_662’, ‘06_662’, ‘06_662’, ‘06_662’, ‘06_662’],
‘MONITOR’: [‘a.1.CUELLOS’, ‘a.1.CUELLOS’, ‘a.1.CUELLOS’, ‘a.1.CUELLOS’, ‘b.1.AXILAS’, ‘b.1.AXILAS’, ‘b.1.AXILAS’, ‘b.1.AXILAS’],
‘DIVERSE_SCENARIOS’: [‘PROPER-FV_2_ILLCFL_30-1.2.SRI_STw’, ‘PROPER-FV_2_ILLCFL_30-2.2.SRI_LIw’,
‘PROPER-FV_2_ILLCFL_30-3.2.SRI_HUw’,
‘PROPER-FV_2_ILLCFL_30-4.2.SRI_CRw’,
‘PROPER-FV_2_ILLCFL_30-1.2.SRI_STw’,
‘PROPER-FV_2_ILLCFL_30-2.2.SRI_LIw’,
‘PROPER-FV_2_ILLCFL_30-3.2.SRI_HUw’,
‘PROPER-FV_2_ILLCFL_30-4.2.SRI_CRw’],
‘SRI’: [78.25, 76.79, 83.55, 76.19, 77.53, 81.73, 79.79, 85.2 ],
‘MON_SCEN’: [(‘a.1.CUELLOS’, ‘PROPER-FV_2_ILLCFL_30-1.2.SRI_STw’),
(‘a.1.CUELLOS’, ‘PROPER-FV_2_ILLCFL_30-2.2.SRI_LIw’),
(‘a.1.CUELLOS’, ‘PROPER-FV_2_ILLCFL_30-3.2.SRI_HUw’),
(‘a.1.CUELLOS’, ‘PROPER-FV_2_ILLCFL_30-4.2.SRI_CRw’),
(‘b.1.AXILAS’, ‘PROPER-FV_2_ILLCFL_30-1.2.SRI_STw’),
(‘b.1.AXILAS’, ‘PROPER-FV_2_ILLCFL_30-2.2.SRI_LIw’),
(‘b.1.AXILAS’, ‘PROPER-FV_2_ILLCFL_30-3.2.SRI_HUw’),
(‘b.1.AXILAS’, ‘PROPER-FV_2_ILLCFL_30-4.2.SRI_CRw’)],
‘PROC_WASH_PROD’: [(‘02.MACHINE’, ‘01_01’, ‘06_662’),
(‘02.MACHINE’, ‘01_01’, ‘06_662’),
(‘02.MACHINE’, ‘01_01’, ‘06_662’),
(‘02.MACHINE’, ‘01_01’, ‘06_662’),
(‘02.MACHINE’, ‘01_01’, ‘06_662’),
(‘02.MACHINE’, ‘01_01’, ‘06_662’),
(‘02.MACHINE’, ‘01_01’, ‘06_662’),
(‘02.MACHINE’, ‘01_01’, ‘06_662’)]}

dff_stacked = pd.DataFrame(data=data_array)

source = ColumnDataSource(data=dff_stacked)

########################
########################

START TO GRAPH

l_products = list(dff_stacked.PRODUCT.unique())
l_washes = list(dff_stacked.WASH.unique())
l_processes = list(dff_stacked.PROCESS.unique())
l_monitors = list(dff_stacked.MONITOR.unique())

SET UP WIDGETS AND CALLBACKS

mc_product = MultiChoice(value=list(l_products[:1]), options=l_products, title=“Products / Technologies”, width=570)
mc_wash = MultiChoice(value=list(l_washes[:1]), options=l_washes, title=“Number of washes”)
mc_process = MultiChoice(value=list(l_processes[:1]), options=l_processes, title=“Process / Drying”)
mc_monitor = MultiChoice(value=list(l_monitors[:1]), options=l_monitors, title=“Monitors”, width=1200)

custom_filter = CustomJSFilter(args=dict(moni_choice=mc_monitor), code="""
const indices =
for (var i = 0; i < source.get_length(); i++) {
if (source.data[‘MONITOR’][i] == moni_choice.value
) {
indices.push(true)
} else {
indices.push(false)
}
console.log(source.data[‘MONITOR’][i])
}
console.log(moni_choice.value)
console.log(source.get_length())
console.log(indices)
return indices
“”")

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

mc_product.js_on_change(‘value’, CustomJS(args=dict(source=source), code="""
source.change.emit()
“”"))
mc_wash.js_on_change(‘value’, CustomJS(args=dict(source=source), code="""
source.change.emit()
“”"))
mc_process.js_on_change(‘value’, CustomJS(args=dict(source=source), code="""
source.change.emit()
“”"))
mc_monitor.js_on_change(‘value’, CustomJS(args=dict(source=source), code="""
source.change.emit()
“”"))

moni_scen_list = list(np.unique(source.data[‘MON_SCEN’]))
proc_wash_prod_list = list(np.unique(source.data[‘PROC_WASH_PROD’]))

TOOLTIPS = [(‘Scenario’, ‘@DIVERSE_SCENARIOS’), (‘SRI’, ‘@SRI’)]

plot = figure(x_range=FactorRange(*moni_scen_list), y_range=FactorRange(*proc_wash_prod_list), x_axis_location=“above”, toolbar_location=‘below’, tools=“save,hover,pan,wheel_zoom,box_zoom,reset”, tooltips=TOOLTIPS, plot_width=1300, plot_height=550)

mapper = LinearColorMapper(palette=list(reversed(cc.b_rainbow_bgyr_35_85_c72)), low=80, high=100)

plot.rect(x=“MON_SCEN”, y=“PROC_WASH_PROD”, width=1, height=1, source=source, view=view, line_color=None, fill_color=transform(‘SRI’, mapper))

color_bar = ColorBar(color_mapper=mapper, location=(0, 0), ticker=BasicTicker(desired_num_ticks=10), title=‘SRI’)

plot.xaxis.group_label_orientation = ‘horizontal’
plot.xaxis.group_text_font_size = “12px”
plot.xaxis.major_label_text_font_size = “0px”
plot.xaxis.major_label_orientation = 0

plot.yaxis.group_text_font_size = “14px”
plot.yaxis.subgroup_text_font_size = “14px”
plot.yaxis.major_label_text_font_size = “14px”
plot.yaxis.major_label_orientation = “vertical”

plot.x_range.group_padding = 0.5
plot.x_range.factor_padding = 0.02
plot.y_range.group_padding = 0.04
plot.y_range.subgroup_padding = 0.05
plot.y_range.factor_padding = 0.02

plot.add_layout(color_bar, ‘right’)

########################
########################

SET UP FINAL GRAPH

inputs = layout([[mc_product, mc_wash, mc_process],[mc_monitor]])

graph_final = layout(column(inputs, plot))

show(graph_final)

@CONSUMERTEC please edit your post to use code formatting so that the code is intelligible (either with the </> icon on the editing toolbar, or triple backtick ``` fences around the code blocks)

Hello Bryan. Sorry to disturb you with this. I am new in the field. Below the code using the fences:

import pandas as pd
import numpy as np
import colorcet as cc

from bokeh.plotting import figure, output_notebook, show
from bokeh.layouts import column, layout, row
from bokeh.models import ColumnDataSource, HoverTool, MultiChoice, CustomJSFilter, CDSView, CustomJS, FactorRange, LinearColorMapper, ColorBar, BasicTicker, Title, Div
from bokeh.palettes import Spectral5
from bokeh.palettes import palettes
from bokeh.transform import linear_cmap, factor_cmap, transform

data_array = {'WASH': ['01_01', '01_01', '01_01', '01_01', '01_01', '01_01', '01_01', '01_01'], 
'PROCESS': ['02.MACHINE', '02.MACHINE', '02.MACHINE', '02.MACHINE', '02.MACHINE', '02.MACHINE', '02.MACHINE', '02.MACHINE'], 
'PRODUCT': ['06_662', '06_662', '06_662', '06_662', '06_662', '06_662', '06_662', '06_662'], 
'MONITOR': ['a.1.CUELLOS', 'a.1.CUELLOS', 'a.1.CUELLOS', 'a.1.CUELLOS', 'b.1.AXILAS', 'b.1.AXILAS', 'b.1.AXILAS', 'b.1.AXILAS'], 
'DIVERSE_SCENARIOS': ['PROPER-FV_2_ILLCFL_30-1.2.SRI_STw', 'PROPER-FV_2_ILLCFL_30-2.2.SRI_LIw',
       'PROPER-FV_2_ILLCFL_30-3.2.SRI_HUw',
       'PROPER-FV_2_ILLCFL_30-4.2.SRI_CRw',
       'PROPER-FV_2_ILLCFL_30-1.2.SRI_STw',
       'PROPER-FV_2_ILLCFL_30-2.2.SRI_LIw',
       'PROPER-FV_2_ILLCFL_30-3.2.SRI_HUw',
       'PROPER-FV_2_ILLCFL_30-4.2.SRI_CRw'], 
       'SRI': [78.25, 76.79, 83.55, 76.19, 77.53, 81.73, 79.79, 85.2 ], 
       'MON_SCEN': [('a.1.CUELLOS', 'PROPER-FV_2_ILLCFL_30-1.2.SRI_STw'),
       ('a.1.CUELLOS', 'PROPER-FV_2_ILLCFL_30-2.2.SRI_LIw'),
       ('a.1.CUELLOS', 'PROPER-FV_2_ILLCFL_30-3.2.SRI_HUw'),
       ('a.1.CUELLOS', 'PROPER-FV_2_ILLCFL_30-4.2.SRI_CRw'),
       ('b.1.AXILAS', 'PROPER-FV_2_ILLCFL_30-1.2.SRI_STw'),
       ('b.1.AXILAS', 'PROPER-FV_2_ILLCFL_30-2.2.SRI_LIw'),
       ('b.1.AXILAS', 'PROPER-FV_2_ILLCFL_30-3.2.SRI_HUw'),
       ('b.1.AXILAS', 'PROPER-FV_2_ILLCFL_30-4.2.SRI_CRw')], 
       'PROC_WASH_PROD': [('02.MACHINE', '01_01', '06_662'),
       ('02.MACHINE', '01_01', '06_662'),
       ('02.MACHINE', '01_01', '06_662'),
       ('02.MACHINE', '01_01', '06_662'),
       ('02.MACHINE', '01_01', '06_662'),
       ('02.MACHINE', '01_01', '06_662'),
       ('02.MACHINE', '01_01', '06_662'),
       ('02.MACHINE', '01_01', '06_662')]}

dff_stacked = pd.DataFrame(data=data_array)

source = ColumnDataSource(data=dff_stacked)

##
########################
######################## 
## START TO GRAPH
l_products = list(dff_stacked.PRODUCT.unique())
l_washes = list(dff_stacked.WASH.unique())
l_processes = list(dff_stacked.PROCESS.unique())
l_monitors = list(dff_stacked.MONITOR.unique())
##
## SET UP WIDGETS AND CALLBACKS
mc_product = MultiChoice(value=list(l_products[:1]), options=l_products, title="Products / Technologies", width=570)
mc_wash = MultiChoice(value=list(l_washes[:1]), options=l_washes, title="Number of washes")
mc_process = MultiChoice(value=list(l_processes[:1]), options=l_processes, title="Process / Drying")
mc_monitor = MultiChoice(value=list(l_monitors[:1]), options=l_monitors, title="Monitors", width=1200)
##
##

custom_filter = CustomJSFilter(args=dict(moni_choice=mc_monitor), code="""
    const indices = []
    for (var i = 0; i < source.get_length(); i++) {
        if (source.data['MONITOR'][i] == moni_choice.value 
            ) {
            indices.push(true)
        } else {
            indices.push(false)
        }
        console.log(source.data['MONITOR'][i])
    }
    console.log(moni_choice.value)
    console.log(source.get_length())
    console.log(indices)
    return indices
""")

##
view = CDSView(source=source, filters=[custom_filter])

##

mc_product.js_on_change('value', CustomJS(args=dict(source=source), code="""
source.change.emit()
"""))
mc_wash.js_on_change('value', CustomJS(args=dict(source=source), code="""
source.change.emit()
"""))
mc_process.js_on_change('value', CustomJS(args=dict(source=source), code="""
source.change.emit()
"""))
mc_monitor.js_on_change('value', CustomJS(args=dict(source=source), code="""
source.change.emit()
"""))
##
moni_scen_list = list(np.unique(source.data['MON_SCEN']))
proc_wash_prod_list = list(np.unique(source.data['PROC_WASH_PROD']))

##
TOOLTIPS = [('Scenario', '@DIVERSE_SCENARIOS'), ('SRI', '@SRI')]
##        
plot = figure(x_range=FactorRange(*moni_scen_list), y_range=FactorRange(*proc_wash_prod_list), x_axis_location="above", toolbar_location='below', tools="save,hover,pan,wheel_zoom,box_zoom,reset", tooltips=TOOLTIPS, plot_width=1300, plot_height=550)       
##
mapper = LinearColorMapper(palette=list(reversed(cc.b_rainbow_bgyr_35_85_c72)), low=80, high=100) 
##      
plot.rect(x="MON_SCEN", y="PROC_WASH_PROD", width=1, height=1, source=source, view=view, line_color=None, fill_color=transform('SRI', mapper))

##
color_bar = ColorBar(color_mapper=mapper, location=(0, 0), ticker=BasicTicker(desired_num_ticks=10), title='SRI')
##
##
plot.xaxis.group_label_orientation = 'horizontal'
plot.xaxis.group_text_font_size = "12px"
plot.xaxis.major_label_text_font_size = "0px"
plot.xaxis.major_label_orientation = 0
##
plot.yaxis.group_text_font_size = "14px"
plot.yaxis.subgroup_text_font_size = "14px"
plot.yaxis.major_label_text_font_size = "14px"
plot.yaxis.major_label_orientation = "vertical"
##
plot.x_range.group_padding = 0.5
plot.x_range.factor_padding = 0.02
plot.y_range.group_padding = 0.04
plot.y_range.subgroup_padding = 0.05
plot.y_range.factor_padding = 0.02
##
##
plot.add_layout(color_bar, 'right')
##
##
########################
########################        
## SET UP FINAL GRAPH
inputs = layout([[mc_product, mc_wash, mc_process],[mc_monitor]])

graph_final = layout(column(inputs, plot))

show(graph_final)
##

Hope this help