4

I've spent the last few weeks learning the Bokeh package (which for visualizations, is excellent in my opinion).

Unfortunately, I have come across a problem that I can't for the life of me, figure out how to solve.

The below two links have been helpful, but I can't seem to replicate for my problem.

Using bokeh to plot interactive pie chart in Jupyter/Python - refer to answer #3

https://github.com/bokeh/bokeh/blob/0.12.9/examples/howto/notebook_comms/Jupyter%20Interactors.ipynb

The below code (in Jupyter) displays the graph correctly and displays the slider correctly, but I'm unsure how to connect the two as when I move the slider, the graph remains static.

I am using Python 3.6 and Bokeh 12.9

N = 300

source = ColumnDataSource(data={'x':random(N), 'y':random(N)}) 

plot = figure(plot_width=950, plot_height=400) 

plot.circle(x='x', y='y', source=source)

callback = CustomJS(code=""" 
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "update_plot(" + cb_obj.value + ")";
    kernel.execute(cmd, {}, {})}; 
""")

slider = Slider(start=100, end=1000, value=N, step=10, callback=callback)

def callback(attr, old, new):
    N = slider.value
    source.data={'x':random(N), 'y':random(N)}

slider.on_change('value', callback)

layout = column(slider, plot) 

curdoc().add_root(layout)

show(widgetbox(slider, width = 300)) 

show(plot)

After reading the bokeh documentation and reading a view threads on GitHub, the 'callback' function is a little unclear for me as I'm not entirely sure what to parse to it (if in fact attr, old, new need certain elements parsed too it)

Any help would be greatly appreciated

Hopefully, I haven't missed anything glaringly obvious.

Kind Regards,

Adrian

3
  • Have you seen this guide? You are missing the important push_notebook. Commented Oct 3, 2017 at 10:26
  • Thanks for replying! I have executed this code within Jupyter and it works perfectly. However, adding push_notebook to my code doesn't change anything. Have you executed this on your machine? Commented Oct 3, 2017 at 10:32
  • I think you also need notebook_handle=True in the show function. Currently I cannot run your code, but I will have a look this evening if noone else can help you. Maybe if you add imports etc. to have running example more people might help you sooner. I'm not super sure why you are doing the custom js callback? Probably from the wedge example? I think for plot.circle you don't need that, but I can only test this evening. Commented Oct 3, 2017 at 11:32

2 Answers 2

5

You are currently mixing different ways for interactivity but unfortunately you always miss something for each different way.

The slider you use is from bokeh, but unfortunately it looks like slider.on_change only works if you run through the bokeh server. From the documentation:

Use bokeh serve to start the Bokeh server and set up event handlers with .on_change (or for some widgets, .on_click).

I couldn't really find that much on running jupyter notebook and bokeh server, but this issue seems to discuss that possibility. It also mentions bokeh.application but I've never used that, so no idea how that works.

You also use additionally a custom js callback, which calls into the jupyter kernel and tries to execute update_plot(value), but you never defined such a function, so it does nothing.

Then you need a method to push the data to the output. I guess bokeh server can somehow do that nativly, for jupyter notebooks without the bokeh server push_notebook seems to be the solution. Note that you need show(..., notebook_handle=True) to be able to push.

Solution 1 use the bokeh server

Sliders and others widgets automatically sync their state back to python, so you can use slider.on_change. You don't need the CustomJS. Data flow should look as following:

python script -> bokeh server -> html -> userinput -> bokeh server -> python callbacks -> bokeh server updates plots

Solution 2 use bokeh sliders but sync via CustomJS

If you don't want to run a seperate process you can use the jupyter kernel to execute code in your python notebook. Dataflow:

jupyter notebook -> html -> user input -> customjs -> jupyter kernel -> python callbacks -> push_notebook to update plots

output_notebook()

N = 300

source = ColumnDataSource(data={'x':random(N), 'y':random(N)}) 

plot = figure(plot_width=950, plot_height=400) 

plot.circle(x='x', y='y', source=source)

