10000 legend(bbox_transform) don't work with tight layout in 2.2.x · Issue #11041 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

legend(bbox_transform) don't work with tight layout in 2.2.x #11041

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
fredrik-1 opened this issue Apr 12, 2018 · 11 comments
Closed

legend(bbox_transform) don't work with tight layout in 2.2.x #11041

fredrik-1 opened this issue Apr 12, 2018 · 11 comments
Labels
topic: geometry manager LayoutEngine, Constrained layout, Tight layout

Comments

@fredrik-1
Copy link
Contributor
fredrik-1 commented Apr 12, 2018

The following code worked as expected in matplotlib 2.1.x but doesn't work in matplotlib 2.2.x

fig=plt.figure(1)
fig.clf()
ax=[fig.add_subplot(2,2,i+1) for i in range(4)]
for j in range(3):
      for i in range(2):
            ax[j].plot([0,i])
ax[0].legend(['1','2'], 
          bbox_to_anchor=(0.1, 1), 
          bbox_transform=ax[-1].transAxes, loc="upper left")
ax[-1].axis("off")
fig.set_tight_layout(True)

The subplots are in the wrong place and have the wrong size (mostly outside the figure window in my real ploting code ...) and its get even worse if you interact with them.

python 3.6
matplotlib 2.2.0 and 2.2.2 has been tested.

@jklymak
Copy link
Member
jklymak commented Apr 12, 2018

ax.legend is now counted as part of the bbox for the axes when calculating tight_layout. In this case, the axes is ax[0], and so tight_layout tries (and fails) to provide enough room for the legend in the spacing between ax[0] and the other axes. The relevant PR is #9164.

The solution is to use fig.legend. For some cases, that will mean you will need to cart around the lines that you want as part of the legend. I acknowledge that is a bit of an inconvenience, but the converse is that legends are not equal artists in tight_layout or constrained_layout and you end up with axes and legends overlapping. For this layout, I think the idea that this is a legend that belongs to the figure is more sensical than the idea that this is the legend for ax[0].

8000 @jklymak jklymak added the topic: geometry manager LayoutEngine, Constrained layout, Tight layout label Apr 12, 2018
@fredrik-1
Copy link
Contributor Author
fredrik-1 commented Apr 12, 2018

Ok, thanks. I guess that is a useful property in most cases but it broke my code spectacularly. I think the following small change solve it for me

linesLabel=[line for line in ax[0].lines if not line.get_label()=='_nolegend_']
ax[0].figure.legend(linesLabel, legendString,
          bbox_to_anchor=(0, 1), 
          bbox_transform=ax[-1].transAxes, loc="upper left")

And the main part of the extra is to avoid to put a legend on the nolabel lines

@ImportanceOfBeingErnest
Copy link
Member

Would it in general be an idea to let artists decide whether they want to take part in geometry management or not?
I could imagine something similar to the animated property. Say an artist has a property geomanageable (which is set to True for axes, ticks, titles, legends etc by default), the geometry manager could query that property and only include it if it is set.
For the cases where you don't want that, you set it to False. I guess this would also solve the recent issue about long titles destroying the layout.

@jklymak
Copy link
Member
jklymak commented Apr 12, 2018

Right now all both layout managers do is query the tight_bbox of the axes objects, and other figure-level artists. So if you added such a flag to individual artists, it would affect the tight_bbox of the axes, and other uses of tight_bbox (savefig(bbox_inches='tight') being the major one that comes to mind) would similarly ignore the artists that are not included.

I'm not wholesale against this, but its pretty fiddly. I'd rather we were more disciplined in where in the figure hierarchy we place artists. To my mind saying a legend is a member of axes[0] but drawing in in axes[3] is basically asking for automatic layout not to work too well. That it worked before is to my mind an oversight in not including legends in the bbox for the axes.

Similarly I'd be mildly against letting people have axes titles that are wildly too wide for the figure. Taking the title out of the bbox is possible, but then the automatic layout would no longer make room for it vertically. Better just to note that automatic layout doesn't work anymore, fix the title, or turn automatic layout off.

But we can keep an eye on it. If there are lots of users trying to attach artists to an axis, but have the artist plotted far off on another axis, maybe its worth adding the extra layer of bookkeeping. ConnectionPatch is one instance where I don't really know what tight_bbox does with the artists, and it has a legitimate need to plot off the axes.

Finally, another option is to do all your layout, call the layout manager, turn off the layout manager, and add the extra do-hickeys.

fig=plt.figure(1)
fig.clf()
ax=[fig.add_subplot(2,2,i+1) for i in range(4)]
for j in range(3):
      for i in range(2):
            ax[j].plot([0,i])
ax[-1].axis("off")
fig.set_tight_layout(True)
fig.canvas.draw()
fig.set_tight_layout(False)
ax[0].legend(['1','2'],
          bbox_to_anchor=(0.1, 1),
          bbox_transform=ax[-1].transAxes, loc="upper left")
plt.show()

works fine.

@jklymak
Copy link
Member
jklymak commented Apr 12, 2018

BTW, see #10682 where I propose making this worse 😉

@ImportanceOfBeingErnest
Copy link
Member

Yep I suppose this is the correct thing to do. I was rather wondering if there was an easy option to opt out of this - which may still be useful in certain cases. I guess the main argument here is simply that some automatic layout system cannot guess what you trying to do. So certain cases cannot be handled reasonably by it. E.g. the annotation bbox example - I suppose it's not actually clear what a user expects if calling tight_layout on that one. In such cases it would probably be useful to have the option to get the annotation box included or excluded on a per case basis.

@fredrik-1
Copy link
Contributor Author

I think my use case was kind of natural. I have many subplots that should have the same legend, the legend are to large to be nicely be shown in a single subplot. So I used the plot data from one of the subplots and put it in another clear subplot. The workaround is not that difficult but it is definitely more complicated and more error prone than my old code.

I doubt that it is that easy to implement but it would be nice if the legend in my use case was handled together with the ax/subplot that it actually exist in and not together with the ax that it got the data from.

The problem with turning on and off tight layout are that it doesn't work well when resizing the figure.

@fredrik-1
Copy link
Contributor Author

The following example doesn't work as expected

fig=plt.figure(100)
fig.clf()
ax=[fig.add_subplot(2,2,i+1) for i in range(4)]
for j in range(3):
       for i in range(3):
             ax[j].plot([0,i])
 n=35          
 legendString=['1'*n,'2'*n,'3'*n]
 ax[-1].legend(ax[0].lines, legendString,
                  bbox_to_anchor=(0, 1),  loc="upper left")
ax[-1].axis("off")
fig.set_tight_layout(True)

The subplots are to small

@jklymak
Copy link
Member
jklymak commented Apr 13, 2018

Again the legend over spilling them axismakes the bounding box get larger. That makes the space between subplots smaller. It’s working as designed, the difference is the API change between 2.1 and 2.2.

@fredrik-1
Copy link
Contributor Author

Ok, I understand that now. I think that it would be good to have a parameter that sets if a legend (or other artists) should be included in the tight_layout calculation or not. I believe that would be useful when you work with data and want to have reasonable good plots that work with different figure sizes and different legend lengths. It is better to show nice and large plots and a cut legend or overlapping titles instead of a good looking legend and small plots. Tight_layout are good if you want larger plots in many applications and not just to avoid overlaps.

@jklymak
Copy link
Member
jklymak commented Apr 29, 2018

Note the workaround is easier using get_legend_handles_labels (which I didn't know about until I tried to write it, and got a name conflict).

lines, labels = ax[0].get_legend_handles_labels()
ax[0].figure.legend(lines, labels,
          bbox_to_anchor=(0, 1), 
          bbox_transform=ax[-1].transAxes, loc="upper left")

I am also suggesting in #10682 that we add an inbbox property to artists, so they can be excluded from tight_layout via leg.inbbox = False

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
0