Get Bokeh dropdown value without interaction - bokeh

Below is the code for a small working example of a Bokeh dashboard.
When you make a selection in the first dropdown menu, the second one is updated dynamically and the chart is updated with a new source. This works for options A1/A2 because the data arrays are the same length.
Once you select option B1 in the first dropdown, the second dropdown changes to B2.
But the source update doesn't happen because you cant get the Yselector.value.
How can you retrieve the Yselector.value without using Bokeh's on_change method and pass it to a function like source_selector?
import numpy as np
import pandas as pd
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Tabs, Select
from bokeh.layouts import column, row, Spacer
from bokeh.io import curdoc
from bokeh.plotting import figure, curdoc, show
#Plotting points on initial chart.
df_AB = pd.DataFrame(np.random.randint(0,100,size=(500, 2)), columns=list('XY'), index=[str(i) for i in range(1,500+1)])
pointchart=figure(plot_width=800, plot_height=700, tools=['lasso_select','box_select','wheel_zoom'],title="Point scatter")
pointchart_source= ColumnDataSource(df_AB[["X","Y"]])
pointchart.circle("X","Y",source=pointchart_source)
#Dropdown X
selectoroptions=['','Series A1', 'Series A2','Series B1','Series B2']
Xselector = Select(title="Dropdown X:", value="", options=selectoroptions)
#Dropdown Y
selectoroptions=['','Series A1', 'Series A2','Series B1','Series B2']
Yselector = Select(title="Dropdown Y:", value="", options=selectoroptions)
#Will list multiple sources to feed the chart based on dropdown selection.
def source_selector(selection):
if selection=='Series A1':
newvalues= pd.Series(list(np.random.randint(100, size=500)),name="")
elif selection=='Series A2':
newvalues= pd.Series(list(np.random.randint(200, size=500)),name="")
elif selection=='Series B1':
newvalues= pd.Series(list(np.random.randint(20, size=20)),name="")
elif selection=='Series B2':
newvalues= pd.Series(list(np.random.randint(10, size=20)),name="")
return newvalues
#Once dropdown X seelction is made, the options of dropdown Y will dynamically change.
#Data used for X axis is updated.
def X_switch_selection_and_source(attr, old, new):
if new == '':
pointchart_source.data = ColumnDataSource(df_AB[["X","Y"]]).data
#Other dropdown changed dynamically
elif new=='Series A1':
Yselector.options=['Series A2']
elif new=='Series A2':
Yselector.options=['Series A1']
elif new=='Series B1':
Yselector.options=['Series B2']
elif new=='Series B2':
Yselector.options=['Series B1']
#Updating source based on this dropdown's selection/ X values.
new_x_values=source_selector(new)
#Issue is right here. This line will only work if y is the same length as new x.
new_y_values=list(pointchart_source.data["Y"])
#If the lenghths are different I want to update the source for y by getting the y dropdown value.
if len(new_x_values)!= len(new_y_values):
new_y_values=source_selector(Yselector.value) # Does not get the dynamically changed value in the Y dropdown.
sourcedf=pd.DataFrame({"X":new_x_values,"Y":new_y_values})
pointchart_source.data= ColumnDataSource(sourcedf).data
Xselector.on_change("value", X_switch_selection_and_source)
#Show
layout=row(column(Xselector,Yselector, Spacer(width=400, height=500)),pointchart)
curdoc().add_root(layout)
!powershell -command {'bokeh serve --show Bokeh_dropdown_helped_v2.ipynb'}
My goal is for the user to be able to make selections from the dropdowns and for the other dropdown to show only the appropriate selections based on the first selection.
The data used for the axis' will be updated based on the selections.
Thank you for any advice.

Change the Select widget values instead of its options. If you change the options, you must make sure that its value is one of the options.
elif new=='Series A1':
Yselector.value='Series A2'
elif new=='Series A2':
Yselector.value='Series A1'
elif new=='Series B1':
Yselector.value='Series B2'
elif new=='Series B2':
Yselector.value='Series B1'

Related

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.

How to find the selected ChekboxButton in a Column in Bokeh? Calendar using Checkboxes

I am trying to make a calendar with CheckButton groups in a column. The problem is that I can't figure out which button is selected.
group1 = CheckboxButtonGroup(
labels=["05", "06","07", "08","09"], height=10)
group2 = CheckboxButtonGroup(
labels=["12", "13","14", "15","16"] , height=10)
group3 = CheckboxButtonGroup(
labels=["19", "20","21", "23","24"] , height=10)
calendar = column(group1, group2, group3)
I want a function as follows:
def returnDaySelected(calendar):
return SelectedDay
I understand that I have three groups, and I have to make a few ifs to get the group I want. But still. I can't find the groups inside the column.
I tried printing dir(calendar) and dir(calendar.children[0]) to see what is inside the column. But I could not find an attribute that would give me back the groups, let alone the selected button.
Any ideas in how to return the selected button? (Or all of the selected buttons)
Try calendar.children[0].children[0].active returns a list of the buttons pressed, e.g., if the 2nd and 3rd buttons are pressed it will return [1,2] (counting from 0).
The column.children[0] returns just the WidgetBox. Another call is required to access its children.
def returnDaysSelected(calendar):
selectedDays = []
for iGroup in calendar.children[0].children:
active = iGroup.active
for iDay in active:
selectedDays.append(iGroup.labels[iDay])
return selectedDays

ipywidgets dropdown widgets: what is the onchange event?

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.

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)

(PyQt) QTreeView - want to expand/collapse all children and grandchildren

I want to be able to expand or collapse all children of a particular branch in a QTreeView. I am using PyQt4.
I know that QTreeView's have an expand all children feature that is bound to *, but I need two things: It needs to be bound to a different key combination (shift-space) and I also need to be able to collapse all children as well.
Here is what I have tried so far:
I have a subclass of a QTreeView wherein I am checking for the shift-space key combo. I know that QModelIndex will let me pick a specific child with the "child" function, but that requires knowing the number of children. I am able to get a count of the children by looking at the internalPointer, but that only gives me info for the first level of the hierarchy. If I try to use recursion, I can get a bunch of child counts, but then I am lost as to how to get these converted back into a valid QModelIndex.
Here is some code:
def keyPressEvent(self, event):
"""
Capture key press events to handle:
- enable/disable
"""
#shift - space means toggle expanded/collapsed for all children
if (event.key() == QtCore.Qt.Key_Space and
event.modifiers() & QtCore.Qt.ShiftModifier):
expanded = self.isExpanded(self.selectedIndexes()[0])
for cellIndex in self.selectedIndexes():
if cellIndex.column() == 0: #only need to call it once per row
#I can get the actual object represented here
item = cellIndex.internalPointer()
#and I can get the number of children from that
numChildren = item.get_child_count()
#but now what? How do I convert this number into valid
#QModelIndex objects? I know I could use:
# cellIndex.child(row, 0)
#to get the immediate children's QModelIndex's, but how
#would I deal with grandchildren, great grandchildren, etc...
self.setExpanded(cellIndex, not(expanded))
return
Here is the beginning of the recursion method I was investigating, but I get stuck when actually trying to set the expanded state because once inside the recursion, I lose "contact" with any valid QModelIndex...
def toggle_expanded(self, item, expand):
"""
Toggles the children of item (recursively)
"""
for row in range(0,item.get_child_count()):
newItem = item.get_child_at_row(row)
self.toggle_expanded(newItem, expand)
#well... I'm stuck here because I'd like to toggle the expanded
#setting of the "current" item, but I don't know how to convert
#my pointer to the object represented in the tree view back into
#a valid QModelIndex
#self.setExpanded(?????, expand) #<- What I'd like to run
print "Setting", item.get_name(), "to", str(expand) #<- simple debug statement that indicates that the concept is valid
Thanks to all for taking the time to look at this!
Ok... siblings did not actually get me to where I wanted to go. I managed to get the code working as follows (and it seems like a decent implementation). Kudos still to Prof.Ebral who got me going on the right track with the idea of siblings (turns out I needed to use QModelIndex.child(row, column) and iterate recursively from there).
Note that there is the following assumption in the code: It assumes that your underlying data store objects have the ability to report how many children they have (get_child_count() in my code). If that is not the case, you will somehow have to get a child count differently... perhaps by just arbitrarily trying to get child indexes - using QModelIndex.child(row, col) - with an ever increasing row count till you get back an invalid index? - this is what Prof.Ebral suggested and I might still try that (It is just that I already have an easy way to get the child count by requesting it from my data store).
Also note that I actually expand/collpase each node at a different point in the recursion based on whether I am expanding or collapsing. This is because, through trial and error, I discovered that animated tree views would stutter and pop if I just did it at one place in the code. Now, by reversing the order in which I do it based on whether I am at the top level (i.e. the root of the branch I am affecting - not the root of the entire treeview) I get a nice smooth animation. This is documented below.
The following code is in a QTreeView subclass.
#---------------------------------------------------------------------------
def keyPressEvent(self, event):
if (event.key() == QtCore.Qt.Key_Space and self.currentIndex().column() == 0):
shift = event.modifiers() & QtCore.Qt.ShiftModifier
if shift:
self.expand_all(self.currentIndex())
else:
expand = not(self.isExpanded(self.currentIndex()))
self.setExpanded(self.currentIndex(), expand)
#---------------------------------------------------------------------------
def expand_all(self, index):
"""
Expands/collapses all the children and grandchildren etc. of index.
"""
expand = not(self.isExpanded(index))
if not expand: #if collapsing, do that first (wonky animation otherwise)
self.setExpanded(index, expand)
childCount = index.internalPointer().get_child_count()
self.recursive_expand(index, childCount, expand)
if expand: #if expanding, do that last (wonky animation otherwise)
self.setExpanded(index, expand)
#---------------------------------------------------------------------------
def recursive_expand(self, index, childCount, expand):
"""
Recursively expands/collpases all the children of index.
"""
for childNo in range(0, childCount):
childIndex = index.child(childNo, 0)
if expand: #if expanding, do that first (wonky animation otherwise)
self.setExpanded(childIndex, expand)
subChildCount = childIndex.internalPointer().get_child_count()
if subChildCount > 0:
self.recursive_expand(childIndex, subChildCount, expand)
if not expand: #if collapsing, do it last (wonky animation otherwise)
self.setExpanded(childIndex, expand)
model.rowCount(index) is the method you want.
model = index.model() # or some other way of getting it
for i in xrange(model.rowCount(index)):
child = model.index(i,0, index)
# do something with child
model.index(row,col, parent) is essentially the same as calling index.child(row,col); just with fewer indirections.
I would recommend using a QTreeWidget which inherits QTreeView. You can then grab the children as a QTreeWidgetItem.
Since you do not want to use the QTreeWidget but want to stick to your current model .. you can iterate through the 'possible' children using, .isValid(). You should not use the internalPointer() though. Instead use the cellItem you have, as it is the original ModalIndex .. then attempt to find it's siblings. Something like
x = 0; y =0
while cellIndex.sibling(x, y).isValid():
child = cellIndex.sibling(x, y)
x += 1
I make a evnetFilter Class for that.
My particular use case is shift click the drop indicator then expand all or collapse all the child nodes like software maya outliner.
class MTreeExpandHook(QtCore.QObject):
"""
MTreeExpandHook( QTreeView )
"""
def __init__(self, tree):
super(MTreeExpandHook, self).__init__()
tree.viewport().installEventFilter(self)
self.tree = tree
def eventFilter(self, receiver, event):
if (
event.type() == QtCore.QEvent.Type.MouseButtonPress
and event.modifiers() & QtCore.Qt.ShiftModifier
):
pos = self.tree.mapFromGlobal(QtGui.QCursor.pos())
index = self.tree.indexAt(pos)
if not self.tree.isExpanded(index):
self.tree.expandRecursively(index)
return True
return super(MTreeExpandHook, self).eventFilter(self.tree, event)
Usage Example below
import sys
from PySide2 import QtCore,QtGui,QtWidgets
class MTreeExpandHook(QtCore.QObject):
"""
MTreeExpandHook( QTreeView )
"""
def __init__(self, tree):
super(MTreeExpandHook, self).__init__()
self.setParent(tree)
# NOTE viewport for click event listen
tree.viewport().installEventFilter(self)
self.tree = tree
def eventFilter(self, receiver, event):
if (
# NOTE mouse left click
event.type() == QtCore.QEvent.Type.MouseButtonPress
# NOTE keyboard shift press
and event.modifiers() & QtCore.Qt.ShiftModifier
):
# NOTE get mouse local position
pos = self.tree.mapFromGlobal(QtGui.QCursor.pos())
index = self.tree.indexAt(pos)
if not self.tree.isExpanded(index):
# NOTE expand all child
self.tree.expandRecursively(index)
return True
return super(MTreeExpandHook, self).eventFilter(self.tree, event)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
model = QtGui.QStandardItemModel()
# NOTE create nested data
for i in range(3):
parent = QtGui.QStandardItem('Family {}'.format(i))
for j in range(3):
child = QtGui.QStandardItem('Child {}'.format(i*3+j))
for k in range(3):
sub_child = QtGui.QStandardItem("Sub Child")
child.appendRow([sub_child])
for x in range(2):
sub_child_2 = QtGui.QStandardItem("Sub Child 2")
sub_child.appendRow([sub_child_2])
parent.appendRow([child])
model.appendRow(parent)
treeView = QtWidgets.QTreeView()
treeView.setHeaderHidden(True)
MTreeExpandHook(treeView)
treeView.setModel(model)
treeView.show()
sys.exit(app.exec_())
example gif

Resources