My app has become fairly complex, about 1500 lines across several files. There is a button that creates tabs and adds them to an existing panel. Here is a minimal working example of how the button works:
def create_new_tab():
paragraph = Paragraph(text="Hello!")
tab = Panel(child=paragraph, title="tab")
tab.closable = True
return tab
def append_new_tab():
new_tab = create_new_tab()
curdoc().select_one({'name': 'tabs'}).tabs.append(new_tab)
button = Button(label='append new tab')
button.on_click(append_new_tab)
tab1 = Panel(child=button, title='button tab')
tabs = Tabs(tabs = [tab1], name='tabs')
curdoc().add_root(tabs)
In my actual program there is a bug somewhere, because when I click the button I get the message that some set changed during iteration:
error handling message Message 'EVENT' (revision 1) content: '{"event_name":"button_click","event_values":{"model_id":"1002"}}': RuntimeError('Set changed size during iteration',)
I have spent some time trying to debug, without success. I have tried to create a minimal not working example, but all my minimal examples work. I keep staring at the error message and wonder what else can I do to find out what is going wrong?
Remark: I am already using bokeh serve myapp/ --dev
You can debug your app using this code (Bokehh v1.1.0):
from bokeh.models import Panel, Tabs, Button, Paragraph
from tornado.ioloop import IOLoop
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
def modify_doc(doc):
def create_new_tab():
paragraph = Paragraph(text="Hello!")
tab = Panel(child=paragraph, title="tab")
tab.closable = True
return tab
def append_new_tab():
new_tab = create_new_tab()
doc.select_one({'name': 'tabs'}).tabs.append(new_tab)
button = Button(label='append new tab')
button.on_click(append_new_tab)
tab1 = Panel(child=button, title='button tab')
tabs = Tabs(tabs = [tab1], name='tabs')
doc.add_root(tabs)
io_loop = IOLoop.current()
server = Server(applications = {'/app': Application(FunctionHandler(modify_doc))}, io_loop = io_loop, port = 5001)
server.start()
server.show('/app')
io_loop.start()
Related
I would like to work in Jupyter Notebook though the project with one plot. Update it with a new data or new layout, maybe add/delete additional plot nearby it and so on… In Bokeh I could use smth similar to:
target = show(layout, notebook_handle=True)
push_notebook(handle=target)
In Holoviews I found how to feed new data to the existing plot:
pipe = Pipe(data=[])
Image = hv.DynamicMap(hv.Image, streams=[pipe1])
pipe.send(np.random.rand(3,2)) #data change
But is there any solution to manage live layout update in Holoviews? Is it possible to update existed plot by .opts() construction? In this example I will get a new plot:
pipe = Pipe(data=[])
Image = hv.DynamicMap(hv.Image, streams=[pipe])
Image.opts(width=1000,height=1000)
#######new cell in jupyter notebook############
Image.opts(width=100,height=100)
Here is a brilliant answer I have got on my question:
import param
import panel as pn
import numpy as np
import holoviews as hv
from holoviews.streams import Pipe
pn.extension()
pipe = Pipe(data=[])
class Layout(param.Parameterized):
colormap = param.ObjectSelector(default='viridis', objects=['viridis', 'fire'])
width = param.Integer(default=500)
update_data = param.Action(lambda x: x.param.trigger('update_data'), label='Update data!')
#param.depends("update_data", watch=True)
def _update_data(self):
pipe.send(np.random.rand(3,2))
layout = Layout()
Image = hv.DynamicMap(hv.Image, streams=[pipe]).apply.opts(cmap=layout.param.colormap, width=layout.width)
pdmap = pn.panel(Image)
playout = pn.panel(layout)
def update_width(*events):
for event in events:
if event.what == "value":
pdmap.width = event.new
layout.param.watch(update_width, parameter_names=['width'], onlychanged=False)
pn.Row(playout, pdmap)
https://discourse.holoviz.org/t/how-to-update-holoviews-plots-width-height/1947
I want iterate over a list of string, output the string as plain text in jupyter lab then interactively highlight a substring to get easily the start index of the substring and the length. The goal is to do a quick annotation of text and get the coordinates of the substring.
Is it easy or even possible to do something like this with jupyter notebook (lab)? If then How?
I had a look at ipywidgets but couldn't find something for this use case.
Here's an example with the RangeSlider:
import ipywidgets
input_string = 'averylongstring'
widg = ipywidgets.IntRangeSlider(
value = [0, len(input_string)],
min=0, max=len(input_string)
)
output_widg = ipywidgets.Text()
display(widg)
display(output_widg)
def chomp_string(widg):
start,end = tuple(widg['new'])
output_widg.value = input_string[start: end]
widg.observe(chomp_string, names='value')
You can implement this using jp_proxy_widgets. See the following screenshot:
Note that there are warnings about compatibility for selection protocols -- I only tested this on Chrome on a Mac. Also I don't know why the indices are off by one
(select_callback(startOffset+1, endOffset+1);)
Please see https://github.com/AaronWatters/jp_proxy_widget for more information
Edit: Here is the pastable text as requested:
import jp_proxy_widget
select_widget = jp_proxy_widget.JSProxyWidget()
txt = """
Never gonna give you up.
Never gonna let you down.
Never gonna run around and
desert you.
"""
selected_text = None
def select_callback(startOffset, endOffset):
global selected_text
selected_text = txt[startOffset: endOffset]
print ("Selected", startOffset, endOffset, repr(selected_text))
select_widget.js_init("""
// (Javascript) Add a text area.
element.empty()
$("<h3>please select text:</h3>").appendTo(element);
var textarea = $('<textarea cols="50" rows="5">' + txt + "</textarea>").appendTo(element);
// Attach a select handler that calls back to select_callback.
var select_handler = function(event) {;
var target = event.target;
var startOffset = target.selectionStart;
var endOffset = target.selectionEnd;
select_callback(startOffset+1, endOffset+1);
};
textarea[0].addEventListener('select', select_handler);
""", txt=txt, select_callback=select_callback)
# display the widget
select_widget.debugging_display()
I'm making a scatter plot of some data using Bokeh from a pandas dataframe (df). The dataframe is university data, and has columns "faculty_salary", "tuition", "sector" and "institution_name". The plot is faculty_salary vs tuition, and I'm using the sector to color the data, as shown below.
I currently have the tap tool which greys-out all other data points except for the one you clicked, as shown below.
What I want to do is use the AutocompleteInput widget to select an institution_name from the autocomplete and have it highlight that data point by graying out all the others, just like the tap tool. The AutocompleteInput widget is working fine, it's just the code to make it highlight that data point that I'm struggling to figure out. Here's where I'm at:
source = ColumnDataSource(df)
autocomp = AutocompleteInput(completions=df['name'].tolist())
callback = CustomJS(args=dict(source=source), code="""
what do I put here?? It doesn't seem to have a cb_obj
""")
autocomp.js_on_change('value',callback)
Please let me know how I can achieve the functionality I described: use the selected value in the AutocompleteInput to highlight the datapoint it corresponds to. I should note that for my application I would prefer a CustomJS solution, and not a Bokeh server solution.
Edit: It turns out that my browser (Chrome in Linux) doesn't recognize that an autocompletion value has been selected, but Firefox for Linux does. I think this must be because Jquery UI says:
This widget manipulates its element's value programmatically, therefore a native change event may not be fired when the element's value changes.
Suggestions for how to get around this in Bokeh are welcome.
Here is the Bokeh server solution :
from bokeh.layouts import layout
from bokeh.io import curdoc
from bokeh.models.widgets import AutocompleteInput
from bokeh.models.widgets import (Panel, Tabs, DataTable, TableColumn,
Paragraph, Slider, Div, Button, Select)
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models import HoverTool,TapTool
def update_selected(wttr,old,new):
a_val = autocomp.value
names = source.data['names']
ind = [i for i,x in enumerate(names) if x == a_val]
source.selected={'0d': {'glyph': None, 'indices': []}, '1d': {'indices': ind}, '2d': {}}
data_dict = {'names':['test2','test3','hello','goodbye'],
'x':[0,1,2,3], 'y':[10,20,30,40]}
source = ColumnDataSource(data_dict)
autocomp = AutocompleteInput(completions=['test2','test3','hello','goodbye'])
autocomp.on_change('value',update_selected )
fig = figure(plot_width=400,
plot_height=400,
x_axis_label='x',
y_axis_label='y',
tools=["pan","hover","box_zoom","reset,save"])
fig.scatter('x','y',source=source)
layout = layout([[fig, autocomp]])
curdoc().add_root(layout)
EDIT : here is the solution using customJS
from bokeh.layouts import layout
from bokeh.io import curdoc
from bokeh.models.widgets import AutocompleteInput
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models.callbacks import CustomJS
data_dict = {'names':['test2','test3','hello','test3'],
'x':[0,1,2,3], 'y':[10,20,30,40]}
source = ColumnDataSource(data_dict)
fig = figure(plot_width=400,
plot_height=400,
x_axis_label='x',
y_axis_label='y',
tools=["pan","hover","box_zoom","reset,save"])
fig.scatter('x','y',source=source)
autocomp = AutocompleteInput(completions=['test2','test3','hello','goodbye'])
code = """
var autocomplete = cb_obj.value
var names = source.data.names
function getAllIndexes(arr, val) {
var indexes = [], i;
for(i = 0; i < arr.length; i++)
if (arr[i] === val)
indexes.push(i);
return indexes;
}
var index = getAllIndexes(names,autocomplete)
source.selected = {'0d':{indices: [0]}, '1d':{indices: index},'2d':{indices: [0]}}
"""
callback = CustomJS(args={'source':source}, code=code)
autocomp.callback = callback
layout = layout([[fig, autocomp]])
curdoc().add_root(layout)
I want only one instance of my app to be running at each time. but when the user attempts to open it the second time, I want the first window to be brought to the front (it could be just minimized or minimized to the corner of the taskbar and the user doesn't know how to open it)
I have this code that does the detection job and it doesn't allow the second instance. I have trouble with the part that it has to open the original window. I have commented out some of my attempt.
import sys
from PyQt4 import QtGui, QtCore
import sys
class SingleApplication(QtGui.QApplication):
def __init__(self, argv, key):
QtGui.QApplication.__init__(self, argv)
self._activationWindow=None
self._memory = QtCore.QSharedMemory(self)
self._memory.setKey(key)
if self._memory.attach():
self._running = True
else:
self._running = False
if not self._memory.create(1):
raise RuntimeError(
self._memory.errorString().toLocal8Bit().data())
def isRunning(self):
return self._running
def activationWindow(self):
return self._activationWindow
def setActivationWindow(self, activationWindow):
self._activationWindow = activationWindow
def activateWindow(self):
if not self._activationWindow:
return
self._activationWindow.setWindowState(
self._activationWindow.windowState() & ~QtCore.Qt.WindowMinimized)
self._activationWindow.raise_()
self._activationWindow.activateWindow()
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.label = QtGui.QLabel(self)
self.label.setText("Hello")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.label)
if __name__ == '__main__':
key = 'random _ text'
app = SingleApplication(sys.argv, key)
if app.isRunning():
#app.activateWindow()
sys.exit(1)
window = Window()
#app.setActivationWindow(window)
#print app.topLevelWidgets()[0].winId()
window.show()
sys.exit(app.exec_())
I've made this work on Windows using the win32 api (I'm not entirely sure, but there are probably equivalent calls on macos/unix).
Add the following import to your application,
import win32gui
set the window title to a fixed name (instead of doing this, you could store its whndl in the shared memory)
window = Window()
window.setWindowTitle('Single Application Example')
window.show()
and then change your activateWindow method to something like the following:
def activateWindow(self):
# needs to look for a named window ...
whndl = win32gui.FindWindowEx(0, 0, None, "Single Application Example")
if whndl is 0:
return #couldn't find the name window ...
#this requests the window to come to the foreground
win32gui.SetForegroundWindow(whndl)
You might be interested by the solutions proposed here
For instance, I would try:
app = SingleApplication(sys.argv, key)
if app.isRunning():
window = app.activationWindow()
window.showNormal()
window.raise()
app.activateWindow()
sys.exit(1)
window = Window()
app.setActivationWindow(window)
window.setWindowFlags(Popup)
window.show()
I have a PyQt4.9 window where I would like to toggle the translucency on or off. The reason being is that it sometimes shows a full size phonon video control which doesn't work when the WA_TranslucentBackground attribute is set. (Due to a Qt bug https://bugreports.qt.io/browse/QTBUG-8119)
The problem I have is, after I turn WA_TranslucentBackground attribute back to false, after it has been true, the Window will no longer redraw, so it remains stuck showing the same thing from that point on. Interestingly, click events still respond.
Some example code follows. Click the increment button, and it will update the button text. Click the toggle button and then click the increment button again, and updates no longer show. Clicking the exit button closes the window, showing the events are still responding.
If anyone has any solutions, workarounds or fixes I'd appreciate them. Thanks.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Settings(QWidget):
def __init__(self, desktop):
QWidget.__init__(self)
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setWindowFlags(Qt.FramelessWindowHint)
self.istransparent = True
self.count = 0
self.setWindowTitle("Transparent")
self.resize(300, 150)
self.incr_button = QPushButton("Increment")
toggle_button = QPushButton("Toggle Transparency")
exit_button = QPushButton("Exit")
grid = QGridLayout()
grid.addWidget(self.incr_button, 0, 0)
grid.addWidget(toggle_button, 1, 0)
grid.addWidget(exit_button, 2, 0)
self.setLayout(grid)
self.connect(toggle_button, SIGNAL("clicked()"), self.toggle)
self.connect(self.incr_button, SIGNAL("clicked()"), self.increment)
self.connect(exit_button, SIGNAL("clicked()"), self.close)
def increment(self):
self.count = self.count + 1
self.incr_button.setText("Increment (%i)" % self.count)
def toggle(self):
self.istransparent = not self.istransparent
self.setAttribute(Qt.WA_TranslucentBackground, self.istransparent)
if __name__ == "__main__":
app = QApplication(sys.argv)
s = Settings(app.desktop())
s.show()
sys.exit(app.exec_())
Try to replace self.setAttribute(Qt.WA_TranslucentBackground, ...) calls in __init__ and toggle with following method.
def set_transparency(self, enabled):
if enabled:
self.setAutoFillBackground(False)
else:
self.setAttribute(Qt.WA_NoSystemBackground, False)
self.setAttribute(Qt.WA_TranslucentBackground, enabled)
self.repaint()
Tested on PyQt-Py2.7-x86-gpl-4.9-1 (Windows 7)