Skip to content

tight_layout for plot with non-clipped screen-unit items causes issues on zoom #12256

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
xo-HADES-xo opened this issue Sep 24, 2018 · 7 comments
Assignees
Labels
topic: geometry manager LayoutEngine, Constrained layout, Tight layout

Comments

@xo-HADES-xo
Copy link

Originally posted by @jklymak in #12239 (comment)

@xo-HADES-xo
Copy link
Author

xo-HADES-xo commented Sep 24, 2018

For a figure with multiple items, clicking the zoom button causes irregular change in layout.
Notes:
1. Hitting "Home" button does not change issues.
2. Using "Configure subplots>>Tight Layout" fixes plot to original.
3. This only occurs if plot has many elements, if only one curve is present issue is not repeated.

Attached is a code to demo issue in PYQT4. This is only present in new V3 of matplotlib.

from PyQt4 import QtCore, QtGui
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(745, 556)
        self.verticalLayout = QtGui.QVBoxLayout(Form)
        self.verticalLayout.setObjectName("verticalLayout")
        QtCore.QMetaObject.connectSlotsByName(Form)
        Form.setWindowTitle("Matplotlib V3 Zoom Error")
        self.setup_plot()
        self.plotFigure()

    def setup_plot(self):
        dpi = 75
        fig1 = plt.figure(dpi=dpi, facecolor='#617f8a', tight_layout=True)
        mpl.rc('axes', facecolor='#ffffff')
        self.plot1 = fig1.add_subplot(111)
        self.canvas1 = FigureCanvas(fig1)
        self.verticalLayout.addWidget(self.canvas1)
        toolbar = NavigationToolbar(self.canvas1, self.canvas1)
        toolbar.setMaximumSize(QtCore.QSize(16777215, 35))
        self.verticalLayout.addWidget(toolbar)

    def plotFigure(self):
        # Data for plotting
        t = np.arange(0.0, 2.0, 0.01)
        s1 = 1 + np.sin(2 * np.pi * t)
        self.plot1.plot(t, s1, c='r', lw='2', zorder=1, label='Sin')
        self.plot1.set(xlabel='time (s)', ylabel='voltage (mV)', title='About as simple as it gets, folks')

        self.plot1.scatter(1.25, 2.0, c='b', zorder=2, lw=2.0)
        annotxt = 'max. voltage: {0:0.2g} mV'.format(2.0)
        self.plot1.annotate(annotxt, fontsize=13, xy=(1.25, 2), 
                            xytext=(1.5, 1.75),
                            arrowprops=dict(facecolor='black', shrink=0.15),
                            )
        self.plot1.grid(True)
        self.canvas1.draw()

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Form = QtGui.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())`

Here are images of original plot and response to zoom behavior:

image

image2

@jklymak
Copy link
Member

jklymak commented Sep 25, 2018

Yes that’s because the annotation is still in its old spot so the axes is made small enough to still show the annotation. If you’d like to exclude the annotation from the layout then you can set its inlayout property to False (on a phone so cant look up exact syntax, but it’s as in the API note)

@ImportanceOfBeingErnest ImportanceOfBeingErnest added the topic: geometry manager LayoutEngine, Constrained layout, Tight layout label Sep 25, 2018
@jklymak
Copy link
Member

jklymak commented Sep 25, 2018

So the work around is to exclude the annotation from the layout:

ann = self.plot1.annotate(annotxt, fontsize=13, xy=(1.25, 2), 
                            xytext=(1.5, 1.75),
                            arrowprops=dict(facecolor='black', shrink=0.15),
                            )
ann.set_in_layout(False)

I'd have thought that setting annotation_clip=True would have also worked (it clips the annotation to the axes), but it doesn't seem to set the clipping bounding box to the axes, so thats a bug.

Sorry this behaviour has changed; But then we also get bug reports the other way, that someone's annotation is off the page after tight layout has been called. I think we are open to arguments that some artists should default to something other than set_in_layout(True). Certainly it is annoying to put an annotation on a plot and have it collapse when you start zooming.

One other todo here: I changed constrained layout to simply not adjust the layout when these sorts of collapses happen. Its easy to change tight_layout to do the same.

@jklymak
Copy link
Member

jklymak commented Sep 25, 2018

import numpy as np
import matplotlib.pyplot as plt

t = np.arange(0.0, 2.0, 0.01)
s1 = 1 + np.sin(2 * np.pi * t)

fig, ax = plt.subplots(tight_layout=True)

ax.plot(t, s1, c='r', lw='2', zorder=1, label='Sin')
ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='About as simple as it gets, folks')

ax.scatter(1.25, 2.0, c='b', zorder=2, lw=2.0)
annotxt = 'max. voltage: {0:0.2g} mV'.format(2.0)
ann = ax.annotate(annotxt, fontsize=13, xy=(1.25, 2),
                    xytext=(1.5, 1.75),
                    arrowprops=dict(facecolor='black', shrink=0.15),
                    annotation_clip=True)
ann.set_in_layout(True)
plt.show()

@xo-HADES-xo
Copy link
Author

Ok, so issue is understood (thanks).
However, it seems to me that old behavior should be preserved, i.e. have ann.set_in_layout(False) by default. This would eliminate the need to go through all code previously developed.

@jklymak jklymak changed the title tight_layout for plot with multiple items causes issues on zoom tight_layout for plot with non-clipped screen-unit items causes issues on zoom Sep 25, 2018
@jklymak jklymak self-assigned this Sep 25, 2018
@jklymak
Copy link
Member

jklymak commented Sep 26, 2018

import numpy as np
import matplotlib.pyplot as plt

t = np.arange(0.0, 2.0, 0.01)
s1 = 1 + np.sin(2 * np.pi * t)

fig, ax = plt.subplots(tight_layout=True)

ax.plot(t, s1, c='r', lw='2', zorder=1, label='Sin')
ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='About as simple as it gets, folks')

ax.scatter(1.25, 2.0, c='b', zorder=2, lw=2.0)
annotxt = 'max. voltage: {0:0.2g} mV'.format(2.0)
ann = ax.annotate(annotxt, fontsize=13, xy=(1.25, 2),
                    xytext=(1.5, 1.75),
                    arrowprops=dict(facecolor='black', shrink=0.15),
                    clip_on=True)
plt.show()

also works fine, though its clipped in the first rendering because its outside of the axes.

The annotation is part of the axes. I think it makes sense to make room for it as part of the axes if its been attached to the axes.

I think there is an argument for making a fig.annotation that doesn't belong to any axes.

@jklymak
Copy link
Member

jklymak commented Oct 21, 2018

Discussed on weekly call a couple of weeks ago and we are still going to include axes artists in the layout by default. So I’ll close as won’t fix. Sorry though that this broke something for you.

@jklymak jklymak closed this as completed Oct 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: geometry manager LayoutEngine, Constrained layout, Tight layout
Projects
None yet
Development

No branches or pull requests

3 participants