I want to add filters dynamically in bokeh, i.e. every time a button is pressed, a new filter is appended. However, the layout gets broken after a new widgets are added: new ones get written over old ones instead of the layout being recomputed. Code example
from bokeh.layouts import row, column
from bokeh.models.widgets import Button, Select
from bokeh.io import curdoc
def add_select():
feature = Select(value='feat', options=["a"])
dynamic_col.children.append(feature)
b1 = Button(label="Add condition", button_type="success")
b1.on_click(add_select)
b2 = Button(label="Apply", button_type="success")
dynamic_col = column()
curdoc().add_root(column(b1, dynamic_col, b2))
Layout before clicking "Add" button
Layout after Select widget gets added
Why don't you use a single list to handle all your widgets ?
from bokeh.layouts import column
from bokeh.models.widgets import Button, Select
from bokeh.io import curdoc
def add_select():
feature = Select(value='feat', options=["a"])
dynamic_col.children.insert(-1, feature)
b1 = Button(label="Add condition", button_type="success")
b1.on_click(add_select)
b2 = Button(label="Apply", button_type="success")
dynamic_col = column(b1, b2)
curdoc().add_root(dynamic_col)
I "insert" instead of "append" the widget to let the 2nd button at the end of the list
I got this result :
Related
I am looking to open a URL from bokeh using OpenURL, but from within a callback for a button, not using taptool. The code below reproduces the issue I'm running into i.e. I can open new tabs when using taptool with OpenURL, but nothing happens when I use OpenURL in a button callback.
(Much of the example is from the bokeh docs: http://docs.bokeh.org/en/0.12.5/docs/user_guide/examples/interaction_open_url.html)
from bokeh.models import ColumnDataSource, OpenURL, TapTool
from bokeh.models.widgets import Button
from bokeh.plotting import figure, output_file, show
from bokeh.layouts import column, widgetbox
from bokeh.io import curdoc
p = figure(plot_width=400, plot_height=400,
tools="tap", title="Click the Dots")
source = ColumnDataSource(data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
color=["navy", "orange", "olive", "firebrick", "gold"]
))
p.circle('x', 'y', color='color', size=20, source=source)
url = "http://www.colors.commutercreative.com/#color/"
taptool = p.select(type=TapTool)
taptool.callback = OpenURL(url=url)
button = Button(label="Generate", button_type="success")
def button_callback():
print('button callback')
OpenURL(url="http://www.google.com")
button.on_click(button_callback)
doc = curdoc()
doc.add_root(column([p,widgetbox(button)]))
doc.title = "Hello World"
Thank you!
OpenURL is a custom version of CustomJS, so it runs only in JavaScript. It's also a class and not a function - you can't just construct an object of class OpenURL, you also have to use its other methods to make it work.
With that being said, you cannot use it with Button since OpenURL expects a data source to replace all the placeholders in the URL. And Button can't have a data source.
Instead, what you need is a regular CustomJS:
b.js_on_click(CustomJS(args=dict(urls=['https://www.google.com',
'https://stackoverflow.com/']),
code="urls.forEach(url => window.open(url))"))
Note that how the solution above will work depends on your browser. E.g. in my case Google Chrome was opening only the first URL, and I had to explicitly allow pop-ups for the generated web page before it started to also open the second URL.
And you don't need bokeh serve to make it work - it will work even on a static web page generated by a call to save or show.
With Bokeh, how to hide the icon of a tool while keeping it enabled?
Some context, I have multiple p.line() plots in a single figure. Each line plot has its own hover tool as per this question.
I don't find it appealing though, to have each hover tool its own icon:
.
So I thought about keeping the multiple Hover tools but hiding them from the user.
What are my options?
Thanks in advance.
First, I would ask myself if a user would like a hidden hover that can't be disabled?
So first option I would suggest is adding a tooltips not to each renderer but to the figure which creates a single HoverTool icon that applies to all lines in the plot like this: (Bokeh v1.3.0)
from bokeh.plotting import figure, show
from bokeh.models import HoverTool, Column, Button, CustomJS
import numpy as np
p = figure(toolbar_location='above',
tooltips=[("x", "#x")]
)
lines = [p.line(np.arange(10), np.random.random(10)) for i in range(3)]
for i in range(len(lines)):
p.add_tools(HoverTool(tooltips=[("x", "#x")], renderers=[lines[i]]))
# button = Button(label='Hide Hover Icon')
# code = ''' hover_btns = document.getElementsByClassName('bk-tool-icon-hover')
# for(i=0; i<hover_btns.length; i++)
# hover_btns[i].style.display = 'none' '''
#
# button.callback = CustomJS(code=code)
show(Column(p,
# button,
))
Then if you really don't want to see any hover icon in your toolbar you could add a JS callback executed on button click that hides all hover icons like this:
from bokeh.plotting import figure, show
from bokeh.models import HoverTool, Column, Button, CustomJS
import numpy as np
p = figure(toolbar_location='above',
# tooltips=[("x", "#x")]
)
lines = [p.line(np.arange(10), np.random.random(10)) for i in range(3)]
for i in range(len(lines)):
p.add_tools(HoverTool(tooltips=[("x", "#x")], renderers=[lines[i]]))
button = Button(label='Hide Hover Icon')
code = ''' hover_btns = document.getElementsByClassName('bk-tool-icon-hover')
for(i=0; i<hover_btns.length; i++)
hover_btns[i].style.display = 'none' '''
button.callback = CustomJS(code=code)
show(Column(p,
button,
))
But doing so requires a user to click on the button first, so maybe a more elegant way to do this would be to call the JS code already at page load using an approach described here.
Is there a way to control the placement and alignment of ipywidgets (inside jupyter notebook)?
from ipywidgets import widgets
from IPython.display import Javascript, display
event_type_ui = widgets.Text(value='open', description='Test Event')
platform_ui = widgets.Text(value='Android', description='Control Event')
display(event_type_ui)
display(platform_ui)
I would like to specify an offset (in pixels?) to allow the label to fit and to have two controls aligned vertically.
After some fiddling, I was able to get this:
Before:
After:
Here is a copy-paste snippet, if interested:
from ipywidgets import widgets
from IPython.display import Javascript, display
align_kw = dict(
_css = (('.widget-label', 'min-width', '20ex'),),
margin = '0px 0px 5px 12px'
)
platform_ui = widgets.Dropdown(description = 'platform',options=['iPhone','iPad','Android'], **align_kw)
event_type_ui = widgets.Text(value='open', description='Test Event', **align_kw)
os_version_ui = widgets.Text(value='iOS9', description='Operating System', **align_kw)
refresh_ui = widgets.Checkbox(description='Force Refresh', **align_kw)
display(platform_ui,event_type_ui,os_version_ui,refresh_ui)
The layout and styling documentation for ipywidgets says:
Every Jupyter interactive widget has a layout attribute exposing a number of css properties that impact how widgets are laid out.
I was able to coax it into aligning the labels:
from ipywidgets import Text, HBox, VBox, Box
from IPython.display import display
widget1 = Text(value='Cats', description='Test Event', layout='margin-right:5px')
widget2 = Text(value='Oranges', description='Another Test Event')
widget1.width = '200px'
widget2.width = '150px'
display(VBox([widget1, widget2]))
to produced this:
But in general, I doesn't seem like we can directly target layout properties of the description label, just the entire widget itself.
The ipywidgets also have an add_class that can be used for getting into the weeds when you really need it:
import ipywidgets as widgets
from IPython.display import display, HTML
widget_list={}
for label in ("longish description","brief"):
widget_list[label]=widgets.Text(description=label)
widget_list[label].add_class("balanced-text")
display(HTML("<style>div.balanced-text .widget-label {max-width:200px; width:200px}</style>"))
display(widgets.VBox(list(widget_list.values())))
I'm trying to add new widgets (in the example below I use labels) during the runtime by pressing on a button. Here the example:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Widget(QWidget):
def __init__(self, parent= None):
super(Widget, self).__init__()
btn_new = QPushButton("Append new label")
self.connect(btn_new, SIGNAL('clicked()'), self.add_new_label)
#Container Widget
self.widget = QWidget()
#Layout of Container Widget
layout = QVBoxLayout(self)
for _ in range(20):
label = QLabel("test")
layout.addWidget(label)
self.widget.setLayout(layout)
#Scroll Area Properties
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(False)
scroll.setWidget(self.widget)
#Scroll Area Layer add
vLayout = QVBoxLayout(self)
vLayout.addWidget(btn_new)
vLayout.addWidget(scroll)
self.setLayout(vLayout)
def add_new_label(self):
label = QLabel("new")
self.widget.layout().addWidget(label)
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = Widget()
dialog.show()
app.exec_()
When I start the application everything looks ok, the list of labels is correctly shown and their size is also correct. But, when I press several times on the button to add new labels, the new ones are added to the list but their size change. All labels of the list go smaller.
How do I fix this error?
The problem is the line:
scroll.setWidgetResizable(False)
which obviously stops the widget resizing when you add more child widgets to it (and so they all get squashed together in the same space).
So reset it to True and add a stretchable space to the bottom of the widget's layout:
layout.addStretch()
self.widget.setLayout(layout)
...
scroll.setWidgetResizable(True)
scroll.setWidget(self.widget)
then insert the new labels before the spacer:
def add_new_label(self):
label = QLabel("new")
layout = self.widget.layout()
layout.insertWidget(layout.count() - 1, label)
I have a Qlistwidget in icon mode and I'm using setItemWidget to display my elements in my custom widgets, so far this is working.
Pretty much is like this one:
https://stackoverflow.com/questions/3639468/what-qt-widgets-to-use-for-read-only-scrollable-collapsible-icon-list
The only problem I have is that when I select the items, they don't look selected (no frame around them). They are being selected as I'm getting the right signals but you can't see the selection on the UI.
Any ideas on how to make them appear selected?
** Edit to add sample code **
(it is a modification on the code found on the previous link)
import sys
from PyQt4 import QtGui, QtCore
class displayItem(QtGui.QWidget): #A simple widget to display, just centers a digit in a 100x100 widget
def __init__(self,num):
QtGui.QWidget.__init__(self)
self.size=100
self.resize(self.size,self.size)
self.setMinimumSize(self.size,self.size)
self.text = num
def paintEvent(self,event):
p = QtGui.QPainter(self)
p.drawText(self.size//2,self.size//2,str(self.text))
app = QtGui.QApplication(sys.argv)
#Build the list widgets
list1 = QtGui.QListWidget() #This will contain your icon list
list1.setMovement(QtGui.QListView.Static) #otherwise the icons are draggable
list1.setResizeMode(QtGui.QListView.Adjust) #Redo layout every time we resize
list1.setViewMode(QtGui.QListView.IconMode) #Layout left-to-right, not top-to-bottom
listItem = QtGui.QListWidgetItem(list1)
listItem.setSizeHint(QtCore.QSize(100,100)) #Or else the widget items will overlap (irritating bug)
list1.setItemWidget(listItem,displayItem(1))
listItem = QtGui.QListWidgetItem(list1) #Add a few more items
listItem.setSizeHint(QtCore.QSize(100,100))
list1.setItemWidget(listItem,displayItem(2))
listItem = QtGui.QListWidgetItem(list1)
listItem.setSizeHint(QtCore.QSize(100,100))
list1.setItemWidget(listItem,displayItem(3))
list1.show() #kick off the app in standard PyQt4 fashion
sys.exit(app.exec_())
Thanks
/J
Yes. .
it is related to the viewMode.
When I set the viewMode for the list1 as ListMode, selected items look selected(highlighted)
list1.setViewMode(QtGui.QListView.ListMode)
still trying to figure out why it is not working with the iconMode. . .