Skip to content

Commit b37023d

Browse files
committed
QtCairo backend.
1 parent bf2977a commit b37023d

File tree

5 files changed

+86
-54
lines changed

5 files changed

+86
-54
lines changed

lib/matplotlib/backends/backend_qt5.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,11 @@ def __init__(self, figure):
192192
# http://pyqt.sourceforge.net/Docs/PyQt5/pyqt4_differences.html#cooperative-multi-inheritance
193193
super(FigureCanvasQT, self).__init__(figure=figure)
194194
self.figure = figure
195+
self._draw_pending = False
196+
self._draw_rect_callback = lambda painter: None
197+
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
195198
self.setMouseTracking(True)
196-
w, h = self.get_width_height()
197-
self.resize(w, h)
198-
199+
self.resize(*self.get_width_height())
199200
# Key auto-repeat enabled by default
200201
self._keyautorepeat = True
201202

@@ -381,6 +382,53 @@ def stop_event_loop(self, event=None):
381382
if hasattr(self, "_event_loop"):
382383
self._event_loop.quit()
383384

385+
def draw(self):
386+
"""Render the figure, and queue a request for a Qt draw.
387+
"""
388+
# The Agg draw is done here; delaying causes problems with code that
389+
# uses the result of the draw() to update plot elements.
390+
super(FigureCanvasQT, self).draw()
391+
self.update()
392+
393+
def draw_idle(self):
394+
"""Queue redraw of the Agg buffer and request Qt paintEvent.
395+
"""
396+
# The Agg draw needs to be handled by the same thread matplotlib
397+
# modifies the scene graph from. Post Agg draw request to the
398+
# current event loop in order to ensure thread affinity and to
399+
# accumulate multiple draw requests from event handling.
400+
# TODO: queued signal connection might be safer than singleShot
401+
if not self._draw_pending:
402+
self._draw_pending = True
403+
QtCore.QTimer.singleShot(0, self._draw_idle)
404+
405+
def _draw_idle(self):
406+
if self.height() < 0 or self.width() < 0:
407+
self._draw_pending = False
408+
return
409+
try:
410+
self.draw()
411+
except Exception:
412+
# Uncaught exceptions are fatal for PyQt5, so catch them instead.
413+
traceback.print_exc()
414+
finally:
415+
self._draw_pending = False
416+
417+
def drawRectangle(self, rect):
418+
# Draw the zoom rectangle to the QPainter. _draw_rect_callback needs
419+
# to be called at the end of paintEvent.
420+
if rect is not None:
421+
def _draw_rect_callback(painter):
422+
pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio,
423+
QtCore.Qt.DotLine)
424+
painter.setPen(pen)
425+
painter.drawRect(*(pt / self._dpi_ratio for pt in rect))
426+
else:
427+
def _draw_rect_callback(painter):
428+
return
429+
self._draw_rect_callback = _draw_rect_callback
430+
self.update()
431+
384432

385433
class MainWindow(QtWidgets.QMainWindow):
386434
closing = QtCore.Signal()

lib/matplotlib/backends/backend_qt5agg.py

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,7 @@ class FigureCanvasQTAggBase(FigureCanvasAgg):
3232

3333
def __init__(self, figure):
3434
super(FigureCanvasQTAggBase, self).__init__(figure=figure)
35-
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
36-
self._agg_draw_pending = False
3735
self._bbox_queue = []
38-
self._drawRect = None
39-
40-
def drawRectangle(self, rect):
41-
if rect is not None:
42-
self._drawRect = [pt / self._dpi_ratio for pt in rect]
43-
else:
44-
self._drawRect = None
45-
self.update()
4636

