bokeh with IPython Notebook widget: extra figures apppearing - python

Clicking the 'up' button in the following code produces a sequence of figures rather than updating the existing figure. How does one update an existing figure?
from IPython.html import widgets
import bokeh.plotting as bp
from IPython.display import display
from numpy.random import randn
bp.output_notebook()
m = 1000
n = 1000
df = pandas.DataFrame(randn(m, n)).cumsum()
bp.hold()
bp.figure(figsize=(4, 8))
def doplot(i):
bp.line(df.index, df.icol(i), color='red', legend='here')
bp.show()
class A:
def __init__(self):
self.i = 0
self.doplot()
def incr(self, something):
self.i += 1
print(self.i)
self.doplot()
def decr(self, something):
self.i -= 1
self.doplot()
def doplot(self):
doplot(self.i)
a = A()
button = widgets.ButtonWidget(description='up')
button.on_click(a.incr)
buttond = widgets.ButtonWidget(description='down')
buttond.on_click(a.decr)
display(button)
display(buttond)

I actually got it to work with the ipywidgets function interact
This way everytime you interact with a widget the bokeh plot will be reloaded instead of a new one being appended to the existing one.
Here a minimal example, with minor updates to make it work with the new versions:
import ipywidgets
import bokeh.plotting as bp
from IPython.display import display
from numpy.random import randn
import pandas
bp.output_notebook()
m = 1000
n = 1000
df = pandas.DataFrame(randn(m, n)).cumsum()
def create_plot(swapp_axis):
fig = bp.figure()
if swapp_axis:
fig.scatter(df.icol(0), df.index)
else:
fig.scatter(df.index, df.icol(0))
bp.show(fig)
swapp_axis = ipywidgets.Checkbox(
description='swapp_axis',
value=False)
ipywidgets.interact( create_plot, swapp_axis=swapp_axis)
p.s.
I see this an old answer. If you found another solution in the meantime, please let me know.

Related

show progress of a nested function triggered by ipywidget

