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]: add_subfigure does not respect GridSpec parameters #28132

Open
nawendt opened this issue Apr 24, 2024 · 4 comments
Open

[Bug]: add_subfigure does not respect GridSpec parameters #28132

nawendt opened this issue Apr 24, 2024 · 4 comments

Comments

@nawendt
Copy link

nawendt commented Apr 24, 2024

Bug summary

According the documentation for Figure.add_subfigure, you should be able to use GridSpec to create a set of subfigures to fill a certain space of a parent figure. However, no matter what settings are input into GridSpec, the parent figure is divided into equally sized subfigures. Doing this same process with Figure.add_subplot produces the expected results with the axes bounded by the input extent and separated by the correct hspace and wspace.

Code for reproduction

import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import numpy as np

dpi = 100
width = 600
height = 600

main = plt.figure(figsize=(width / dpi, height / dpi), dpi=100,
                  facecolor='#9a9a9a', frameon=False)

gs = GridSpec(2, 4, left=.1, right=.9, bottom=0.1,
              top=0.9, wspace=0.15, hspace=0.15)

colors = np.array([
    ['red', 'black', 'green', 'blue'],
    ['brown', 'gray', 'white', 'gold']
])

panels = []
for row in range(2):
    for col in range(4):
        main.add_subfigure(gs[row, col], facecolor=colors[row][col])

plt.show()

Actual outcome

The output is equally sized subfigures without any space between them and the full extent being used despite the input restrictions.
test_add_subfig

Expected outcome

What is expected is a properly spaced areas with the proper extent. This can be produced when using add_subplot instead of add_subfigure.
test_add_subplot

Additional information

No response

Operating system

No response

Matplotlib Version

3.8.3

Matplotlib Backend

No response

Python version

3.12.2

Jupyter version

No response

Installation

conda

@rcomer
Copy link
Member

rcomer commented Apr 24, 2024

There was some discussion at #25511 about if and when subfigures should respect the gridspec's white space settings.

@ksunden
Copy link
Member

ksunden commented Apr 24, 2024

It is true that spacing/padding is ignored when computing the position in add_subfigure when given a subplot spec.

The code for this is here, where we recompute sizes of boxes based only on height/width ratios:

# need to figure out *where* this subplotspec is.
gs = self._subplotspec.get_gridspec()
wr = np.asarray(gs.get_width_ratios())
hr = np.asarray(gs.get_height_ratios())
dx = wr[self._subplotspec.colspan].sum() / wr.sum()
dy = hr[self._subplotspec.rowspan].sum() / hr.sum()
x0 = wr[:self._subplotspec.colspan.start].sum() / wr.sum()
y0 = 1 - hr[:self._subplotspec.rowspan.stop].sum() / hr.sum()
self.bbox_relative.p0 = (x0, y0)
self.bbox_relative.p1 = (x0 + dx, y0 + dy)

Replacing this with:

bbox = self._subplotspec.get_position(self)
self.bbox_relative.p0 = bbox.p0
self.bbox_relative.p1 = bbox.p1

Resolves to the expected behavior of this issue, though does cause ~6 test failures (though actually mostly relatively minor, to be honest... some slight tweaking to text/colorbar positions, though some more distinct differences as well).

I suspect that the reason for ignoring the padding has to do with the idea that a subfigure has its own internal padding usually, and so not having the padding may in many cases make the subfigures look better by default (perhaps especially when juxtaposed with non-subfigure subplots?)

That said, using Figure.subfigures actually does allow adding hspace/wspace (but not left/right/top/bottom, actually), and is respected, so perhaps it makes sense make the two more consistent?

I will tag in @jklymak and @anntzer to provide opinions here (as original author and the one who rewrote the code linked above, respectively)

If we decide to change behavior, the technical solution is pretty easy, though may need to either replace test files or adjust test code to get the reproduce the old figures (e.g. by explicitly setting hspace/wspace to 0).

Xrefs:

I do not see specific conversation in these threads as to whether to account for padding or not.

@jklymak
Copy link
Member

jklymak commented Apr 24, 2024

The original subfigures was not meant to account for paddings in a GridSpec. #25960 added keywords for subfigures, but does not look at the GridSpec.

  1. subfigures are meant to be mixed with subplots. Almost certainly the padding around a single subfigure in an array of subplots will need to be different than the subplots spacing.

  2. subfigure padding should almost certainly not be the default subplot padding as the layout needs are not the same.

Overall I think the current API is probably the correct one (ignore GridSpec, and control with keyword arguments). As stated before, users should not typically be creating gridspecs themselves.

The above can be accomplished with

subfigs = main.subfigures(2, 4, wspace=0.15, hspace=0.15)

The only thing I'd add is I wouldn't objects if we wanted to add bottom, top etc to fig.subfigures.

@nawendt
Copy link
Author

nawendt commented Apr 24, 2024

It certainly would be good to have options like top, etc., to fig.subfigures. Using that method, I have an example that is a little more specific to what I am intending to do.

import matplotlib.pyplot as plt
import numpy as np

dpi = 100
width = 600
height = 600

main = plt.figure(figsize=(width / dpi, height / dpi), dpi=100,
                             facecolor='#000000', frameon=False)

colors = np.array([
    ['red', 'black', 'green', 'blue'],
    ['brown', 'gray', 'white', 'gold']
])

sf = main.subfigures(2, 4)

for row in range(2):
    for col in range(4):
        sf[row, col].set_facecolor(colors[row, col])
        sf[row, col].add_subplot(xticks=[], yticks=[])

plt.show()

test_subfigures

The idea is that I can highlight an axes based on some other information with the facecolor of the subfigure. Being able to define the extent in figure.subfigures would help with making room for other things like text, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants