Combining JS + Python Callbacks

This is probably a reach, but I’ve been wondering about how to combine a fast/responsive interaction with some complex processing.

It seems to me that it would be useful if you could implement most callbacks in Javascript but, for some processing that really needs to revisit the data, call Python on the server. In particular, the idea would be that only Javascript is used while the user “plays” with the controls. But once there is a pause in events the callback is made to Python. The idea being that the Python callback is slower and should only be made once the interactive adjustments are complete.

Now I imagine I can do this manually. And I can also see that it could open a can of worms when you get into the details.

But I wondered if this was already implemented somehow. If not in Bokeh itself then perhaps someone has an example?

Thanks,

Andrew

Here is the Bokeh reference page for JS and Python callbacks.
When it comes to combining both I can give you some examples (run “bokeh serve --show app.py” or remove comments to run via “python app.py”):

from functools import partial
from bokeh.models.widgets import Button, Paragraph
from bokeh.models import CustomJS
from bokeh.layouts import widgetbox
from bokeh.io import curdoc

from bokeh.client import push_session

minus = Button(label = ‘-’)
plus = Button(label = ‘+’)
clear = Button(label = ‘None’)
text = Paragraph(text = ‘Which button was clicked?’)

def callback(text = text, foo = None):
text.text = foo
print (foo)

code = “button.label = ‘Clicked’;”

minus.on_click(partial(callback, foo = “minus”))
plus.on_click(partial(callback, foo = “plus”))

minus.callback = CustomJS(args = {‘button’: minus}, code = code)
plus.callback = CustomJS(args = {‘button’: plus}, code = code)
clear.callback = CustomJS.from_py_func(callback)

layout = widgetbox(minus, plus, clear, text)
curdoc().add_root(layout)

session = push_session(document = curdoc())

session.show(layout)

session.loop_until_closed()

``

And here is another one. I hope it helps:

from random import randint
from datetime import date
from bokeh.models import ColumnDataSource, TableColumn, DateFormatter, DataTable, CustomJS
from bokeh.layouts import column
from bokeh.models.widgets import TextInput
from bokeh.plotting import curdoc

from bokeh.client import push_session

selected_source = ColumnDataSource({‘column’: [-1], ‘row’: [-1]})
source = ColumnDataSource(dict(dates = [date(2014, 3, i + 1) for i in range(10)], downloads = [randint(0, 100) for i in range(10)]))

columns = [TableColumn(field = “dates”, title = “Date”, formatter = DateFormatter()), TableColumn(field = “downloads”, title = “Downloads”)]
data_table = DataTable(source = source, columns = columns, width = 400, height = 280, editable = True)

text_row = TextInput(value = None, title = “Row index:”)
text_column = TextInput(value = None, title = “Column Index:”)
text_date = TextInput(value = None, title = “Date:”)
text_downloads = TextInput(value = None, title = “Downloads:”)

def table_click(row, column):
print (‘Row %s Column %s clicked’ % (row, column))

source_code = “”"
var grid = document.getElementsByClassName(‘grid-canvas’)[0].children;
var row = ‘’;
var col = ‘’;

for (var i=0,max=grid.length;i<max;i++) {
if (grid[i].outerHTML.includes(‘active’)) {
row=i;
for (var j = 0, jmax = grid[i].children.length; j < jmax; j++)
if(grid[i].children[j].outerHTML.includes(‘active’))
col = j
}
}
source.data = { ‘column’: [col], ‘row’: [row] }
“”"

def function_source(attr, old, new):
try:
selected_index = source.selected[“1d”][“indices”][0]
text_row.value = str(selected_source.data[“row”][0])
text_date.value = str(source.data[“dates”][selected_index])
text_downloads.value = str(source.data[“downloads”][selected_index])
text_column.value = str(selected_source.data[“column”][0])
source.selected.update({“0d”:{“indices”: }, “1d”:{“indices”:}, “2d”:{“indices”:}})
table_click(selected_source.data[“row”][0], selected_source.data[“column”][0])
except IndexError:
pass

source.callback = CustomJS(args = dict(source = selected_source), code = source_code)
source.on_change(‘selected’, function_source)

curdoc().add_root(column(data_table, text_row, text_column, text_date, text_downloads))

session = push_session(document = curdoc())

session.show()

session.loop_until_closed()

``

I think JS callback is called first then Python callback.

In both cases you could possibly check some conditions and delay execution, JS: setTimeout() , Python: time.sleep()…

···

On Saturday, January 12, 2019 at 7:05:13 PM UTC+1, andrew cooke wrote:

This is probably a reach, but I’ve been wondering about how to combine a fast/responsive interaction with some complex processing.

It seems to me that it would be useful if you could implement most callbacks in Javascript but, for some processing that really needs to revisit the data, call Python on the server. In particular, the idea would be that only Javascript is used while the user “plays” with the controls. But once there is a pause in events the callback is made to Python. The idea being that the Python callback is slower and should only be made once the interactive adjustments are complete.

Now I imagine I can do this manually. And I can also see that it could open a can of worms when you get into the details.

But I wondered if this was already implemented somehow. If not in Bokeh itself then perhaps someone has an example?

Thanks,

Andrew