Callback function for CheckboxGroup and Button

I want to create a visualization with CheckboxGroup and ‘Button’, which shows the line of the currency in the graph if the checkbox of this currency is activated. In addition i want one Button ‘Select all’ and one ‘Select none’ to select all or none currency at once.

By now, I have this code but I get the following error and I don’t know what to write in ‘code’. In general I am pretty lost regarding the callbacks of CheckbockGroup and Button. I would really appreciate a check of my code and some help. Thank you!

unexpected attribute ‘checkbox’ to CustomJS, possible attributes are args, code, js_event_callbacks, js_property_callbacks, name, subscribed_events or tags

import pandas as pd
from bokeh.io import output_file, show, save
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, HoverTool, CheckboxGroup, CustomJS, Button
from bokeh.layouts import row, column
...
source_dict = {'dates': dates}
for c in currencies:
    source_dict[c] = df_returns.loc[:,c].values.tolist()

source = ColumnDataSource(data=source_dict)


p = figure(plot_width=1000, plot_height=800, x_axis_type="datetime")
p.title.text = 'Daily Returns during 2020'

lines = []
names = []
args = []
for c in range(len(currencies)):
    line = p.line(x='dates', y=currencies[c], line_width=2, alpha=1, name=currencies[c], legend_label=currencies[c], source=source)
    lines.append(line)
    names.append(currencies[c])
    args += [('line'+str(c), line)]


p.sizing_mode = "stretch_width"
p.legend.visible = False

hover = HoverTool(tooltips=[("date", "@dates{%F}"),
                            ('currency','$name'),
                            ('return','@$name{0.00%}')],
                  formatters={'@dates': 'datetime'})

p.add_tools(hover)

checkbox_group = CheckboxGroup(labels=names, active=list(range(len(currencies))))

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

def callback_button_on():
    checkbox_group.active = list(range(len(currencies)))
    source.data = source_dict

def callback_button_off():
    checkbox_group.active = []
    source.data = {}

select_all = Button(label='Select all')
select_all.on_click(callback_button_on)

select_none = Button(label='Select none')
select_none.on_click(callback_button_off)

group = column(checkbox_group)

show(row(group, p))
output_file("Daily_Returns.html")
save(p)

@loorenaa There’s several issues with the code above. The first and most important is that on_change and real Python callbacks only work if you are running a Bokeh server application. Using output_file and show only generate a static HTML output file that has no connection to any Python process. In this case only JavaScript callbacks (with js_on_change) are possible. So you have two options, and we should figure out that question first before diving in to more:

  • Continue generating standalone HTML output with output_file and show if Javascript callbacks are sufficient to your needs.
  • Create a Bokeh Server Application if you want or need to use real Python callbacks with on_change
1 Like

First of all, thank you for your help!
Since I am quite new in the programming world, I don’t know the difference between the two suggested ways or what would be better in my case.
I need this interactive visualization to explore the data. So I guess creating a Bokeh Server Application would be better?
And with that, I need to change this part:

group = column(select_all, select_none, checkbox_group)
show(row(group, p))
output_file("Daily_Returns.html")
save(p)

into that:

group = column(select_all, select_none, checkbox_group)
curdoc().add_root(row(group, p))
curdoc().title = "Daily_Returns"

Correct me if I am wrong!

@loorenaa That’s roughly the change, yes, but also you would need to run the app by executing

bokeh serve --show myapp.py

and not just by running python myapp.py. That’s probably the most important thing to understand: the app is only accessible as long as the bokeh server is running. If this is just for your local use, then you can simply run it as above (much like a Jupyter notebook server). But if you wan to publish this for audience, you would need to host the server running all the time somewhere.

Okay yes thanks!

I don‘t want to publish it, but one person needs to be able to use it as well, because she needs to have a look at my analysis, since I am using this for a thesis. But mainly it‘s for my analysis, so I guess this is okay.

But what would be the problem if I use the HTML output version?

There’s not a problem, per se. But browsers have no ability to run Python code, so all that is available are JavaScript callbacks (so no numpy or pandas, etc). Lots of things are still possible with JavaScript callbacks:

But whether they are sufficient depends on the details off what you want to do.

Okay, so I guess I‘ll go with the Bokeh Server Application.

Now, how do I have to implement the CheckboxGroup and the Buttons?
I want to have all the (76 i guess) checkboxes, such that at the beginning all are ticked and so all the lines in the plot are shown. When un-ticking a checkbox I want the line to disappear from the plot.
In addition I want a „Select all“ and „Select none“ button.

@loorenaa can you quickly look over (and ideally try out) the examples we have created for users:

