FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

legend(bbox_transform) don't work with tight layout in 2.2.x · Issue #11041 · matplotlib/matplotlib · GitHub

Repository navigation

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

Closed

Description

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.

Activity

  1. jklymak commented on Apr 12, 2018

    Member

    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].

  2. fredrik-1 commented on Apr 12, 2018

    ContributorAuthor

    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

  3. ImportanceOfBeingErnest commented on Apr 12, 2018

    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.

  4. jklymak commented on Apr 12, 2018

    Member

    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.

  5. jklymak commented on Apr 12, 2018

    Member

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

  6. ImportanceOfBeingErnest commented on Apr 12, 2018

    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.

  7. fredrik-1 commented on Apr 13, 2018

    ContributorAuthor

    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.

  8. fredrik-1 commented on Apr 13, 2018

    ContributorAuthor

    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

  9. jklymak commented on Apr 13, 2018

    Member

    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.

  10. fredrik-1 commented on Apr 16, 2018

    ContributorAuthor

    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.

  11. jklymak commented on Apr 29, 2018

    Member

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: geometry managerLayoutEngine, Constrained layout, Tight layout

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL