12

I want to know if there is a manner to put a scrollbar (horizontal or vertical) on a matplotlib showing page (plt.show) that contains several sublots (sublot2grid). At the moment, the only solution I find is to make the subplots very small, which isn't very elegant at all.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
belkacem mekakleb
  • 584
  • 2
  • 7
  • 22

2 Answers2

31

The window showing the matplotlib figure does not have the option to add scrollbars. It will automatically resize itself to the figure size. And inversely, if it is resized the figure will resize as well.

An option would be to build a custom window which does have this ability. For that purpose, one can use PyQt. An example is given below, where instead of calling plt.show() a custom class is called with the figure to draw as an argument. The figure size should be set to the figure fig beforehands and that custom class will not change it. Instead it puts the figure into a canvas with scrollbars, such that the figure retains it's original size and can be scrolled within the Qt window. You wouln't have to deal with the details inside the class but only the call at the end of the script.

This example is for PyQt4, see below for a PyQt5 example.

import matplotlib.pyplot as plt
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar

class ScrollableWindow(QtGui.QMainWindow):
    def __init__(self, fig):
        self.qapp = QtGui.QApplication([])

        QtGui.QMainWindow.__init__(self)
        self.widget = QtGui.QWidget()
        self.setCentralWidget(self.widget)
        self.widget.setLayout(QtGui.QVBoxLayout())
        self.widget.layout().setContentsMargins(0,0,0,0)
        self.widget.layout().setSpacing(0)

        self.fig = fig
        self.canvas = FigureCanvas(self.fig)
        self.canvas.draw()
        self.scroll = QtGui.QScrollArea(self.widget)
        self.scroll.setWidget(self.canvas)

        self.nav = NavigationToolbar(self.canvas, self.widget)
        self.widget.layout().addWidget(self.nav)
        self.widget.layout().addWidget(self.scroll)

        self.show()
        exit(self.qapp.exec_()) 


# create a figure and some subplots
fig, axes = plt.subplots(ncols=4, nrows=5, figsize=(16,16))
for ax in axes.flatten():
    ax.plot([2,3,5,1])

# pass the figure to the custom window
a = ScrollableWindow(fig)

enter image description here


Here is a version for PyQt5.
import matplotlib
# Make sure that we are using QT5
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar


class ScrollableWindow(QtWidgets.QMainWindow):
    def __init__(self, fig):
        self.qapp = QtWidgets.QApplication([])

        QtWidgets.QMainWindow.__init__(self)
        self.widget = QtWidgets.QWidget()
        self.setCentralWidget(self.widget)
        self.widget.setLayout(QtWidgets.QVBoxLayout())
        self.widget.layout().setContentsMargins(0,0,0,0)
        self.widget.layout().setSpacing(0)

        self.fig = fig
        self.canvas = FigureCanvas(self.fig)
        self.canvas.draw()
        self.scroll = QtWidgets.QScrollArea(self.widget)
        self.scroll.setWidget(self.canvas)

        self.nav = NavigationToolbar(self.canvas, self.widget)
        self.widget.layout().addWidget(self.nav)
        self.widget.layout().addWidget(self.scroll)

        self.show()
        exit(self.qapp.exec_()) 


# create a figure and some subplots
fig, axes = plt.subplots(ncols=4, nrows=5, figsize=(16,16))
for ax in axes.flatten():
    ax.plot([2,3,5,1])

# pass the figure to the custom window
a = ScrollableWindow(fig)


