'NavigationToolbar2Tk' object has no attribute '_active`

Hi everyone,

I am getting the same error here as shown here: Bug introduced in V3.3.0 (not in V3.2.2) 'NavigationToolbar2Tk' object has no attribute '_active' · Issue #18148 · matplotlib/matplotlib · GitHub and I haven’t been able to solve the issue. Essentially, this legacy code that I am working with uses _active quite a bit in this python file but simply changing the self._active to self.ax.get_navigate_mode() doesn’t work. Not only that, get_navigate_mode() shown here matplotlib.axes.Axes.get_navigate_mode — Matplotlib 3.1.2 documentation only gives the options PAN, ZOOM or None and the legacy code also has options for SELECT, SELECT2, and SELECT3. Does anyone have an idea of a solution? Any insight is greatly appreciated. For completeness, I have added the code to the file below. Just search _active and you will see the 18 calls on this item.


from __future__ import division

from builtins import range
from past.utils import old_div
import os
import os.path as osp
import matplotlib as mpl

# Python Qt4 bindings for GUI objects
from .tools.qt import QtGui
from .tools.qt import QtCore
from .tools import qt

# import the Qt4Agg FigureCanvas object, that binds Figure to
# Qt4Agg backend. It also inherits from QWidget
from matplotlib.backend_bases import cursors as mplCursors

# Matplotlib Figure object
from matplotlib.figure import Figure
import matplotlib

# care about axes creation kwargs
from distutils.version import StrictVersion

if StrictVersion(matplotlib.__version__) >= StrictVersion("2.0.0"):
    axescreationdict = dict(frameon=True, facecolor="w")
else:
    axescreationdict = dict(frameon=True, axisbg="w")

if qt.API == "pyqt5":
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
else:
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas

    try:
        from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
    except ImportError:
        from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar


class MplCanvas(FigureCanvas):
    """Class to represent the FigureCanvas widget"""

    def __init__(self):
        self.fig = Figure()
        self.fig.set_facecolor("w")
        self.image_layout()
        # initialization of the canvas
        FigureCanvas.__init__(self, self.fig)
        # we define the widget as expandable
        FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
        # notify the system of updated policy
        FigureCanvas.updateGeometry(self)

    def image_layout(self):
        """prepare the layout for displaying a single pic only
        """
        # setup Matplotlib Figure and Axis
        self.fig.clf()

        # the image
        self.ax1 = self.fig.add_axes((0.05, 0.05, 0.75, 0.75), **axescreationdict)

        # vertical profile
        self.ax2 = self.fig.add_axes((0.05, 0.81, 0.75, 0.16), sharex=self.ax1, **axescreationdict)
        for label_i in self.ax2.get_xticklabels():
            label_i.set_fontsize(0.0)
            label_i.set_visible(False)
            
        # horizontal profile
        self.ax3 = self.fig.add_axes((0.81, 0.05, 0.16, 0.75), sharey=self.ax1, **axescreationdict)
        for label in self.ax3.get_yticklabels():
            label.set_visible(False)
            label.set_fontsize(0.0)

        # space for the colorbar
        self.ax4 = self.fig.add_axes((0.88, 0.84, 0.03, 0.13), **axescreationdict)
        for label in self.ax4.get_xticklabels():
            label.set_visible(False)
            label.set_fontsize(0.0)


class MplWidget(QtGui.QWidget):
    """Widget defined in Qt Designer"""

    def __init__(self, parent=None):
        # initialization of Qt MainWindow widget
        QtGui.QWidget.__init__(self, parent)
        # set the canvas to the Matplotlib widget
        self.figCanvas = MplCanvas()
        # create a vertical box layout
        self.vbl = QtGui.QVBoxLayout()
        # add mpl widget to vertical box
        self.vbl.addWidget(self.figCanvas)
        # set the layout to the vertical box
        self.setLayout(self.vbl)
        # self.mpl_toolbar = NavigationToolbar(self.canvas, self)
        self.mpl_toolbar = Toolbar(self.figCanvas, self)
        self.vbl.addWidget(self.mpl_toolbar)


class SignalEmitter(QtCore.QObject):
    """This is the Pyside way to define custom signals"""

    pickEvent = QtCore.Signal(float, float, float, float)
    pickEvent2 = QtCore.Signal(float, float, float, float)
    pickEvent3 = QtCore.Signal(float, float)