I have an input field, button and output placed in a Jupyter notebook using a function. On the button there is an on_click that will trigger a function that will plot a graph in the output. However, this function is using another function to generate the data for the graph. Now I want to show the progress of this 2nd function in the output. How would I go about doing this?
I looked into using tqdm, but then I just get the initial progress bar in the log (which doesn't update), not in the notebook cell
from random import randint
from matplotlib import pyplot as plt
from tqdm.notebook import tqdm
from ipywidgets import Layout, Button, Box, FloatText, Label, Output
def form_plot_simulation():
button = Button(
description='Plot simulation results',
disabled=False,
tooltip='Plot simulation results')
global simulations_field
simulations_field = FloatText(value='10')
global out_plot_sim
out_plot_sim = Output()
button.on_click(plot_data)
form_items = [
Box([simulations_field]),
Box([button]),
Box([out_plot_sim])
]
form_plot_sim = Box(form_items, layout=Layout(
display='flex',
flex_flow='column',
border='solid 2px',
align_items='stretch',
width='600px'))
return form_plot_sim
def plot_data(b):
simulations = int(simulations_field.value)
data = simulate_test(simulations)
plt.figure(figsize=(12,8))
plt.plot(data)
with out_plot_sim:
out_plot_sim.clear_output()
plt.show()
def simulate_test(simulations):
data = []
for i in tqdm(range(0,simulations)):
datapoint = randint(0,10)
data.append(datapoint)
return data
The cell in the notebook just has this code to display the form:
display(form_plot_simulation())
EDIT: Note: I hate the usage of global, but didn't know how to pass the field value on in the functions.
I don't have tqdm here, but you can adapt your example. I used a functools partial to bundle another argument to the on_click function.
from random import randint
from matplotlib import pyplot as plt
# from tqdm.notebook import tqdm
from ipywidgets import Layout, Button, Box, FloatText, Label, Output
import time
import functools
def form_plot_simulation():
button = Button(
description='Plot simulation results',
disabled=False,
tooltip='Plot simulation results')
global simulations_field
simulations_field = FloatText(value='10')
global out_plot_sim
out_plot_sim = Output()
output_progress = Output(height='20px')
button.on_click(functools.partial(plot_data, output_progress))
form_items = [
simulations_field,
button,
output_progress,
out_plot_sim,
]
form_plot_sim = Box(form_items, layout=Layout(
display='flex',
flex_flow='column',
border='solid 2px',
align_items='stretch',
width='600px'))
return form_plot_sim
def plot_data(output_widget, b):
simulations = int(simulations_field.value)
data = simulate_test(simulations, output_widget)
plt.figure(figsize=(12,8))
plt.plot(data)
with out_plot_sim:
out_plot_sim.clear_output()
plt.show()
def simulate_test(simulations, output_widget):
data = []
for i in range(0,simulations):
datapoint = randint(0,10)
data.append(datapoint)
time.sleep(0.1)
with output_widget:
output_widget.clear_output()
print(i)
return data

Ipywidgets observe method on interactive instead of widget

Both ipython widgets and interactive objects have observe() methods. (See the results of the print statements.)
With the following example, I can confirm the actions of the observe() method on a slider widget but not on the interactive (ie) object.
Q: Is there any way to use the interactive's observe method or I have to call separate observe() method on it's all widget components ? If so, why ?
Expected behavior: Printing 'ie change observed' after changing inp1,
from IPython.display import display
import ipywidgets as widgets
int_range0_slider = widgets.IntSlider()
int_range1_slider = widgets.IntSlider()
output = widgets.Output()
def interactive_function(inp0,inp1):
with output:
print('ie changed. int_range0_slider: '+str(inp0)+' int_range1_slider: '+str(inp1))
return
def report_int_range0_change(change):
with output:
print('int_range0 change observed'+str(change))
return
def report_ie_change(change):
with output:
print('ie change observed'+str(change))
return
ie = widgets.interactive(interactive_function, inp0=int_range0_slider,inp1=int_range1_slider)
print(int_range0_slider.observe)
print(ie.observe)
int_range0_slider.observe(report_int_range0_change, names='value')
ie.observe(report_ie_change)
display(int_range0_slider,int_range1_slider,output)
I'm a newbie, any help on the correct usage would be appreciated.
Yeah, no you can't do that. The interactive object's observe will be for changes in children. So, no change there.
What you could do instead is put an observe on the children. Like this.
for child in ie.children:
child.observe(report_ie_change)
so, your code will look something like this:
from IPython.display import display
import ipywidgets as widgets
int_range0_slider = widgets.IntSlider()
int_range1_slider = widgets.IntSlider()
output = widgets.Output()
def interactive_function(inp0,inp1):
with output:
print('ie changed. int_range0_slider: '+str(inp0)+' int_range1_slider: '+str(inp1))
return
def report_int_range0_change(change):
with output:
print('int_range0 change observed'+str(change))
return
def report_ie_change(change):
with output:
print('ie change observed'+str(change))
return
ie = widgets.interactive(interactive_function, inp0=int_range0_slider,inp1=int_range1_slider)
# print(int_range0_slider.observe)
# print(ie.observe)
# int_range0_slider.observe(report_int_range0_change, names='value')
for child in ie.children:
child.observe(report_ie_change)
display(int_range0_slider,int_range1_slider,output)

How to update a python bokeh plot as soon as data is available? [duplicate]

I want Bokeh to update periodically and arbitrarily when the results from a separate algorithm running in python returns results, not based on any input from the Bokeh interface.
I've tried various solutions but they all depend on a callback to a some UI event or a periodic callback as in the code below.
import numpy as np
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, Plot, LinearAxis, Grid
from bokeh.models.glyphs import MultiLine
from time import sleep
from random import randint
def getData(): # simulate data acquisition
# run slow algorith
sleep(randint(2,7)) #simulate slowness of algorithm
return dict(xs=np.random.rand(50, 2).tolist(), ys=np.random.rand(50, 2).tolist())
# init plot
source = ColumnDataSource(data=getData())
plot = Plot(
title=None, plot_width=600, plot_height=600,
min_border=0, toolbar_location=None)
glyph = MultiLine(xs="xs", ys="ys", line_color="#8073ac", line_width=0.1)
plot.add_glyph(source, glyph)
xaxis = LinearAxis()
plot.add_layout(xaxis, 'below')
yaxis = LinearAxis()
plot.add_layout(yaxis, 'left')
plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))
curdoc().add_root(plot)
# update plot
def update():
bokeh_source = getData()
source.stream(bokeh_source, rollover=50)
curdoc().add_periodic_callback(update, 100)
This does seem to work, but is this the best way to go about things? Rather than having Bokeh try to update every 100 milliseconds can I just push new data to it when it becomes available?
Thanks
You can use zmq and asyncio to do it. Here is the code for the bokeh server, it wait data in an async coroutine:
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, curdoc
from functools import partial
from tornado.ioloop import IOLoop
import zmq.asyncio
doc = curdoc()
context = zmq.asyncio.Context.instance()
socket = context.socket(zmq.SUB)
socket.connect("tcp://127.0.0.1:1234")
socket.setsockopt(zmq.SUBSCRIBE, b"")
def update(new_data):
source.stream(new_data, rollover=50)
async def loop():
while True:
new_data = await socket.recv_pyobj()
doc.add_next_tick_callback(partial(update, new_data))
source = ColumnDataSource(data=dict(x=[0], y=[0]))
plot = figure(height=300)
plot.line(x='x', y='y', source=source)
doc.add_root(plot)
IOLoop.current().spawn_callback(loop)
to send the data just run following code in another python process:
import time
import random
import zmq
context = zmq.Context.instance()
pub_socket = context.socket(zmq.PUB)
pub_socket.bind("tcp://127.0.0.1:1234")
t = 0
y = 0
while True:
time.sleep(1.0)
t += 1
y += random.normalvariate(0, 1)
pub_socket.send_pyobj(dict(x=[t], y=[y]))