It’s always easier to help when questions are as narrow/focused as possible, e.g. “Here is what I have tried (with some code), how do I fix/finish it?” Otherwise the very broad general statement I can make is that you can set or unset Bokeh object properties like .active inside callbacks, and when you do, the displayed app will automatically update, and the work most callbacks do is to set properties on various Bokeh objects to update or change the UI.

So here is what I have so far. I have a dictionary containing alls currencies and their daily return, which is used as source_dict for the ColumnDataSource. Then I plot the line of every currency. Don’t mind the ‘args’ and ‘names’ list, I was just trying out something I found in the internet, which didn’t work.
So the main question is now how to implement the CheckboxGroup, since I have absolutely no idea.

For the two buttons i have the following approach. In the according callback I give to souce.data the dictionary that should be used. I.e. callback_button_off() should un-tick all boxes, so no line should appear in the plot and so source.data is just an empty dictionary. But since this doesn’t work, I would appreaciate some help here as well.

import pandas as pd
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, HoverTool, CheckboxGroup, CustomJS, Button
from bokeh.layouts import row, column

...

source_dict = {'dates': dates}
for c in currencies:
    source_dict[c] = df_returns.loc[:,c].values.tolist()

source = ColumnDataSource(data=source_dict)

# creating visulization
p = figure(plot_width=1000, plot_height=800, x_axis_type="datetime")
p.title.text = 'Daily Returns during 2020'

lines = []
names = []
args = []
for c in range(len(currencies)):
    line = p.line(x='dates', y=currencies[c], line_width=2, alpha=1, name=currencies[c], legend_label=currencies[c], source=source)
    lines.append(line)
    names.append(currencies[c])
    args += [('line'+str(c), line)]



p.sizing_mode = "stretch_width"
p.legend.visible = False

hover = HoverTool(tooltips=[("date", "@dates{%F}"),
                            ('currency','$name'),
                            ('return','@$name{0.00%}')],
                  formatters={'@dates': 'datetime'})

p.add_tools(hover)


???
checkbox_group = CheckboxGroup(labels=names, active=list(range(len(currencies))))

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

def callback_button_on():
    checkbox_group.active = list(range(len(currencies)))
    source.data = source_dict

def callback_button_off():
    checkbox_group.active = []
    source.data = {}

select_all = Button(label='Select all')
select_all.on_click(callback_button_on)

select_none = Button(label='Select none')
select_none.on_click(callback_button_off)

group = column(select_all, select_none, checkbox_group)

curdoc().add_root(row(group, p))
curdoc().title = "Daily_Returns"

@loorenaa If I understand what you want correctly, then instead of changing the actual data, I would suggest merely toggling the visibility of things on or off. Here is a relevant example that uses checkboxes to toggle the visibility of lines:

https://github.com/bokeh/bokeh/blob/branch-2.3/examples/app/line_on_off.py

Specifically, it does this:

l0 = p.line(...)
l1 = p.line(...)
l2 = p.line(...)

checkbox = CheckboxGroup(labels=["Line 0", "Line 1", "Line 2"], 
                         active=[0, 1, 2])

def update(attr, old, new):
    l0.visible = 0 in checkbox.active
    l1.visible = 1 in checkbox.active
    l2.visible = 2 in checkbox.active

That’s fine for only three lines. If you have lots of lines, it’s probably better to put all the renderers (the l0, l1, etc) into a list, then you could just loop:

# `lines` is a list of line renderers

for i in range(len(lines)):
    lines[i].visible = i in checkbox.active

Thanks, this worked perfectly. And I also managed to impelement the buttons.

Now I am struggling with the following:
I want that the HoverTool shows the color of the corresponding line. So I put all the colors in in a list and then into the ColumnDataSource. However, the HoverTool says ‘color unknown’. Do you see my mistake? I tried various combinations but none of them worked.

source = ColumnDataSource(data=source_dict)
...
colors = []
lines = []
for curr,color in zip(range(len(currencies)),color_palette):
    line = p.line(x='dates', y=currencies[curr], line_width=2, alpha=1, color=color, name=currencies[curr], legend_label=currencies[curr], source=source)
    lines.append(line)
    colors.append(color)

source_dict['color'] = colors

p.sizing_mode = "stretch_width"
p.legend.visible = False

hover = HoverTool(tooltips=[("date", "@dates{%F}"),
                            ('currency','$name'),
                            ('return','@$name{0.00%}'),
                            ('line color', "$color[hex, swatch]:color")],
                  formatters={'@dates': 'datetime'})

Hi @loorenaa please open separate topics for separate questions so that we can keep this resource organized for all users