class Toolbar(NavigationToolbar):
    def __init__(self, *args, **kwargs):
        super(Toolbar, self).__init__(*args, **kwargs)
        pixmap = QtGui.QPixmap()
        thisPath = osp.dirname(__file__)
        pixmap.load(osp.join(thisPath, "crosshairs2.svg"))
        mplCursors.SELECT_POINT = pixmap

        pixmap = QtGui.QPixmap()
        pixmap.load(osp.join(thisPath, "crosshairsgreen2.svg"))
        mplCursors.SELECT_POINT2 = pixmap

        pixmap = QtGui.QPixmap()
        pixmap.load(osp.join(thisPath, "crosshairsbluethin.svg"))
        mplCursors.SELECT_POINT3 = pixmap

        self.sigMan = SignalEmitter()


        # Everything below is from the method _init_toolbar method but is since deprecated
        #print(os.path.join(mpl.get_data_path(), 'images'))
        self.basedirr = os.path.join(mpl.get_data_path(), 'images')
        a = self.addAction(self._icon(osp.join(self.basedirr, "home.png")), "Home", self.home)
        a.setToolTip("Reset original view")
        a = self.addAction(self._icon(osp.join(self.basedirr, "back.png")), "Back", self.back)
        a.setToolTip("Back to previous view")
        a = self.addAction(
            self._icon(osp.join(self.basedirr, "forward.png")), "Forward", self.forward
        )
        a.setToolTip("Forward to next view")
        self.addSeparator()
        w = self.addAction(self._icon(osp.join(self.basedirr, "move.png")), "Pan", self.pan)
        a.setToolTip("Pan axes with left mouse, zoom with right")
        a = self.addAction(
            self._icon(osp.join(self.basedirr, "zoom_to_rect.png")), "Zoom", self.zoom
        )
        a.setToolTip("Zoom to rectangle")

        self.addSeparator()

        thisPath = osp.dirname(__file__)
        a = self.addAction(
            QtGui.QIcon(osp.join(thisPath, "crosshairs.png")), "Select", self.selectPointMode
        )
        a.setToolTip("Select the roi")
        a = self.addAction(
            QtGui.QIcon(osp.join(thisPath, "crosshairsgreen.png")), "Select2", self.selectPointMode2
        )
        a.setToolTip("Select the area for normalization")
        a = self.addAction(
            QtGui.QIcon(osp.join(thisPath, "crosshairsblue.png")), "Select3", self.selectPointMode3
        )
        a.setToolTip("set a center for cross sections")

        # self.addSeparator()
        # a = self.addAction(self._icon('subplots.png'), 'Subplots', self.configure_subplots)
        # a.setToolTip('Configure subplots')
        a = self.addAction(
            self._icon(osp.join(self.basedirr, "filesave.png")), "Save", self.save_figure
        )
        a.setToolTip("Save the figure")

        self.buttons = {}

        # Add the x,y location widget at the right side of the toolbar
        # The stretch factor is 1 which means any resizing of the toolbar
        # will resize this label instead of the buttons.
        if self.coordinates:
            self.locLabel = QtGui.QLabel("", self)
            self.locLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop)
            self.locLabel.setSizePolicy(
                QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Ignored)
            )
            labelAction = self.addWidget(self.locLabel)
            labelAction.setVisible(True)

        # reference holder for subplots_adjust window
        self.adj_window = None

    # qt5 navbar has this. disable!
    def _update_buttons_checked(self):
        pass

    def mouse_move(self, event):
        # print 'mouse_move', event.button
        if not event.inaxes or not self._active:
            if self._lastCursor != mplCursors.POINTER:
                self.set_cursor(mplCursors.POINTER)
                self._lastCursor = mplCursors.POINTER
        else:
            if self._active == "ZOOM":
                if self._lastCursor != mplCursors.SELECT_REGION:
                    self.set_cursor(mplCursors.SELECT_REGION)
                    self._lastCursor = mplCursors.SELECT_REGION
                if self._xypress and self._xypress is not None:
                    x, y = event.x, event.y
                    s = self._xypress[0]
                    if len(s) == 6:
                        lastx, lasty, a, ind, lim, trans = s
                    elif len(s) == 5:
                        lastx, lasty, a, ind, other = s
                    else:
                        lastx, lasty = x, y
                    self.draw_rubberband(event, x, y, lastx, lasty)
            elif self._active == "PAN" and self._lastCursor != mplCursors.MOVE:
                self.set_cursor(mplCursors.MOVE)
                self._lastCursor = mplCursors.MOVE
            elif self._active == "SELECT":
                if self._lastCursor != mplCursors.SELECT_POINT:
                    QtGui.QApplication.restoreOverrideCursor()
                    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(mplCursors.SELECT_POINT))
                    self._lastCursor = mplCursors.SELECT_POINT
            elif self._active == "SELECT2":
                if self._lastCursor != mplCursors.SELECT_POINT2:
                    QtGui.QApplication.restoreOverrideCursor()
                    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(mplCursors.SELECT_POINT2))
                    self._lastCursor = mplCursors.SELECT_POINT2
            elif self._active == "SELECT3":
                if self._lastCursor != mplCursors.SELECT_POINT3:
                    QtGui.QApplication.restoreOverrideCursor()
                    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(mplCursors.SELECT_POINT3))
                    self._lastCursor = mplCursors.SELECT_POINT3

        if event.inaxes and event.inaxes.get_navigate():
            try:
                s = event.inaxes.format_coord(event.xdata, event.ydata)
            except ValueError:
                pass
            except OverflowError:
                pass
            else:
                if len(self.mode):
                    self.set_message("%s : %s" % (self.mode, s))
                else:
                    self.set_message(s)
        else:
            self.set_message(self.mode)

    def selectPointMode(self, *args):
        if self._active == "SELECT":
            self._active = None
        else:
            self._active = "SELECT"

        if self._idPress is not None:
            self._idPress = self.canvas.mpl_disconnect(self._idPress)
            self.mode = ""
        if self._idRelease is not None:
            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
            self.mode = ""

        if self._active:
            self._idRelease = self.canvas.mpl_connect("button_press_event", self.selectPoint)
            self.mode = "roi select mode"
            self.canvas.widgetlock(self)
        else:
            self.canvas.widgetlock.release(self)
        self.set_message(self.mode)

    def selectPoint(self, event):
        if event.inaxes and event.inaxes.get_navigate():
            self.xdatastart = event.xdata
            self.ydatastart = event.ydata
            self.xstart = event.x
            self.ystart = event.y
            self._banddraw = self.canvas.mpl_connect("motion_notify_event", self.drawband)
            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
            self._idRelease = self.canvas.mpl_connect(
                "button_release_event", self.selectSecondPoint
            )

    def selectSecondPoint(self, event):
        if event.inaxes and event.inaxes.get_navigate():
            self._banddraw = self.canvas.mpl_disconnect(self._banddraw)
            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
            self._idRelease = self.canvas.mpl_connect("button_press_event", self.selectPoint)
            self.draw_rubberband(event, 0, 0, 0, 0)
            self.sigMan.pickEvent.emit(self.xdatastart, self.ydatastart, event.xdata, event.ydata)

    def selectPointMode2(self, *args):
        if self._active == "SELECT2":
            self._active = None
        else:
            self._active = "SELECT2"

        if self._idPress is not None:
            self._idPress = self.canvas.mpl_disconnect(self._idPress)
            self.mode = ""
        if self._idRelease is not None:
            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
            self.mode = ""

        if self._active:
            self._idRelease = self.canvas.mpl_connect("button_press_event", self.selectPoint2)
            self.mode = "refernce roi select mode"
            self.canvas.widgetlock(self)
        else:
            self.canvas.widgetlock.release(self)
        self.set_message(self.mode)

    def selectPointMode3(self, *args):
        if self._active == "SELECT3":
            self._active = None
        else:
            self._active = "SELECT3"

        if self._idPress is not None:
            self._idPress = self.canvas.mpl_disconnect(self._idPress)
            self.mode = ""
        if self._idRelease is not None:
            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
            self.mode = ""

        if self._active:
            self._idRelease = self.canvas.mpl_connect("button_press_event", self.selectPoint3)
            self.mode = "center select mode"
            self.canvas.widgetlock(self)
        else:
            self.canvas.widgetlock.release(self)
        self.set_message(self.mode)

    def selectPoint2(self, event):
        if event.inaxes and event.inaxes.get_navigate():
            self.xdatastart = event.xdata
            self.ydatastart = event.ydata
            self.xstart = event.x
            self.ystart = event.y
            self._banddraw = self.canvas.mpl_connect("motion_notify_event", self.drawband)
            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
            self._idRelease = self.canvas.mpl_connect(
                "button_release_event", self.selectSecondPoint2
            )

    def selectSecondPoint2(self, event):
        if event.inaxes and event.inaxes.get_navigate():
            self._banddraw = self.canvas.mpl_disconnect(self._banddraw)
            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
            self._idRelease = self.canvas.mpl_connect("button_press_event", self.selectPoint2)
            self.draw_rubberband(event, 0, 0, 0, 0)
            self.sigMan.pickEvent2.emit(self.xdatastart, self.ydatastart, event.xdata, event.ydata)

    def selectPoint3(self, event):
        if event.inaxes and event.inaxes.get_navigate():
            self.xdatastart = event.xdata
            self.ydatastart = event.ydata
            self.xstart = event.x
            self.ystart = event.y
            # self._banddraw = self.canvas.mpl_connect('motion_notify_event',self.drawband)
            #            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
            self.sigMan.pickEvent3.emit(event.xdata, event.ydata)

    def drawband(self, event):
        self.draw_rubberband(event, self.xstart, self.ystart, event.x, event.y)

It looks like maybe you are using mplcursors to get “select2” etc? GitHub - anntzer/mplcursors: Interactive data selection cursors for Matplotlib. Maybe the documentation there explains how to check the navigation state.

Hi @jklymak thanks for the reply. It seems SELECT2 and SELECT3 come from matplotlib and not mplcursors. I checked mplcursors and didn’t see any indication that the two come from there.