1

I am trying to add checkboxes in my bokeh plot so that I can hide or show different lines in my plot. I found some code from github and modified it to fulfil my purpose. Please have a look into the below code,

    for data, name, color in zip([AAPL, IBM, MSFT, GOOG], ["AAPL", "IBM", "MSFT", "GOOG"], Spectral4):          
        df = pd.DataFrame(data) 
        source = ColumnDataSource(data = dict(date = pd.to_datetime(df['date']), close = df['close']))                      
        plt = fig.line('date', 'close', line_width=2, color=color, alpha=0.8,
               muted_color=color, muted_alpha=0.2, legend = name, source=source)

        graph_labels.append(name)
        plots.append(plt)

    checkbox = CheckboxGroup(labels = graph_labels, active = [0,1,2,3]) 

    checkbox.callback = CustomJS(args = dict(line0 = plots[0], line1=plots[1], line2=plots[2], line3=plots[3]),  code=""" 
        //console.log(cb_obj.active);
        line0.visible = false;
        line1.visible = false;
        line2.visible = false;
        line3.visible = false;

        for (i in cb_obj.active) {
            //console.log(cb_obj.active[i]);
            if (cb_obj.active[i] == 0) {
                line0.visible = true;
            } else if (cb_obj.active[i] == 1) {
                line1.visible = true;
            } else if (cb_obj.active[i] == 2) {
                line2.visible = true;
            } else if (cb_obj.active[i] == 3) {
                line3.visible = true;
            }
        }
    """)

    layout = row(fig, widgetbox(checkbox), sizing_mode='fixed')

    show(layout)     

This code works perfectly. However my requirment is something else. In my case the number of plts will be different each time I run the code as my data is different. So I tried to modify this code but have not had any success yet.

The changes I made are

    checkbox = CheckboxGroup(labels = graph_labels, active = list(range(0, len(plots))))
    arg_list = []

    for idx in range(0, len(plots)):
        arg_list.append('line' + str(idx))
        arg_list.append(plots[idx])

    i = iter(arg_list)  
    checkbox.callback = CustomJS(args = dict(izip(i, i)),  code=""" 
        // Here I don't know how to use dynamic names for line0 and line1 and use them to control their visibility
        // As type of line0 is object and if I 'm trying to make a dynamic string I can't convert it to object and it fails

I also tried using

    source = ColumnDataSource(data = dict( ... ) ...
    callback = CustomJS(args=dict(source=source), code="""

But it failed as well and did not show any plot. I'm using the latest version of Bokeh and python 2.7 Any suggestion is highly appriciated and thanks in advance !! :)

neo
  • 37
  • 5

2 Answers2

1

You can do something like this:

from bokeh.io import show
from bokeh.plotting import figure
from bokeh.models import CustomJS, CheckboxGroup
from bokeh.layouts import Row
from bokeh.palettes import Category20_20

from random import random,choice

N_lines = int(100*random())/10 # undefined but known from the start number of lines.

x= range(3)
fig = figure()
args = []
code = "active = cb_obj.active;"
for i in range(N_lines):
    glyph = fig.line(x,[random() for j in x],color=choice(Category20_20))
    args += [('glyph'+str(i),glyph)]
    code += "glyph{}.visible = active.includes({});".format(i,i)

checkbox = CheckboxGroup(labels=[str(i) for i in range(N_lines)],active=range(N_lines))

checkbox.callback = CustomJS(args={key:value for key,value in args},code=code)

show(Row(fig,checkbox))

This builds the callback code based on the number of lines. Since you asked for the code to adapt to your data, you can certainly determine the number of lines from the data.

After that, if you also want to add lines dynamically through interactions you need to update:

checkbox.labels (just add one label name)

checkbox.active (set it to a range() list with one more number)

checkbox.callback (with one more pair for "args" and one more entry for "code")

Seb
  • 1,765
  • 9
  • 23
  • thank you so much for your response. Unfortunately this method did not work for me though. When I tried to run my code after the modification that you have suggested, it did not show any plot at all. – neo Mar 15 '18 at 19:20
  • @neo the code I provided definitely works, in all browsers. Could you have missed something when adapting it to yours? – Seb Mar 18 '18 at 17:54
  • great answer. I wanted to that outside a for-loop: just to let other know, the `code` is something like `active = cb_obj.active;glyph0.visible = active.includes(0);glyph1.visible = active.includes(1);` – roschach Dec 31 '19 at 10:48
  • @FrancescoBoi thanks, however note that was before the implementation of interactive legends in bokeh to show/hide lines, now I would recommend using that instead of making custom checkboxes – Seb Jan 02 '20 at 17:20
-2

Actually I found out the answer to this question from the following post

How to interactively display and hide lines in a Bokeh plot?

In the Comment section user2561747 has suggested something similar. Initially I was having problems becasue I wanted to use hover tool as well for which I was trying to name each plot differently and then something like that

    for column, color in zip(df.columns.values, Spectral4):
    ...... 
    ...... 
         plt = fig.line('date', 'value', name = column, line_width = 2, color = color, alpha = 0.8,
                   muted_color = color, muted_alpha = 0.2, legend = column, source = source)

         graph_labels.append(column)
         plots.append(plt)
    ......
    ......

    checkbox = CheckboxGroup(labels = graph_labels, active = list(range(0, len(plots))))    

    checkbox.callback = CustomJS.from_coffeescript(args = dict(plot = fig, checkbox = checkbox), code=""" 
    rends = []; 
    rends.push plot.select(label) for label in checkbox.labels;
    rends[i].visible = i in checkbox.active for i in [0...rends.length];
    """)

I could never figure out what the problem was but I ended up naming all the plots "hideable" and it worked for the hover tool as well. So the final code looks something like this,

    checkbox.callback = CustomJS.from_coffeescript(args = dict(plot = fig, checkbox = checkbox), code=""" 
    rends = plot.select("hideable");
    rends[i].visible = i in checkbox.active for i in [0...rends.length];
    """)

If anyone can find out the problem with the 1st approach that would be awesome. I saw absolutely no error in the browser console however the elements of rends array had different structures in both cases.

neo
  • 37
  • 5