0

I am trying to implement matplotlib crosshair for two axis using Multicursor . I want new feature that would just draw the horizontal cursor line for the axes where the pointer is, and not for any other

Sample code :

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_())

sample output is given below: out put of my code

Is there any other process which i can follow please suggest.

Note : I have used here python pyqt5 , matplotlib library

2 Answers 2

2

EDIT: based on the comments below, I think you are looking for a "figure-level" cursor, which follows the mouse regardless of the axes you're hovering over. I've created a new class, based on the code for MultiCursor that should do what you had in mind.

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()

In your own code:

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

enter image description here

EDIT: the part below is my earlier attempt at answering the question

I have written a class that inherits from MultiCursor and that, instead of accepting True/False for horizOn= and vertOn= takes a list of axes on which to draw either the horizontal or vertical lines.

I found the behavior of MutiCursor a bit strange when the mouse moves from one ax to the other, but I did not change that behavior. This code should get you started if you want to modify the class further. In particular, you could override the onmove() function.

enter image description here

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]

and in your class, create an instance:

    self.multi = MyMultiCursor(self.canvas, (axes, axes2), color='r', lw=1, horizOn=[axes], vertOn=[axes2])
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your answer. I am facing some issue self.multi = MyMultiCursor(self.canvas, (axes, axes2), color='r', lw=1, horizOn=[axes], vertOn=[axes2,axes]). when i hover my mouse axes2 horizontal line stay on axes . but i want horizontal line also come to axes2 . so how can i modify Onmove funtion ? issue i describe on image that link dropbox.com/s/8d0gwg4rnyczl0e/Screenshot_1.png?dl=0
The behavior is consistent with the behavior of MultiCursor, which I also found odd... I think what you want is more of a "Figure-level" cursor, see edit in my answer
dear sir Thank you for your reply.Here I am getting error like this ........................ self.vline = fig.add_artist(Line2D([.5, .5], [0., 1.], visible=vertOn, transform=self.fig.transFigure, AttributeError: 'Figure' object has no attribute 'add_artist' ----------------------------------------------------------------------------------------------------
The code works for me using matplotlib 3.0.1. But anyway, I've replaced the lines by something that should work with older versions, see updated code
Thank you ... but there is some issue . when i generate new plot with new data in the existing figure. the cursor remain and new cursor generate . Every time i clear figure before generate plot
2

I have edit some code beacuse of whenever i try to plot new trace i show a previous coursor show. So i hide the default hline and vline changing coordinate value.

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()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.