You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Updating a plot doesn't clear old plots, if event trigger came from another figure? #10183
I am updating a line plot. There is an event trigger that starts this updating. If the trigger came from the figure that contains the plot, everything is fine. However, if the trigger came from another figure, then weird results happen: the line that's been updated appears to leave its trace uncleared.
You should use fig.canvas.draw() (a matplotlib method), not update() (a Qt method). Because the call was to update(), the window was not cleared first... except that pause() itself triggers a draw(), but only on the active window (and is explicitly documented as such), which is the one you clicked on:
def pause(interval):
"""
Pause for *interval* seconds.
If there is an active figure, it will be updated and displayed before the
pause, and the GUI event loop (if any) will run during the pause.
This can be used for crude animation. For more complex animation, see
:mod:`matplotlib.animation`.
Notes
-----
This function is experimental; its behavior may be changed or extended in a
future release.
"""
manager = _pylab_helpers.Gcf.get_active()
if manager is not None:
canvas = manager.canvas
if canvas.figure.stale:
canvas.draw_idle()
show(block=False)
canvas.start_event_loop(interval)
else:
time.sleep(interval)
I guess pause() could be changed to redraw all pyplot-managed windows (so leaving this issue open for discussion).
No, update is a Qt object method that schedules a paint event for the near future which eventually results in Qt calling the paintEvent method which copies pixels values from the Agg buffer to the screen (the Qt backend is implemented as QImage that is painted onto the widget).
When you call draw_artist that renders the artist onto the current Agg buffer (without clearing anything currently on the canvas). The subsequent call to update eventually copies the updated buffer to the screen. If you use blit it uses repaint under the hood which updates the screen now, rather than when the Qt paint event make's it's way to the top of the GUI event loop.
In the right-hand axes I am not quite sure what is going on to end up with those ticks, but each curve is drawn with a different set of limits (none of which match the tick labels).
Below is code that does what I think you want.
importmatplotlib.pyplotaspltimportnumpyasnpimporttimebg_cache=Nonecurrent_timer=Nonedefonclick(event):
globalcurrent_timerj=0cv=event.canvasfig=cv.figureframes=100defupdate():
nonlocalj# restore the backgroundcv.restore_region(bg_cache)
# update the dataii=j*np.pi/framesy1=y*np.sin(ii)
# update the line on the fixed limit axesline1.set_ydata(y1)
ax.draw_artist(line1)
# update the line on the limit adjusted axesline2.set_ydata(-y1)
ax2.set_ylim(y1.min(), y1.max())
fig.draw_artist(ax2)
# update the screencv.blit(fig.bbox)
# update counterj+=1# if we are more than 100 times through, bail!ifj>frames:
print(f'fps : {frames/ (time.time() -start_time)}')
returnFalsestart_time=time.time()
current_timer=cv.new_timer(
interval=100, callbacks=[(update, (), {})])
current_timer.start()
defondraw(event):
globalbg_cachecv=event.canvasfig=cv.figurebg_cache=cv.copy_from_bbox(fig.bbox)
ax.draw_artist(line1)
fig.draw_artist(ax2)
x=np.linspace(0, 2*np.pi, 100)
y=np.sin(x)
fig=plt.figure()
ax=fig.add_subplot(1, 2, 1)
line1, =ax.plot(x, y, animated=True)
ax2=fig.add_subplot(1, 2, 2)
ax2.set_animated(True)
line2, =ax2.plot(x, y)
d_cid=fig.canvas.mpl_connect('draw_event', ondraw)
bp_cid=fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()
The various globals are required so things do not get garbage collected. The two callbacks should probably be bound into a class and some checking like (if a timer is running, don't start another one).
For a 640x480 pix figure I can get ~60 fps and 200fps if only updating the left axes (if you turn the interval down to 1ms) on a 4 year old laptop.
@tacaswell Thanks a lot for the reply. It is very helpful to me. I think I had several misunderstandings before this thread, and thanks to all the help I got here I could clear them up. I will write those as notes (with some more questions) here in case someone else had the same confusions.
plt.pause() actually calls draw_idle(). I saw some random online snippets that do canvas.draw() then plt.pause(), which would actually be duplicate.
I think in many cases why you need plt.pause() is because you need flush_events(). You can call it directly. Speaking of which, I am not sure I entirely understand flush_events(), I can find limited information about this online. the document says 'Flush the GUI events for the figure.' Does this just give the backend a chance to do the plotting?
There is no actual 'updating' in plotting. What I mean is this: new things get plotted to the canvas, with all the stuff plotted before still on it. Once an artist gets plotted to the canvas, the handle to the artist has no bearing on the curve that's plotted on the canvas. I had this misunderstanding for a long time; you can see in my original snippet it could be interpreted wrongly that things are 'updated' in the sense of 'moved'. No, draw_artist(artist1) does a new plotting of artist1 to canvas (by renders the artist onto the current Agg buffer). canvas.update() which is a Qt method schedules this plotting event (in the event loop?)
@tacaswell 's reply is a great snippet to teach blitting. IMHO it's actually better than the official document found at https://matplotlib.org/api/animation_api.html. The official document is rather abstract, and it's associated with FuncAnimation which I don't use. I have another question about blitting: from the official doc it seems the reused background is a bitmap? It's not actually pixelated bitmap that's being reused, right? (In the sense that you can still save the figure as the vector format.)
So back to my original question, there are two easy solutions: one is blitting as in @tacaswell's answer. Another option is do the draw_artist(ax.patch) just before updating the line. This re-draw the empty space in the axes, and cover the original plots.
I am an end user of matplotlib, and I don't have much knowledge of the backstage stuff. I learned through scraping information online and reading bits and pieces of the documentation. If anyone out there is in a similar situation and end up with similar confusions, I hope this thread helps you.
Ok, one more questoin: why is it that in my original code, after all the updating is done, when you resize the firgure, all the previous curves but the latest are gone?
yes, but as of 1.5 draw_idle only actually re-draws if the figure is 'stale'
see DOC: Start to document interactive figures #4779 for lots of details, but in short the actual render is delay until the GUI framework decides to update which in most cases is via an event on the GUI event loop which requires the event loop to run.
Matplotlib is split in to layers, one layer (the Artists holds the logical representation of the plot (the Line2D objects) and the renders (which take the Artists and turn them into some manifested version of the plot). Agg is a compositing renderer so it's bit map is just the accumulation of all the artists that have been drawn to it (but with no further connection) so calling draw_artist just changes pixels in the Agg buffer and (as you say) update (in a round about way) moves that buffer to the screen. See http://www.aosabook.org/en/matplotlib.html
I moved a version of that snippet to DOC: add a blitting tutorial #10187 , is it still helpful? Yes, with blitting you are storing a bit-map of the background and then using that to 'clean' the Agg buffer (which is also a bit map). If you want to save the figure as a vector format, the figure is rendered from scratch from the Artists to the vector format based on their current state. This is the same reason resizing the figure clears the extra lines. When the figure is re-sized the number of pixels changes so it is automatically re-rendered from scratch based on the current state of the artists, hence giving you only one line
yes, but blitting is more robust against things like having non-animated artists in the axes (like annotations, inward facing ticks, other lines, etc) and partially transparent patches.
Where would you have expected to look in the documentation to have found this information? Do the two PRs linked above make it better or worse?
I would also consider using FuncAnimation it does all of the blit management behind the scenes for you and by separating the update-my-artists logic from the source-my-data logic will (hopefully) lead to slightly easier to maintain and re-use code for you.
I've tried to use FuncAnimation several times, but with left it in the end. The reason being it's somewhat intrusive in the thinking process of designing my package. I have to work around FuncAnimation, not just "now plot A and B" then "let's update B". In my code, the user would be doing computations and then updating the plots accordingly (but I don't know when). Not just playing a certain animation and be done. Maybe it's just my way of thinking.
Your example above is very good. I wish I had read this before I wrote my last project.
I am not sure where should this info go. Your explanation of the layers, Artists vs Agg, is very helpful to me. The explanation of blitting and "updateing" in the document was kind of confusing for me, since I have no prior exposure to these things. I would think explaining the actual plotting flow here would make a lot of sense.
A further problem with set_animated, if the ax and the figure are created in a class, the ax won't show up, it's missing from the figure. Try the following code to reproduce:
import matplotlib.pyplot as plt
import numpy as np
class Plotter:
def __init__(self):
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111)
self.ax.set_animated(True)
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
plotter = Plotter()
plotter.ax.plot(x, y)
plotter.fig.draw_artist(plotter.ax)
plt.show()
The issue is that draw_artist renders the artists onto the internal buffer, but does not move the rendered buffer to the screen, for that you need to use blit as above:
where should I place this line? I tried place it after fig.canvas.draw(), then I still get nothing. If placed before that, I get AttributeError: 'FigureCanvasQTAgg' object has no attribute 'renderer'.
Also, in your example above , why do the axes show up initially? Is it because ax is not 'animated', only line1 is?
line1, = ax.plot(x, y, animated=True)
Also in the example, cv.blit(fig.bbox) only draws those that were set to be animated, i.e., line and ax2. Is that correct? I tried setting them not to be animated, i.e., line1, = ax.plot(x, y, animated=False), ax2.set_animated(False). Seems the animation still works, with no fps change.
The Qt method update() does blitting internally. Thus, whether I do blitting explicitly myself, or do it via update(), I shouldn't expect much speed difference, is that correct?
In my original example, why does it matter where the event trigger is from the same figure or not? Is it because of the way update() works internally?
This seems to be more like a user question than a feature request or a bug report, so I'm going to close this. You can continue any questions on https://discourse.matplotlib.org/
Activity
anntzer commentedon Jan 6, 2018
You should use fig.canvas.draw() (a matplotlib method), not update() (a Qt method). Because the call was to update(), the window was not cleared first... except that pause() itself triggers a draw(), but only on the active window (and is explicitly documented as such), which is the one you clicked on:
I guess pause() could be changed to redraw all pyplot-managed windows (so leaving this issue open for discussion).
clsmt commentedon Jan 6, 2018
For my actual application, draw() is way too slow, and I can't use it. I am kinda stuck with update() cause it's much faster. Any suggestions?
tacaswell commentedon Jan 6, 2018
@clsmt You have half re-invented blitting, see https://matplotlib.org/api/animation_api.html?highlight=blitting#funcanimation
The thing you are missing is caching and restoring the figure without your animated artist on it.
tacaswell commentedon Jan 6, 2018
and if you want to change the limits you will have to re-draw the whole axes (all of the artists in it + the tick labels).
clsmt commentedon Jan 6, 2018
@tacaswell
I am not sure if I understand it correctly -
tacaswell commentedon Jan 7, 2018
No,
updateis a Qt object method that schedules a paint event for the near future which eventually results in Qt calling thepaintEventmethod which copies pixels values from the Agg buffer to the screen (the Qt backend is implemented asQImagethat is painted onto the widget).When you call
draw_artistthat renders the artist onto the current Agg buffer (without clearing anything currently on the canvas). The subsequent call toupdateeventually copies the updated buffer to the screen. If you useblitit usesrepaintunder the hood which updates the screen now, rather than when the Qt paint event make's it's way to the top of the GUI event loop.In the right-hand axes I am not quite sure what is going on to end up with those ticks, but each curve is drawn with a different set of limits (none of which match the tick labels).
Below is code that does what I think you want.
The various globals are required so things do not get garbage collected. The two callbacks should probably be bound into a class and some checking like (if a timer is running, don't start another one).
For a 640x480 pix figure I can get ~60 fps and 200fps if only updating the left axes (if you turn the interval down to 1ms) on a 4 year old laptop.
tacaswell commentedon Jan 7, 2018
@clsmt Do you find the text at #10187 helpful?
clsmt commentedon Jan 8, 2018
@tacaswell Thanks a lot for the reply. It is very helpful to me. I think I had several misunderstandings before this thread, and thanks to all the help I got here I could clear them up. I will write those as notes (with some more questions) here in case someone else had the same confusions.
plt.pause()actually callsdraw_idle(). I saw some random online snippets that docanvas.draw()thenplt.pause(), which would actually be duplicate.plt.pause()is because you needflush_events(). You can call it directly. Speaking of which, I am not sure I entirely understandflush_events(), I can find limited information about this online. the document says 'Flush the GUI events for the figure.' Does this just give the backend a chance to do the plotting?canvas, with all the stuff plotted before still on it. Once an artist gets plotted to the canvas, the handle to the artist has no bearing on the curve that's plotted on the canvas. I had this misunderstanding for a long time; you can see in my original snippet it could be interpreted wrongly that things are 'updated' in the sense of 'moved'. No,draw_artist(artist1)does a new plotting ofartist1tocanvas(by renders the artist onto the current Agg buffer).canvas.update()which is aQtmethod schedules this plotting event (in the event loop?)FuncAnimationwhich I don't use. I have another question about blitting: from the official doc it seems the reused background is a bitmap? It's not actually pixelated bitmap that's being reused, right? (In the sense that you can still save the figure as the vector format.)draw_artist(ax.patch)just before updating the line. This re-draw the empty space in the axes, and cover the original plots.I am an end user of matplotlib, and I don't have much knowledge of the backstage stuff. I learned through scraping information online and reading bits and pieces of the documentation. If anyone out there is in a similar situation and end up with similar confusions, I hope this thread helps you.
clsmt commentedon Jan 8, 2018
Ok, one more questoin: why is it that in my original code, after all the updating is done, when you resize the firgure, all the previous curves but the latest are gone?
tacaswell commentedon Jan 9, 2018
draw_idleonly actually re-draws if the figure is 'stale'Artistsholds the logical representation of the plot (theLine2Dobjects) and the renders (which take theArtistsand turn them into some manifested version of the plot). Agg is a compositing renderer so it's bit map is just the accumulation of all the artists that have been drawn to it (but with no further connection) so callingdraw_artistjust changes pixels in the Agg buffer and (as you say)update(in a round about way) moves that buffer to the screen. See http://www.aosabook.org/en/matplotlib.htmlArtiststo the vector format based on their current state. This is the same reason resizing the figure clears the extra lines. When the figure is re-sized the number of pixels changes so it is automatically re-rendered from scratch based on the current state of the artists, hence giving you only one lineWhere would you have expected to look in the documentation to have found this information? Do the two PRs linked above make it better or worse?
tacaswell commentedon Jan 9, 2018
I would also consider using
FuncAnimationit does all of the blit management behind the scenes for you and by separating the update-my-artists logic from the source-my-data logic will (hopefully) lead to slightly easier to maintain and re-use code for you.clsmt commentedon May 19, 2018
Deepest apologies for not responding sooner.
I've tried to use
FuncAnimationseveral times, but with left it in the end. The reason being it's somewhat intrusive in the thinking process of designing my package. I have to work aroundFuncAnimation, not just "now plot A and B" then "let's update B". In my code, the user would be doing computations and then updating the plots accordingly (but I don't know when). Not just playing a certain animation and be done. Maybe it's just my way of thinking.Your example above is very good. I wish I had read this before I wrote my last project.
I am not sure where should this info go. Your explanation of the layers,
ArtistsvsAgg, is very helpful to me. The explanation ofblittingand "updateing" in the document was kind of confusing for me, since I have no prior exposure to these things. I would think explaining the actual plotting flow here would make a lot of sense.A blitting tutorial would be great as well.
clsmt commentedon Dec 29, 2018
A further problem with
set_animated, if theaxand thefigureare created in a class, theaxwon't show up, it's missing from the figure. Try the following code to reproduce:tacaswell commentedon Dec 30, 2018
This is not related to being in the class, you can reproduce it via
The issue is that
draw_artistrenders the artists onto the internal buffer, but does not move the rendered buffer to the screen, for that you need to useblitas above:clsmt commentedon Dec 30, 2018
where should I place this line? I tried place it after
fig.canvas.draw(), then I still get nothing. If placed before that, I getAttributeError: 'FigureCanvasQTAgg' object has no attribute 'renderer'.Also, in your example above , why do the axes show up initially? Is it because
axis not 'animated', onlyline1is?Also in the example,
cv.blit(fig.bbox)only draws those that were set to be animated, i.e.,lineandax2. Is that correct? I tried setting them not to be animated, i.e.,line1, = ax.plot(x, y, animated=False),ax2.set_animated(False). Seems the animation still works, with no fps change.The Qt method
update()does blitting internally. Thus, whether I do blitting explicitly myself, or do it viaupdate(), I shouldn't expect much speed difference, is that correct?In my original example, why does it matter where the event trigger is from the same figure or not? Is it because of the way
update()works internally?QuLogic commentedon Apr 2, 2021
This seems to be more like a user question than a feature request or a bug report, so I'm going to close this. You can continue any questions on https://discourse.matplotlib.org/