I have a class that uses IPython widgets to render rich output. This output changes over time. I manage this by adding a callback to the IOLoop that alters the output periodically.
class Foo(object):
def _ipython_display_(self, **kwargs):
html = HTML("<h2>hello</h2>")
def f():
html.value = ... # TODO: replace with weakrefs to avoid cycle
from tornado.ioloop import PeriodicCallback
html._pc = PeriodicCallback(f, 1000)
html._pc.start()
return html._ipython_display_(**kwargs)
However, I also want this PeriodicCallback to stop once html is no longer visible on the screen. Normally I would use __del__ or finalize or something, but it appears that the html object will likely be kept in the notebook's output history (variables like _10) and so my periodic callback survives forever.
Is there any way to get a signal when a displayable element is no longer displayed?
As you may know, widgets on the back end (the IPython kernel) are backed by a single object, while at the front end (the browser) it will have a model, and zero to N views.
Every time you display a widget, a new view is created. Since ipywidgets 7 it is possible to keep track of the number of views on the front end (the browser), by setting (on widget construction) the '_view_count' trait to 0, however, beware of what the help string says:
EXPERIMENTAL: The number of views of the model displayed in the frontend. This attribute is experimental and may change or be removed in the future. None signifies that views will not be tracked. Set this to 0 to start tracking view creation/deletion. The default is None, which will make it not track the view count (since for many widgets it will lead to a lot of traffic going around).
Assuming you are in the Jupyter notebook:
import ipywidgets
slider = ipywidgets.FloatSlider(value=2, _view_count=0)
assert slider._view_count == 0
For the next cell, you display it:
slider
And in the next cell you can check that the view count has gone up
assert slider._view_count == 1
Since this '_view_count' is a trait, you can also listen to changes
# use a output widget to lot lose output
output = ipywidgets.Output()
output
def view_count_changed(change):
with output:
print('View count has changes', change)
slider.observe(view_count_changed, '_view_count')
A more advanced example notebook that might be useful is here, where I make an 'ExpensivePlot' object, that will keep track of the 'dirty' state of the plot, and the _view_count to only update the plot when needed.
PS: This last example of course is not a really expensive plot, but a stripped down version of what I do in vaex when I use the bqplot for plotting, where it can take >1 second to update a plot).
Related
I have a Plotly figure built in Python that updates automatically. I want to preserve dashboard zooms even with automatic updates. The documentation in Plotly says this can be done using the layout uirevision field, per the this community writeup. The docs give this as an example of the return dictionary:
return {
'data': data,
'layout': {
# `uirevsion` is where the magic happens
# this key is tracked internally by `dcc.Graph`,
# when it changes from one update to the next,
# it resets all of the user-driven interactions
# (like zooming, panning, clicking on legend items).
# if it remains the same, then that user-driven UI state
# doesn't change.
# it can be equal to anything, the important thing is
# to make sure that it changes when you want to reset the user
# state.
#
# in this example, we *only* want to reset the user UI state
# when the user has changed their dataset. That is:
# - if they toggle on or off reference, don't reset the UI state
# - if they change the color, then don't reset the UI state
# so, `uirevsion` needs to change when the `dataset` changes:
# this is easy to program, we'll just set `uirevision` to be the
# `dataset` value itself.
#
# if we wanted the `uirevision` to change when we add the "reference"
# line, then we could set this to be `'{}{}'.format(dataset, reference)`
'uirevision': dataset,
'legend': {'x': 0, 'y': 1}
}
}
However, my figure is built more like this:
import plotly.express as px
#app.callback(
Output("graph", "figure"),
[Input("interval-component", "n_intervals")])
def display_graph(n_intervals):
# Logic for obtaining data/processing is not shown
my_figure = px.line(my_data_frame, x=my_data_frame.index, y=['line_1', 'line_2'],
title='Some Title', template='plotly_dark')
return my_figure
In other words, since I am not returning a dictionary, but a plotly express figure directly, how can I directly access the uirevision value so that UI changes from the user are preserved?
You can use the update_layout member function of the figure.
my_figure.update_layout(uirevision=<your data>)
More information here: https://plotly.com/python/creating-and-updating-figures/#updating-figure-layouts
Use the figure dictionary, which can be accessed like so:
my_figure['layout']['uirevision'] = 'some_value'
This can also be used to access other useful aspects of the figure, such as changing the line color of a specific line entry:
my_figure['data'][2]['line']['color'] = '#FFFF00'
To see the other entry options, print out my_figure in a Python session.
Note: since the uirevision option isn't documented very well (at least, not in my searching online), I thought it worth posting this as an option.
I'd like to be able to change things about the slider (the value, the start/end values) programmatically.
So I take the standard slider.py demo, and just add this at the end:
for i in range(5):
amp_slider.value = amp_slider.value + 1
time.sleep(1)
That should move the value upwards every second for a few seconds. But the slider doesn't move. What am I doing wrong? Or similarly if I try to change the .end or .start value.
[I know sliders are supposed to be INPUT not OUTPUT devices. But nonetheless I'm trying to control its behavior.]
bokeh show() outputs the chart as html & javascript. Once it has done this it can no longer be modified (unless you wrote some javascript which was included to modify the page).
You need a library that renders in a 'dynamic' window (such as matplotlib to be able to replot a chart like this.
The only code inside your program that will be used again once the page is created is in the callback functions. If you adjust sliders.py so it reads:
def update_title(attrname, old, new):
amplitude.value += 1
Every time you update the text, the amplitude will increase.
I'm brand new to Python and I'm trying to make my first program with PyQt4. My problem is basically the following: I have two checkboxes (Plot1 and Plot2), and a "End" push button, inside my class. When I press End, I would like to see only the plots that the user checks, using matplotlib. I'm not being able to do that. My first idea was:
self.endButton.clicked.connect(self.PlotandEnd)
self.plot1Checkbox.clicked.connect(self.Plot1)
self.plot2Checkbox.clicked.conncet(self.Plot2)
def PlotandEnd(self)
plot1=self.Plot1()
pyplot.show(plot1)
plot2=self.Plot2()
pyplot.show(plot2)
def Plot1(self)
plot1=pyplot.pie([1,2,5,3,2])
return plot1
def Plot2(self)
plot2=pyplot.plot([5,3,5,8,2])
return plot2
This doesn't work, of course, because "PlotandEnd" will plot both figures, regardless of the respective checkbox. How can I do what I'm trying to?
Wrap the plot creation in an if statement that looks at the state of the check boxes. For example:
def PlotandEnd(self)
if self.plot1Checkbox.isChecked():
plot1=self.Plot1()
pyplot.show(plot1)
if self.plot2Checkbox.isChecked():
plot2=self.Plot2()
pyplot.show(plot2)
You also don't need the following lines:
self.plot1Checkbox.clicked.connect(self.Plot1)
self.plot2Checkbox.clicked.conncet(self.Plot2)
This does nothing useful at the moment! Qt never uses the return value of your PlotX() methods, and you only want things to happen when you click the End button, not when you click a checkbox. The PlotX() methods are only currently useful for your PlotandEnd() method.
I am writing an application in Python using Qt (currently PyQt) that receives telemetry (position data) over UDP and displays that data. It can handle several packet types each with several data fields each. I have a separate thread that handles receiving and decoding packets and they emits a signal to send that data to the Qt GUI for display. The data get displayed in several ways: a current value display, scrolling log display, and a 3D view display. All this runs great and in real time with each packet updating at up to about 30Hz.
I want to add another tab that shows a live plot. The user can select which data streams to plot and it will update in real time, showing a sliding window of the last 60sec of data (maybe configurable at some point). However my first approach is particularly slow. It is barely keeping up plotting only one line with our emulator that runs much slower than 30Hz.
I am using pyqtgraph to do the plotting. I have a PlotWidget instantiated in my .ui file and create a PlotDataItem for each data line that could be drawn on the plot. I am using deques to store the data to be plotted, both the value and the time. This way I can quickly add data as it comes in and remove it as it falls outside of the sliding window. I am storing all this in a dict for each packet type and field:
self.plotData[pktType][field] = {}
self.plotData[pktType][field]['curve'] = self.pwPlot.plot()
self.plotData[pktType][field]['starttime'] = time
self.plotData[pktType][field]['data'] = coll.deque()
self.plotData[pktType][field]['realtime'] = coll.deque()
self.plotData[pktType][field]['time'] = coll.deque()
'starttime' stores an initial datetime value for computing elapsed seconds. 'realtime' stores datetime values of when each packet was received (I am not currently using this, so I could drop it if it would save time). 'time' stores elapsed seconds from the 'starttime' for easier plotting and 'data' stores the values.
When a packet that comes in, I store data in the deques for each field I might want to parse. I then trim off any data outside the sliding window. Finally, the deque gets packaged in a numpy array and passed to the PlotDataItem setData method. Here's a simplified version of the code that runs for each received packet:
def updatePlot(self, time, pktData):
pktType = pktData['ptype']
keys = self.getPlottableFromType(pktType) # list of fields that can be plotted
if keys == None:
return
for k in keys:
self.plotData[pktType][k]['data'].append(pktData[k])
self.plotData[pktType][k]['realtime'].append(time)
runtime = (time - self.plotData[pktType][k]['starttime']).total_seconds()
if self.plotRate == 0:
self.plotData[pktType][k]['time'].append(runtime)
else:
if self.plotData[pktType][k]['time']: # has items
nexttime = self.plotData[pktType][k]['time'][-1] + 1. / self.plotRate
else:
nexttime = 0
self.plotData[pktType][k]['time'].append(nexttime)
while (self.plotData[pktType][k]['time'][-1] - self.plotData[pktType][k]['time'][0]) > self.plotRangeSec:
self.plotData[pktType][k]['data'].popleft()
self.plotData[pktType][k]['realtime'].popleft()
self.plotData[pktType][k]['time'].popleft()
self.drawPlot(pktType, k)
def drawPlot(self, pktType, k):
if self.plotIsEnabled(pktType, k) and self.plotData[pktType][k]['time']: # has items
npt = np.array(self.plotData[pktType][k]['time'])
npd = np.array(self.plotData[pktType][k]['data'])
self.plotData[pktType][k]['curve'].setData(npt, npd)
else:
self.plotData[pktType][k]['curve'].clear()
self.plotRate can be used to plot the data either using wall time or force the time axis to a fixed update rate. This is useful for using with the emulator since it runs slower than the real system.
First thing I should probably do is not call .clear() every time for plots that are not being plotted (just complicates the logic a little bit). Other than that, anyone have any tips for improving performance? Is the deque to numpy array a good strategy? Is there a better way to update the data since I am only changing a few values per line (adding a point and possibly removing a point or two)?
I want to know when a frame has been resized, so I can save the size and remember it the next time the application launches. Here is my on_resize method:
def on_resize(self, event):
logic.config_set('main_frame_size',
(event.Size.width, event.Size.height))
event.Skip()
And it's bound like this:
self.Bind(wx.EVT_SIZE, self.on_resize)
The problem is performance. For safety, my logic module saves the config file every time a setting changes, and writing the config file every time the resize event fires is way too performance taxing.
What would be the best/easiest way of monitoring for when the user is done resizing the frame?
Update
My config_set function:
def config_set(key, value):
"""Set a value to the config file."""
vprint(2, 'Setting config value: "{}": "{}"'.format(key, value))
config[key] = value
# Save the config file.
with open(config_file_path, 'w') as f:
pickle.dump(config, f)
You could handle EVT_IDLE which is triggered when the event queue is empty:
wx.IdleEvent: This class is used for EVT_IDLE events, which are generated and sent when the application becomes idle. In other words, the when the event queue becomes empty then idle events are sent to all windows (by default) and as long as none of them call RequestMore then there are no more idle events until after the system event queue has some normal events and then becomes empty again.
The process of resizing or moving a window should keep the event queue jammed so it won't become empty (and trigger the idle event) until the resizing/moving is done.
Set a dirty flag in EVT_SIZE and check it in the EVT_IDLE handler. If the flag is set, save the new size and reset the flag:
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None)
self.resized = False # the dirty flag
self.Bind(wx.EVT_SIZE,self.OnSize)
self.Bind(wx.EVT_IDLE,self.OnIdle)
def OnSize(self,event):
self.resized = True # set dirty
def OnIdle(self,event):
if self.resized:
# take action if the dirty flag is set
print "New size:", self.GetSize()
self.resized = False # reset the flag
app = wx.PySimpleApp()
frame = Frame().Show()
app.MainLoop()
EVT_SIZE may also be triggered when restoring a minimized window (the window size remains the same). If you want to cut down on unnecessary saves, you may want to check if the size is actually different before you save it to the config (you could keep track of it in a variable containing the last saved size).
You may want to add EVT_MOVE to keep track of the window position.
You could start a timer and have it check for changes every so often, kind of like the auto-save in Microsoft Word. Or you could set some kind of flag when EVT_SIZING or EVT_SIZE occurs and then bind to EVT_LEAVE_WINDOW as you'll almost certainly leave the window when you're done resizing. Thus when that event fires, you check the flag that was set and if it is set, you save and reset the flag.
On windows, you can save the configuration in the registry, which results in no performance hit when the window is resized.
On other OS's, where there is no registry, I guess you need to use a file. However, I am surprised that even this gives the kind of performance penalty that you would notice.
Are you sure that whatever poor performance you are seeing is due to this? ( Perhaps your redrawing code is slow? )
I would think that any modern OS would look after such a small file write without getting in your way. Perhaps it is Python problem?
I urge you to look into the above questions first. However, to answer your actual question:
The way to do this is to save the window size in a variable, and only write it to a file when your application quits.
Took a look at the code you just posted. I am not a python expert, but it looks like you are re-opening the file on every update. If so, no wonder it is slow!
Keep the file open all the time.
Only write the file when your app quits.
You might also take a look at the wxWidget wxConfig class.
You definitely shouldn't be saving the window geometry on every resize, it should be normally only done when the frame is closed. If you want extra safety (but, honestly, how do you manage to crash in Python?), you can also call the same function you call on frame close from a EVT_TIMER handler. But window geometry is hardly a critical resource so I don't think there is ever any real need to do this.