Skip to content

Qt4Agg backend changes figure size #9913

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
ImportanceOfBeingErnest opened this issue Dec 2, 2017 · 20 comments
Closed

Qt4Agg backend changes figure size #9913

ImportanceOfBeingErnest opened this issue Dec 2, 2017 · 20 comments

Comments

@ImportanceOfBeingErnest
Copy link
Member

ImportanceOfBeingErnest commented Dec 2, 2017

Edit: Note that this issue is also still present with Qt5Agg and matplotlib 3.0.1

Showing a figure with Qt4Agg backend changes the figure size. This is observed with Windows 8.1, python 2.7, matplotlib 2.1.

import matplotlib
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([1,2,4])

def update(evt):
    print(fig.get_size_inches())
       
cid = fig.canvas.mpl_connect("key_press_event", update)

print(fig.get_size_inches())
plt.show()

The original figure is [ 6.4 4.8]. After the figure is drawn (and in this case you press a key), it is only [ 6.4 4.78].
One could argue that this is only a small amount, but saving the figure from within the event loop now gives a different figure size of the saved figure. To make this more obvious, consider the following code which saves a new image after each press of space.

import matplotlib
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
import numpy as np

class NextPlotter(object):
    def __init__(self, fig, func, n):
        self.__dict__.update(locals())
        self.i = 0
        self.cid = self.fig.canvas.mpl_connect("key_press_event", self.adv)
        
    def adv(self, evt):
        if evt.key == " " and self.i < self.n:
            self.func(self.i)
            self.i+=1
        elif self.i >= self.n:
            plt.close("all")

# Create data
pos = range(8)
scatterdata = np.random.normal(0, 5, size=(50,len(pos)))

#Create plot
fig, ax = plt.subplots()
ax.axis([-10,10,-10,10])
scatter = ax.scatter([], [], c="crimson", s=60)

# define updating function
def update(i):
    print("++ update " + str(i))
    scatter.set_offsets(np.c_[pos,scatterdata[i,:]])
    fig.canvas.draw()
    print(fig.get_size_inches())
    plt.savefig("plot_{i}.png".format(i=i))
    print(fig.get_size_inches())

# instantiate NextPlotter; press <space> to advance to the next image        
c = NextPlotter(fig, update, len(scatterdata))

plt.show()

resulting in
nextplotter_qtagg3

Is this a problem of PyQt4? Can anyone reproduce this outside of Windows 8?

@afvincent
Copy link
Contributor

@ImportanceOfBeingErnest Thanks for the neat illustrations of the issue :).

I cannot reproduce this on Linux (Arch 4.13.12), with Python 2.7.11 (from conda) and Matplotlib 2.1.0 and master (from git), with the Qt5Agg and Qt4Agg backends. Would this suggest the problem to be a platform specific issue?

@ImportanceOfBeingErnest
Copy link
Member Author

I've done some further tests:

  • Windows 10, python 2.7, PyQt4 ("Qt4Agg"), matplotlib 2.1: The issue is completely the same. Figure is [ 6.4 4.78] and shrinks itself upon saving repeatedly.
  • Windows 8.1, python 2.7, PyQt4 ("Qt4Agg"), matplotlib 1.5.3: The first issue persists; the figure is smaller than expected [ 8. 5.975] instead of [8. 6.]. But it does not shrink itself continuously using the second code from above.
  • Windows 10, python 2.7, PyQt4 ("Qt4Agg"), matplotlib 1.5.3: exact same as with Windows 8 and mpl 1.5.3: Figure is smaller, but no continuous shrinking.

So there are actually 2 issues here: The fact that the figure is smaller in PyQt window is present throughout matplotlib versions. The fact that it shrinks each time it is saved is an issue with matplotlib 2 compared to matplotlib 1.5.
In any case it may be specific to Windows. (Unfortunately I can currently neither test with Linux, nor with PyQt5.)

@jklymak
Copy link
Member

jklymak commented Dec 2, 2017