How to append coordinates from Bokeh Tap event to a python object?

In the example below, I am trying to get the x and y coordinates that appear in the Div next to the plot when the bokeh plot is Tapped to be appended to the data dictionary coordList in their respective list.
import numpy as np
from bokeh.io import show, output_notebook
from bokeh.plotting import figure
from bokeh.models import CustomJS, Div
from bokeh.layouts import column, row
from bokeh.events import Tap
coordList = dict(x=[], y=[])
output_notebook()
def display_event(div, attributes=[], style = 'float:left;clear:left;font_size=10pt'):
"Build a suitable CustomJS to display the current event in the div model."
return CustomJS(args=dict(div=div), code="""
var attrs = %s; var args = [];
for (var i = 0; i<attrs.length; i++) {
args.push(Number(cb_obj[attrs[i]]).toFixed(2));
}
var line = "<span style=%r>(" + args.join(", ") + ")</span>\\n";
var text = div.text.concat(line);
var lines = text.split("\\n")
if (lines.length > 35)
lines.shift();
div.text = lines.join("\\n");
""" % (attributes, style))
x = np.random.random(size=4000) * 100
y = np.random.random(size=4000) * 100
radii = np.random.random(size=4000) * 1.5
colors = ["#%02x%02x%02x" % (int(r), int(g), 150) for r, g in zip(50+2*x, 30+2*y)]
p = figure(tools="pan,wheel_zoom,zoom_in,zoom_out,reset")
p.scatter(x, y, radius=np.random.random(size=4000) * 1.5,
fill_color=colors, fill_alpha=0.6, line_color=None)
div = Div(width=400, height=p.plot_height)
layout = row(p, div)
point_attributes = ['x', 'y']
p.js_on_event(Tap, display_event(div, attributes=point_attributes))
show(layout)
I'm not sure how the coordinates are saved and how to access them and append them to the lists.
There is no way to append to coordinates to a python object with code like above, because that code is generating standalone output (i.e. it is using "show"). Standalone output is pure static HTML and Bokeh JSON that is sent to browser, without any sort of connection to any Python process. If you want to connect Bokeh visualizations to a real running Python process, that is what the Bokeh server is for.
If you run a Bokeh server application, then you can use on_event with a real python callback to run whatever python code you want with the Tap even values:
def callback(event):
# use event['x'], event['y'], event['sx'], event['sy']
p.on_event(Tap, callback)

How do I work with images in Bokeh (Python)

For example you can plot an image in matplotlib using this code:
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img=mpimg.imread('image.png')
plt.imshow(img)
Is something like this possible with Bokeh(0.10)?
You can use the ImageURL glyph (image_url plot method)to load images locally or from the web.
from bokeh.plotting import figure, show, output_file
output_file('image.html')
p = figure(x_range=(0,1), y_range=(0,1))
p.image_url(url=['tree.png'], x=0, y=1, w=0.8, h=0.6)
## could also leave out keywords
# p.image_url(['tree.png'], 0, 1, 0.8, h=0.6)
show(p)
One gotcha - if you graph only an image (and no other data), you'll have to explicitly set the plot ranges.
Here's the docs:
http://docs.bokeh.org/en/latest/docs/reference/models/glyphs.html#bokeh.models.glyphs.ImageURL
The earlier answer was helpful. However, I wanted an image only option without any additional object. So, adding the answer for Bokeh version 0.12.0 and removed all the grids, axes and toolbar.
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, Range1d
bosch_logo = "static/tree.jpg"
logo_src = ColumnDataSource(dict(url = [bosch_logo]))
page_logo = figure(plot_width = 500, plot_height = 500, title="")
page_logo.toolbar.logo = None
page_logo.toolbar_location = None
page_logo.x_range=Range1d(start=0, end=1)
page_logo.y_range=Range1d(start=0, end=1)
page_logo.xaxis.visible = None
page_logo.yaxis.visible = None
page_logo.xgrid.grid_line_color = None
page_logo.ygrid.grid_line_color = None
page_logo.image_url(url='url', x=0.05, y = 0.85, h=0.7, w=0.9, source=logo_src)
page_logo.outline_line_alpha = 0
curdoc().add_root(page_logo)
Another option is to display the image in a div.:
from bokeh.io import output_notebook, show
from bokeh.models.widgets import Div
output_notebook()
div_image = Div(text="""<img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/7.png" alt="div_image">""", width=150, height=150)
show(div_image)
ImageURL can't get updated dynamically with a callback. However, using a div, you can do so by treating the div_image.text as a regular Python string, for example:
from ipywidgets import interact
from bokeh.io import output_notebook, show, push_notebook
from bokeh.models.widgets import Div
output_notebook()
div_image = Div(text="""<img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png" alt="div_image">""", width=100, height=100)
def update(pokemon_number=1):
div_image.text = """<img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png" alt="div_image">""".format(pokemon_number)
push_notebook()
show(div_image, notebook_handle=True)
interact(update, pokemon_number=[1, 4, 7])
Of course, the image source can also point to a local file.
(Tested in Python 3.7.3 and bokeh 1.2.0)
Running this example using bokeh serve is a bit more tricky. I suggest to setup working directory properly:
server_folder/
+main.py
+static/
+logo.png
.. and run bokeh serve command from directory ABOVE server_folder
bokeh serve server_folder --show
Then this code works for me
#main.py file
from bokeh.plotting import figure, curdoc
x_range = (-20,-10) # could be anything - e.g.(0,1)
y_range = (20,30)
p = figure(x_range=x_range, y_range=y_range)
#img_path = 'https://docs.bokeh.org/en/latest/_static/images/logo.png'
img_path = 'server_folder/static/logo.png'
p.image_url(url=[img_path],x=x_range[0],y=y_range[1],w=x_range[1]-x_range[0],h=y_range[1]-y_range[0])
doc = curdoc()
doc.add_root(p)
Here is a simple example that works, almost the same simple format as you requested:
from PIL import Image
import numpy as np
from bokeh.plotting import figure, output_notebook, show
output_notebook()
#load image
im = Image.open('Segment_image.png') # just replace any image that you want here
p = figure()
imarray = np.array(im.convert("RGBA"))
plotted_image = p.image_rgba(image=[imarray.view("uint32").reshape(imarray.shape[:2])], x=0, y=0, dw=imarray.shape[0], dh=imarray.shape[1])
show(p)

Categories