Repository navigation
Closed
Milestone
Description
Bug report
Bug summary
My app worked well in previous matplotlib versions. After upgrading to 2.1.0, it is completely broken, flooded with QWidget::repaint: Recursive repaint detected messages.
Code for reproduction
import matplotlib as mpl
import matplotlib.lines
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
class Qt5Canvas(FigureCanvasQTAgg):
def __init__(self, parent=None, aspect_ratio=3/4, dpi=96,
width=None, height=None):
if width is not None:
figsize = (width, height)
else:
figsize = mpl.figure.figaspect(aspect_ratio)
fig = mpl.figure.Figure(figsize=figsize, dpi=dpi)
super().__init__(fig)
self.fig = fig
self.ax = fig.add_subplot(111)
self.setParent(parent)
class TestMplBug:
def __init__(self):
self.artist = None
self.canvas = None
self._blit_region = None
self._drawed = False
def set_canvas(self, canvas):
self.canvas = canvas
self.canvas.mpl_connect('draw_event', self._on_mpl_draw)
def _on_mpl_draw(self, event):
self._drawed = True
self.blit_update()
def draw(self):
self._on_draw()
self.canvas.draw()
def _on_draw(self):
if self.artist is None:
import numpy as np
x = np.arange(0.0, 3.0, 0.01)
y = np.sin(2*np.pi*x)
line = mpl.lines.Line2D(x, y, color='red', animated=True)
self.canvas.ax.add_line(line)
self.artist = line
def blit_update(self):
if self.canvas is None:
return
self.canvas.ax.draw_artist(self.artist)
if self._drawed:
self.canvas.blit()
def main():
from PyQt5 import QtWidgets, QtCore, QtGui
import sys
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
t = TestMplBug()
q = Qt5Canvas()
t.set_canvas(q)
t.draw()
t.canvas.show()
try:
__IPYTHON__
except NameError as e:
app.exec_()
return t
if __name__ == '__main__':
main()Actual outcome
QWidget::repaint: Recursive repaint detected
<many similar entries omited...>
QWidget::repaint: Recursive repaint detected
Traceback (most recent call last):
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/backends/backend_qt5agg.py", line 149, in __draw_idle_agg
self.draw()
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/backends/backend_qt5agg.py", line 127, in draw
super(FigureCanvasQTAggBase, self).draw()
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py", line 430, in draw
self.figure.draw(self.renderer)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 55, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/figure.py", line 1295, in draw
renderer, self, artists, self.suppressComposite)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/image.py", line 138, in _draw_list_compositing_images
a.draw(renderer)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 55, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/axes/_base.py", line 2399, in draw
mimage._draw_list_compositing_images(renderer, self, artists)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/image.py", line 138, in _draw_list_compositing_images
a.draw(renderer)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 55, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/axis.py", line 1133, in draw
ticks_to_draw = self._update_ticks(renderer)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/axis.py", line 974, in _update_ticks
tick_tups = list(self.iter_ticks())
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/axis.py", line 917, in iter_ticks
majorLocs = self.major.locator()
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/ticker.py", line 1957, in __call__
return self.tick_values(vmin, vmax)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/ticker.py", line 1965, in tick_values
locs = self._raw_ticks(vmin, vmax)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/ticker.py", line 1907, in _raw_ticks
nbins = np.clip(self.axis.get_tick_space(),
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/axis.py", line 2039, in get_tick_space
tick = self._get_tick(True)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/axis.py", line 1748, in _get_tick
return XTick(self.axes, 0, '', major=major, **tick_kw)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/axis.py", line 152, in __init__
self.apply_tickdir(tickdir)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/axis.py", line 403, in apply_tickdir
self.stale = True
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 241, in stale
self.stale_callback(self, val)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 68, in _stale_axes_callback
self.axes.stale = val
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 241, in stale
self.stale_callback(self, val)
File "/home/statistician/.local/lib/python3.6/site-packages/matplotlib/figure.py", line 56, in _stale_figure_callback
self.figure.stale = val
RecursionError: maximum recursion depth exceeded while calling a Python object
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::end: Painter not active, aborted
<many similar entries omited...>
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::end: Painter not active, aborted
Expected outcome
Draw properly as in previous versions.
Matplotlib version
- Operating system: Arch Linux
- Matplotlib version: 2.1.0 (installed from pip)
- Matplotlib backend (
print(matplotlib.get_backend())): qt5 - Python version: 3.6.2
- Jupyter version (if applicable):
- Other libraries:
Metadata
Metadata
Assignees
Labels
No labels
Activity
tacaswell commentedon Oct 15, 2017
If you comment out
it will work as expected on 2.1 and seems to work correctly on 2.0.2 and 1.5.3.
The issue is that we now defer actually rendering the canvas as long as physically possible which means that the draw now happens inside of
paintEvent. This means that the draw_event and the associated callbacks also happen inside of thepaintEvent. You are callingblitwhich in internally usesrepaintwhich at http://doc.qt.io/qt-5/qwidget.html#repaint comes with the warningwhich is what you are getting here.
The reason that this works without calling
blitis that the figure is first drawn to an internal buffer. Callingdraw_artistwill update that buffer (but not push it to the screen). In all cases, the draw_event comes out before the buffer is copied to the screen so the blit is redundant, you update the relevant rectangle and then the regular pass through will replace it (and the rest of the canvas).kawing-chiu commentedon Oct 15, 2017
Oh, I see. After using blit() for quite a long time and built complex app on it, I thought I've understood it quite well... So to sum up: 1) After all callbacks for draw_event are processed, matplotlib will handle the copying of the internal buffer into the backend buffer. 2) Calling blit() is not allowed in draw_event's handlers. Should I expect these behaviors in other backends?
tacaswell commentedon Oct 15, 2017
Yes, it looks like the only place that
CanvasBase.draw_event(which is the function that triggers the draw_event callbacks) is only called at the bottom ofFigure.drawso for all of the backends (the ship) the callbacks will be processed before the result of the rendering is pushed to the screen (or saved)this may be a Qt only restriction and will depend on the details of how the backend implements blitting (or not)
If it makes you feel any better, I only have this on the top of my head because there was a fair amount of work done around managing dpi changes (from moving Qt windows between screens), resizing the window, and blitting for 2.1.
MNT: project Qt backends from recursive draws
MNT: project Qt backends from recursive draws
MNT: project Qt backends from recursive draws
tacaswell commentedon Oct 23, 2017
Closed by #9505
kawing-chiu commentedon Oct 25, 2017
Thank you @tacaswell for the prompt and kind help!