☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

Selection Filter w Slider + Altering color theme with Radio Group

Community Support
gmerritt123
gmerritt123

I have 2 linked plots with the same datasource: one showing scatter points, another patches. As per my last post (Use a slider to create selection, customjs), I am altering the selection via a slider (or sliders) that move a selection box up/down/left/right.

I have implemented this as desired and am now trying to add a radiogroup widget that will allow changing the color theme of the patches. I’ve successfully implemented the callback for the radiogroup (it works as intended if you never touch the slider), however as soon as I move the slider to filter the selection, the color theme reverts back to its original theme and the radio group no longer functions.

Here is my working reproducible example, built mostly off my last one:

from bokeh.io import output_notebook, show
import numpy as np
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, Patches, Slider, Quad,CustomJS,RadioGroup
from bokeh.plotting import figure
import bokeh

output_notebook()

#points
x = list(range(-20, 21))
y = [abs(xx) for xx in x]

#making patches
N =41
xp = np.linspace(-2, 2, N)
yp = xp**2
xpts = np.array([-.09, -.12, .0, .12,  .09])
ypts = np.array([-.1,   .02, .1, .02, -.1])

source = ColumnDataSource(dict(x=x,y=y,
        xs=[xpts*(1+i/10.0)+xx for i, xx in enumerate(xp)],
        ys=[ypts*(1+i/10.0)+yy for i, yy in enumerate(yp)],
                               cvalsA = list(range(0,41)),
                               cvalsB = list(np.geomspace(0.1,41,41))
    ))

#colortheming dictionary
Amap = bokeh.models.LinearColorMapper(palette='Spectral11', low = 0, high = 41)
Bmap = bokeh.models.LinearColorMapper(palette='Turbo256', low = 0, high = 41)

cdict = {0:{'field':'cvalsA','transform':Amap},1:{'field':'cvalsB','transform':Bmap}}

TOOLS = "box_select,lasso_select"

# create a new plot and add a renderer
left = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
left.circle('x', 'y', source=source)

# create another new plot and add a renderer
right = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
glyph = Patches(xs="xs", ys="ys", fill_color=cdict[0])
patchrenderer = right.add_glyph(source, glyph)

#create the selection "quad"
qsource = ColumnDataSource({'bottom':[0],'top':[20],'left':[-20],'right':[20]})
selector = Quad(bottom='bottom',top='top',left='left',right='right',fill_color='green',fill_alpha=0.05)
renderer = left.add_glyph(qsource,selector)

hslider = Slider(start=-20, end=20, value=-20, step=1, title="hselector")

hcallback = CustomJS(args=dict(renderer=renderer), code="""
    renderer.glyph.left = cb_obj.value;
""")
hslider.js_on_change('value',hcallback)

rbutton = RadioGroup(labels=['A theme','B theme'], active=0)

selcallback = CustomJS(args=dict(source=source,rbutton=rbutton,r=patchrenderer,cdict=cdict), code="""
        var indices = [];
        for (var i = 0; i <= source.data['x'].length; i++){
            if (source.data['x'][i] > cb_obj.value) {
            indices.push(i)
            }
            };
        var l_selected = source.selected;
        l_selected.indices = indices;
        source.selected = l_selected;
        r.glyph.fill_color = cdict[rbutton.active];
        console.log(r.glyph.fill_color)
        r.change.emit();
        """)
hslider.js_on_change('value',selcallback)


rbutton_callback = CustomJS(args=dict(r=patchrenderer,cdict=cdict),code="""
                            r.glyph.fill_color = cdict[this.active]""")
rbutton.js_on_click(rbutton_callback)

p = gridplot([[left, right],[hslider,rbutton]])
show(p)

See the console.log(r.glyph.fill_color) in the slider callback? Looking at it… it actually shows it as updating correctly… i.e. it returns the correct dictionary entry when i move the slider, then change the radiogroup, then move the slider again:

image

I’m missing something with how bokeh handles the selections…I think. Any help much appreciated.

⌁   Link to post
gmerritt123
gmerritt123

Thx for reply!.. when I console.log(r.selection_glyph) I get a string: ‘auto’. So I can’t just go r.selection_glyph.fill_color = cdict[whatever] because ‘auto’ string has no fill_color property…

hmmm gonna try a few other options with this now though.

⌁   Link to post
gmerritt123
gmerritt123

Got it!

When you instantiate the renderer w add_glyph(), you need to feed it args for selection_glyph and nonselection glyph:

glyph = Patches(xs="xs", ys="ys", fill_color=cdict[0])
nsglyph = Patches(xs="xs", ys="ys", fill_color=cdict[0],fill_alpha=0.1)

patchrenderer = right.add_glyph(source, glyph
                                ,selection_glyph=glyph
                                ,nonselection_glyph=nsglyph)

This makes the renderer NOT have ‘auto’ for the selection_glyph/nonselection_glyph properties, but actual legitimate glyphs that actually have a fill_color assigned to them. These selection/nonselection glyphs seem to both obey the fill color assigned to the whole renderer, which gets changed when i change the button.

So I don’t need to even use r.glyph.selection_glyph etc. in the selection callback at all!

from bokeh.io import output_notebook, show
import numpy as np
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, Patches, Slider, Quad,CustomJS,RadioGroup
from bokeh.plotting import figure
import bokeh

output_notebook()

#points
x = list(range(-20, 21))
y = [abs(xx) for xx in x]

#making patches
N =41
xp = np.linspace(-2, 2, N)
yp = xp**2
xpts = np.array([-.09, -.12, .0, .12,  .09])
ypts = np.array([-.1,   .02, .1, .02, -.1])

source = ColumnDataSource(dict(x=x,y=y,
        xs=[xpts*(1+i/10.0)+xx for i, xx in enumerate(xp)],
        ys=[ypts*(1+i/10.0)+yy for i, yy in enumerate(yp)],
                               cvalsA = list(range(0,41)),
                               cvalsB = list(np.geomspace(0.1,41,41))
    ))

#colortheming dictionary
Amap = bokeh.models.LinearColorMapper(palette='Spectral11', low = 0, high = 41)
Bmap = bokeh.models.LinearColorMapper(palette='Turbo256', low = 0, high = 41)

cdict = {0:{'field':'cvalsA','transform':Amap},1:{'field':'cvalsB','transform':Bmap}}

TOOLS = "box_select,lasso_select"

# create a new plot and add a renderer
left = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
left.circle('x', 'y', source=source)

# create another new plot and add a renderer
right = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)

glyph = Patches(xs="xs", ys="ys", fill_color=cdict[0])
nsglyph = Patches(xs="xs", ys="ys", fill_color=cdict[0],fill_alpha=0.1)

patchrenderer = right.add_glyph(source, glyph
                                ,selection_glyph=glyph
                                ,nonselection_glyph=nsglyph)

#create the selection "quad"
qsource = ColumnDataSource({'bottom':[0],'top':[20],'left':[-20],'right':[20]})
selector = Quad(bottom='bottom',top='top',left='left',right='right',fill_color='green',fill_alpha=0.05)
renderer = left.add_glyph(qsource,selector)


hslider = Slider(start=-20, end=20, value=-20, step=1, title="hselector")

hcallback = CustomJS(args=dict(renderer=renderer), code="""
    renderer.glyph.left = cb_obj.value;
""")
hslider.js_on_change('value',hcallback)

rbutton = RadioGroup(labels=['A theme','B theme'], active=0)

selcallback = CustomJS(args=dict(source=source,rbutton=rbutton
                                 ,glyph=glyph
                                 ,r=patchrenderer
                                 ,cdict=cdict), code="""
        var indices = [];
        for (var i = 0; i <= source.data['x'].length; i++){
            if (source.data['x'][i] > cb_obj.value) {
            indices.push(i)
            }
            };
        var l_selected = source.selected;
        l_selected.indices = indices;
        source.selected = l_selected;
        """)

hslider.js_on_change('value',selcallback)


rbutton_callback = CustomJS(args=dict(r=patchrenderer.glyph,cdict=cdict),code="""
                            r.fill_color = cdict[this.active]""")
rbutton.js_on_click(rbutton_callback)

p = gridplot([[left, right],[hslider,rbutton]])
show(p)

Thanks so much for that clue!

⌁   Link to post

End of archived discussion · Replies are disabled