Skip to content

add_subplot(..., axes_cls=...) #18548

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
anntzer opened this issue Sep 23, 2020 · 9 comments
Closed

add_subplot(..., axes_cls=...) #18548

anntzer opened this issue Sep 23, 2020 · 9 comments
Milestone

Comments

@anntzer
Copy link
Contributor

anntzer commented Sep 23, 2020

I would like to suggest adding an axes_cls parameter to add_subplot()/add_axes(), to support creating axes using custom Axes subclasses -- specifically with the idea of better integrating axes_grid/axisartist into Matplotlib.

Currently, using an axes_grid/axisartist Axes requires a rather unusual sequence of calls, of the form

from mpl_toolkits.<...> import SomeAxesSubclass
ax = SomeAxesSubclass(fig, 111, **kwargs)
fig.add_subplot(ax)  # It's quite rare to call add_subplot() with an Axes instance!

instead, I propose that one can simply write

from mpl_toolkits.<...> import SomeAxesSubclass
ax = fig.add_subplot(1, 1, 1, axes_cls=SomeAxesSubclass, **kwargs)
# or, in the common case (add_subplot defaults to 111):
ax = fig.add_subplot(axes_cls=SomeAxesSubclass, **kwargs)

Note that there is precedence in that plt.figure likewise takes a FigureClass argument for custom figure subclasses. (If we wanted to be consistent we could name the new kwarg AxesClass instead of axes_cls, but... ugh.) Also note that axes_class would conflict with the projection kwarg, because projections (e.g. polar) are also implemented as custom axes subclasses, but that's just how things are: you can't automatically combine a mpl_toolkits subclass with a projection subclass.

See also #17335 (comment).

One side advantage is that this would also make all the explicit Subplot classes (HostAxes vs. SubplotHost, axislines.Subplot vs. axislines.Axes) unnecessary: add_subplot would dynamically create the subclasses, with exactly the same machinery as it does for the normal Axes/Subplot.

I don't think the implementation will be particularly hard; as usual the hardest is deciding whether we want this or not :-)

@dopplershift
Copy link
Contributor

What does this get us versus the existing fig.add_subpot(1, 1, 1, projection='polar')? This is how CartoPy currently makes things work. (I had no idea fig.add_subplot(ax) was a thing.)

@jklymak
Copy link
Member

jklymak commented Sep 23, 2020

It means that all the subclasses don't need to do an extra registration step, which I guess axisgrid doesn't do? I think its less mysterious to pass the class than import and then pass to the class rather than import and then pass a string that we have to look up?

@dopplershift
Copy link
Contributor

Since the projection option wasn't mentioned at all above, I was curious how it impacts the proposal. IMO, I'm not convinced we should have both projection and axes_cls options. This complicates things for the user by having kwargs with similar functionality.

I also would not be a fan of deprecating projection seeing as it's used in pretty much every piece of code out there using CartoPy.

@anntzer
Copy link
Contributor Author

anntzer commented Sep 23, 2020

Certainly, you could also abuse the projection mechanism for passing axes subclasses that have nothing to do with projections, which has actually been done in the wild(!): https://github.com/astrofrog/mpl-scatter-density#scatter_density-method, but let's say that ax.add_subplot(..., projection="axisartist.Host") would be really weird (... I think?).
To be clear I am absolutely not suggesting to deprecate projection (even if ignoring cartopy, there's far too many uses of projection="polar"/"3d" in the wild anyways), but just to add axes_cls (name up to bikeshedding) as another kwarg with similar functionality (and which cannot be passed at the same time as projection), except that it directly takes a class object rather than a string. (And again, if I had to choose between projection and axes_cls, I would certainly rather keep projection as it's much more commonly used -- but I think we can have both).
As a concrete example, an axisartist example that currently reads

ax1 = SubplotHost(fig, 1, 2, 2, grid_helper=grid_helper)
fig.add_subplot(ax1)

would now read

ax1 = fig.add_subplot(1, 2, 2, axes_cls=HostAxes, grid_helper=grid_helper)

Personally I've always thought that the first snippet felt extremely... unmatplotlibish (it's quite rare on the user-facing side to have to explicitly instantiate artists by calling the class constructor), whereas the second snippet feels more natural ("it's just a normal add-a-subplot-thing"). I guess even

ax1 = fig.add_subplot(1, 2, 2, projection="axisartist.HostAxes", grid_helper=grid_helper)

(or even

ax1 = fig.add_subplot(1, 2, 2, projection=axisartist.HostAxes, grid_helper=grid_helper)

expanding projection to directly support classes) would be OK, but again the name of the kwarg would be pretty confusing...

@jklymak
Copy link
Member

jklymak commented Sep 23, 2020

Do we mind aliases? if so I'd do

ax1 = fig.add_subplot(1, 2, 2, projection=axisartist.HostAxes, grid_helper=grid_helper)

and add an alias kwarg axes_cls that is just an alias of projection....

@timhoffm
Copy link
Member

To summarize:

  • The current way of instantiating an Axes and adding the to the figure is unmatplotlibish and cumbersome.
  • We always use factory functions. add_subplot() is one, it's just missing a way to generically define the class.

So the idea is reasonable, the only question is how to define the API.

  • We already have polar=True (equal to projection='polar') and the more general projection parameter covering a subset of the proposed functionality. Unfortunately their naming was chosen too narrow and projection=axisartist.HostAxes would indeed be confusing. We cannot reuse the existing parameters.
  • A new axes_cls (naming t.b.d.) parameter is the clean way. We probably must leave projection and polar for backward compatibility. The redundancy is ugly but the lesser evil. Docs of both existing parameters should state that they are other ways of defining axes_cls. Giving more than on is an error and should fail.
    I'm not convinced axes_cls should be an alias of projection. Coding-wise it would be the easy way. But it would imply an equality instead of the actual logic hirarchy (it would also technically allow the confusing projection=axisartist.HostAxes). I think we should go the extra mile, accept that we have some redundancy for backcompatibility reasons and properly parse and document everything.
  • Naming: Is there a reason not to use axes_class? That's already used in axesgrid1. axes_cls feels a bit like an unnecessary abbreviation.
  • Argument type: Passing a class in feels reasonable. OTOH we should support the same values as projection, so probably both a class and a str should be supported.

@jklymak
Copy link
Member

jklymak commented Sep 23, 2020

I don't think that projection=axisartist.HostAxes is particularly confusing, but nor do I think we should document it. I think we have too many places where we do a bunch of magical kwarg wrangling for little practical benefit, and the cost that a change in one place doesn't get made somewhere else. If someone wants to do projection=axisartist.HostAxes what is the harm?

I prefer axes_class to axes_cls.

@dopplershift
Copy link
Contributor

  • @timhoffm 's nice write-up has convinced me the redundancy is ok.
  • I also prefer axes_class
  • The prototypical Cartopy usage is with class instances: ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())--I'm not sure if that makes anyone feel more or less at peace with stuffing classes themselves through projection.

@anntzer
Copy link
Contributor Author

anntzer commented Nov 1, 2020

closed by #18573.

@anntzer anntzer closed this as completed Nov 1, 2020
@QuLogic QuLogic added this to the v3.4.0 milestone Nov 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants