diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index 46a65f203e65..ca6b8b5a65e5 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -10,6 +10,16 @@ out what caused the breakage and how to fix it by updating your code. For new features that were added to Matplotlib, please see :ref:`whats-new`. +API Changes in 2.2.0 +==================== + +.. toctree:: + :glob: + :maxdepth: 1 + + next_api_changes/* + + API Changes in 2.1.2 ==================== diff --git a/doc/api/backend_qt4cairo_api.rst b/doc/api/backend_qt4cairo_api.rst new file mode 100644 index 000000000000..590465d7fbc0 --- /dev/null +++ b/doc/api/backend_qt4cairo_api.rst @@ -0,0 +1,8 @@ + +:mod:`matplotlib.backends.backend_qt4cairo` +=========================================== + +.. automodule:: matplotlib.backends.backend_qt4cairo + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/backend_qt5cairo_api.rst b/doc/api/backend_qt5cairo_api.rst new file mode 100644 index 000000000000..73df7ac128a1 --- /dev/null +++ b/doc/api/backend_qt5cairo_api.rst @@ -0,0 +1,8 @@ + +:mod:`matplotlib.backends.backend_qt5cairo` +=========================================== + +.. automodule:: matplotlib.backends.backend_qt5cairo + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/index_backend_api.rst b/doc/api/index_backend_api.rst index ce03c707e902..813c3770214e 100644 --- a/doc/api/index_backend_api.rst +++ b/doc/api/index_backend_api.rst @@ -19,7 +19,9 @@ backends backend_pgf_api.rst backend_ps_api.rst backend_qt4agg_api.rst + backend_qt4cairo_api.rst backend_qt5agg_api.rst + backend_qt5cairo_api.rst backend_svg_api.rst backend_tkagg_api.rst backend_webagg_api.rst diff --git a/doc/api/api_changes/01-11-2017-DS.rst b/doc/api/next_api_changes/01-11-2017-DS.rst similarity index 100% rename from doc/api/api_changes/01-11-2017-DS.rst rename to doc/api/next_api_changes/01-11-2017-DS.rst diff --git a/doc/api/api_changes/171225-AL-tickbools.rst b/doc/api/next_api_changes/171225-AL-tickbools.rst similarity index 100% rename from doc/api/api_changes/171225-AL-tickbools.rst rename to doc/api/next_api_changes/171225-AL-tickbools.rst diff --git a/doc/api/api_changes/2017-09-22-AL-contour.rst b/doc/api/next_api_changes/2017-09-22-AL-contour.rst similarity index 100% rename from doc/api/api_changes/2017-09-22-AL-contour.rst rename to doc/api/next_api_changes/2017-09-22-AL-contour.rst diff --git a/doc/api/api_changes/2017-09-22-AL-removal-of-deprecated-features.rst b/doc/api/next_api_changes/2017-09-22-AL-removal-of-deprecated-features.rst similarity index 100% rename from doc/api/api_changes/2017-09-22-AL-removal-of-deprecated-features.rst rename to doc/api/next_api_changes/2017-09-22-AL-removal-of-deprecated-features.rst diff --git a/doc/api/api_changes/2017-11-07-JMK.rst b/doc/api/next_api_changes/2017-11-07-JMK.rst similarity index 100% rename from doc/api/api_changes/2017-11-07-JMK.rst rename to doc/api/next_api_changes/2017-11-07-JMK.rst diff --git a/doc/api/api_changes/2017-11-19_svg_size.rst b/doc/api/next_api_changes/2017-11-19_svg_size.rst similarity index 100% rename from doc/api/api_changes/2017-11-19_svg_size.rst rename to doc/api/next_api_changes/2017-11-19_svg_size.rst diff --git a/doc/api/api_changes/2017-11-23-AL.rst b/doc/api/next_api_changes/2017-11-23-AL.rst similarity index 100% rename from doc/api/api_changes/2017-11-23-AL.rst rename to doc/api/next_api_changes/2017-11-23-AL.rst diff --git a/doc/api/api_changes/2017-11-24-AL.rst b/doc/api/next_api_changes/2017-11-24-AL.rst similarity index 100% rename from doc/api/api_changes/2017-11-24-AL.rst rename to doc/api/next_api_changes/2017-11-24-AL.rst diff --git a/doc/api/api_changes/2017-11-31-AL.rst b/doc/api/next_api_changes/2017-11-31-AL.rst similarity index 100% rename from doc/api/api_changes/2017-11-31-AL.rst rename to doc/api/next_api_changes/2017-11-31-AL.rst diff --git a/doc/api/api_changes/2017-12-01-AL.rst b/doc/api/next_api_changes/2017-12-01-AL.rst similarity index 100% rename from doc/api/api_changes/2017-12-01-AL.rst rename to doc/api/next_api_changes/2017-12-01-AL.rst diff --git a/doc/api/api_changes/2017-12-02-AL.rst b/doc/api/next_api_changes/2017-12-02-AL.rst similarity index 100% rename from doc/api/api_changes/2017-12-02-AL.rst rename to doc/api/next_api_changes/2017-12-02-AL.rst diff --git a/doc/api/api_changes/2017-12-06-imports-AL.rst b/doc/api/next_api_changes/2017-12-06-imports-AL.rst similarity index 100% rename from doc/api/api_changes/2017-12-06-imports-AL.rst rename to doc/api/next_api_changes/2017-12-06-imports-AL.rst diff --git a/doc/api/api_changes/2017-12-10-TH.rst b/doc/api/next_api_changes/2017-12-10-TH.rst similarity index 100% rename from doc/api/api_changes/2017-12-10-TH.rst rename to doc/api/next_api_changes/2017-12-10-TH.rst diff --git a/doc/api/api_changes/2017-12-14-JMK.rst b/doc/api/next_api_changes/2017-12-14-JMK.rst similarity index 100% rename from doc/api/api_changes/2017-12-14-JMK.rst rename to doc/api/next_api_changes/2017-12-14-JMK.rst diff --git a/doc/api/api_changes/2017-12-15-AL.rst b/doc/api/next_api_changes/2017-12-15-AL.rst similarity index 100% rename from doc/api/api_changes/2017-12-15-AL.rst rename to doc/api/next_api_changes/2017-12-15-AL.rst diff --git a/doc/api/api_changes/2017-12-17-AL.rst b/doc/api/next_api_changes/2017-12-17-AL.rst similarity index 99% rename from doc/api/api_changes/2017-12-17-AL.rst rename to doc/api/next_api_changes/2017-12-17-AL.rst index 7e8202bc6f1c..02a3003dd535 100644 --- a/doc/api/api_changes/2017-12-17-AL.rst +++ b/doc/api/next_api_changes/2017-12-17-AL.rst @@ -2,6 +2,7 @@ Removal of deprecated rcParams `````````````````````````````` The following deprecated rcParams have been removed: + - ``axes.color_cycle`` (see ``axes.prop_cycle``), - ``legend.isaxes``, - ``svg.embed_char_paths`` (see ``svg.fonttype``), diff --git a/doc/api/api_changes/2017-12-26-AL.rst b/doc/api/next_api_changes/2017-12-26-AL.rst similarity index 100% rename from doc/api/api_changes/2017-12-26-AL.rst rename to doc/api/next_api_changes/2017-12-26-AL.rst diff --git a/doc/api/api_changes/2018-01-29-AL.rst b/doc/api/next_api_changes/2018-01-29-AL.rst similarity index 100% rename from doc/api/api_changes/2018-01-29-AL.rst rename to doc/api/next_api_changes/2018-01-29-AL.rst diff --git a/doc/api/next_api_changes/2018-02-03-AL.rst b/doc/api/next_api_changes/2018-02-03-AL.rst new file mode 100644 index 000000000000..d011616d8975 --- /dev/null +++ b/doc/api/next_api_changes/2018-02-03-AL.rst @@ -0,0 +1,73 @@ +Changes to Qt backend class MRO +``````````````````````````````` + +To support both Agg and cairo rendering for Qt backends all of the +non-Agg specific code previously in +:class:`.backend_qt5agg.FigureCanvasQTAggBase` has been moved to +:class:`.backend_qt5.FigureCanvasQT` so it can be shared with the cairo +implementation. The :meth:`.FigureCanvasQTAggBase.paintEvent`, +:meth:`.FigureCanvasQTAggBase.blit`, and +:meth:`.FigureCanvasQTAggBase.print_figure` methods have moved to +:meth:`.FigureCanvasQTAgg.paintEvent`, :meth:`.FigureCanvasQTAgg.blit`, and +:meth:`.FigureCanvasQTAgg.print_figure`. The first two methods assume that +the instance is also a :class:`QWidget` so to use +:class:`FigureCanvasQTAggBase` it was required to multiple inherit +from a :class:`QWidget` sub-class. + +Having moved all of its methods either up or down the class hierarchy +:class:`FigureCanvasQTAggBase` has been deprecated. To do this with +out warning and to preserve as much API as possible, +:class:`.backend_qt5.FigureCanvasQTAggBase` now inherits from +:class:`.backend_qt5.FigureCanvasQTAgg`. + +The MRO for :class:`FigureCanvasQTAgg` and +:class:`FigureCanvasQTAggBase` used to be :: + + + [matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg, + matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase, + matplotlib.backends.backend_agg.FigureCanvasAgg, + matplotlib.backends.backend_qt5.FigureCanvasQT, + PyQt5.QtWidgets.QWidget, + PyQt5.QtCore.QObject, + sip.wrapper, + PyQt5.QtGui.QPaintDevice, + sip.simplewrapper, + matplotlib.backend_bases.FigureCanvasBase, + object] + +and :: + + + [matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase, + matplotlib.backends.backend_agg.FigureCanvasAgg, + matplotlib.backend_bases.FigureCanvasBase, + object] + + +respectively. They are now :: + + [matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg, + matplotlib.backends.backend_agg.FigureCanvasAgg, + matplotlib.backends.backend_qt5.FigureCanvasQT, + PyQt5.QtWidgets.QWidget, + PyQt5.QtCore.QObject, + sip.wrapper, + PyQt5.QtGui.QPaintDevice, + sip.simplewrapper, + matplotlib.backend_bases.FigureCanvasBase, + object] + +and :: + + [matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase, + matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg, + matplotlib.backends.backend_agg.FigureCanvasAgg, + matplotlib.backends.backend_qt5.FigureCanvasQT, + PyQt5.QtWidgets.QWidget, + PyQt5.QtCore.QObject, + sip.wrapper, + PyQt5.QtGui.QPaintDevice, + sip.simplewrapper, + matplotlib.backend_bases.FigureCanvasBase, + object] diff --git a/doc/api/api_changes/README.rst b/doc/api/next_api_changes/README.rst similarity index 89% rename from doc/api/api_changes/README.rst rename to doc/api/next_api_changes/README.rst index f59c3cb6edc4..c2b71d495ca9 100644 --- a/doc/api/api_changes/README.rst +++ b/doc/api/next_api_changes/README.rst @@ -1,3 +1,7 @@ +Adding API change notes +``````````````````````` + + For changes which require an entry in `api_changes.rst` please create a file in this folder with the name :file:`YYYY-MM-DD-[initials].rst` (ex :file:`2014-07-31-TAC.rst`) with contents following the form: :: diff --git a/doc/api/api_changes/removed-attributes.rst b/doc/api/next_api_changes/removed-attributes.rst similarity index 100% rename from doc/api/api_changes/removed-attributes.rst rename to doc/api/next_api_changes/removed-attributes.rst diff --git a/doc/users/next_whats_new/more-cairo.rst b/doc/users/next_whats_new/more-cairo.rst new file mode 100644 index 000000000000..8f6f62f630a3 --- /dev/null +++ b/doc/users/next_whats_new/more-cairo.rst @@ -0,0 +1,5 @@ +Cairo rendering for Qt canvases +------------------------------- + +The new ``Qt4Cairo`` and ``Qt5Cairo`` backends allow Qt canvases to use Cairo +rendering instead of Agg. diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 13a77762f982..a43efda774b9 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -429,6 +429,9 @@ def draw(self): # if toolbar: # toolbar.set_cursor(cursors.WAIT) self.figure.draw(self.renderer) + # A GUI class may be need to update a window using this draw, so + # don't forget to call the superclass. + super(FigureCanvasAgg, self).draw() finally: # if toolbar: # toolbar.set_cursor(toolbar._lastCursor) diff --git a/lib/matplotlib/backends/backend_qt4cairo.py b/lib/matplotlib/backends/backend_qt4cairo.py new file mode 100644 index 000000000000..f94851da382d --- /dev/null +++ b/lib/matplotlib/backends/backend_qt4cairo.py @@ -0,0 +1,6 @@ +from .backend_qt5cairo import _BackendQT5Cairo + + +@_BackendQT5Cairo.export +class _BackendQT4Cairo(_BackendQT5Cairo): + pass diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 6b5e8bbfb54f..4d0bd2438e1b 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -8,6 +8,7 @@ import signal import sys from six import unichr +import traceback import matplotlib @@ -226,19 +227,16 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase): # QtCore.Qt.XButton2: None, } - def _update_figure_dpi(self): - dpi = self._dpi_ratio * self.figure._original_dpi - self.figure._set_dpi(dpi, forward=False) - @_allow_super_init def __init__(self, figure): _create_qApp() super(FigureCanvasQT, self).__init__(figure=figure) - figure._original_dpi = figure.dpi self.figure = figure + # We don't want to scale up the figure DPI more than once. + # Note, we don't handle a signal for changing DPI yet. + figure._original_dpi = figure.dpi self._update_figure_dpi() - self.resize(*self.get_width_height()) # In cases with mixed resolution displays, we need to be careful if the # dpi_ratio changes - in this case we need to resize the canvas # accordingly. We could watch for screenChanged events from Qt, but @@ -248,13 +246,23 @@ def __init__(self, figure): # needed. self._dpi_ratio_prev = None + self._draw_pending = False + self._is_drawing = False + self._draw_rect_callback = lambda painter: None + + self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent) self.setMouseTracking(True) + self.resize(*self.get_width_height()) # Key auto-repeat enabled by default self._keyautorepeat = True palette = QtGui.QPalette(QtCore.Qt.white) self.setPalette(palette) + def _update_figure_dpi(self): + dpi = self._dpi_ratio * self.figure._original_dpi + self.figure._set_dpi(dpi, forward=False) + @property def _dpi_ratio(self): # Not available on Qt4 or some older Qt5. @@ -263,6 +271,26 @@ def _dpi_ratio(self): except AttributeError: return 1 + def _update_dpi(self): + # As described in __init__ above, we need to be careful in cases with + # mixed resolution displays if dpi_ratio is changing between painting + # events. + # Return whether we triggered a resizeEvent (and thus a paintEvent) + # from within this function. + if self._dpi_ratio != self._dpi_ratio_prev: + # We need to update the figure DPI. + self._update_figure_dpi() + self._dpi_ratio_prev = self._dpi_ratio + # The easiest way to resize the canvas is to emit a resizeEvent + # since we implement all the logic for resizing the canvas for + # that event. + event = QtGui.QResizeEvent(self.size(), self.size()) + self.resizeEvent(event) + # resizeEvent triggers a paintEvent itself, so we exit this one + # (after making sure that the event is immediately handled). + return True + return False + def get_width_height(self): w, h = FigureCanvasBase.get_width_height(self) return int(w / self._dpi_ratio), int(h / self._dpi_ratio) @@ -453,6 +481,60 @@ def stop_event_loop(self, event=None): if hasattr(self, "_event_loop"): self._event_loop.quit() + def draw(self): + """Render the figure, and queue a request for a Qt draw. + """ + # The renderer draw is done here; delaying causes problems with code + # that uses the result of the draw() to update plot elements. + if self._is_drawing: + return + self._is_drawing = True + try: + super(FigureCanvasQT, self).draw() + finally: + self._is_drawing = False + self.update() + + def draw_idle(self): + """Queue redraw of the Agg buffer and request Qt paintEvent. + """ + # The Agg draw needs to be handled by the same thread matplotlib + # modifies the scene graph from. Post Agg draw request to the + # current event loop in order to ensure thread affinity and to + # accumulate multiple draw requests from event handling. + # TODO: queued signal connection might be safer than singleShot + if not (self._draw_pending or self._is_drawing): + self._draw_pending = True + QtCore.QTimer.singleShot(0, self._draw_idle) + + def _draw_idle(self): + if self.height() < 0 or self.width() < 0: + self._draw_pending = False + if not self._draw_pending: + return + try: + self.draw() + except Exception: + # Uncaught exceptions are fatal for PyQt5, so catch them instead. + traceback.print_exc() + finally: + self._draw_pending = False + + def drawRectangle(self, rect): + # Draw the zoom rectangle to the QPainter. _draw_rect_callback needs + # to be called at the end of paintEvent. + if rect is not None: + def _draw_rect_callback(painter): + pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio, + QtCore.Qt.DotLine) + painter.setPen(pen) + painter.drawRect(*(pt / self._dpi_ratio for pt in rect)) + else: + def _draw_rect_callback(painter): + return + self._draw_rect_callback = _draw_rect_callback + self.update() + class MainWindow(QtWidgets.QMainWindow): closing = QtCore.Signal() diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index 777167132ad6..f0268299bad4 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -7,7 +7,6 @@ import six import ctypes -import traceback from matplotlib import cbook from matplotlib.transforms import Bbox @@ -19,32 +18,11 @@ from .qt_compat import QT_API -class FigureCanvasQTAggBase(FigureCanvasAgg): - """ - The canvas the figure renders into. Calls the draw and print fig - methods, creates the renderers, etc... - - Attributes - ---------- - figure : `matplotlib.figure.Figure` - A high-level Figure instance - - """ +class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT): def __init__(self, figure): - super(FigureCanvasQTAggBase, self).__init__(figure=figure) - self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent) - self._agg_draw_pending = False - self._agg_is_drawing = False + super(FigureCanvasQTAgg, self).__init__(figure=figure) self._bbox_queue = [] - self._drawRect = None - - def drawRectangle(self, rect): - if rect is not None: - self._drawRect = [pt / self._dpi_ratio for pt in rect] - else: - self._drawRect = None - self.update() @property @cbook.deprecated("2.1") @@ -57,27 +35,10 @@ def paintEvent(self, e): In Qt, all drawing should be done inside of here when a widget is shown onscreen. """ - # if there is a pending draw, run it now as we need the updated render - # to paint the widget - if self._agg_draw_pending: - self.__draw_idle_agg() - # As described in __init__ above, we need to be careful in cases with - # mixed resolution displays if dpi_ratio is changing between painting - # events. - if self._dpi_ratio != self._dpi_ratio_prev: - # We need to update the figure DPI - self._update_figure_dpi() - self._dpi_ratio_prev = self._dpi_ratio - # The easiest way to resize the canvas is to emit a resizeEvent - # since we implement all the logic for resizing the canvas for - # that event. - event = QtGui.QResizeEvent(self.size(), self.size()) - # We use self.resizeEvent here instead of QApplication.postEvent - # since the latter doesn't guarantee that the event will be emitted - # straight away, and this causes visual delays in the changes. - self.resizeEvent(event) - # resizeEvent triggers a paintEvent itself, so we exit this one. + if self._update_dpi(): + # The dpi update triggered its own paintEvent. return + self._draw_idle() # Only does something if a draw is pending. # if the canvas does not have a renderer, then give up and wait for # FigureCanvasAgg.draw(self) to be called @@ -100,72 +61,20 @@ def paintEvent(self, e): reg = self.copy_from_bbox(bbox) buf = reg.to_string_argb() qimage = QtGui.QImage(buf, w, h, QtGui.QImage.Format_ARGB32) + # Adjust the buf reference count to work around a memory leak bug + # in QImage under PySide on Python 3. + if QT_API == 'PySide' and six.PY3: + ctypes.c_long.from_address(id(buf)).value = 1 if hasattr(qimage, 'setDevicePixelRatio'): # Not available on Qt4 or some older Qt5. qimage.setDevicePixelRatio(self._dpi_ratio) origin = QtCore.QPoint(l, self.renderer.height - t) painter.drawImage(origin / self._dpi_ratio, qimage) - # Adjust the buf reference count to work around a memory - # leak bug in QImage under PySide on Python 3. - if QT_API == 'PySide' and six.PY3: - ctypes.c_long.from_address(id(buf)).value = 1 - # draw the zoom rectangle to the QPainter - if self._drawRect is not None: - pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio, - QtCore.Qt.DotLine) - painter.setPen(pen) - x, y, w, h = self._drawRect - painter.drawRect(x, y, w, h) + self._draw_rect_callback(painter) painter.end() - def draw(self): - """Draw the figure with Agg, and queue a request for a Qt draw. - """ - # The Agg draw is done here; delaying causes problems with code that - # uses the result of the draw() to update plot elements. - if self._agg_is_drawing: - return - - self._agg_is_drawing = True - try: - super(FigureCanvasQTAggBase, self).draw() - finally: - self._agg_is_drawing = False - self.update() - - def draw_idle(self): - """Queue redraw of the Agg buffer and request Qt paintEvent. - """ - # The Agg draw needs to be handled by the same thread matplotlib - # modifies the scene graph from. Post Agg draw request to the - # current event loop in order to ensure thread affinity and to - # accumulate multiple draw requests from event handling. - # TODO: queued signal connection might be safer than singleShot - if not (self._agg_draw_pending or self._agg_is_drawing): - self._agg_draw_pending = True - QtCore.QTimer.singleShot(0, self.__draw_idle_agg) - - def __draw_idle_agg(self, *args): - # if nothing to do, bail - if not self._agg_draw_pending: - return - # we have now tried this function at least once, do not run - # again until re-armed. Doing this here rather than after - # protects against recursive calls triggered through self.draw - # The recursive call is via `repaintEvent` - self._agg_draw_pending = False - # if negative size, bail - if self.height() < 0 or self.width() < 0: - return - try: - # actually do the drawing - self.draw() - except Exception: - # Uncaught exceptions are fatal for PyQt5, so catch them instead. - traceback.print_exc() - def blit(self, bbox=None): """Blit the region in bbox. """ @@ -182,23 +91,13 @@ def blit(self, bbox=None): self.repaint(l, self.renderer.height / self._dpi_ratio - t, w, h) def print_figure(self, *args, **kwargs): - super(FigureCanvasQTAggBase, self).print_figure(*args, **kwargs) + super(FigureCanvasQTAgg, self).print_figure(*args, **kwargs) self.draw() -class FigureCanvasQTAgg(FigureCanvasQTAggBase, FigureCanvasQT): - """ - The canvas the figure renders into. Calls the draw and print fig - methods, creates the renderers, etc. - - Modified to import from Qt5 backend for new-style mouse events. - - Attributes - ---------- - figure : `matplotlib.figure.Figure` - A high-level Figure instance - - """ +@cbook.deprecated("2.2") +class FigureCanvasQTAggBase(FigureCanvasQTAgg): + pass @_BackendQT5.export diff --git a/lib/matplotlib/backends/backend_qt5cairo.py b/lib/matplotlib/backends/backend_qt5cairo.py new file mode 100644 index 000000000000..6778716f5d9e --- /dev/null +++ b/lib/matplotlib/backends/backend_qt5cairo.py @@ -0,0 +1,38 @@ +from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo +from .backend_qt5 import QtCore, QtGui, _BackendQT5, FigureCanvasQT +from .qt_compat import QT_API + + +class FigureCanvasQTCairo(FigureCanvasQT, FigureCanvasCairo): + def __init__(self, figure): + super(FigureCanvasQTCairo, self).__init__(figure=figure) + self._renderer = RendererCairo(self.figure.dpi) + + def paintEvent(self, event): + self._update_dpi() + dpi_ratio = self._dpi_ratio + width = dpi_ratio * self.width() + height = dpi_ratio * self.height() + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + self._renderer.set_ctx_from_surface(surface) + self._renderer.set_width_height(width, height) + self.figure.draw(self._renderer) + buf = surface.get_data() + qimage = QtGui.QImage(buf, width, height, + QtGui.QImage.Format_ARGB32_Premultiplied) + # Adjust the buf reference count to work around a memory leak bug in + # QImage under PySide on Python 3. + if QT_API == 'PySide' and six.PY3: + ctypes.c_long.from_address(id(buf)).value = 1 + if hasattr(qimage, 'setDevicePixelRatio'): + # Not available on Qt4 or some older Qt5. + qimage.setDevicePixelRatio(dpi_ratio) + painter = QtGui.QPainter(self) + painter.drawImage(0, 0, qimage) + self._draw_rect_callback(painter) + painter.end() + + +@_BackendQT5.export +class _BackendQT5Cairo(_BackendQT5): + FigureCanvas = FigureCanvasQTCairo diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 7b8baf8aae3a..1a01f0cade9c 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -29,20 +29,22 @@ from matplotlib.fontconfig_pattern import parse_fontconfig_pattern from matplotlib.colors import is_color_like - # Don't let the original cycler collide with our validating cycler from cycler import Cycler, cycler as ccycler -# interactive_bk = ['gtk', 'gtkagg', 'gtkcairo', 'qt4agg', -# 'tkagg', 'wx', 'wxagg', 'webagg'] + # The capitalized forms are needed for ipython at present; this may # change for later versions. - interactive_bk = ['GTK', 'GTKAgg', 'GTKCairo', 'MacOSX', 'Qt4Agg', 'Qt5Agg', 'TkAgg', 'WX', 'WXAgg', 'GTK3Cairo', 'GTK3Agg', 'WebAgg', 'nbAgg'] - - +interactive_bk = ['GTK', 'GTKAgg', 'GTKCairo', 'GTK3Agg', 'GTK3Cairo', + 'MacOSX', + 'nbAgg', + 'Qt4Agg', 'Qt4Cairo', 'Qt5Agg', 'Qt5Cairo', + 'TkAgg', + 'WebAgg', + 'WX', 'WXAgg'] non_interactive_bk = ['agg', 'cairo', 'gdk', 'pdf', 'pgf', 'ps', 'svg', 'template'] all_backends = interactive_bk + non_interactive_bk diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 07d5759bbe0c..ca174878ca13 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -21,6 +21,7 @@ def _get_testable_interactive_backends(): for deps, backend in [(["cairocffi", "pgi"], "gtk3agg"), (["cairocffi", "pgi"], "gtk3cairo"), (["PyQt5"], "qt5agg"), + (["cairocffi", "PyQt5"], "qt5cairo"), (["tkinter"], "tkagg"), (["wx"], "wxagg")]: reason = None
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: