Swap between data source

I would like to swap between two CDS with a select widget.
I tried to use CDView but the processing on it is long, so I would rather to use pandas to do the job and then create CDS and use Select to change the glyph. I know that @Bryan @carolyn did an example here before showing that you need to name the CDS as the value that you will use to select/filter it with the widget, although I can not find it here, perhaps by the fact that after three months the topic is closed it will expire.

src1 = ColumnDataSource(df1)
src2 = ColumnDataSource(df2)

p1 = figure()

p1.vbar(x=‘x’, top=‘y’)

select = Select(title=‘Select’, align=‘start’, value=‘A’, options=[‘src1’, ‘src2’])

select.js_on_change(‘value’, CustomJS(args=dict(src1=src1, src2=src2, select=select1), code="""

    ?????
"""))

Any help?

Usually it is much, much simpler to just make two separate glyphs up front, and toggle their .visible properties appropriately.

1 Like

Ok, I like the approach. Could you send me a link for an example, please?

Just a different widget. Thanks for the advice.

Using the visibility toggling approach:

from bokeh.models import ColumnDataSource, CustomJS, Select
from bokeh.plotting import figure, show
from bokeh.layouts import column


src1 = ColumnDataSource(data={'x':[0,1,2],'y':[3,2,1]})
src2 = ColumnDataSource(data={'time':[4,5,6],'distance':[2,5,6]})

p1 = figure()

#get variables pointing to the renderers that are being drive by each respective src
r1 = p1.vbar(x='x', top='y', source=src1)
r2 = p1.line(x='time',y='distance',source=src2,visible=False) #initialize as not visible

select = Select(title='Select', align='start', value='xy', options=['xy', 'timedist'])

#pass the renderers, not the sources, as you aren't manipulating the datasources but instead the visibility property of the renderer
#just need to look up how to write an if statement in javascript for the code really
select.js_on_change('value', CustomJS(args=dict(r1=r1, r2=r2, select=select), code='''
                                      if (select.value=='xy'){
                                              r1.visible=true
                                              r2.visible=false}
                                      else {
                                          r1.visible=false
                                          r2.visible=true}'''
                                      ))

show(column([p1,select]))
2 Likes

@gmerritt123 thanks buddy. I will have to give you credit once I finish this personal project.
I really appreciate the help.

1 Like

@gmerritt123 a question about it still.
You used the same figure then renderers, in my case I have a column with few figures on it. I did follow your logic and it worked like a charm expect by the fact that before I alter the initial value of the Select widget it shows all the figures in it, once I change the Select value it will show just the one selected and follow the else if statement passed in the callback. I believe that I could insert the column layout in the callback and allow just the initial value to be visible, is it possible? it goes up to t04.

show(column([select, t21, t20, t19, t18, t17]))

@gmerritt123 I found this out. I believe that it will do the trick.
Guess whom came up with this answer?! @Bryan is like a Bokeh’s wizard.

Yep, you got it. Like renderers, bokeh “figure” objects also have a visible property so you can control the figure’s visibility in the callback itself too, just need to pass it in as an arg and write out the required logic in the JS if statements etc. The magic part is how the layout automatically resizes for you when various figures turn on and off (no idea how that works but I love it).

@gmerritt123 I believe that it will be mostly based on the axis and figure properties. @Bryan will know how the JS backend is working for sure.

I did not manage to get it fixed. Could ye help me out. I stopped the if statement earlier to simply the code here. Still I was looking into some nested loops for JS, perhaps a for loop to make the other plots not visible, instead write one by one this way.


    r = gridplot([[pw, pp, pf, pl]], merge_tools=True, toolbar_location='below', width=190, height=160)

    dfL = pd.read_csv(f'dfL{year}.csv', delimiter=',', index_col=0)
    pL = LPlot(dfL, year)
    
    t1 = Panel(child=pCons, title='Overall')
    t2 = Panel(child =pL, title='By Grand Prix')
    tabs = Tabs(tabs= [t1, t2])
    
    col = column([tabs, r], align='start')  
    
    return col
> 
> def tabs():
>     t04, t05, t06, t07, t08, t09, t10  = Constplot(2004), Constplot(2005), Constplot(2006), Constplot(2007), Constplot(2008), Constplot(2009), Constplot(2010)
>     t11, t12, t13, t14, t15, t16, t17 = Constplot(2011), Constplot(2012), Constplot(2013), Constplot(2014), Constplot(2015),Constplot(2016), Constplot(2017) 
>     t18, t19, t20, t21 = Constplot(2018), Constplot(2019), Constplot(2020), Constplot(2021)

