Reputation: 513
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
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:
Upvotes: 2
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