Skip to content
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

[Bug]: subfigures messes up with fig.legend zorder #22011

Closed
cgadal opened this issue Dec 20, 2021 · 10 comments · Fixed by #26926
Closed

[Bug]: subfigures messes up with fig.legend zorder #22011

cgadal opened this issue Dec 20, 2021 · 10 comments · Fixed by #26926
Labels
Difficulty: Medium https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues Good first issue Open a pull request against these issues if there are no active ones! topic: figures and subfigures
Milestone

Comments

@cgadal
Copy link
Contributor

cgadal commented Dec 20, 2021

Bug summary

When using subfigures, legends called with fig.legend() are hidden behind the subfigure background and the axes background.

Code for reproduction

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(1, 10, 10)
y1 = x
y2 = -x

fig = plt.figure(constrained_layout=True)
subfigs = fig.subfigures(nrows=2, ncols=1)

for subfig in subfigs:
    axarr = subfig.subplots(1, 2)
    for ax in axarr.flatten():
        l1, = ax.plot(x, y1, label='line1')
        l2, = ax.plot(x, y2, label='line2')
            #
        ax.set_facecolor('none')  # Comment this line
    subfig.set_facecolor('none')   # and this line to make the legend hidden behing the white backgrounds
l = fig.legend(handles=[l1, l2], loc='upper center', ncol=2)
plt.savefig('subfigures_figlegend.png', dpi=200)
plt.show()

Actual outcome

With ax.set_facecolor('none') and subfig.set_facecolor('none') commented:
subfigures_figlegend2
With ax.set_facecolor('none') and subfig.set_facecolor('none') not commented, i.e transparent backgrounds:

subfigures_figlegend

Expected outcome

Legend should be on the top of the backgrounds, as produced when building the figure with plt.subplots():

fig, axarr = plt.subplots(2, 2, constrained_layout=True)
for ax in axarr.flatten():
    l1, = ax.plot(x, y1, label='line1')
    l2, = ax.plot(x, y2, label='line2')
    #
    ax.legend()
fig.legend(handles=[l1, l2], loc='upper center', ncol=2)
plt.savefig('subplots_figlegend.png', dpi=200)
plt.show()

subplots_figlegend

Additional information

  • This does not happen for ax.legend() and subfig.legend().

Operating system

Ubuntu 20

Matplotlib Version

3.5.0

Matplotlib Backend

QtAgg

Python version

3.8.10

Jupyter version

No response

Installation

pip

@jklymak
Copy link
Member

jklymak commented Dec 20, 2021

It is true that subfigures are drawn after other figure artists, and hence will cover the figure artists. Subfugures are new enough that I think we could easily change the order - it does seem that if someone adds a figure-level artist they would want it to show up above any subfigures.

@rustamzh
Copy link

Are there any updates or workaround? I have tried changing zorder on the figure's legend, but it does not work.

@tacaswell tacaswell added this to the v3.9.0 milestone Sep 19, 2023
@tacaswell tacaswell added Good first issue Open a pull request against these issues if there are no active ones! Difficulty: Medium https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues labels Sep 19, 2023
@github-actions
Copy link

Good first issue - notes for new contributors

This issue is suited to new contributors because it does not require understanding of the Matplotlib internals. To get started, please see our contributing guide.

We do not assign issues. Check the Development section in the sidebar for linked pull requests (PRs). If there are none, feel free to start working on it. If there is an open PR, please collaborate on the work by reviewing it rather than duplicating it in a competing PR.

If something is unclear, please reach out on any of our communication channels.

@tacaswell
Copy link
Member

There is no work around, currently the sub-figures do not participate in the z-order

self.patch.draw(renderer)
mimage._draw_list_compositing_images(
renderer, self, artists, self.suppressComposite)
for sfig in self.subfigs:
sfig.draw(renderer)

try:
renderer.open_group('subfigure', gid=self.get_gid())
self.patch.draw(renderer)
mimage._draw_list_compositing_images(
renderer, self, artists, self.figure.suppressComposite)
for sfig in self.subfigs:
sfig.draw(renderer)
renderer.close_group('subfigure')
finally:
self.stale = False

The fix is to add them to the artists returned by _get_draw_artists and treat them like any other Artist in the parent (Sub)Figure rather than specially.

I think this is a good first issue as it only requires understanding a few methods in the Figure classes, understanding how the draw logic works, and the API choice is "be like everything else". However labeling it as medium difficulty as there will be a need for an API change note and a discussion about what the default z-order should be for sub-figures.

@cgadal
Copy link
Contributor Author

cgadal commented Sep 20, 2023

If there is no rush, I'd like to get started on this as my first contribution to matplotlib. It will also give me the opportunity to dive deeper into how the API works.

@rcomer
Copy link
Member

rcomer commented Sep 20, 2023

@cgadal please go ahead! Our contributors’ guide is here:
https://matplotlib.org/devdocs/devel/index.html

@cgadal
Copy link
Contributor Author

cgadal commented Sep 22, 2023

I've made the following changes so far: main...cgadal:matplotlib:subfigure-zorder

  • changed the self.draw() method for Figure and SubFigure so subfigures behave like regular artists
  • changed _get_draw_artists so it also returns subfigures
  • zorder attribute, and self.set_zorder()/self.get_zorder() methods are inherited from the Artist class.

This seems to be enough to solve this issue, and the user can properly control the zorder of subfigures as shown by this small example:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(1, 10, 10)
y1 = x
y2 = -x

fig = plt.figure(constrained_layout=True)
subfigs = fig.subfigures(nrows=1, ncols=2)

for subfig in subfigs:
    axarr = subfig.subplots(2, 1)
    for ax in axarr.flatten():
        (l1,) = ax.plot(x, y1, label="line1")
        (l2,) = ax.plot(x, y2, label="line2")

subfigs[0].set_zorder(6)
l = fig.legend(handles=[l1, l2], loc="upper center", ncol=2)
plt.savefig("subfigures_figlegend2.png", dpi=200)
plt.show()

subfigures_figlegend2

Remaining question:

  • About the default zorder for subfigures, I could not find any user case where it should differ from 0. What do you think ?

@tacaswell
Copy link
Member

Interesting.

@cgadal That looks good. This still needs tests and a whats-new entry (and possibly an API change note). Can you open a PR (maybe in draft) from that branch?

@jklymak Is there a reason we were positively removing the sub-figures from the artist list?

@jklymak
Copy link
Member

jklymak commented Sep 22, 2023

Not off the top of my head. I think subfigures is relatively well tested so if this breaks something we will know.

@cgadal
Copy link
Contributor Author

cgadal commented Sep 29, 2023

@tacaswell Done here: #26926 . Also, current test are running fine, and I added a Whats new entry note. I'm still unsure about what to add ad an API change note though.

ksunden added a commit that referenced this issue Jan 23, 2024
Closes #22011: Changes to SubFigures so it behaves like a regular artist
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Difficulty: Medium https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues Good first issue Open a pull request against these issues if there are no active ones! topic: figures and subfigures
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants