ipywidgets dropdown widgets: what is the onchange event? - jupyter-notebook

I can register a handler to button.on_click in ipython notebook widgets, but I don't know how to do the same for a dropdown widget
import ipywidgets as widgets
from IPython.display import display
def on_button_clicked(b):
print("Button clicked.")
button = widgets.Button(description="Click Me!")
display(button)
button.on_click(on_button_clicked)
But for
choose_task = widgets.Dropdown(
options=['Addition', 'Multiplication', 'Subtraction'],
value='Addition',
description='Task:',
)
there seems to be only
on_trait_change(...)
if I register a handler with this, can I use it to access the value of the widget?
I have seen examples with the handler and the widget belong to a subclass, and the handler can use self to introspect. But if I don't want to use a subclass, how does the handler know what widget was the target of the event.?

Between this link and the traitlet docs on github and just playing around, I finally figured this out:
w = widgets.Dropdown(
options=['Addition', 'Multiplication', 'Subtraction', 'Division'],
value='Addition',
description='Task:',
)
def on_change(change):
if change['type'] == 'change' and change['name'] == 'value':
print("changed to %s" % change['new'])
w.observe(on_change)
display(w)
Overall this looks a lot richer than the deprecated interface, but it could definitely use more examples.

You can specify the change name in observe. This makes for cleaner code, and the handler is not called for changes you don't need:
from IPython.display import display
from ipywidgets import Dropdown
def dropdown_eventhandler(change):
print(change.new)
option_list = (1, 2, 3)
dropdown = Dropdown(description="Choose one:", options=option_list)
dropdown.observe(dropdown_eventhandler, names='value')
display(dropdown)

Put it all together
Inspired on previous answers and lambda expressions I use this:
def function(option):
print(option)
w = widgets.Dropdown(
options=['None', 'Option 1', 'Option 2', 'Option 3'],
description='Option:',
disabled=False
)
w.observe(
lambda c: plot_content(c['new']) if (c['type'] == 'change' and c['name'] == 'value') else None
)
display(w)

I agree that event handling is not as thorough as would be desired: I have been filtering the events as you receive multiple events for a typical dropdown change as the index changes, the value changes, i.e., change['name'].
I am doing the following:
def on_dropdown_change(change):
if change['name'] == 'value' and (change['new'] != change['old']):
print('do something with the change')
dropdown = ipywidgets.Dropdown({options=['one','two','three'],
value='one'})
dropdown.observe(on_dropdown_change)

I believe the idea is to use trait name, e.g. value. For example:
from ipywidgets import Dropdown
def handle_change():
print type_sel.value
type_sel = Dropdown(description="Keypoint type", options=['surf', 'orb'])
type_sel.on_trait_change(handle_change, name="value")
display(type_sel)
SciPy 2015 Advanced Jupyter Video Tutorial

I had the same issue. This also begs the next question, how to interface button actions based on dropdown menu selections.
# Common Imports for Widgets
from __future__ import print_function
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets
'''
Precusor:
<class 'traitlets.utils.bunch.Bunch'> It is a dictionary-like object containing:
{'name': 'value', 'old': 'what_ever_the_old_value_was', 'new': 'what_ever_the_new_value_is',
'owner': Dropdown(description='the_user_defined_label:', index=1, # I'm not sure what this is
options=()#list of options passed,
value='value_kwarg_value'), 'type': 'change'} # type: action_or_event type
For more information see:
https://traitlets.readthedocs.io/en/stable/using_traitlets.html#default-values-and-checking-type-and-value
or
https://github.com/jupyter-widgets/tutorial/blob/master/notebooks/08.00-Widget_Events.ipynb
or a long but well done SciPy talk on the use of widgets #
https://www.youtube.com/watch?v=HaSpqsKaRbo
'''
foo = ['a','b','c'] # List to use
# Function to apply to drop box object
def bar(x):
'''
I am intentionally passing what it is made of so you can see the output.
'''
print(x,'\n') # Whole object
print(x.new,'\n') # New value
# Function for the button to select user input and do work
def get_user_selection(a): # A default arg is needed here, I am guessing to pass self
# Displays the current value of dropbox1 and dropbox two
display(dropbox1.value,dropbox2.value)
# creation of a widget dropdown object called dropbox1
dropbox1 = widgets.Dropdown(
options=foo, # Object to iterate over
description='Letter:', # User defined
value=foo[1], # Default value selection
rows=len(foo), # The number of rows to display when showing the box
interactive=True, # This makes the box interactive, I believe this is true by default
);
# Drop box of k,v like pairs
dropbox2 = widgets.Dropdown(
options=[('One', 1), ('Two', 2), ('Three', 3)],
value=2,
description='Number:',
)
# Button to click
select_button = widgets.Button(
description='Click', # User defined
disabled=False
)
# Event Handlers
dropbox1.observe(bar,names='value')
dropbox2.observe(bar,names='value')
select_button.on_click(get_user_selection)
# I you need more help with commands try things like:
# interact_manual?
# display(arg.keys,arg.traits)
# print(widgets.widget_type_here.widget_function_or_attr.__doc__)
# Create a UI object to display things. There are other ways of organizing them.
ui = widgets.HBox([dropbox1,dropbox2,select_button]) # pass an array of widgets to the ui
# display the UI
display(ui)
This will display the following after a couple of clicks.

Related

use ipywidget interactive() with option manual=True

I am updating a jupyter notebooks with interactive widgets to a more recent version (ipywidgets 7.2.1).
It used to have interactive() functions that were executed manually by clicking on a button (feature __manual=True). However now I cannot reproduce the same behavior.
Here is a minimal example:
from ipywidgets import interactive, interact_manual
import ipywidgets as widgets
def do_sth(x):
#do sth with the argument passed
print("done "+str(x))
nb = widgets.BoundedIntText(description='Number:')
#Interaction in accordion nested in a tab
tab = widgets.Tab()
tab.set_title(0, 'Page 1')
#old method
#problem: it is not manual anymore
w = interactive(do_sth, x=nb, __manual=True)
#new solution 1
#problem: the widget appears also outside the tab/accordion
w1 = interact_manual(do_sth, x=nb)
w1.widget.children[1].description = 'do sth' #seems a bit of a hack
#new solution 2
w2 = interactive(do_sth, x=nb, manual=True) #does no pass the manual option
#if I set it manually with:
#w2.manual = True
#It generates an error (AttributeError: 'interactive' object has no attribute 'manual_button')
accordion = widgets.Accordion(children=[w, w1.widget, w2])
accordion.set_title(0, 'old interaction 0')
accordion.set_title(1, 'new interaction 1')
accordion.set_title(2, 'new interaction 2')
tab.children = [accordion]
tab
Is it possible to use solution 1 and prevent the widget from appearing twice? Otherwise, is there another way to do this?
Seems they may have moved it into a dict when adding the ability to relabel the button.
Try
w2 = interactive(do_sth, {'manual' : True, 'manual_name' : 'Do Something'}, x=nb)

ipywidgets - display next value in list after clicking

I'm trying to use ipywidgets in order to display next value in my list (in Jupyter notebook) given as display_vals after clicking the button. However, with the following code snippet, I got error as
local variable 'i' referenced before assignment
Here is the snippet that I have
from IPython.display import display, clear_output
import ipywidgets as widgets
button = widgets.Button(description="Click Me!")
display(button)
display_vals = ['a', 'b', 'c']
i = 0
def on_button_clicked(b):
print(display_vals[i])
clear_output()
i += 1
button.on_click(on_button_clicked)
I guess I just don't know the way to parse my variable when button gets clicked.
You could do it by declaring i as a global and including an if statement like so:
from IPython.display import display, clear_output
import ipywidgets as widgets
button = widgets.Button(description="Click Me!")
display(button)
display_vals = ['a', 'b', 'c']
i = 0
def on_button_clicked(b):
global i
clear_output()
if i < len(display_vals):
print(display_vals[i])
i += 1
button.on_click(on_button_clicked)
However you should rename i to be something a little bit more unique if you do decide to go this route.

Using on_trait_change in ipython notebook widgets

I'm trying to use the IntSlider widget in IPython.html.widgets, and I want to call a function whenever the slider value changes. Additionally, I want to pass multiple arguments into the function. I was able to use on_trait_change() to call a function but have no idea how to pass arguments:
def somefn(parameter1, parameter2):
print (parameter1, parameter2)
slider = widgets.IntSlider(min=0,max=3,step=1)
slider.on_trait_change(somefn)
I would suggest using a partial.
from IPython.display import display
import ipywidgets as widgets
def somefn(parameter1,my_arg):
print ("parameter1: {}".format(parameter1))
print ("my_arg: {}".format(my_arg))
from functools import partial
somefn_arg_0 = partial(somefn, my_arg=0)
slider = widgets.IntSlider(min=0,max=3,step=1)
slider.observe(somefn_arg_0)
display(slider)
Note I have replaced the deprecated on_trait_change with observe.

Is there a way to make an IPython Notebook output interactivly create an input and execute it?

