Brain_overflowed
Brain_overflowed

Reputation: 420

Bokeh: How to update legends with interactive plotting with Checkbox?

I have an interactive Bokeh plot where (for the sake of simplicity) I can click on multiple Checkboxes (think of them as light switches) and plot their sum (the cumulative brightness in the room). This is plotted with a theoretical set of non-interactive plots.

How can I update the legend to illustrate which Checkbox buttons were clicked? (I am not good with JS) The code for the JS Callback that updates the plots are as follows:

callback = CustomJS(args=dict(source=source), code="""
    const labels = cb_obj.labels;
    const active = cb_obj.active;
    const data = source.data;
    const sourceLen = data.combined.length;
    const combined = Array(sourceLen).fill(undefined);
    if (active.length > 0) {
        const selectedColumns = labels.filter((val, ind) => active.includes(ind));
        for(let i = 0; i < sourceLen; i++) {
            let sum = 0;
            for(col of selectedColumns){
                sum += data[col][i];
            }
            combined[i] = sum;
        }
    }
    data.combined=combined;
    source.change.emit();
""")

checkbox_group = CheckboxButtonGroup(labels=col_names[3:], active=[], callback=callback)

An image of the plot is shown below. There are buttons at the bottom that adds to the plots when clicked.

enter image description here

Upvotes: 0

Views: 2407

Answers (1)

hyles_lineata
hyles_lineata

Reputation: 416

Here's one way to do it. Legends (and LegendItems) are Bokeh models, so you can update them via the CustomJS callback.

I made some assumptions about how you wanted the legend to update. In this example, it changes the string for the combined line's label to include which elements it's summing. If you've got something else in mind, you can apply the basic callback structure to your idea.

from bokeh.plotting import show, figure
from random import random
from bokeh.models import ColumnDataSource, CustomJS, CheckboxButtonGroup, Legend
from bokeh.layouts import column
import numpy as np
import pandas as pd

x = np.arange(10)
a = np.random.rand(10, ) * 20
b = np.random.rand(10, ) * 40
c = np.random.rand(10, ) * 60
df = pd.DataFrame(data={'x': x, 'a': a, 'b': b, 'c': c, 'combined': [0]*10})

source = ColumnDataSource(df)
button_labels = ['a', 'b', 'c']
p = figure(plot_width=1000, plot_height=500, y_range=(0, max(c)*2))

a_line = p.line('x', 'a', source=source, color='red')
b_line = p.line('x', 'b', source=source, color='blue')
c_line = p.line('x', 'c', source=source, color='orange')
combined_line = p.line('x', 'combined', source=source, color='green', line_dash='dashed')

legend = Legend(items=[
    ('a', [a_line]),
    ('b', [b_line]),
    ('c', [c_line]),
    ('combined (none)', [combined_line]),
], location="center")
p.add_layout(legend, 'right')

callback = CustomJS(args=dict(source=source, legend_item=legend.items[3]), code="""
    const labels = cb_obj.labels;
    const active = cb_obj.active;
    const data = source.data;
    const sourceLen = data.combined.length;
    const combined = Array(sourceLen).fill(undefined);
    var combined_label = ''
    
    if (active.length > 0) {
        const selectedColumns = labels.filter((val, ind) => active.includes(ind));
        for(let i = 0; i < sourceLen; i++) {
            let sum = 0;
            for(var col of selectedColumns){
                sum += data[col][i];
            }
            combined[i] = sum;
        }

        // get index positions of active buttons; use that to retrieve labels to build "combined" label string
        for (let j=0; j < active.length; j++) {
            combined_label += labels[active[j]]+'+';
        } 
        combined_label = '('+combined_label.substring(0, combined_label.length - 1)+')';
    }
    else {  // if there are no active buttons, label as 'none'
      combined_label = '(none)';
    }

    legend_item.label.value = 'combined '+combined_label;
    data.combined=combined;
    source.change.emit();
""")

checkbox_group = CheckboxButtonGroup(labels=button_labels, active=[], callback=callback, width=400)

final_col = column(p, checkbox_group)
show(final_col)

legend_updated_by_button_callback

Upvotes: 1

Related Questions