4737
def paintEvent(self, e):
4838
"""Copy the image from the Agg canvas to the qt.drawable.
@@ -77,49 +67,10 @@ def paintEvent(self, e):
7767
if QT_API == 'PySide' and six.PY3:
7868
ctypes.c_long.from_address(id(buf)).value = 1
7969

80-
# draw the zoom rectangle to the QPainter
81-
if self._drawRect is not None:
82-
pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio,
83-
QtCore.Qt.DotLine)
84-
painter.setPen(pen)
85-
x, y, w, h = self._drawRect
86-
painter.drawRect(x, y, w, h)
70+
self._draw_rect_callback(painter)
8771

8872
painter.end()
8973

90-
def draw(self):
91-
"""Draw the figure with Agg, and queue a request for a Qt draw.
92-
"""
93-
# The Agg draw is done here; delaying causes problems with code that
94-
# uses the result of the draw() to update plot elements.
95-
super(FigureCanvasQTAggBase, self).draw()
96-
self.update()
97-
98-
def draw_idle(self):
99-
"""Queue redraw of the Agg buffer and request Qt paintEvent.
100-
"""
101-
# The Agg draw needs to be handled by the same thread matplotlib
102-
# modifies the scene graph from. Post Agg draw request to the
103-
# current event loop in order to ensure thread affinity and to
104-
# accumulate multiple draw requests from event handling.
105-
# TODO: queued signal connection might be safer than singleShot
106-
if not self._agg_draw_pending:
107-
self._agg_draw_pending = True
108-
QtCore.QTimer.singleShot(0, self.__draw_idle_agg)
109-
110-
def __draw_idle_agg(self, *args):
111-
if self.height() < 0 or self.width() < 0:
112-
self._agg_draw_pending = False
113-
return
114-
try:
115-
super(FigureCanvasQTAggBase, self).draw()
116-
self.update()
117-
except Exception:
118-
# Uncaught exceptions are fatal for PyQt5, so catch them instead.
119-
traceback.print_exc()
120-
finally:
121-
self._agg_draw_pending = False
122-
12374
def blit(self, bbox=None):
12475
"""Blit the region in bbox.
12576
"""
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import cairocffi as cairo
2+
3+
from .backend_cairo import RendererCairo
4+
from .backend_qt5 import (
5+
QtCore, QtGui, _BackendQT5, FigureCanvasQT, FigureManagerQT,
6+
NavigationToolbar2QT)
7+
8+
9+
class FigureCanvasQTCairo(FigureCanvasQT):
10+
def __init__(self, figure):
11+
super(FigureCanvasQTCairo, self).__init__(figure=figure)
12+
self._renderer = RendererCairo(self.figure.dpi)
13+
14+
def paintEvent(self, event):
15+
width = self.width()
16+
height = self.height()
17+
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
18+
self._renderer.set_ctx_from_surface(surface)
19+
# This should be really done by set_ctx_from_surface...
20+
self._renderer.set_width_height(width, height)
21+
self.figure.draw(self._renderer)
22+
qimage = QtGui.QImage(surface.get_data(), width, height,
23+
QtGui.QImage.Format_ARGB32_Premultiplied)
24+
painter = QtGui.QPainter(self)
25+
painter.drawImage(QtCore.QPoint(0, 0), qimage)
26+
self._draw_rect_callback(painter)
27+
painter.end()
28+
29+
30+
@_BackendQT5.export
31+
class _BackendQT5Cairo(_BackendQT5):
32+
FigureCanvas = FigureCanvasQTCairo

lib/matplotlib/rcsetup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
# change for later versions.
4040

4141
interactive_bk = ['GTK', 'GTKAgg', 'GTKCairo', 'MacOSX',
42-
'Qt4Agg', 'Qt5Agg', 'TkAgg', 'WX', 'WXAgg',
42+
'Qt4Agg', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'WX', 'WXAgg',
4343
'GTK3Cairo', 'GTK3Agg', 'WebAgg', 'nbAgg']
4444

4545

lib/matplotlib/tests/test_backends_interactive.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def _get_testable_interactive_backends():
2929
backend)
3030
for module_name, backend in [
3131
("PyQt5", "qt5agg"),
32+
("PyQt5", "qt5cairo"),
3233
("tkinter", "tkagg"),
3334
("wx", "wxagg")]]
3435

0 commit comments

Comments
 (0)