2
votes

I am trying to make a bokeh serve plot with a CheckButtonGroup. I manage to update my source.data but the plot does not get updated. What am I doing wrong?

In reality, I import the dataset from my computer, but for now I will create an example pandas dataframe. I want to select the 'x' column (as x-axis variable) and one or more of the other columns (as y-axis variables).

import pandas as pd
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.layouts import row, widgetbox
from bokeh.models.widgets import CheckboxButtonGroup
from bokeh.models import ColumnDataSource

dataset = pd.DataFrame(columns=['x','y1','y2','y3'])
dataset['x'] = [1, 2, 3, 4]
dataset['y1'] = [10, 20, 30, 40]
dataset['y2'] = [11, 21, 31, 41]
dataset['y3'] = [12, 22, 32, 43]

pos_cols = ['y1', 'y2', 'y3'] # possible column names
col_list = ['y1', 'y2'] # default columns in plotted data
use_data = dataset[col_list]
use_data['x'] = dataset.loc[:, 'x']

source = ColumnDataSource(use_data)

p = figure(
   tools="pan,box_zoom,wheel_zoom,reset,save",
   x_axis_label='xtitle', y_axis_label='ytitle',
   title="Simulations"
)

# make default plot with the two columns
for column in col_list:
    p.line('x', column, source=source)

check = CheckboxButtonGroup(labels=["y1", "y2", "y3"], active=[0, 1]) # A check box for every column

def update_lines(new):

    col_list = [pos_cols[i] for i in new]
    use_data = dataset[col_list]
    use_data['x'] = dataset.loc[:, 'x']
    source.data = source.from_df(use_data)

    print(source.data) # source.data is correctly updated, but does not seem to trigger a new plot

check.on_click(update_lines)

doc = curdoc()
doc.add_root(row(check, p, width=800))
doc.title = "Simulations"

I save the code as try.py and run it from the windows prompt with bokeh serve try.py. The plot is visible at http://localhost:5006

1

1 Answers

1
votes

The problem is that you are creating glyphs for columns like 'y3' up front, but not actually sending any column 'y3' to start. Bokeh does not like that (you can see error messages about trying to access non-existent columns in the browser JS console)

A better approach, that also does not unnecessarily re-send all the data, might be to just toggle the .visible attribute of the glyph renderers. Here is a minimal example (that starts with all lines visible, but you could change that):

import pandas as pd
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.models import CheckboxButtonGroup, ColumnDataSource

dataset = pd.DataFrame(columns=['x','y1','y2','y3'])
dataset['x'] = [1, 2, 3, 4]
dataset['y1'] = [10, 20, 30, 40]
dataset['y2'] = [11, 21, 31, 41]
dataset['y3'] = [12, 22, 32, 43]

source = ColumnDataSource(data=dataset)

p = figure( )

lines = []
for column in ['y1', 'y2', 'y3']:
    lines.append(p.line('x', column, source=source))

check = CheckboxButtonGroup(labels=["y1", "y2", "y3"], active=[0, 1, 2])

def update_lines(new):
    for i in [0, 1, 2]:
        if i in new:
            lines[i].visible = True
        else:
            lines[i].visible = False

check.on_click(update_lines)

doc = curdoc()
doc.add_root(row(check, p, width=800))

Alternatively, if you are just looking to be able to hide or mute lines, an much easier way would be to use Bokeh's built in Interactive Legends:

http://docs.bokeh.org/en/latest/docs/user_guide/interaction/legends.html#userguide-interaction-legends