I was wondering if I can make an output interactively run a piece of code. So if for example I had a class (parts in pseudo-code):
import numpy as np
class test(object):
def __init__():
self.a = np.random.randn(10)
print ## Interactive Output: Click me to view data array##
def show():
print a
So when I create a class instance it should output some interactive link (maybe in html) or something like that and when I click it, the show() method should be called. However, I have no idea how to achieve that.
You could use the widgets shipped with the notebook (for jupyter they are an independent package).
Something like this could do what you want (Python 3):
from IPython.html import widgets
from IPython.display import display
import numpy as np
class Test(object):
def __init__(self, arraylen):
self.a = np.random.randn(arraylen)
self.button = widgets.Button(description = 'Show')
self.button.on_click(self.show)
display(self.button)
def show(self, ev = None):
display(self.a)
self.button.disabled = True
test = Test(10)
You create a button widget when you initialise the class widgets.Button(description = 'Show')
Attach an event to it button.on_click(self.show)
And display the button display(self.button)
In the show method I included a way to disable the button functionality once the array is showed self.button.disabled = True. You can comment this line if you want to show more times the array.

How to deselect all items in QTreeWidget?

I have tried every suggestion I can come across online, from setting flags to using selectionModel
def DatabaseLoadWrapper(self,database, init):
self.treeWidget.currentItemChanged.disconnect(self.updateStackedWidget)
self.DatabaseLoad(database, init)
self.treeWidget.clearSelection()
self.treeWidget.setCurrentItem(self.treeWidget.findItems(self.selectedDatabase,Qt.MatchExactly|Qt.MatchRecursive)[0])
self.treeWidget.currentItemChanged.connect(self.updateStackedWidget)
This is where my code needs to force a selection on the QTreeWidget, none of the code I use throws up any errors but also has no effect on the selection. And I end up with this where the user has selected Database 1 but I need to revert back to having only Database 2 selected:
Edit: The Tree Widget is built using this code:
def setupMenu(self):
self.DatabaseParent = QTreeWidgetItem(['Databases'])
for item in NamesInDatabase():
self.DatabaseParent.addChild(QTreeWidgetItem([item]))
self.AverageParent = QTreeWidgetItem(['Averaged Database'])
self.SortingParent = QTreeWidgetItem(['Waste Composition'])
self.ResultParent = QTreeWidgetItem(['Results'])
self.treeWidget.addTopLevelItem(self.DatabaseParent)
self.treeWidget.addTopLevelItem(self.AverageParent)
self.treeWidget.addTopLevelItem(self.SortingParent)
self.treeWidget.addTopLevelItem(self.ResultParent)
It basically is adding databases, averaged database, waste compisition & results, as fixed parts of the navigation menu and then populating children of databases with the names of the databases in the save file.
Your question fails to expose the part of the code that is causing the problem. By default, setting the current item, as you do, also sets the selection. So this code, for example, correctly sets the selection to item "b":
from PySide import QtCore,QtGui
if __name__ == '__main__':
import sys
qApp = QtGui.QApplication(sys.argv)
treeWidget = QtGui.QTreeWidget()
parent = QtGui.QTreeWidgetItem(['Databases'])
items = []
for item_text in ["a","b","c"]:
item = QtGui.QTreeWidgetItem([item_text])
items.append(item)
parent.addChild(item)
treeWidget.addTopLevelItem(parent)
treeWidget.setCurrentItem(items[1])
treeWidget.show()
sys.exit(qApp.exec_())
However, I suspect there is code elsewhere in your project that is affecting this. For example, if you had set the selection mode for the QTableWidget selection model to MultiSelection then selections become cumulative:
from PySide import QtCore,QtGui
if __name__ == '__main__':
import sys
qApp = QtGui.QApplication(sys.argv)
treeWidget = QtGui.QTreeWidget()
parent = QtGui.QTreeWidgetItem(['Databases'])
items = []
for item_text in ["a","b","c"]:
item = QtGui.QTreeWidgetItem([item_text])
items.append(item)
parent.addChild(item)
treeWidget.addTopLevelItem(parent)
treeWidget.setSelectionMode(QtGui.QAbstractItemView.MultiSelection)
treeWidget.setCurrentItem(items[0])
treeWidget.setCurrentItem(items[2])
treeWidget.show()
sys.exit(qApp.exec_())
However, that still doesn't explain your issue because the clearSelection call should have cleared the preceding selection in any case. Further debugging of your code is needed, for example to check that the wrapper function and the setCurrentItem are being called as you claim. Also check what else is being called subsequent to the DatabaseLoadWrapper.
In Pyside2, This works for me:
If you click on treewidget the selection will be clear.
self.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ContiguousSelection)

Resources