Access data from bokeh widgets in a jupyter notebook - python

I want to use a text input widget in a jupyter notebook with autocompletion. I therefore used AutocompleteInput() from bokeh.models.widgets.inputs.
from bokeh.models.widgets.inputs import AutocompleteInput
from bokeh.io import output_notebook
from bokeh.plotting import show
output_notebook()
txt_input = AutocompleteInput(completions=['val1', 'val2'])
show(txt_input)
Displaying the widget and autocompletion works fine, but how can I access the value of the input widget upon change? txt_input.value only returns the default value (an empty string).

As of Bokeh 0.12.3, fuller integration of Bokeh widgets in the Jupyter notebook is still an open issue.
However, there are some workarounds, though they may be considered somewhat clunky. Here is a CustomJS callback you can pass to the widget that will set the value of a python value:
from bokeh.models import CustomJS
callback = CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
var kernel = IPython.notebook.kernel;
cmd = "widget_value = '" + cb_obj.value + "'";
kernel.execute(cmd, {}, {});
}
""")
The result looks like:
The value of the cmd variable in the CustomJS code is string of python code that will be executed in your currently running Jupyter kernel. If you need to call some python function, e.g., you could do that too.

Related

Bokeh TapTool running custom JS and tooltips

I have an application in Bokeh showing a map with different sets of circles. I want to be able to use the TapTool to show a small display when a circle is tapped, similar to the one shown with HoverTool, running JS code and using a custom HTML template. I found a solution in the answer to Fixed HoverTool TOOLTIPS when taping an element of a Bokeh plot, whose output is below
However, its behavior is not as expected. Instead of having the information displayed next to the selected circle with the TapTool, like HoverTool does, the information is displayed to the right of the plot, as it can be seen here .
I know there is a very good explanation for this, like the Bokeh version in use (I tried 1.0.4, 1.4.0 and 2.0.0, with the same output) or some other configuration issue, but I cannot find it. I have also tried different browsers, just in case, but the output is the same.
The issue is that that Div ends up being wrapped in another div that's shifted to the right because it belongs to the same Row as the main plot.
Here's an updated segment of the code that works with Bokeh 2.0.0:
div = Div(text='<div id="tooltip"></div>')
code = ''' if (cb_data.source.selected.indices.length > 0){
const selected_index = cb_data.source.selected.indices[0];
const tooltip = document.getElementById("tooltip");
const tooltip_wrapper = tooltip.parentElement.parentElement;
if (tooltip_wrapper.className !== 'bk')
throw new Error('Unable to find the correct tooltip wrapper element');
tooltip_wrapper.style.left = Number(cb_data.geometries.sx) + Number(20) + 'px';
tooltip_wrapper.style.top = Number(cb_data.geometries.sy) - Number(20) + 'px';
tooltip.style.display = 'block';
tp = tp.replace('#imgs', cb_data.source.data.imgs[selected_index]);
tp = tp.replace('#desc', cb_data.source.data.desc[selected_index]);
tp = tp.replace('#fonts{safe}', cb_data.source.data.fonts[selected_index]);
tp = tp.replace('$index', selected_index);
tp = tp.replace('$x', Math.round(cb_data.geometries.x));
tp = tp.replace('$y', Math.round(cb_data.geometries.y));
tooltip.innerHTML = tp;
} '''
p.select(TapTool).callback = CustomJS(args={'circles': circles, 'tp': TOOLTIPS}, code=code)

How do I not display a GlyphRenderer table when plotting bokeh?

Sorry if this is a basic question, but I haven't been able to find an answer in the bokeh documentation. I want to be able to plot a bokeh plot without the long GlyphRenderer list displaying.
I have tried saving the p.hexbin line to a variable called 'test'. However, this new 'test' variable is being saved as a tuple and can no longer be used with the 'show()' function to display a bokeh plot. The example code I am using here is straight from the bokeh documentation site.
import numpy as np
from bokeh.models import HoverTool
from bokeh.plotting import figure, show
x = 2 + 2*np.random.standard_normal(500)
y = 2 + 2*np.random.standard_normal(500)
p = figure(match_aspect=True, tools="wheel_zoom,reset")
p.background_fill_color = '#440154'
p.grid.visible = False
p.hexbin(x, y, size=0.5, hover_color="pink", hover_alpha=0.8)
hover = HoverTool(tooltips=[("count", "#c"), ("(q,r)", "(#q, #r)")])
p.add_tools(hover)
show(p)
I only want the hexbin plot to display when I run the code, not the Glyph tuple.
I have tried saving the p.hexbin line to a variable called 'test'. However, this new 'test' variable is being saved as a tuple and can no longer be used with the 'show()' function to display a bokeh plot.
Printing outputs is standard Python behavior, there is nothing we can do about that. The function returns a list, so Python will print a list. The only thing to suppress that behavior, as you have noted, is to assign the output to a variable. However, since you don't care about its value, it can/should be ignored. There is no reason to pass it to show, you should continue to call show, on p, exactly the way you have been without any change:
rs = p.hexbin(x, y, size=0.5, hover_color="pink", hover_alpha=0.8)
show(p)

Interactive Selection on Jupyter Notebook through Select Widget raise "Models must be owned by only a single document" error

How can I create an interactive bar plot with bokeh through a selection widget on Jupyter Notebook?
Why the Notebook raise the following error:
"RuntimeError: Models must be owned by only a single document, Title(id='1044', ...) is already in a doc
ERROR:tornado.access:500 GET /autoload.js?bokeh-autoload-element=1002&bokeh-absolute-url=http://localhost:54277&resources=none (::1) 117.01ms"
I read carefully this example from github and this similar situation from a Google Bokeh Group, in the latter they run a bokeh server not a the jupyter kernel
output_notebook()
dct={'Date' : ["2018-01-07", "2018-01-12", "2018-01-13", "2018-01-14", "2018-01-20", "2018-01-24"],'Activity' : ['A','B','A','B','A','B'],'Count' : [1, 2, 5, 3, 7, 1]}
df=pd.DataFrame(dct)
activity_list=df['Activity'].unique().tolist().copy()
activity_selected='A'
def modify_doc(doc):
def make_plot(cdf):
plot = figure()
plot.vbar(x=cdf.Date, top=cdf.Count, width=0.9)
push_notebook()
show(plot, notebook_handle = True)
return plot
def update_plot(attr, old, new):
activity = select.value
sdf = df.copy()
sdf = sdf[sdf['Activity'] == activity]
layout.children[0] = make_plot(sdf)
select = Select(title='Select Activity', value=activity_selected, options=activity_list)
select.on_change('value', update_plot)
p=make_plot(df)
layout=column(select, p)
doc.add_root(layout)
show(modify_doc)
What I expect is something like this in the snapshoot:
I am using Bokeh 1.0.4
It's possible we need to make some documentation improvements, because there are several parts of your code that do not make sense.
push_notebook is complementary to showing a real Bokeh server app (i.e. passing modify_doc to show). I can't think of any situation where it would make sense to use them together. The Bokeh server app capability is a strict superset of push_notebook, so since you are already making a Bokeh server app, you should just update everything in the standard way.
show should never be called inside the Bokeh server app code (in this case, inside modify_doc) This is in fact the proximate cause of the exception you are getting. You should
You should not make a new plot every time! The entire purpose of both push_notebook and the Bokeh server is that updates can happen efficiently without making a whole new plot every time. You should be updating the data and attributes of the existing plot.
There is a complete wokring example of a Bokeh server app in a notebook here:
https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
You should study and emulate that as it represents best practice.
The correct code for selecting the plot through the Select widget is:
activity_list=df['Activity'].unique().tolist().copy()
df['Date']=pd.to_datetime(df['Date'])
activity_selected='A'
def modify_doc(doc):
df_r=df.copy()
source = ColumnDataSource(data=df_r)
plot=figure(title='Daily Hours',x_axis_type="datetime")
plot.vbar(x="Date", top="Count",source=source, width=4)
def update_plot(attr, old, new):
activity = select.value
data = df_r[df_r['Activity'] == activity]
source.data = ColumnDataSource(data=data).data
select = Select(title='Select Activity', value=activity_selected, options=activity_list)
select.on_change('value', update_plot)
layout=column(select, plot)
doc.add_root(layout)
show(modify_doc)
And this will be the output that you see:

Bokeh | Jupyter Notebook | Python | Plot Not Showing

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
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
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")

How to sort key names in ipywidgets interactive widgets (slider)?

I am trying to make interactive sliders with ipywidgets on jupyter notebook to change the data of a plot when a user changes a slider Widget, which is simple and we can find example codes easily. The problem that I have is twofold: 1) when there are many parameters (= variables,sliders) in my function to be displayed, sliders are vertically arranged so that it is getting hard to control them without scrolling the jupyter page. Is there any way to arrange sliders as I wish like m by n grid? 2) To pass a large number of integer-/float-valued sliders, I made a single dictionary to be passed to the function interactive. Here, the key (=slider/variable/parameter) names are displayed seemingly in random order. I tried to pass the dictionary after sorting by the key names beforehand, but it does not still resolve the issue.
I'd appreciate it if you can share any ideas.
def myfun(**var_dict):
v = [value for value in var_dict.values()]
return np.sum(v)
var_dict = {'var1':1,'var2':2,'var3':3,'var4':4}
w = interactive(myfun,**var_dict)
display(w)
ipywidgets interactive sliders
You will not be able to solve this using **kwargs. As stated in PEP468
"The only reason they [keyword arguments] don't work is because the interpreter throws that ordering information away."
"Starting in version 3.5 Python will preserve the order of keyword arguments as passed to a function"
So if you want to get this behavior you should either:
name your arguments when you use 'interactive':
from ipywidgets import interactive
from IPython.display import display
import numpy as np
def myfun(**kwargs):
return np.sum(list(kwargs.itervalues()))
w=interactive(myfun,var1=1,var2=2,var3=3,var4=4)
display(w)
or, if you really want to use a dict, as far as I know, the best is to build the widgets yourself, without the use of 'interactive'.
You could do this this way:
from IPython.display import display,clear_output
from ipywidgets import widgets
from collections import OrderedDict
var_dict = OrderedDict([('var1',1),('var2',2),('var3',3),('var4',4)])
def myfct(change,list_widgets):
clear_output()
print np.sum([widget.value for widget in list_widgets])
list_widgets=[]
# create the widgets
for name,value in var_dict.iteritems():
list_widgets.append(widgets.IntSlider(value=value,min=value-2,max=value+2,description=name))
# link the widgets with the function
for widget in list_widgets:
widget.observe(lambda change:myfct(change,list_widgets),names='value',type='change')
# group the widgets into a FlexBox
w = widgets.VBox(children=list_widgets)
# display the widgets
display(w)
Enjoy :-)

Categories