>     select = Select(title='Select year:', align='start', value='Campionship 2021', options=['Campionship 2021', 'Campionship 2020',
>                                                                                            'Campionship 2019', 'Campionship 2018',
>                                                                                            'Campionship 2017','Campionship 2016',
>                                                                                            'Campionship 2015','Campionship 2014',
>                                                                                            'Campionship 2013','Campionship 2012',
>                                                                                            'Campionship 2011','Campionship 2010',
>                                                                                            'Campionship 2009','Campionship 2008',
>                                                                                            'Campionship 2007','Campionship 2006',
>                                                                                            'Campionship 2005','Campionship 2004'])
> 
>     select.js_on_change('value', CustomJS(args=dict(t21=t21, t20=t20, t19=t19, t18=t18, t17=t17, t16=t16,
>                                                     t15=t15, t14=t14, t13=t13, t12=t12, t11=t11, t10=t10,
>                                                     t09=t09, t08=t08, t07=t07, t06=t06, t05=t05,t04=t04, 
>                                                     select=select), code='''
>                                           if (select.value=='Campionship 2021'){
>                                                   t21.visible=true
>                                                   t20.visible=false
>                                                     ....}
>                                           ...else {
>                                                   ...
>                                                   t04.visible=visible}'''
>                                           ))
> 
>     show(column([select, t21, t20, t19, t18, t17, t16, t15, t14, t13,t12,t11,t10,t09,t08,t07,t06,t05,t04]))

>     tabs()

@gmerritt123

Hi @Reinhold83 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)

I see 2 key things you’re missing and maybe I’m missing a key element of what you’re trying to do too… but here goes:

  1. Your if statement setup doesn’t cover all cases. Like looking at your first if statement —> “if select.value == 'Campionship 2021 then turn on t20 and turn off t21” What if t04 is currently visible when the user selects Campionship 2021? Your if statement currently won’t turn off t04… I don’t think that’s the behaviour you’re going for here… I think you want all tabs off except for t21 correct?

  2. Hopefully this doesn’t get too confusing: there’s a key nuance when you get into tabs/panels and visibility. Do you want the tab to entirely disappear/reappear from your view, or do you want the tab to remain available but have the children of that tab (i.e. the figure/layout) change visibility? Or do you want the figures and tabs to stay visible but you want their renderers to change visibility? To do the former, you want to not modify the visibility property of anything but instead update the tabs property with a new list. To do the latter 2, you want the visibility property set true/false.

It’s actually kinda funny, in my last reply I thought about mentioning how freaking useful dictionaries are for organization and passing stuff back and forth between python and customjs… and now I see your next post and realize you really needed that advice :joy:

Take a look at this example organizing stuff with dictionaries. I make 26 tabs, one for each letter of the alphabet and populate 3 dictionaries storing the panels, the panel’s children (i.e. a single dummy figure), and the renderer on that figure, with the letter being the key for each. Pass these dictionaries to the callback allows me to quickly retrieve what I want to manipulate when the user changes the select value.

from bokeh.models import Tabs, CustomJS, Select, Panel
from bokeh.plotting import figure,show
from bokeh.layouts import column

import numpy as np
import string

#26 tabs ABCDE...
abcs = list(string.ascii_uppercase)

#organize your sh$t with dictionaries!
#keys are letters, and corresponding bokeh models we want to have control over are values.
panel_dict = {} #holding all the panels you're gonna make
fig_dict = {} #holding all the figures you're gonna put on each panel
rend_dict = {} #holding all the renderers that live on each figure
for l in abcs:
    fig_dict[l] = figure(height=250,width=250) #dummy empty figure for each
    rend_dict[l] = fig_dict[l].line(x=np.random.random(5),y=np.random.random(5))
    panel_dict[l] = Panel(child=fig_dict[l],title=l)
    
#create a tab with the panel dictionary values
tab = Tabs(tabs=list(panel_dict.values()))
#create a select with the panel dictionary keys
sel = Select(value='A',options =list(panel_dict.keys()))

cb = CustomJS(args=dict(sel=sel,tab=tab,panel_dict=panel_dict
                        ,fig_dict=fig_dict,rend_dict=rend_dict)
              
              ,code='''
              //get the value of the select
              var sv = sel.value
              //use panel_dict and the select value to update the tabs property of tab to make it the only tab available (I think this is what you want?)
              tab.tabs = [panel_dict[sv]]
              
              //ALTERNATIVELY, you can also control the visibility property of the figures/renderers - 
              //e.g. turn off all figures whose key doesnt correspond to the current value of the select, 
              //OR turn off all renderers whose key doesnt correspond to the current value of the select
              //All thanks to having everything stored in dictionaries that we pass to the callback
              // E.G.
              // just need to read up on how to iterate through keys and values of javascript object
              //for (var [k,v] of Object.entries(fig_dict)){
                //      if (k==sv){
                  //            v.visible=true}
                    //  else {
                       //   v.visible=false}
                     // }
              
              ''')
sel.js_on_change('value',cb)
lo = column([tab,sel])
show(lo)
1 Like

@gmerritt123 thanks a lot buddy. I will work on it later or tomorrow. The if statement was incomplete because it was too long to add there, like I made the select.value == x.visible and the rest not, so it took tonnes of lines to build, reason why I believed that a for loop certainly would be more practical.
I will copy your code and visualize element by element then I can get the logic behind it properly to them apply it to mine.
Again thanks a lot, now with pyscript_dev I can start to use python to create the callbacks which I am very comfortable with. I made a test and the only problem seem that the page dimensions seemed to be smaller. Anyhow that will change a lot the visualization market and web dev market for whom works with python.

It works smoothly buddy. Thanks.
https://f1projectbokeh.herokuapp.com