Able to use widget but not plot in the same function (bokeh) - python

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

Related

Python Bokeh 2.0.1 - a JS callback for UndoTool?

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)

How to get a list of Bokeh widget events and attributes (which can be used to trigger a Python callback)

The real (general) question
I am new to Bokeh and I am trying to build a plot which can be dynamically updated based on input provided by a widget. However, usage of Python callbacks is not thoroughly documented for most widgets and therefore I'm stuck.
How can I know which widget method I should use to attach my callback? I can guess the available choices by probing the widgets attributes in an interactive console, but that's not elegant and I'm sure it's written somewhere in the documentation.
Provided that I would know about the method to use (e.g. on_event or on_change), I still have to figure out its signature and arguments. For instance, if I'm using on_change, which widget attributes can I monitor?
Once I know which attribute I can monitor, how can I know the data structure which will be yielded by the event?
Some more context and the (not-as-useful) specific question
Here is an appropriate example. I am using a notebook-embedded server like in this example. As an exercise, I would like to replace the slider with a DataTable with arbitrary values. Here is the code I currently have:
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, DataTable
from bokeh.plotting import figure
from bokeh.io import show, output_notebook
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature
output_notebook()
def modify_doc(doc):
df = sea_surface_temperature.copy()
source = ColumnDataSource(data=df)
source_table = ColumnDataSource(data={"alpha": [s for s in "abcdefgh"],
"num": list(range(8))})
plot = figure(x_axis_type='datetime', y_range=(0, 25),
y_axis_label='Temperature (Celsius)',
title="Sea Surface Temperature at 43.18, -70.43")
plot.line('time', 'temperature', source=source)
def callback(attr, old, new):
# This is the old callback from the example. What is "new" when I use
# a table widget?
if new == 0:
data = df
else:
data = df.rolling('{0}D'.format(new)).mean()
source.data = ColumnDataSource(data=data).data
table = DataTable(source=source_table,
columns=[TableColumn(field="alpha", title="Alpha"),
TableColumn(field="num", title="Num")])
# How can I attach a callback to table so that the plot gets updated
# with the "num" value when I select a row?
# table.on_change("some_attribute", callback)
doc.add_root(column(table, plot))
show(modify_doc)
This answer was given for Bokeh v1.0.4 and may not be compliant with the latest documentation
JavaScript callbacks and Python callbacks, are very powerful tools in Bokeh and can be attached to any Bokeh model element. Additionally you can extend Bokeh functionality by writing your own extensions with TypeScript (eventually compiled to JS)
JS callbacks can be added using either of both methods:
Model.js_on_event('event', callback)
Model.js_on_change('attr', callback)
Python callbacks are mainly used for widgets:
Widget.on_event('event, onevent_handler)
Widget.on_change('attr', onchange_handler)
Widget.on_click(onclick_handler)
The exact function signature for event handlers very per widget and can be:
onevent_handler(event)
onchange_handler(attr, old, new)
onclick_handler(new)
onclick_handler()
The attr can be any widget class (or it's base class) attribute. Therefore you need always to consult the Bokeh reference pages. Also expanding the JSON Prototype helps to find out which attributes are supported e.g. looking at Div we cannot see directly the id, name, style or text attributes which come from its base classes. However, all of these attributes are present in the Div's JSON Prototype and hence are supported by Div:
{
"css_classes": [],
"disabled": false,
"height": null,
"id": "32025",
"js_event_callbacks": {},
"js_property_callbacks": {},
"name": null,
"render_as_text": false,
"sizing_mode": "fixed",
"style": {},
"subscribed_events": [],
"tags": [],
"text": "",
"width": null
}
Coming back to your question: Many times you can achieve the same result using different approaches.
To my knowledge, there is no nice method that lists all supported events per widget but reading documentation and digging into the base classes helps a lot.
Using methods described above it is possible to check which widget attributes you can use in your callbacks. When it comes to events I advice you to look at and explore the bokeh.events class in your IDE. You can find there extended description for every event. In time it will come naturally when using your programmer's intuition to select the right event that your widget supports (so no button_click for Plot and no pan event for Button but the other way around).
Decision to which widget (model element) attach the callback and which method to choose or to which event bound the callback is yours and depends mainly on: which user action should trigger your callback?
So you can have a JS callback attached to any widget (value change, slider move, etc...), any tool (TapTool, HoverTool, etc...), data_source (clicking on glyph), plot canvas (e.g. for clicks on area outside a glyph) or plot range (zoom or pan events), etc...
Basically you need to know that all Python objects have their equivalents in BokehJS so you can use them the same way in both domains (with some syntax differences, of course).
This documentation shows for example that ColumnDataSource has a "selected" property so for points you can inspect source.selected.indices and see which point on the plot are selected or like in your case: which table rows are selected. You can set a breakpoint in code in Python and also in the browser and inspect the Python or BokehJS data structures. It helps to set the environment variable BOKEH_MINIFIED to no either in you IDE (Run Configuration) or in Terminal (e.g. BOKEH_MINIFIED=no python3 main.py) when running your code. This will make debugging the BokehJS in the browser much easier.
And here is your code (slightly modified for "pure Bokeh" v1.0.4 as I don't have Jupiter Notebook installed)
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, DataTable, TableColumn
from bokeh.plotting import figure, curdoc
from bokeh.io import show, output_notebook
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature
# output_notebook()
def modify_doc(doc):
df = sea_surface_temperature.copy()
source = ColumnDataSource(data = df)
source_table = ColumnDataSource(data = {"alpha": [s for s in "abcdefgh"],
"num": list(range(8))})
plot = figure(x_axis_type = 'datetime', y_range = (0, 25),
y_axis_label = 'Temperature (Celsius)',
title = "Sea Surface Temperature at 43.18, -70.43")
plot.line('time', 'temperature', source = source)
def callback(attr, old, new): # here new is an array containing selected rows
if new == 0:
data = df
else:
data = df.rolling('{0}D'.format(new[0])).mean() # asuming one row is selected
source.data = ColumnDataSource(data = data).data
table = DataTable(source = source_table,
columns = [TableColumn(field = "alpha", title = "Alpha"),
TableColumn(field = "num", title = "Num")])
source_table.selected.on_change('indices', callback)
doc().add_root(column(table, plot))
modify_doc(curdoc)
# show(modify_doc)
Result:

How to link a multiselect widget to a datatable using bokeh in a jupyter notebook?

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.

Running Bokeh with interactive Widgets

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.

how to change data source from select bokeh

I am using Bokeh and Python 2.7
Im trying to update the Data Source to change the plot based on Select Box.
But I am not able to update the plot.
what am I doing wrong? or is there a better way?
Code:
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, output_file, show, output_notebook
from bokeh.models.widgets import Select
from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.io import output_file, show
from bokeh import models
import pandas as pd
d1 = dict(x= [10,4,6,4], y = [6,2,8,10])
d2 = dict(x= [23,12,50,30], y = [5,10,23,18,12])
source = ColumnDataSource(data=d1)
p = figure()
select = Select(title="Select d", options=['d1', 'd2'])
def update_plot(attrname, old, new):
if new == 'd1':
newSource = d1
if new == 'd2':
newSource = d2
source.data = newSource
p.line(x='x', y='y',source = source)
select.on_change('value', update_plot)
layout = column(row(select, width=400), p)
curdoc().add_root(layout)
show(layout)
You need to start bokeh with the bokeh server, like this:
bokeh serve myscript.py
And then open localhost:5006 in your browser.
If you start bokeh without the server then it just creates a static html file and there is no way you can either make the page call your functions (that's why you don't see the prints) or alter the page with your python code after the initial load. From the docs:
The architecture of Bokeh is such that high-level “model objects” (representing things like plots, ranges, axes, glyphs, etc.) are created in Python, and then converted to a JSON format that is consumed by the client library, BokehJS. [...] However, if it were possible to keep the “model objects” in python and in the browser in sync with one another, then more [you could also] respond to UI and tool events generated in a browser with computations or queries using the full power of python

Categories

Resources