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
Related
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 recently picked up learning bokeh and I am completely lost making callbacks work.
What I would like to do is update the source using the PointDrawTool. It does update the plot and the table, but apparently it does not update the renderer or the source. This has me seriously confused and I'd appreciate some help.
What I have working is as follows:
from bokeh.models.glyphs import Circle
from bokeh.plotting import figure, show, output_notebook, Column, Row
from bokeh import events
from bokeh.models import DataTable, TableColumn, PointDrawTool, ColumnDataSource, CustomJS
output_notebook()
p = figure(width = 400, height = 600)
source = ColumnDataSource({
'x': [38], 'y': [-12], 'color': ['red']
})
renderer = p.circle(x='x', y='y',
source=source,
color='color',
size=10)
columns = [TableColumn(field="x", title="x"),
TableColumn(field="y", title="y"),
TableColumn(field='color', title='color')]
table = DataTable(source=source, columns=columns, editable=True, height=200)
draw_tool = PointDrawTool(renderers=[renderer],
empty_value='red')
p.add_tools(draw_tool)
p.toolbar.active_tap = draw_tool
show(Row(p,table))
Using your method of rendering the chart (show) it isn't possible to update the source for the chart (unless you write custom JavaScript to do so). In order to achieve this you would need to use the Bokeh server, as described here.
Basically, put all your code in a file called 'main.py', and then save this in a folder with your project name. Then in the terminal run
bokeh serve --show project_name
I haven't used the PointDrawTool before, but if it's a widget, you'll also need to write functions to program how the source data should be updated, using the on_click or on_change methods described here.
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)
From the last days, I have been trying to use Bokeh to plot real-time data and display on a .html in order to be embeed in a webpage. I have sucessuly adapted one of the bokeh examples to my needs. I am using a buffer of 50 elements on the plot and I am noting the following behaviour:
1) In case I run the script and go to the browser the x_range fully adapts to incomming data and everything works correctly
2) If I click on "Refresh" on the browser the x_range stops to adapt to incoming data and freezes to the last value.
I tried to force the x_axis to initial and end values but the visualization behaves poorly.
I think I am not correctly understanding what does the "Refresh" hit impacts my code and how I can workaround this issue.
""" To view this example, first start a Bokeh server:
bokeh serve --allow-websocket-origin=localhost:8000
And then load the example into the Bokeh server by
running the script:
python animated.py
in this directory. Finally, start a simple web server
by running:
python -m SimpleHTTPServer (python 2)
or
python -m http.server (python 3)
in this directory. Navigate to
http://localhost:8000/animated.html
"""
from __future__ import print_function
import io
from numpy import pi, cos, sin, linspace, roll
from bokeh.client import push_session
from bokeh.embed import server_session
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource
fa = open('Accelerometer.txt', 'r')
source = ColumnDataSource(data=dict(x=[], y=[]))
fg = figure(width=250, plot_height=250, title="RT-Test")
fg.line(x='x', y='y', color="olive", source=source)
fg.x_range.follow = "end"
# Visualization scale and aesthetics
fg.xgrid.grid_line_color = None
fg.ygrid.grid_line_color = None
fg.background_fill_color = "snow"
# add the plot to curdoc
curdoc().add_root(fg)
# open a session which will keep our local doc in sync with server
session = push_session(curdoc())
html = """
<html>
<head></head>
<body>
%s
</body>
</html>
""" % server_session(fg, session_id=session.id, relative_urls=False)
with io.open("animated.html", mode='w+', encoding='utf-8') as f:
f.write(html)
print(__doc__)
def update():
line = fa.readline().split(',')
x = float(line[0])
y = float(line[1])
print(x, y)
# construct the new values for all columns, and pass to stream
new_data = dict(x=[x], y=[y])
source.stream(new_data, rollover=50)
curdoc().add_periodic_callback(update, 100)
session.loop_until_closed() # run forever
This kind of usage of the Bokeh server, with the actual code running in a separate process and calling session.loop_until_closed, is discouraged in the strongest terms. In the next release, all of the examples of this sort will be deleted, and mentions of this approach removed from the docs. This usage is inherently inferior in many ways, as outlined here, and I would say that demonstrating it so prominently for so long was a mistake on our part. It is occasionally useful for testing, but nothing else.
So what is the good/intended way to use the Bokeh server? The answer is to have a Bokeh app run in the Bokeh server itself, unlike the code above. This can be done in a variety of ways, but one common way os to wirte a simple script, then execute that script with
bokeh serve -show myapp.py
I don't have access to your "Accelerate.py" dataset, but a rough pass at updating your code would look like:
# myapp.py
from numpy import pi, cos, sin, linspace, roll
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource
fa = open('Accelerometer.txt', 'r')
source = ColumnDataSource(data=dict(x=[], y=[]))
fg = figure(width=250, plot_height=250, title="RT-Test")
fg.line(x='x', y='y', color="olive", source=source)
fg.x_range.follow = "end"
fg.xgrid.grid_line_color = None
fg.ygrid.grid_line_color = None
fg.background_fill_color = "snow"
curdoc().add_root(fg)
def update():
line = fa.readline().split(',')
x = float(line[0])
y = float(line[1])
# construct the new values for all columns, and pass to stream
new_data = dict(x=[x], y=[y])
source.stream(new_data, rollover=50)
curdoc().add_periodic_callback(update, 100)
Now if you run this script with the bokeh serve command, then any refresh will get you a brand new session of this app. It's also worth nothing that code written in this way is considerably simpler and shorter.
These kinds of apps can be embedded in Jupyter notebooks, Flask and other web-apps, or made into "regular" python scripts run with python instead of bokeh serve. For more information see Running a Bokeh Server in the User's Guide.
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.