While this answer shows a way to scroll a complete figure, if you are interested in scrolling the content of an axes check out this answer
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Thank's a lot, this is what I am looking for ^^ – belkacem mekakleb Mar 06 '17 at 12:15
  • I have a problem, when I close the Qt window to go back to Tkinter interface, the app crashes, – belkacem mekakleb Mar 07 '17 at 15:45
  • What does "go back to Tkinter interface" mean? – ImportanceOfBeingErnest Mar 07 '17 at 15:48
  • Sorry, my english is bad, So, when I close the Pyqt window, my app crashes. What is shown on Pyqt interface is a dashboard, and there is a Tkinter interface which call the dashboard which is supposed to remain pending; – belkacem mekakleb Mar 07 '17 at 16:09
  • Fore example if you put a print("hello world") after the script above, you'll get (Process finished with exit code -1073741819 (0xC0000005)) – belkacem mekakleb Mar 07 '17 at 16:09
  • The point is that the code from the answer exits the program when the window is closed (`exit(self.qapp.exec_())`). If there is another part of the program still running it will be killed as well. So you need to somehow combine the two. But I guess this not part of this question. – ImportanceOfBeingErnest Mar 07 '17 at 18:19
  • Sorry but I never used QtGui, I used to use Tkinter, Could you tell me please, how to change the code above so that it leaves the interface Qt without leaving the program? – belkacem mekakleb Mar 08 '17 at 09:30
  • Replacing `exit(self.qapp.exec_())` by `self.qapp.exec_()` would not exit the script. I'm not whether this will work in combination with some other tkinter GUI though. – ImportanceOfBeingErnest Mar 08 '17 at 09:46
  • Thats exactly what I am looking for but I struggle to pass it on pyQt5 (and python 3.6), do you have any insights? – Mayeul sgc Mar 09 '17 at 10:50
  • @Mayeul Since I don't know what you mean by "struggle" (i.e. I don't know the code you're using) and since I also don't have PyQt5 available, it's hard to say anything. – ImportanceOfBeingErnest Mar 09 '17 at 10:58
  • ok, by struggle I mean that I have the package PyQt5 but as i've never coded on UI, the translation is a bit complicated ahah – Mayeul sgc Mar 09 '17 at 11:00
  • I edited to answer with a PyQt5 version. Can you test it and report back if it's working? – ImportanceOfBeingErnest Mar 09 '17 at 11:38
  • It is perfectly working, I am gonna compare it with my code, i couldn't get the figure to show inside the window – Mayeul sgc Mar 10 '17 at 02:45
  • The PyQt5 version works well for me. But do you know how I can get the scrollable window's icon to match that of the figure that's passed in, please? `self.setWindowIcon(self.fig.???)`. I want the standard Matplotlib icon to show in the window's title bar. – snark Jul 05 '18 at 11:02
  • The PyQt5 version works for me. However, with ipython, I had to `import sys` and replace `exit` by `sys.exit`. – Pierre ALBARÈDE Feb 28 '20 at 21:32
6

Because the code was a bit complicated and confused for me, I refactored it more readable, refactorable like below. I hope this will be helpful. Thank you!

import sys
import random
import matplotlib
import matplotlib.pyplot as plt

from PyQt5.QtWidgets import (
                        QWidget,
                        QApplication,
                        QMainWindow,
                        QVBoxLayout,
                        QScrollArea,
                    )

from matplotlib.backends.backend_qt5agg import (
                        FigureCanvasQTAgg as FigCanvas,
                        NavigationToolbar2QT as NabToolbar,
                    )

# Make sure that we are using QT5
matplotlib.use('Qt5Agg')

# create a figure and some subplots
FIG, AXES = plt.subplots(ncols=4, nrows=5, figsize=(16,16))

for AX in AXES.flatten():
    random_array = [random.randint(1, 30) for i in range(10)]
    AX.plot(random_array)

def main():
    app = QApplication(sys.argv)
    window = MyApp(FIG)
    sys.exit(app.exec_())

class MyApp(QWidget):
    def __init__(self, fig):
        super().__init__()
        self.title = 'VERTICAL, HORIZONTAL SCROLLABLE WINDOW : HERE!'
        self.posXY = (700, 40)
        self.windowSize = (1200, 800)
        self.fig = fig
        self.initUI()

    def initUI(self):
        QMainWindow().setCentralWidget(QWidget())

        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().setSpacing(0)

        canvas = FigCanvas(self.fig)
        canvas.draw()

        scroll = QScrollArea(self)
        scroll.setWidget(canvas)

        nav = NabToolbar(canvas, self)
        self.layout().addWidget(nav)
        self.layout().addWidget(scroll)

        self.show_basic()

    def show_basic(self):
        self.setWindowTitle(self.title)
        self.setGeometry(*self.posXY, *self.windowSize)
        self.show()


if __name__ == '__main__':
    main()
KaySuparx
  • 71
  • 1
  • 3