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
Related
I am currently struggling trying to use the panel library in Python, in order to build an interactive dashboard to analyze and display CSV data. My current goal is to let the user enter an initial and a final date, which will be used to filter a DataFrame once a button is pressed. However, whenever I press the button, the on_click function is not completely executed before the script stops running. The code snippet is the following:
import panel as pn
pn.extension()
def acquire_data(dateBeginning, dateEnd):
eventDF = pd.read_csv('multi.csv')
eventDF['Date']= pd.to_datetime(eventDF['Date'])
dateDF = eventDF[eventDF.upvotes > 8]
print(eventDF)
def register_dates(event, save=True):
dateBeginning = date1Picker.value
dateEnd = date2Picker.value
if dateBeginning < dateEnd:
text = pn.widgets.StaticText(name='Static Text', value='A string')
spinner = pn.indicators.LoadingSpinner(width=50, height=50, value=True, color='info', bgcolor='light')
layout = pn.Column(text, spinner, align='center')
layout.app()
print('getting in')
acquire_data(dateBeginning, dateEnd)
print('getting out')
spinner.value = False
else:
print('Not working')
#pn.pane.Alert('## Alert\nThis is a warning!')
return save
date1Picker = pn.widgets.DatePicker(name='Date Initiale', margin=25)
date2Picker = pn.widgets.DatePicker(name='Date Finale', margin=25)
button = pn.widgets.Button(name="Analyse", button_type='primary', margin=(25, 0, 20, 200), width=200)
button.on_click(register_dates)
dateLayout = pn.Row(date1Picker, date2Picker)
layout = pn.Column(dateLayout, button, width=200, align='center')
layout.app()
I was also aiming at having the first layout be replaced by the one with the spinner and the text once the button is pressed, but I haven't found anything in the doc mentioning how to do so. If anyone could give me a hint regarding these issues, that would really help me!
In def acquire_data(dateBeginning, dateEnd):
pd.read_csv('multi.csv'), pd.to_datetime(eventDF['Date'])
For start, in this function I think you forgot to import panda and your app just crash.
add: import pandas as pd
Ex:
import panel as pn
import pandas as pd
Introduction
I am trying to make a small tool for classifying images using the ipywidgets in a Jupyter Notebook, but I am having some trouble aligning the classes and the images. Do you have any suggestion how to fix this.
What I did
import ipywidgets as widgets
from IPython.display import display
import glob
# My images
image_paths = glob.glob("./images/*.png")
# Display image
def display_image(path):
file = open(path, "rb")
image = file.read()
return widgets.Image(
value=image,
format='png',
width=700,
height=700,
)
# Dropdown
def create_dropdown():
return widgets.Dropdown(
options=["1","2","3","4","5","6","7","8","9","10"],
value='5',
description='Category:',
disabled=False
)
# Creating widgets
input_dropdown = create_dropdown()
button = widgets.Button(description="Submit")
output_image = widgets.Image()
output_image.value = display_image(image_paths[-1]).value
# Define function to bind value of the input to the output variable
def bind_input_to_output(sender):
image_path = image_paths[-1]
image_score = input_dropdown.value
next_image_path = image_paths.pop()
print(image_score, image_path)
output_image.value = display_image(next_image_path).value
# Tell the text input widget to call bind_input_to_output() on submit
button.on_click(bind_input_to_output)
# Displaying widgets
display(output_image, input_dropdown, button)
Results
With the above code I end up categorising the upcoming picture, but I really don't understand why. It seems the widgets does not update the image the first time I press the button.
def bind_input_to_output(sender):
image_path = image_paths.pop()
image_score = input_dropdown.value
next_image_path = image_paths[-1]
print(image_score, image_path)
output_image.value = display_image(next_image_path).value
pop first and give next filename at last item
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]))
I'm studying gmaps and I'm trying refresh gmap marker using widgets.button, but I cannot refresh map when I click in button.
Maybe is a simple question, but I'm trying it for hours and can't solve.
Follow my code.
from IPython.display import display
import ipywidgets as widgets
import gmaps
gmaps.configure(api_key='')
class AcledExplorer(object):
"""
Jupyter widget for exploring the ACLED dataset.
The user uses the slider to choose a year. This renders
a heatmap of civilian victims in that year.
"""
def __init__(self):
self.marker_locations = [(None, None)]
self._slider = None
self._slider2 = None
title_widget = widgets.HTML(
'<h3>MY TEST, my test</h3>'
'<h4>test1 ACLED project</h4>'
)
map_figure = self._render_map(-15.7934036, -47.8823172)
control = self._render_control()
self._container = widgets.VBox([title_widget, control, map_figure])
def render(self):
display(self._container)
def on_button_clicked(self, b):
latitude = self.FloatSlider1.value
longitude = self.FloatSlider2.value
print("Button clicked.")
self.markers = gmaps.marker_layer([(latitude, longitude)])
return self._container
def _render_control(self):
""" Render the widgets """
self.FloatSlider1 = widgets.FloatSlider(
value=-15.8,
min=-34,
max=4.5,
step=0.2,
description='Latitude:',
disabled=False,
continuous_update=False,
orientation='horizontal',
readout=True,
readout_format='.1f',
)
self.FloatSlider2 = widgets.FloatSlider(
value=-47.9,
min=-74,
max=-33,
step=0.2,
description='Longitude:',
disabled=False,
continuous_update=False,
orientation='horizontal',
readout=True,
readout_format='.1f',
)
self.button = widgets.Button(
description="Plot!"
)
self.button.on_click(self.on_button_clicked)
controls = widgets.VBox(
[self.FloatSlider1, self.FloatSlider2, self.button])
return controls
def _render_map(self, latitude, longitude):
""" Render the initial map """
self.marker_locations = [(latitude, longitude)]
brasilia_coordinates = (-15.7934036, -47.8823172)
fig = gmaps.figure(center=brasilia_coordinates, zoom_level=3)
self.markers = gmaps.marker_layer(self.marker_locations)
fig.add_layer(self.markers)
return fig
AcledExplorer().render()
I start creating widgets, after I link values from Sliders to button. I need refresh marker position when click in button.
In function on_button_click I can view that news values of latitude and longitude are being getting from slider bar, so I'm update self.marker, maybe my mistake stay here.
Problem with your code
In on_button_click, you are not actually updating the marker layer. You currently write:
self.markers = gmaps.marker_layer([(latitude, longitude)])
but that just sets the markers attribute of your class. What you actually want to do is mutate the set of markers in your marker layer. The simplest change you can make is to change that line to:
self.markers.markers = [gmaps.Marker(location=(latitude, longitude))]
This mutates the markers attribute of your marker layer — basically the list of markers. Every time you press plot, it destroys the marker on the map and replaces it with a new one at an updated location.
Improving your solution
Using the high-level factory methods like marker_layer can obscure how jupyter-gmaps uses widgets internally. To make it somewhat more understandable, let's introduce a _create_marker() method that creates a gmaps.Marker object:
def _create_marker(self, latitude, longitude):
return gmaps.Marker(location=(latitude, longitude))
We can now use this in the initial render:
def _render_map(self, latitude, longitude):
""" Render the initial map """
brasilia_coordinates = (-15.7934036, -47.8823172)
fig = gmaps.figure(center=brasilia_coordinates, zoom_level=3)
self.marker_layer = gmaps.Markers()
initial_marker = self._create_marker(latitude, longitude)
self.marker_layer.markers = [initial_marker] # set the first marker
fig.add_layer(self.marker_layer)
return fig
Note that I have renamed self.markers to self.marker_layer to make it clear it's a layer.
Finally, the update code is now:
def on_button_clicked(self, _):
latitude = self.FloatSlider1.value
longitude = self.FloatSlider2.value
# look how closely the following two lines match the construction code
new_marker = self._create_marker(latitude, longitude)
self.marker_layer.markers = [new_marker]
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.