如何自定义 matplotlib 多光标?

Posted

技术标签:

【中文标题】如何自定义 matplotlib 多光标?【英文标题】:How to customize matplotlib Multicursor? 【发布时间】:2018-11-28 10:53:40 【问题描述】:

我正在尝试使用 Multicursor 为两个轴实现 matplotlib 十字准线 .我想要一个新功能,它只为指针所在的轴绘制水平光标线,而不是任何其他的

示例代码:

import sys
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor
from PyQt5.QtWidgets import QMainWindow,QVBoxLayout
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow_code_serarch(object):

    def setup_code_serarch(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(870, 680)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.verticalLayoutWidget.setGeometry(QtCore.QRect(17, 50, 741, 553))
        self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout.setObjectName("verticalLayout")


        self.figure = plt.figure(facecolor='black')
        self.canvas = FigureCanvas(self.figure)
        self.verticalLayout.addWidget(self.canvas)
        axes, axes2 = self.figure.subplots(nrows=2, sharex=True)
        axes.plot([1, 2, 3, 4,5,6,7,8])
        axes2.plot([1, 2, 3, 4,7,8,9])
        axes.set_position([0.02, 0.37, 0.88, 0.6])
        axes2.set_position([0.02, 0.15, 0.88, 0.22])
        axes.tick_params(axis='both', color='#ffffff', labelcolor='#ffffff')
        axes.yaxis.tick_right()
        axes2.tick_params(axis='both', color='#ffffff', labelcolor='#ffffff')
        axes2.grid(color='lightgray', linewidth=.5, linestyle=':')
        axes.grid(color='lightgray', linewidth=.5, linestyle=':')
        axes2.yaxis.tick_right()
        axes.autoscale_view()
        axes2.autoscale_view()
        axes.margins(0, .5)
        axes2.margins(0, .5)
        axes.set_facecolor('#041105')
        axes2.set_facecolor('#041105')

        self.multi = MultiCursor(self.canvas, (axes, axes2), color='r', lw=1,horizOn=True, vertOn=True)

        self.canvas.draw()


        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 246, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        # self.pushButton.clicked.connect(self.graphShowCode)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        # self.pushButton.setText(_translate("MainWindow", "OK"))

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = MainWindow_code_serarch()
    ui.setup_code_serarch(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

示例输出如下:

请问有没有其他的流程可以参考。

注意:我这里用过python pyqt5,matplotlib库

【问题讨论】:

【参考方案1】:

编辑: 基于下面的 cmets,我认为您正在寻找一个“图形级”光标,无论您悬停在哪个轴上,它都会跟随鼠标。我创建了一个基于 the code for MultiCursor 的新类,它应该可以满足您的想法。

class FigureCursor(Widget):
    def __init__(self, fig, horizOn=True, vertOn=True, useblit=True, **lineprops):
        self._cidmotion = None
        self._ciddraw = None
        self.background = None
        self.needclear = False
        self.visible = True
        self.canvas = fig.canvas
        self.fig = fig
        self.horizOn = horizOn
        self.vertOn = vertOn
        self.useblit = useblit
        self.vline, = fig.axes[0].plot([.5, .5], [0., 1.], visible=vertOn, transform=self.fig.transFigure,
                                       clip_on = False, **lineprops)
        self.hline, = fig.axes[0].plot([0., 1.], [.5, .5], visible=horizOn, transform=self.fig.transFigure,
                                       clip_on=False, **lineprops)
        self.connect()

    def connect(self):
        """connect events"""
        self._cidmotion = self.canvas.mpl_connect('motion_notify_event', self.onmove)
        self._ciddraw = self.canvas.mpl_connect('draw_event', self.clear)

    def disconnect(self):
        """disconnect events"""
        self.canvas.mpl_disconnect(self._cidmotion)
        self.canvas.mpl_disconnect(self._ciddraw)

    def clear(self, event):
        """clear the cursor"""
        if self.ignore(event):
            return
        if self.useblit:
            self.background = (
                self.canvas.copy_from_bbox(self.canvas.figure.bbox))
        for line in [self.vline, self.hline]:
            line.set_visible(False)

    def onmove(self, event):
        if self.ignore(event):
            return
        if event.inaxes is None:
            return
        if not self.canvas.widgetlock.available(self):
            return
        self.needclear = True
        if not self.visible:
            return
        trans = event.inaxes.transData + self.fig.transFigure.inverted()
        x_fig, y_fig = trans.transform([event.xdata, event.ydata])
        if self.vertOn:
            self.vline.set_xdata([x_fig, x_fig])
            self.vline.set_visible(self.visible)
        if self.horizOn:
            self.hline.set_ydata([y_fig, y_fig])
            self.hline.set_visible(self.visible)
        self._update()

    def _update(self):
        if self.useblit:
            if self.background is not None:
                self.canvas.restore_region(self.background)
            if self.vertOn:
                self.fig.draw_artist(self.vline)
            if self.horizOn:
                self.fig.draw_artist(self.hline)
            self.canvas.blit(self.canvas.figure.bbox)
        else:
            self.canvas.draw_idle()

在您自己的代码中:

(...)
self.multi = FigureCursor(self.figure, horizOn=True, vertOn=True, color='r', lw=1)
(...)

编辑:以下部分是我之前尝试回答的问题

我编写了一个继承自 MultiCursor 的类,而不是接受 True/FalsehorizOn=vertOn= 采用轴列表来绘制水平线或垂直线。

当鼠标从一个斧头移动到另一个斧头时,我发现MutiCursor 的行为有点奇怪,但我没有改变这种行为。如果您想进一步修改该类,此代码应该可以帮助您入门。特别是,您可以覆盖the onmove() function。

class MyMultiCursor(MultiCursor):
    def __init__(self, canvas, axes, useblit=True, horizOn=[], vertOn=[], **lineprops):
        super(MyMultiCursor, self).__init__(canvas, axes, useblit=useblit, horizOn=False, vertOn=False, **lineprops)

        self.horizAxes = horizOn
        self.vertAxes = vertOn

        if len(horizOn) > 0:
            self.horizOn = True
        if len(vertOn) > 0:
            self.vertOn = True

        xmin, xmax = axes[-1].get_xlim()
        ymin, ymax = axes[-1].get_ylim()
        xmid = 0.5 * (xmin + xmax)
        ymid = 0.5 * (ymin + ymax)

        self.vlines = [ax.axvline(xmid, visible=True, **lineprops) for ax in self.vertAxes]
        self.hlines = [ax.axhline(ymid, visible=True, **lineprops) for ax in self.horizAxes]

在你的类中,创建一个实例:

    self.multi = MyMultiCursor(self.canvas, (axes, axes2), color='r', lw=1, horizOn=[axes], vertOn=[axes2])

【讨论】:

感谢您的回答。我面临一些问题 self.multi = MyMultiCursor(self.canvas, (axes, axes2), color='r', lw=1, horizOn=[axes], vertOn=[axes2,axes])。当我将鼠标悬停在轴上时,轴 2 水平线停留在轴上。但我希望水平线也出现在 axes2 上。那么如何修改 Onmove 功能?我在链接dropbox.com/s/8d0gwg4rnyczl0e/Screenshot_1.png?dl=0的图片上描述的问题 该行为与MultiCursor 的行为一致,我也觉得这很奇怪...我认为您想要的更多是“图形级”光标,请参阅我的回答中的编辑 亲爱的先生,谢谢您的回复。我在这里遇到这样的错误............ self.vline = fig.add_artist(Line2D([.5, .5], [0., 1.], visible=vertOn, transform=self.fig.transFigure, AttributeError: 'Figure' 对象没有属性 'add_artist' --- -------------------------------------------------- --------------------------------------------------------------- 该代码适用于我使用 matplotlib 3.0.1。但无论如何,我已经用旧版本的东西替换了这些行,请参阅更新的代码 谢谢...但是有一些问题。当我在现有图中使用新数据生成新图时。光标保留,新光标生成。每次我在生成情节之前清除图形【参考方案2】:

我已经编辑了一些代码,因为每当我尝试绘制新的轨迹时,我都会显示以前的 coursor show。所以我隐藏了默认的 hline 和 vline 改变坐标值。

from matplotlib.widgets import MultiCursor
from matplotlib.widgets import *
from matplotlib.figure import Figure
class FigureCursor(Widget):
    def __init__(self, fig, horizOn=True, vertOn=True, useblit=True, **lineprops):
        self._cidmotion = None
        self._ciddraw = None
        self.background = None
        self.needclear = False
        self.visible = True
        self.canvas = fig.canvas
        self.fig = fig
        self.horizOn = horizOn
        self.vertOn = vertOn
        self.useblit = useblit
        self.vline, = fig.axes[0].plot([1, 1], [0., 1.], visible=vertOn, transform=self.fig.transFigure,
                                       clip_on = False, **lineprops)
        self.hline, = fig.axes[0].plot([0., 1.], [-1., 0.], visible=horizOn, transform=self.fig.transFigure,
                                       clip_on=False, **lineprops)
        self.connect()

    def connect(self):
        """connect events"""
        self._cidmotion = self.canvas.mpl_connect('motion_notify_event', self.onmove)
        self._ciddraw = self.canvas.mpl_connect('draw_event', self.clear)

    def disconnect(self):
        """disconnect events"""
        self.canvas.mpl_disconnect(self._cidmotion)
        self.canvas.mpl_disconnect(self._ciddraw)

    def clear(self, event):
        """clear the cursor"""
        if self.ignore(event):
            return
        if self.useblit:
            self.background = (
                self.canvas.copy_from_bbox(self.canvas.figure.bbox))
        for line in [self.vline, self.hline]:
            line.set_visible(False)

    def onmove(self, event):
        if self.ignore(event):
            return
        if event.inaxes is None:
            return
        if not self.canvas.widgetlock.available(self):
            return
        self.needclear = True
        if not self.visible:
            return
        trans = event.inaxes.transData + self.fig.transFigure.inverted()
        x_fig, y_fig = trans.transform([event.xdata, event.ydata])
        if self.vertOn:
            self.vline.set_xdata([x_fig, x_fig])
            self.vline.set_visible(self.visible)
        if self.horizOn:
            self.hline.set_ydata([y_fig, y_fig])
            self.hline.set_visible(self.visible)
        self._update()

    def _update(self):
        if self.useblit:
            if self.background is not None:
                self.canvas.restore_region(self.background)
            if self.vertOn:
                self.fig.draw_artist(self.vline)
            if self.horizOn:
                self.fig.draw_artist(self.hline)
            self.canvas.blit(self.canvas.figure.bbox)
        else:
            self.canvas.draw_idle()

【讨论】:

以上是关于如何自定义 matplotlib 多光标?的主要内容,如果未能解决你的问题,请参考以下文章

如何在matplotlib中两个坐标轴之间画一条直线光标

如何根据 barplot 的值在 matplotlib 中创建自定义图例?

如何在 Tensorboard 中显示自定义图像(例如 Matplotlib Plots)?

使用 Windows 窗体应用程序时如何将鼠标光标更改为自定义光标?

如何使用python在Matplotlib中绘制单词出现的时间序列?

如何在启动 OSX spritekit 应用程序时设置自定义光标?