Kdog
Kdog

Reputation: 513

Filtering Bokeh chart source using chart selections

I'm trying to change the source for a bokeh chart by doing a lasso/box selection.

The callback works once, but stops working after the first source change. Also, I can't tell why some points are highlighted after the button click.

I'd like to progressively narrow down the datasource by making the selections.

Here is the small example:

import bokeh
import pandas as pd
import numpy as np
from bokeh.plotting import figure, curdoc, show
from bokeh.layouts import column, row, Spacer
from bokeh.models import ColumnDataSource,GMapOptions, HoverTool
from bokeh.models.widgets import Button


df1 = pd.DataFrame(np.random.randint(0,100,size=(3000, 2)), columns=["X","Y"], index=[str(i) for i in range(1,3000+1)])

pointchart=figure(plot_width=800, plot_height=700, tools=['box_select','lasso_select'])
pointchart_source= ColumnDataSource(df1)
pointchart_glyph= pointchart.circle("X","Y",source=pointchart_source, size=3.5) 


def on_selection_change(attr, old, new):
    global newdataframe
    newdataframe= pd.DataFrame(pointchart_source.data).loc[new]
pointchart_glyph.data_source.selected.on_change('indices', on_selection_change) 


def on_update_button_click():
    pointchart_source.data= ColumnDataSource(newdataframe).data

update_button = Button(label="Update", button_type="success")
update_button.on_click(on_update_button_click)


layout =row(pointchart,update_button)
curdoc().add_root(layout)
!powershell -command {'bokeh serve --show Source_update_interaction.ipynb'}

Thank you

Upvotes: 0

Views: 194

Answers (2)

Tony
Tony

Reputation: 8287

I think this is what you are looking for (Bokeh v1.1.0):

import bokeh
import pandas as pd
import numpy as np
from bokeh.plotting import figure, curdoc, show
from bokeh.layouts import column, row, Spacer
from bokeh.models import ColumnDataSource, GMapOptions, HoverTool
from bokeh.models.widgets import Button

df1 = pd.DataFrame(np.random.randint(0, 100, size = (3000, 2)), columns = ["X", "Y"], index = [str(i) for i in range(1, 3000 + 1)])
pointchart_source = ColumnDataSource(df1)
pointchart = figure(plot_width = 800, plot_height = 700, tools = ['box_select', 'lasso_select'])
pointchart_glyph = pointchart.circle("X", "Y", source = pointchart_source, size = 3.5)

newdataframe = None

def on_selection_change(attr, old, new):
    global newdataframe
    newdataframe = pd.DataFrame(pointchart_source.data).loc[new]
    newdataframe.index = newdataframe['index']
    newdataframe = newdataframe.drop(['index'], axis = 1)

pointchart_glyph.data_source.selected.on_change('indices', on_selection_change)

def on_update_button_click():
    global newdataframe
    if newdataframe is not None:
        if pointchart_source.selected.indices:
            pointchart_source.data = {'X': newdataframe['X'].values, 'Y': newdataframe['Y'].values, 'index': newdataframe.index.values}
            pointchart_source.selected.indices = []

update_button = Button(label = "Update", button_type = "success")
update_button.on_click(on_update_button_click)

layout = row(pointchart, update_button)
curdoc().add_root(layout)

Result:

enter image description here

Upvotes: 2

bigreddot
bigreddot

Reputation: 34568

Don't do this:

def on_update_button_click():
    pointchart_source.data = ColumnDataSource(newdataframe).data

The .data attribute behaves like a dict, but it is actually a heavily instrumented object that is tied to the data source it is created on, and takes care of all the automatic syncing between Python and JS. One .data from a CDS should never be assigned to another different CDS, and attempting to do so will probably raise an explicit exception in the future.

You should always update .data from a plain Python dict. You can use the from_df class method on CDS to construct a dict of the appropriate form:

source.data = ColumnDataSource.from_df(newdataframe)

Upvotes: 1

Related Questions