Skip to content

FigureCanvas.draw() with tight_layout () needs to be called twice with Matplotlib 2.1.0 #10361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
chipmuenk opened this issue Feb 1, 2018 · 4 comments
Milestone

Comments

@chipmuenk
Copy link

chipmuenk commented Feb 1, 2018

Bug report

Bug summary
PyQt widgets with a Matplotlib (versions 2.1.0 and 2.1.2) canvas don't respect tight_layout() unless a redraw is triggered for the second time. The class shown below (excerpt) is instantiated in all plotting widgets in the app under https://github.com/chipmuenk/pyfda .

The full code can be found under
https://github.com/chipmuenk/pyFDA/blob/develop/pyfda/plot_widgets/mpl_widget.py

In the pyfda project, the bug is referenced as chipmuenk/pyfda#113 .

Code for reproduction

from matplotlib.figure import Figure
# the other imports are from PyQt4 / PyQt5 via a compatibility wrapper
class MplWidget(QWidget):
    """
    Construct a subwidget from pyQt Matplotlib canvas and a modified NavigationToolbar.
    """
    def __init__(self, parent):
        super(MplWidget, self).__init__(parent)
        self.fig = Figure()

        self.pltCanv = FigureCanvas(self.fig)
        self.pltCanv.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        self.pltCanv.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.pltCanv.setFocus()
        self.pltCanv.updateGeometry()
        self.mplToolbar = MplToolbar(self.pltCanv, self) # customized toolbar

        #=============================================
        # Main plot widget layout
        #=============================================
        self.layVMainMpl = QVBoxLayout()
        self.layVMainMpl.addWidget(self.mplToolbar)
        self.layVMainMpl.addWidget(self.pltCanv)
        self.setLayout(self.layVMainMpl)
        
    def redraw(self):
        """
        Redraw the figure with new properties (grid, linewidth etc.)
        """
        # only execute when at least one axis exists -> tight_layout crashes otherwise
        if self.fig.axes:
            for ax in self.fig.axes:
                if self.mplToolbar.lock_zoom:
                    ax.axis(self.limits) # restore old limits
                else:
                    self.limits = ax.axis() # save limits
            try:
                # tight_layout() crashes with small figure sizes e.g. when upper
                # and lower limits are swapped
               self.fig.tight_layout(pad = 0.1)
            except(ValueError, np.linalg.linalg.LinAlgError):
                pass

            self.pltCanv.draw() # now (re-)draw the figure

Actual outcome
The plot is surrounded by a lot of whitespace

Expected outcome
The plot should be formatted with a tight_layout() layout. This was the case with matplotlib 1.4.2, 1.5.2, 1.5.4 and 2.0

Matplotlib version

  • Operating system: Win 7, Linux 32 and 64 bit
  • Matplotlib version: 2.1.0, 2.1.2
  • Matplotlib backend: Qt4Agg and Qt5Agg
  • Python version: 2.7, 3.5, 3.6
  • Jupyter version (if applicable): ---
  • Other libraries:

Installation via conda (default channel)

@tacaswell tacaswell added this to the v3.0 milestone Feb 1, 2018
@jklymak
Copy link
Member

jklymak commented Feb 1, 2018

I have some guesses as to whats wrong, but a link to a self contained example (if its too long to post here) would help.

Have you tried self.fig = Figure(tight_layout=True) That should call tight layout whenever draw is called.

If you don't call tight_layout does the white space dramatically decrease in size? Possibly when tight_layout is called the first time its working with a layout that has been made on a virtual canvas that is much smaller than the canvas ends up being.

@chipmuenk
Copy link
Author

Sorry for not providing a self-contained example, I haven't found the time yet to carve out a MWE from my code containing a matplotlib-widget and some dummy code. In the meantime - if you don't mind the trouble (don't hit me!) - pyfda can be installed/deinstalled easily via pip install pyfda or conda install -c Chipmuenk pyfda ...

I've tried your suggestions:

  • Most interestingly: matplotlib 2.0 shows the same (correct) behaviour as matplotlib 1.5, the bug seems to have (re-)entered with matplotlib 2.1.0
  • Commenting out the call to tight_layout() leaves the same (to my eye, not counting pixels) amount of white space with matplotlib 1.5 and 2.1
  • I didn't know the option Figure(tight_layout=True) , but it shows exactly the same behaviour as calling tight_layout() manually for both mpl 1.5 and 2.1 in my case. However, now I can't catch the exceptions occurring when a user tries to shrink the window to a very small size, e.g.
    raise LinAlgError("Singular matrix")
    py.linalg.linalg.LinAlgError: Singular matrix

The problem seems to be around for a while though: https://stackoverflow.com/questions/18678306/matplotlib-qt-tight-layout-doesnt-work-on-the-first-draw#18702991

The proposed measure didn't help in my case though.

@chipmuenk
Copy link
Author

Dynamically updating the plots only when the tab has been made visible fixes this bug as well.

@jklymak
Copy link
Member

jklymak commented Feb 9, 2019

I'm closing this, unless there is a MWE for us to see what the problem is. Thanks!

@jklymak jklymak closed this as completed Feb 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants