11

I have a python project that outputs several Matplotlib figures; each figure contains several charts. The problem that project launches about 15 figures (windows) every run, which I can not reduce.

Is it possible to concatenate all these figures (windows) to a single tabbed window so that each tab represents one figure?

Any help is much appreciated.

Thanks in advance


Workaround

Thanks to @mobiusklein comments below he suggested a workaround, to export the figures as myltipage pdf file as shown here.

Important note about the multipage pdf example mentioned above.

I tried it, but I got an error regarding the LaTeX use in matplotlib. Because fixing this error is beyond the scope of this question, so I suggest if it occurs to anyone, to set plt.rc('text', usetex=False) instead of usetex=True

I still hope if someone have other solution or workaround to post it for the benefit of others.

Mohammad ElNesr
  • 2,477
  • 4
  • 27
  • 44
  • 1
    Are you talking about the GUI rendering backends that `matplotlib` provides, or does your project have its own GUI? – mobiusklein May 20 '16 at 12:39
  • Thank you for you comment. I am using pyCharm with its standard interface. I use no GUI in this project till now, do you suggest an easy one? – Mohammad ElNesr May 20 '16 at 13:01
  • Based on some quick googling, it looks like PyCharm assumes you're using the Tk backend for `matplotlib` by default. I'll write an answer accordingly. – mobiusklein May 20 '16 at 13:06
  • Tk GUI supports only hard-coding, which I do not prefer (as a VB.net programmer). Do you advise me a simpler GUI for Python and pyCharm. – Mohammad ElNesr May 20 '16 at 14:21
  • It depends upon whether you want a convenient display system for your plots for yourself, a GUI in some way associated with PyCharm, or a way to export your figures in a convenient format. – mobiusklein May 20 '16 at 14:23

6 Answers6

10

I wrote a simple wrapper for matplotlib that does something like you're describing. You need pyqt5 for it to work though.

Here is the code, you build a plotWindow object and feed it figure handles. It'll create a new tab for each figure.

import matplotlib
# prevent NoneType error for versions of matplotlib 3.1.0rc1+ by calling matplotlib.use()
# For more on why it's nececessary, see
# https://stackoverflow.com/questions/59656632/using-qt5agg-backend-with-matplotlib-3-1-2-get-backend-changes-behavior
matplotlib.use('qt5agg')

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QTabWidget, QVBoxLayout
import matplotlib.pyplot as plt
import sys

class plotWindow():
    def __init__(self, parent=None):
        self.app = QApplication(sys.argv)
        self.MainWindow = QMainWindow()
        self.MainWindow.__init__()
        self.MainWindow.setWindowTitle("plot window")
        self.canvases = []
        self.figure_handles = []
        self.toolbar_handles = []
        self.tab_handles = []
        self.current_window = -1
        self.tabs = QTabWidget()
        self.MainWindow.setCentralWidget(self.tabs)
        self.MainWindow.resize(1280, 900)
        self.MainWindow.show()

    def addPlot(self, title, figure):
        new_tab = QWidget()
        layout = QVBoxLayout()
        new_tab.setLayout(layout)

        figure.subplots_adjust(left=0.05, right=0.99, bottom=0.05, top=0.91, wspace=0.2, hspace=0.2)
        new_canvas = FigureCanvas(figure)
        new_toolbar = NavigationToolbar(new_canvas, new_tab)

        layout.addWidget(new_canvas)
        layout.addWidget(new_toolbar)
        self.tabs.addTab(new_tab, title)

        self.toolbar_handles.append(new_toolbar)
        self.canvases.append(new_canvas)
        self.figure_handles.append(figure)
        self.tab_handles.append(new_tab)

    def show(self):
        self.app.exec_()

if __name__ == '__main__':
    import numpy as np

    pw = plotWindow()

    x = np.arange(0, 10, 0.001)

    f = plt.figure()
    ysin = np.sin(x)
    plt.plot(x, ysin, '--')
    pw.addPlot("sin", f)

    f = plt.figure()
    ycos = np.cos(x)
    plt.plot(x, ycos, '--')
    pw.addPlot("cos", f)
    pw.show()

This is also posted at: https://github.com/superjax/plotWindow

Hopefully this could be a good starting point for your application.

superjax
  • 252
  • 2
  • 9
3

The backend you choose to use for matplotlib controls how each figure is displayed. Some backends just render figures to file, while others like the tk, qt, or gtk backends render figures in graphical windows. Those backends determine what functionality those GUI windows have.

The existing backends don't support the type of tabbed navigation you're looking for. Someone else here implemented this using Qt4.

You might also try writing your own report files with PDF or HTML which would let you more easily write more complex image arrangements with simpler libraries.

Community
  • 1
  • 1
mobiusklein
  • 1,403
  • 9
  • 12
  • Thank you for your answer. Regarding the last part of your answer, do you mean that matplotlib can export figures to PDF format? – Mohammad ElNesr May 20 '16 at 14:17
  • 1
    Yes. `pdf` is an available format. You can either set the the backend to `pdf` or call `savefig` with a `pdf` file extension to save each figure to an individual file. You can also use http://matplotlib.org/examples/pylab_examples/multipage_pdf.html to save multiple figures to a multipage pdf file – mobiusklein May 20 '16 at 14:20
  • Great, thank you very much for this explanation. I hope I could vote up your answer, but I can't because of my insufficient reputation. For now, I will accept your answer, and edit my question to include the last part of your comment as a workaround. – Mohammad ElNesr May 20 '16 at 14:25
1

Something that is functionally similar might be implemented using the widgets. For example, provide a row of buttons, one button for each "tab", and repaint the graphical portion of the window in response to each button.

DrM
  • 2,404
  • 15
  • 29
1

The buttons example as a workaround is creative. As another stated you can use PyQt to create a tabbed window.

It is for this reason I use PyQtGraph. PyQtGraph only uses PyQt as a backend and therefor "natively" supports both tabbed windows and "docks". Docks allow for movable tabs and splits as well as breaking off a tab or split to a new floating window.

In general, PyQtGraph's docks provide a method for organizing your graphs/plots/images that I haven't been able to get with other libraries.

Bokeh offers tabbed windows through their Panels and Tabs widgets.

I know it is not always feasible to move away from matplotlib but I felt like there was a lack of representation of libraries which have considered and implemented tools specifically for your use case.

John Lunzer
  • 361
  • 2
  • 5
1

I shared my code that allows docking and tabbing with drag-n-drop (qt docking system). It acts as a matplotlib backend, so it's easy to integrate.

https://github.com/peper0/mpldock

peper0
  • 3,111
  • 23
  • 35
0

I recently released a python package which contains a class called DockablePlotWindow which provides a solution similar to superjax's answer but which provides a little more flexibility to organize how the plots are initially displayed.

I'm interested to continue improving this package so feel free to open pull requests or issues on github. You can find information about it here: https://github.com/nanthony21/mpl_qt_viz and here: https://nanthony21.github.io/mpl_qt_viz/

nickexists
  • 404
  • 1
  • 4
  • 12
  • First, thank you for your efforts and for your reply. Second, could you please add pictures and screenshots of what your module does? It is not a good idea to put documentation with code only! should everyone install the module to see what is it doing? – Mohammad ElNesr Jan 18 '21 at 22:55
  • Thanks for the feedback, I'll plan to improve on the documentation in the future. – nickexists Jan 19 '21 at 01:36