callback = CustomJS(code=""" 
if (IPython.notebook.kernel !== undefined) {
    var kernel = IPython.notebook.kernel;
    cmd = "update_plot(" + cb_obj.value + ")";
    kernel.execute(cmd, {}, {})}; 
""")

slider = Slider(start=100, end=1000, value=N, step=10, callback=callback)

# must have the same name as the function that the CustomJS tries to call
def update_plot(N):
    source.data={'x':random(N), 'y':random(N)}
    # push notebooks to update plots
    push_notebook()

layout = column(slider, plot) 
# notebook_handle must be true, otherwise push_notebook will not work
h1 = show(layout, notebook_handle=True)

Solution 3 use ipywidgets

If you are not married to the bokeh widgets you can use the ipywidgets which are designed for interactivity in the jupyter notebook. The data flow is as following:

jupyter notebook -> html -> user input -> ipywidgets sync automatically -> python callbacks -> push_notebook

I use here interact but the other widgets should work as expected.

from ipywidgets import interact

output_notebook()

N = 300

source = ColumnDataSource(data={'x':random(N), 'y':random(N)}) 

plot = figure(plot_width=950, plot_height=400) 

plot.circle(x='x', y='y', source=source)

def update_plot(v):
    N = v
    print(N)
    source.data={'x':random(N), 'y':random(N)}
    # push changed plots to the frontend
    push_notebook()

# notebook_handle must be true so that push_notebook works
show(plot, notebook_handle=True)

Note that you need to install ipywidgets properly, which inlcudes calling jupyter nbextension enable --py --sys-prefix widgetsnbextension if you are not using conda. For details see the documentation

Sign up to request clarification or add additional context in comments.

2 Comments

Solution 2 works perfectly. Thank you for helping me and for the thorough explanation. Very kind of you. More for my own knowledge, but does Solution 1 have any pros and cons over Solution 2 (or vice versa)? I suppose this answer is subjective and dependant on what I want to achieve so if you could share any personal opinions or best practice approaches, that would be great. I tried Solution 3 prior to asking the question, but I felt it didn't offer as much flexibility if I were to starting building Bokeh Dashboards.
The CustomJS ofcourse only works when ipython is there, so if you want to move to a "pure webapplication" bokeh server is probably the way to go. I never tried the bokeh server, but if it really is as simply as in alex answer I would definately try that. It really depends on what you want to do.
3

I suppose your question relates to the server although you have both a CustomJS and a server callback.

I am not familiar with the previous way of doing bokeh server in notebook (push_notebook). The new way would be like this: you wrap your code in a function taking one parameter (a document) and your call to add_layout is made on that document. Then you build an app with that function and show it.

This gives:

from bokeh.models import ColumnDataSource, Slider
from bokeh.layouts import column
from bokeh.plotting import figure, show, output_notebook
from numpy.random import random
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler

output_notebook()

def modify_doc(doc):
    N = 300

    source = ColumnDataSource(data={'x':random(N), 'y':random(N)}) 

    plot = figure(plot_width=950, plot_height=400) 

    plot.circle(x='x', y='y', source=source)

    slider = Slider(start=100, end=1000, value=N, step=10)

    def callback(attr, old, new):
        N = new  # but slider.value would also work
        source.data={'x': random(N), 'y': random(N)}

    slider.on_change('value', callback)

    layout = column(slider, plot) 

    doc.add_root(layout)

app = Application(FunctionHandler(modify_doc))
show(app, notebook_url="localhost:8888")

4 Comments

I couldn't really find much information about that new way in bokeh. Is that already documented in a userguide or is it currently only in the API documentation? Does this also work in a jupyter notebook or only with the bokeh server?
This example runs in the notebook and basically launch a server process (or thread, not sure) that serves the app. As far as I know, this is not fully documented.(in particular in the User Guide)
Correction: actually it is referenced here with an example
First and foremost, thank you to you both for taking the time to help me, it's much appreciated. Both of those documents Alex are really helpful! Will be reading them tonight to get my head around building these applications.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.