Not an issue macOS python3, QT5 or macosx backends. (Sorry, can't test QT4 ;-). I've labelled as above...

@tacaswell
Copy link
Member

There is a feedback loop in the GUI code that resizes the MPL-figure to fit into the window (ex if you drag the window bigger) and if you resize the figure from python the size is forwarded to the GUI (which then may slightly re-size the Figure to fit if there are rounding issues (ie, the requested size + dpi do not result in an integer number of pixels).

@tacaswell tacaswell modified the milestones: v2.1.1, v2.2 Dec 3, 2017
@bidhya
Copy link

bidhya commented Jan 1, 2018

I am not exactly sure, but I just started noticing the figures that were normal size now shrinks in Jupyter Notebook. I run this command to set figure size
%matplotlib inline import matplotlib.pyplot as plt plt.rcParams.update({'figure.figsize': (14, 6)})

When I run this suite of code again, it seems to give the right sized figure as before. I am on Windows 10 with 32-bit Python 2.7 (Anaconda), and Matplotlib 2.1

@tacaswell
Copy link
Member

@bidhya Issues with the inline backend are probably un-related to this issue.

@bidhya
Copy link

bidhya commented Jan 1, 2018

Thank you @tacaswell . I think the problem could be related new Pandas 0.22.0 which I updated this morning. Started noticing this change in plot size with Pandas plot functions.

@kklmn
Copy link

kklmn commented Feb 10, 2018

I confirm the bug exists, it is Windows- and Qt-backend specific.
In Ubuntu 17.10 I don't have it with any backend and in Windows 10 with Tk backend I don't have it either.

In both Windows 10 and Ubuntu 17.10 I have

  • PyQt5 5.10.0
  • mpl 2.1.2

The problem is not directly related to Jupyter/Ipython. I typically run my scripts from Spyder (currently 3.2.6) in its IPython console, where I experience the problem. If I run a script from Spyder but in an external system terminal I do have the shrinkage with Qt5agg backend and don't have it with TkAgg.

A workaround for me is to do
fig.set_size_inches(size_inches)
after each fig.savefig. The size I save at the creation time as
size_inches = fig.get_size_inches()

kklmn added a commit to kklmn/xrt that referenced this issue Feb 10, 2018
@anntzer
Copy link
Contributor

anntzer commented Feb 10, 2018

I think this may be effectively fixed (at least the repeated size changes upon savefig) by #10292?

@bidhya
Copy link

bidhya commented Feb 10, 2018

I put the following command on a separate cell by itself in jupyter notebook.
%matplotlib inline
This seems to solve the problem of figure being resized to smaller size.

@ImportanceOfBeingErnest
Copy link
Member Author

@bidhya Would you mind leaving this issue for the problem concerning the QT backend? If you have a problem with the %matplotlib inline backend, please open a new issue about it.

@tacaswell
Copy link
Member

@ImportanceOfBeingErnest can you test with the current master branch?

@ImportanceOfBeingErnest
Copy link
Member Author

@tacaswell I cannot test with the complete master, but when taking the backend_bases.py from the current master and running the above code, the automatic shrinking is indeed not observable anymore. In that sense I can confirm that #10292 fixes the repeated size changes upon savefig as suggested by @anntzer above.
What remains though is that the figure is [ 6.4 4.78] in size, instead of the expected [6.4,4.8].

@ImportanceOfBeingErnest
Copy link
Member Author

To give another example of this issue, which might make it more obvious and also show more clearly why it is annoying, consider the following:

The following code is expected to draw a figure on screen, then allow the user to interactively change it (e.g. pan/zoom etc) and then save it using a different figure size and dpi upon pressing the key t.

import matplotlib
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt

fig,ax=plt.subplots()
ax.plot([1,3,1])

class AnySizeSaver():
    def __init__(self, fig=None, figsize=None, dpi=None, filename=None):
        if not fig: fig=plt.gcf()
        self.fig = fig
        if not figsize: figsize=self.fig.get_size_inches()
        self.figsize=figsize
        if not dpi: dpi=self.fig.dpi
        self.dpi=dpi
        if not filename: filename="myplot.png"
        self.filename=filename
        self.cid = self.fig.canvas.mpl_connect("key_press_event", self.key_press)

    def key_press(self, event):
        if event.key == "t":
            self.save()
        
    def save(self):
        oldfigsize = self.fig.get_size_inches()
        olddpi=self.fig.dpi
        self.fig.set_size_inches(self.figsize)
        self.fig.set_dpi(self.dpi)
        self.fig.savefig(self.filename, dpi=self.dpi)
        self.fig.set_size_inches(oldfigsize, forward=True)
        self.fig.set_dpi(olddpi)
        self.fig.canvas.draw_idle()
        
ass = AnySizeSaver(fig=fig, figsize=(3,3), dpi=600)

plt.show()

Expected outcome of this code is a saved png file with 1800 x 1800 pixels.
Actual outcome is a png of 1800 x 1000 pixels.

What's more is that once t is pressed the figure on screen shrinks itself to [ 3.19 1.64] inches, which is completely off.

The same script using "TkAgg" works as expected (although pressing t lets the navigation toolbar disappear - but that would be a separate issue, see #9914).

@swift-n-brutal
Copy link

I met the same issue on win8.1 and Qt4. I tried showing figures with (width, height) from (2, 2) to (9, 9). The shrinking always happens in the height dimension and the reduced size is exactly 0.02 inches. When the figure is draw the first time, the size is correct. But after any interaction (no resizing) with the figure, the height will be reduced by 0.02. When the size is too small or too large (exceeding your monitor), it will be set to the min or max value. The code to reproduce the issue

import matplotlib.pyplot as plt
from matplotlib.widgets import Button
from argparse import ArgumentParser

def update(event):
    ax = event.inaxes
    fig = ax.get_figure()
    old_pos = ax.get_position().bounds
    print 'update fig', fig.get_figwidth(), fig.get_figheight()
    print '    ax', old_pos
    ax.set_position((old_pos[0], 0.8 - old_pos[1], old_pos[2], old_pos[3]))
    fig.canvas.draw()

def test(figsize=(4, 4), dpi=100.):
    fig = plt.figure(1, figsize=figsize, dpi=dpi)
    ax = fig.add_axes((0.3, 0.2, 0.4, 0.2))
    button = Button(ax, 'Update')
    button.on_clicked(update)
    print 'before draw', fig.get_figwidth(), fig.get_figheight()
    fig.canvas.draw()
    print 'after draw', fig.get_figwidth(), fig.get_figheight()
    return fig, button

def get_parser(ps=None):
    if ps is None: ps = ArgumentParser()
    ps.add_argument('-s', type=float, default=4.)
    return ps

ps = get_parser()
args = ps.parse_args()
fig, button = test((args.s, args.s))
if hasattr(fig.canvas, 'manager'):
    fig.show()
fig.canvas.start_event_loop(5)

The reduced size seems related to some margin in the height direction.

@tacaswell tacaswell added Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. and removed Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. labels Feb 24, 2019
@tacaswell tacaswell modified the milestones: v3.1.0, v3.2.0 Mar 18, 2019
@tacaswell tacaswell removed this from the v3.2.0 milestone Sep 10, 2019
@tacaswell tacaswell added this to the needs sorting milestone Sep 10, 2019
@QuLogic
Copy link
Member

QuLogic commented Mar 27, 2020

Is this still a problem?

@ImportanceOfBeingErnest
Copy link
Member Author

While some of the tangential problems have been fixed in the meantime, the original problem is still present, that is, a figure that should be (6.4, 4.8) inches becomes (6.4, 4.78) inches when drawn with the QT backend on windows.

@QuLogic
Copy link
Member

QuLogic commented Mar 28, 2020

At 100 dpi, 4-8-4.78 == 22 pixels, which is the size of the statusbar. We only check the size hint for that, and then calculate a window size from it; I'd guess that if you check https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backends/backend_qt5.py#L573 the sbs.height() would be 0.

@QuLogic
Copy link
Member

QuLogic commented May 15, 2020

The statusbar is removed in the default setup on master, so perhaps this is fixed now?

@QuLogic
Copy link
Member

QuLogic commented Jun 3, 2021

Either way, we no longer support Qt4.

@QuLogic QuLogic closed this as completed Jun 3, 2021
@story645 story645 removed this from the future releases milestone Oct 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants