在matplotlib中改变zoom-rect的边缘颜色

Posted

技术标签:

【中文标题】在matplotlib中改变zoom-rect的边缘颜色【英文标题】:Changing the edge color of zoom-rect in matplotlib 【发布时间】:2015-02-19 05:15:00 【问题描述】:

我使用 python+matplotlib+pyqt 编写了一个用于光谱分析的应用程序。绘图需要在应用程序中具有带有白色轴和符号的黑色背景。我保留了 matplotlib 的默认导航工具栏。由于反转颜色设置,我遇到的一个问题是缩放矩形的边缘是不可见的,因为它是黑色的。有没有一种简单的方法可以将缩放矩形的边缘颜色更改为明亮的颜色,例如白色。

提前谢谢你。

【问题讨论】:

您能否添加一些您正在使用的代码,以便我们准确了解您所做的工作? 【参考方案1】:

添加到 Gloweye 的响应中,在 PyQt5 中你应该这样做。

import six

import ctypes
import sys

from PyQt5 import QtCore, QtGui

from PyQt5.QtWidgets import QSizePolicy, QWidget, QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import (
    FigureCanvasQTAgg as FigureCanvas,
    NavigationToolbar2QT as NavigationToolbar)

QT_API = 'PyQt5'
DEBUG = False

_decref = ctypes.pythonapi.Py_DecRef
_decref.argtypes = [ctypes.py_object]
_decref.restype = None

class MplCanvas(FigureCanvas):
    def __init__(self):
        FigureCanvas.__init__(self,self.fig)
        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)


    #Change de color of rectangle zoom toolbar rewriting painEvent
    #the original code is in the backend_qt5agg.py file inside
    #matplotlib/backends directory
    def paintEvent(self, e):
        """
        Copy the image from the Agg canvas to the qt.drawable.
        In Qt, all drawing should be done inside of here when a widget is
        shown onscreen.
        """
        # if the canvas does not have a renderer, then give up and wait for
        # FigureCanvasAgg.draw(self) to be called
        if not hasattr(self, 'renderer'):
            return

        if DEBUG:
            print('FigureCanvasQtAgg.paintEvent: ', self,
                  self.get_width_height())

        if len(self.blitbox) == 0:
            # matplotlib is in rgba byte order.  QImage wants to put the bytes
            # into argb format and is in a 4 byte unsigned int.  Little endian
            # system is LSB first and expects the bytes in reverse order
            # (bgra).
            if QtCore.QSysInfo.ByteOrder == QtCore.QSysInfo.LittleEndian:
                stringBuffer = self.renderer._renderer.tostring_bgra()
            else:
                stringBuffer = self.renderer._renderer.tostring_argb()

            refcnt = sys.getrefcount(stringBuffer)

            # convert the Agg rendered image -> qImage
            qImage = QtGui.QImage(stringBuffer, self.renderer.width,
                                  self.renderer.height,
                                  QtGui.QImage.Format_ARGB32)
            if hasattr(qImage, 'setDevicePixelRatio'):
                # Not available on Qt4 or some older Qt5.
                qImage.setDevicePixelRatio(self._dpi_ratio)
            # get the rectangle for the image
            rect = qImage.rect()
            p = QtGui.QPainter(self)
            # reset the image area of the canvas to be the back-ground color
            p.eraseRect(rect)
            # draw the rendered image on to the canvas
            p.drawPixmap(QtCore.QPoint(0, 0), QtGui.QPixmap.fromImage(qImage))

            # draw the zoom rectangle to the QPainter
            ########################################################
            #        HERE CHANGE THE COLOR, IN THIS EXAMPLE        #
            #         THE COLOR IS WHITE                           #
            ########################################################
            if self._drawRect is not None:
                pen = QtGui.QPen(QtCore.Qt.white, 1 / self._dpi_ratio,
                                 QtCore.Qt.DotLine)
                p.setPen(pen)
                x, y, w, h = self._drawRect
                p.drawRect(x, y, w, h)
            p.end()

            # This works around a bug in PySide 1.1.2 on Python 3.x,
            # where the reference count of stringBuffer is incremented
            # but never decremented by QImage.
            # TODO: revert PR #1323 once the issue is fixed in PySide.
            del qImage
            if refcnt != sys.getrefcount(stringBuffer):
                _decref(stringBuffer)
        else:
            p = QtGui.QPainter(self)

            while len(self.blitbox):
                bbox = self.blitbox.pop()
                l, b, r, t = bbox.extents
                w = int(r) - int(l)
                h = int(t) - int(b)
                t = int(b) + h
                reg = self.copy_from_bbox(bbox)
                stringBuffer = reg.to_string_argb()
                qImage = QtGui.QImage(stringBuffer, w, h,
                                      QtGui.QImage.Format_ARGB32)
                if hasattr(qImage, 'setDevicePixelRatio'):
                    # Not available on Qt4 or some older Qt5.
                    qImage.setDevicePixelRatio(self._dpi_ratio)
                # Adjust the stringBuffer reference count to work
                # around a memory leak bug in QImage() under PySide on
                # Python 3.x
                if QT_API == 'PySide' and six.PY3:
                    ctypes.c_long.from_address(id(stringBuffer)).value = 1

                origin = QtCore.QPoint(l, self.renderer.height - t)
                pixmap = QtGui.QPixmap.fromImage(qImage)
                p.drawPixmap(origin / self._dpi_ratio, pixmap)

            # 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)
                p.setPen(pen)
                x, y, w, h = self._drawRect
                p.drawRect(x, y, w, h)

            p.end()

适用于 MATPLOTLIB 2.2.2

from PyQt5.QtWidgets import QSizePolicy, QWidget, QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import (
    FigureCanvasQTAgg as FigureCanvas,
    NavigationToolbar2QT as NavigationToolbar)
class MplCanvas(FigureCanvas):
    def __init__(self):
        FigureCanvas.__init__(self, self.fig)
        FigureCanvas.setSizePolicy(self,
                                   QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

# HERE CHANGE THE COLOR OF ZOOM RECTANGLE
    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):
                # IN THIS EXAMPLE CHANGE BLACK FOR WHITE
                pen = QtGui.QPen(QtCore.Qt.white, 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 MplWidget (QWidget):

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.canvas = MplCanvas()
        # add the toolbar
        self.ntb = NavigationToolbar(self.canvas, self)
        self.vbl = QVBoxLayout()
        self.vbl.addWidget(self.canvas)
        self.vbl.addWidget(self.ntb)
        self.setLayout(self.vbl)

【讨论】:

【参考方案2】:

在文件中

backend_qt4agg.py

# draw the zoom rectangle to the QPainter
#changed code below...
# change the color of zooming rectangle from black to red 
if self.drawRect:
    p.setPen( QtGui.QPen( QtCore.Qt.red, 1, QtCore.Qt.DotLine ) )
    p.drawRect( self.rect[0], self.rect[1], self.rect[2], self.rect[3] )
p.end()

只需将矩形绘图部分添加/更改为上述代码即可。

【讨论】:

它会变成红色..你可以使用自己的颜色 太棒了。非常感谢。这回答了我的许多问题 matplotlib 图如何与 Qt 结合使用。但是,我想知道是否可以在不直接更改 backend_qt4agg.py 的情况下完成相同的操作。似乎无法从外部访问对象 p... 不确定。出于同样的原因,我使用这个更改后的 backend_qt4agg.py 在pyqt5中文件为:backend_qt5agg.py,【参考方案3】:

可以通过子类化和覆盖(不扩展)paintevent来完成:(代码是从原始paintevent复制粘贴,将颜色更改为变量)

from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg 
import PyQt4.QtCore as QCore
import PyQt4.QtGui as QGui
class FigureCanvas(FigureCanvasQTAgg):
    """
    Subclassing to change the paint event hosted in matplotlib.backends.backend_qt5agg. 
    Removed all comments for sake of brevity. 
    Paintcolor can be set by settings canvas.rectanglecolor to a QColor.
    """
    def paintEvent(self, e):
        paintcolor = QCore.Qt.black if not hasattr(self, "rectanglecolor") else self.rectanglecolor
        if not hasattr(self, 'renderer'):
            return
        if self.blitbox is None:
            if QCore.QSysInfo.ByteOrder == QCore.QSysInfo.LittleEndian:
                stringBuffer = self.renderer._renderer.tostring_bgra()
            else:
                stringBuffer = self.renderer._renderer.tostring_argb()
            refcnt = sys.getrefcount(stringBuffer)
            qImage = QGui.QImage(stringBuffer, self.renderer.width,
                                  self.renderer.height,
                                  QGui.QImage.Format_ARGB32)
            rect = qImage.rect()
            p = QGui.QPainter(self)
            p.eraseRect(rect)
            p.drawPixmap(QCore.QPoint(0, 0), QGui.QPixmap.fromImage(qImage))
            if self._drawRect is not None:
                p.setPen(QGui.QPen(paintcolor, 1, QCore.Qt.DotLine))
                x, y, w, h = self._drawRect
                p.drawRect(x, y, w, h)
            p.end()
            del qImage
            if refcnt != sys.getrefcount(stringBuffer):
                _decref(stringBuffer)
        else:
            bbox = self.blitbox
            l, b, r, t = bbox.extents
            w = int(r) - int(l)
            h = int(t) - int(b)
            t = int(b) + h
            reg = self.copy_from_bbox(bbox)
            stringBuffer = reg.to_string_argb()
            qImage = QGui.QImage(stringBuffer, w, h,
                                  QGui.QImage.Format_ARGB32)
            if QT_API == 'PySide' and six.PY3:
                ctypes.c_long.from_address(id(stringBuffer)).value = 1
            pixmap = QGui.QPixmap.fromImage(qImage)
            p = QGui.QPainter(self)
            p.drawPixmap(QCore.QPoint(l, self.renderer.height-t), pixmap)
            if self._drawRect is not None:
                p.setPen(QGui.QPen(paintcolor, 1, QCore.Qt.DotLine))
                x, y, w, h = self._drawRect
                p.drawRect(x, y, w, h)
            p.end()
            self.blitbox = None

根据您的应用程序,您可能可以缩短一点(比如杀死 PySide 的特定部分)。以上工作,您可以像往常一样使用FigureCanvas。

【讨论】:

以上是关于在matplotlib中改变zoom-rect的边缘颜色的主要内容,如果未能解决你的问题,请参考以下文章

pyqt 中的多个嵌入式 matplotlib 画布在鼠标悬停时改变大小

在 Matplotlib 3.1.2 中使用 qt5agg 后端,get_backend() 会改变行为

有没有办法改变 MatplotLib 中 sublopts 的大小?

在 python 中导入 pandas 会改变 matplotlib 处理日期时间对象的方式?

[fzu 2271]不改变任意两点最短路至多删的边数

PyQt5嵌入matplotlib-plot,改变x-ticks等属性[重复]