简体   繁体   English

在 matplotlib 中更改缩放矩形的边缘颜色

[英]Changing the edge color of zoom-rect in matplotlib

I have written an app for spectral analysis using python+matplotlib+pyqt.我已经使用 python+matplotlib+pyqt 编写了一个用于光谱分析的应用程序。 The plots need to have a black background with white axes and symbols in the app.绘图需要在应用程序中具有带有白色轴和符号的黑色背景。 I kept the default Navigation toolbar of matplotlib.我保留了 matplotlib 的默认导航工具栏。 One problem I have due to the inverted color setup is that the edge of zoom rectangle is invisible because it is black.由于反转颜色设置,我遇到的一个问题是缩放矩形的边缘是不可见的,因为它是黑色的。 Is there a simple way to change the edgecolor of the zoom rectangle to a bright color, such as white.有没有一种简单的方法可以将缩放矩形的边缘颜色更改为明亮的颜色,例如白色。

Thanks you in advance.提前谢谢你。

in the file 在文件中

backend_qt4agg.py 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()

just add/change the rectangle drawing portion to the above code. 只需将矩形绘图部分添加/更改为上面的代码即可。

It can be done by subclassing and overriding (NOT extending) the paintevent: (Code is copy pasted from the original paintevent, changing the color to a variable) 它可以通过子类化和覆盖(不扩展)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

Depending on your application, you may be able to shorten in a bit more (like killing the PySide specific part in there). 根据您的应用程序,您可能会缩短一点(例如杀死那里的PySide特定部分)。 Above works, and you can just use the FigureCanvas as you normally would. 上面的工作,您可以像往常一样使用FigureCanvas。

Adding to Gloweye's response, in PyQt5 you should. 加入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()

FOR MATPLOTLIB 2.2.2 FOR 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)

Forgive the necromancy, but I wanted to throw my hat in the ring for anyone who's trying to solve this problem in tkinter (since most answers here deal with Qt).原谅死灵,但我想向任何试图在 tkinter 中解决这个问题的人表示敬意(因为这里的大多数答案都是关于 Qt 的)。

You'll need to override the NavigationToolbar2Tk.draw_rubberband() method.您需要覆盖NavigationToolbar2Tk.draw_rubberband()方法。 Here I'm using a custom class, but it's not the only way:这里我使用的是自定义 class,但这不是唯一的方法:

class CustomToolbar(mptk.NavigationToolbar2Tk):
    def __init__(self, figcanvas, parent):
        super().__init__(figcanvas, parent)  # init the base class as usual

    # you can copy the method 'draw_rubberband()' right from
    # NavigationToolbar2Tk - we're only changing one line
    def draw_rubberband(self, event, x0, y0, x1, y1):
        self.remove_rubberband()
        height = self.canvas.figure.bbox.height
        y0 = height - y0
        y1 = height - y1
        # this is the line we want to change
        self.lastrect = self.canvas._tkcanvas.create_rectangle(
            x0, y0, x1, y1
            outline = 'red'  # add your outline color here
        )
        # hex color strings -> '#FF3344' and named colors -> 'gainsboro' both work

You can use the CustomToolbar class by name just like you would use NavigationToolbar2Tk您可以按名称使用CustomToolbar class,就像使用NavigationToolbar2Tk一样

self.toolbar = CustomToolbar(self.root, self.canvas, self.frame)  # for example...

Implementing a custom toolbar class that inherits from NavigationToolbar2Tk opens up other possibilities as well, such as modifying toolbar buttons, adding custom tools, and so on...but that's a story for another post.实现从NavigationToolbar2Tk继承的自定义工具栏 class 也开辟了其他可能性,例如修改工具栏按钮、添加自定义工具等……但这是另一篇文章的故事。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM