Is pushing data to a second source using a button possible?

This code works if you set the callback to the selected on change. But what i want is the ability to select points then push it to another source, only if the button is pressed. But js_on_click only takes 2 positional arguments. Is it not possible to push data to another source with a button click?

from bokeh.io import output_file, show
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS, Button, TableColumn, DataTable
from bokeh.plotting import figure, output_notebook

output_notebook()

x1 = list(range(-20, 21))
y1 = [abs(xx) for xx in x]


# create a column data source for the plots to share
s1 = ColumnDataSource(data=dict(x1=x1, y1=y1))
s2 = ColumnDataSource(data=dict(x2=[], y2=[]))

TOOLS = "box_select,lasso_select,help"

# create a new plot and add a renderer
left = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
left.circle(x='x1', y='y1', source=s1)

# create another new plot and add a renderer
right = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
right.circle(x='x2', y='y2', source=s2)

callback = """
        var inds = cb_obj.indices;
        var d1 = s1.data;
        var d2 = s2.data;
        
        for (var i = 0; i < inds.length; i++) {
            d2['x2'].push(d1['x1'][inds[i]])
            d2['y2'].push(d1['y1'][inds[i]])
        }
        s2.change.emit();

    """


s1.selected.js_on_change('indices', CustomJS(args=dict(s1=s1, s2=s2)))

columns = [TableColumn(field ='x2', title = "x2"),
           TableColumn(field ='y2', title = "y2")
                       ]


table = DataTable(source=s2,
                  columns=columns,
                  width=200,
                  height=300,
                 )

PushButton = Button(label="Push", width=80)
PushButton.js_on_click('indices', CustomJS(args=dict(s1=s1, s2=s2),code=callback))

p = row(PushButton, left, right, table)

show(p)

@Eric_Johnson There are a few problems with your code:

  • x is undefined (presumably a typo and you meant x1)

  • js_on_click only takes the callback object as its only parameter

  • The CustomJS callback is on the Button, so the value of cb_obj (short for “callback object”) is the button. A button has not indices property. You want

    const inds = s1.selected.indices
    
  • Generally speaking, it’s good to avoid var (prefer const or let if necessary)

With those small changes, your code works for me.

2 Likes

I changed a couple things to get your code working as-is. Let me know if this is what you’re looking for. Note that this won’t clear the previously selected points from s2, so you might want to add that in.

from bokeh.io import show
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS, Button, TableColumn, DataTable
from bokeh.plotting import figure

x1 = list(range(-20, 21))
y1 = [abs(xx) for xx in x1]

# create a column data source for the plots to share
s1 = ColumnDataSource(data=dict(x1=x1, y1=y1))
s2 = ColumnDataSource(data=dict(x2=[], y2=[]))

TOOLS = "box_select,lasso_select,help"

# create a new plot and add a renderer
left = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
left.circle(x='x1', y='y1', source=s1)

# create another new plot and add a renderer
right = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
right.circle(x='x2', y='y2', source=s2)

callback = """
        var d1 = s1.data;
        var d2 = s2.data;
        var inds = s1.selected.indices;

        for (var i = 0; i < inds.length; i++) {
            d2['x2'].push(d1['x1'][inds[i]])
            d2['y2'].push(d1['y1'][inds[i]])
        }
        s2.change.emit();

    """
PushButton = Button(label="Push", width=80)
PushButton.js_on_click(CustomJS(args=dict(s1=s1, s2=s2), code=callback))

columns = [TableColumn(field='x2', title="x2"),
           TableColumn(field='y2', title="y2")
           ]

table = DataTable(source=s2,
                  columns=columns,
                  width=200,
                  height=300,
                  )
p = row(PushButton, left, right, table)

show(p)
2 Likes

Also, just FYI, if you are developing any sort of CustomJS or extension, it is advised to look in to how to open your browsers debugger, since that is where errors like this will show up:

1 Like

Thanks!!! I was working off this example: Linking behavior — Bokeh 2.3.2 Documentation. Forgot to restart my kernal before posting so the code was working for me as x was still stored.

That make sense to get the selected indices that way.

Yes this works, thanks! I was looking for a way the user can select some points, add them to a new source, look around and select some other points again then add those points to the source, etc. Without clearing them from s2.

If anyone is wondering about clearing the previously selected point, in the callback before the for loop, just add:

d2[‘x2’] = [ ]
d2[‘y2’] = [ ]

1 Like