embed matplotlib with custom Navigation toolbar actions in the Mainwindow toolbar

I am trying not to use the navigation toolbar, instead I want to add these actions in the Mainwindow toolbar. Below is an example of the customization:

import sys
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5.QtWidgets import QToolBar, QMainWindow, QAction, qApp, QApplication
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QIcon
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        zoomact = QAction(QIcon('zoom.png'), 'zoom', self)
        zoomact.setShortcut('Ctrl+z')
        zoomact.triggered.connect(self.zoom)

        self.toolbar = self.addToolBar('zoom')
        self.toolbar.addAction(zoomact)

        self.sc = MplCanvas(self, width=5, height=4, dpi=100)
        self.sc.axes.plot([0,1,2,3,4], [10,1,20,3,40])

        #
        toolbar = NavigationToolbar(self.sc, self)
        
        """Here I don't want to use the NavigationToolbar, but custom actions in the 
            toolbar. So I don't want to show the NavigationToolbar"""
            
        "Is there any custom function in the Figure canvas"    
            

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(toolbar)
        layout.addWidget(self.sc)

        # Create a placeholder widget to hold our toolbar and canvas.
        widget = QtWidgets.QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        self.show()

    "Customize function for a specific figure"
    def zoom(self):
        """here, I want to call the specific zoom function from NavigationToolbar
            to apply to this specific FigureCanvas using lambda function"""
        # My trial. I don't know, I cant figure it out :)
        lambda sc: NavigationToolbar2QT.toolitems["zoom"](self.sc, self)
            
    def home(self):
        pass    
    
    def Editaxis(self):
        pass
    
    def configuresubplots(self):
        pass
    
    def save(self):
        pass
    
    def leftbuttonpans(self):
        pass
    
    def backtopreviousview(self):
        pass
        
    def forwardtonextview(self):
        pass

app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

So,

  • I want to hide the NavigationToolbar and not to use it
  • Then, to create custom actions in the Mainwindow toolbar that can act on the figure in the canvas, such as zoom, edit axis…..etc.

Answer

You have to pass None as parent of the NavigationToolbar2QT:

import sys
import matplotlib

matplotlib.use("Qt5Agg")

from PyQt5.QtWidgets import QAction, QApplication, QMainWindow, QVBoxLayout, QWidget
from PyQt5.QtGui import QIcon

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT
from matplotlib.figure import Figure


class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        print("Text: Name of method")
        for text, _, _, callback in NavigationToolbar2QT.toolitems:
            if isinstance(text, str):
                print(f"{text}: {callback}")

        self.sc = MplCanvas(self, width=5, height=4, dpi=100)
        self.mpl_toolbar = NavigationToolbar2QT(self.sc, None)

        zoomact = QAction(QIcon("zoom.png"), "zoom", self)
        zoomact.setShortcut("Ctrl+z")
        zoomact.triggered.connect(self.mpl_toolbar.zoom)

        self.toolbar = self.addToolBar("zoom")
        self.toolbar.addAction(zoomact)

        self.sc.axes.plot([0, 1, 2, 3, 4], [10, 1, 20, 3, 40])

        widget = QWidget()
        self.setCentralWidget(widget)
        layout = QVBoxLayout(widget)
        layout.addWidget(self.sc)


def main():
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    app.exec_()


if __name__ == "__main__":
    main()

Another option is to remove the QActions from the NavigationToolbar2QT:

import sys
import matplotlib

matplotlib.use("Qt5Agg")

from PyQt5.QtWidgets import QAction, QApplication, QMainWindow, QVBoxLayout, QWidget
from PyQt5.QtGui import QIcon

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT
from matplotlib.figure import Figure


class MplCanvas(FigureCanvasQTAgg):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        print("Text: Name of method")
        for text, _, _, callback in NavigationToolbar2QT.toolitems:
            if isinstance(text, str):
                print(f"{text}: {callback}")

        self.sc = MplCanvas(self, width=5, height=4, dpi=100)
        self.toolbar = NavigationToolbar2QT(self.sc, self)
        self.addToolBar(self.toolbar)
        self.toolbar.clear()

        zoomact = QAction(QIcon("zoom.png"), "zoom", self)
        zoomact.setShortcut("Ctrl+z")
        zoomact.triggered.connect(self.toolbar.zoom)

        self.toolbar.addAction(zoomact)

        self.sc.axes.plot([0, 1, 2, 3, 4], [10, 1, 20, 3, 40])

        widget = QWidget()
        self.setCentralWidget(widget)
        layout = QVBoxLayout(widget)
        layout.addWidget(self.sc)


def main():
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    app.exec_()


if __name__ == "__main__":
    main()