I've recently started working with Bokeh 2.0.1 on Anaconda. My overarching goal is to visualize some datasets as self-contained html files, with Bokeh menu tools.
In addition to existing tools, I've added a functionality where DoubleTap places a label on the plot at the tap's coordinates. It works as planned, however, I want this operation to be undoable via the standard Bokeh UndoTool. I tried adding a CustomJS callback to the UndoTool instance of the figure in question. However, I can't get this to work - when I click on the Undo button in the figure, the added label doesn't disappear. Apparently, the "undoing" callback doesn't get triggered.
I know that the "undoing" callback is not a problem, because I've also bound it to a button and it works as planned.
The concept code is:
from bokeh.plotting import figure
from bokeh.events import MenuItemClick, ButtonClick
from bokeh.models import CustomJS, Button
from bokeh.events import DoubleTap
add_label = CustomJS(--something--)
remove_label = CustomJS(--something else--)
f_h = figure(tools='undo')
f_h.js_on_event(DoubleTap, add_label) # Works as planned - adds a label on a double tap
loc_button = Button()
loc_button.js_on_event(ButtonClick, remove_label) # Also works as planned - removes the last added label
f_h.tools[0].js_on_event(MenuItemClick, remove_label) # Doesn't work - aside from the standard scaling undo behavior nothing happens
Thanks in advance,
P.V.
There's a reset event:
from bokeh.io import show
from bokeh.models import CustomJS
from bokeh.plotting import figure
p = figure(tools='reset')
p.circle(x=0, y=0)
p.js_on_event('reset', CustomJS(code="console.log('Resetting!')"))
show(p)
Related
I'm trying to create a webapp using bokeh. Mostly, it will consist of markdown text and some figures. Right now I'm stuck a getting voilà to show two bokeh figures side by side. Within the notebook everything runs smoothly, but in the voilà visualization I see errors like
Javascript Error: Error rendering Bokeh model: could not find #5db0eeb2-830f-4e00-b6fe-552a45536513 HTML tag
Now, if I try in classic jupyter notebook (within jupyter-lab Help -> Launch Classic Notebook), then it renders fine. However, when it is served from github, I get again javascript errors.
A MWE within jupyterlab but non-working in voilà is:
import numpy as np
import ipywidgets as widgets
import bokeh.plotting
from bokeh.io import output_notebook, show
from bokeh.models import ColumnDataSource, Line
output_notebook()
t = np.arange(10)
signal = np.arange(10)
f_1 = bokeh.plotting.figure(plot_width=400, plot_height=300, toolbar_location=None)
data_source_1 = ColumnDataSource(data=dict(t=t, signal=signal))
f_1.line(x='t', y='signal', source=data_source_1)
out_1 = widgets.Output()
with out_1:
show(f_1)
f_2 = bokeh.plotting.figure(plot_width=400, plot_height=300, toolbar_location=None)
data_source_2 = ColumnDataSource(data=dict(t=t, signal=2*signal))
f_2.line(x='t', y='signal', source=data_source_2)
out_2 = widgets.Output()
with out_2:
show(f_2)
widgets.HBox([out_1, out_2])
This is meant to be part of mini-lecture on analog modulations: the classic notebook version works fine, but when I try to use voilà-served version (click the Voilà button there or just go to this other link) I hit the same problem.
Any clue?
I'm attempting to connect a datatable with a multiselect widget in bokeh. I've searched around and gathered that I need to develop a function to update the data source for the data table, but I seem to have two problems.
I cannot seem to access the value of the multiselect object after I click it.
I cannot seem to push the change to the notebook after receiving the change.
Here's an example of my code:
import pandas as pd
from bokeh.io import push_notebook
from bokeh.plotting import show, output_notebook
from bokeh.layouts import row
from bokeh.models.widgets import MultiSelect, DataTable, TableColumn
from bokeh.models import ColumnDataSource
output_notebook()
df=pd.DataFrame({'year':[2000,2001,2000,2001,2000,2001,2000,2001],
'color':['red','blue','green','red','blue','green','red','blue'],
'value':[ 0,1,2,3,4,5,6,7]})
columns=[TableColumn(field=x, title=x) for x in df.columns]
source=ColumnDataSource(df)
data_table=DataTable(source=source,columns=columns)
years=[2000,2001,2000,2001,2000,2001,2000,2001]
## MultiSelect won't let me store an integer value, so I convert them to strings
multi=MultiSelect(title="Select a Year", value=['2000','2001'],options=[str(y) for y in set(years)])
def update(attr,old, new):
yr=multi.value
yr_vals=[int(y) for y in yr]
new_data=df[df.year.isin(yr_vals)]
source.data=new_data
push_notebook(handle=t)
multi.on_change('value',update)
t=show(row(multi,data_table),notebook_handle=True)
push_notebook is uni-directional. That is, you can only push changes from the IPython kernel, to the JavaScript front end. No changes from the UI are propagated back to the running Python kernel. In other words, on_change is not useful (without more work, see below) If you want that kind of interaction, there are a few options:
Use ipywidgets with push_notebook. Typically this involved the interact function to automatically generate a simple UI with callbacks that use push_notebook to update the plots, etc. based on the widget values. Just to be clear, this approach uses ipywidgets, which are not Bokeh built-in widgets. You can see a full example notebook here:
https://github.com/bokeh/bokeh/blob/master/examples/howto/notebook_comms/Jupyter%20Interactors.ipynb
Embed a Bokeh server application. The Bokeh server is what makes it possible for on_change callbacks on Bokeh widgets to function. Typically this involves making a function that defines the app (by specifying how a new document is created):
def modify_doc(doc):
df = sea_surface_temperature.copy()
source = ColumnDataSource(data=df)
plot = figure(x_axis_type='datetime', y_range=(0, 25))
plot.line('time', 'temperature', source=source)
def callback(attr, old, new):
if new == 0:
data = df
else:
data = df.rolling('{0}D'.format(new)).mean()
source.data = ColumnDataSource(data=data).data
slider = Slider(start=0, end=30, value=0, step=1, title="Smoothing by N Days")
slider.on_change('value', callback)
doc.add_root(column(slider, plot))
Then calling show on that function:
show(modify_doc)
A full example notebook is here:
https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
(Hacky option) some people have combined CustomJS callbacks with Jupyers JS function kernel.execute to propagate values back to the kernel.
I am trying to add certain callbacks to a circles which are plotted on bokeh plot. Each circle is associated with certain record from columndatasource. I want to access that record whenever corresponding circle is clicked. Is there any way to add callbacks to circles in bokeh?
How can i do it?
I am using following code
fig =figure(x_range=(-bound, bound), y_range=(-bound, bound),
plot_width=800, plot_height=500,output_backend="webgl")
fig.circle(x='longitude',y='latitude',size=2,source=source,fill_color="blue",
fill_alpha=1, line_color=None)
Then you want to add an on_change callback to the selected property of the data source. Here is a minimal example. As stated above, python callbacks require the Bokeh server (that is where python callbacks actually get run, since the browser knows nothing of python), so this must be run e.g. bokeh serve --show example.py (Or, if you are in a notebook, following the pattern in this example notebook).
# example.py
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
source = ColumnDataSource(data=dict(x=[1,2,3], y=[4,6,5]))
p = figure(title="select a circle", tools="tap")
p.circle('x', 'y', size=25, source=source)
def callback(attr, old, new):
# This uses syntax for Bokeh >= 0.13
print("Indices of selected circles: ", source.selected.indices)
source.selected.on_change('indices', callback)
curdoc().add_root(p)
I would like to run a Bokeh App with an interactive Widget but cannot get it fully working.
My code demo.py:
# imports
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Dropdown
from bokeh.plotting import figure
from bokeh.sampledata.iris import flowers
# Data
df = pd.DataFrame({'x': flowers['sepal_length'], 'y': flowers['sepal_width'], 'species': flowers['species']})
# Source
SPECIES = 'versicolor'
source = ColumnDataSource(df.loc[df.species == SPECIES])
# Create plots and widgets
plot = figure()
plot.circle(x= 'x', y='y', source=source)
menu = [("setosa", "setosa"), ("versicolor", "versicolor"), None, ("virginica", "virginica")]
dropdown = Dropdown(label="Dropdown species", button_type="warning", menu=menu)
# Add callback to widgets
def callback(attr, old, new):
SPECIES = dropdown.value
source.data=ColumnDataSource(df.loc[df.species == SPECIES])
dropdown.on_change('value', callback)
# Arrange plots and widgets in layouts
layout = column(dropdown, plot)
curdoc().add_root(layout)
When I run this app from the command line interface with bokeh serve --show demo.py, it returns an HTML-page with a plot. The dropdown seems to work, but the plot does not change when a value is selected from the dropdown.
Any suggestions how to fix this?
You are not assigning the correct value to source.data. The value needs to be a regular Python dict that maps column names to lists/arrays of data. There are a variety of ways to do that demonstrated in the docs and examples, but one good way is to use the from_df class method of CDS to generate the right kind of dict:
source.data = ColumnDataSource.from_df(df.loc[df.species == SPECIES])
That line makes your code work as expected.
As an FYI, your code generates an error in the server console output (as should be expected):
error handling message Message 'PATCH-DOC' (revision 1): ValueError("expected an element of ColumnData(String, Seq(Any)), got ColumnDataSource(id='44e09b5e-133b-4c1b-987b-cbf80b803401', ...)",)
As a gentle suggestion, it's always a good idea to include such errors in SO questions.
The code is divided into two classes - table and representation.I am using bokeh for plotting.
When I click on a widget, it creates a class table and takes me to object representation.get_dia(),which should give me a line and a paragraph.
Code snippet from the table
def update_on_button_click():
print(final_data)
rep=representation(final_data)
rep.get_dia()
get_dia() function -
def get_dia(self):
curdoc().clear()
from bokeh.models import Paragraph
p2 = Paragraph(text='Under Construction',width=200, height=100)
p1=figure()
p1.line([1,2,3],[1,2,3])
curdoc().add_root(row(p2,p1))
The function displays the paragraph in the browser but not the plot.
Is there any reason why this happens?
P.S The plot is visible, if I call it from the table function.
#from bokeh.plotting import figure --- adding this (even though I had added
#this at the starting itself)
def get_dia(self):
#curdoc().clear() --- And Removing this solves the problem
from bokeh.models import Paragraph
p2 = Paragraph(text='Under Construction',width=200, height=100)
p1=figure()
p1.line([1,2,3],[1,2,3])
curdoc().add_root(row(p2,p1))