You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
frommpl_toolkits.<...>importSomeAxesSubclassax=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
frommpl_toolkits.<...>importSomeAxesSubclassax=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.
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 :-)
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.)
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?
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 bothprojection 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.
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
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
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.
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?
@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.
Activity
dopplershift commentedon Sep 23, 2020
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 ideafig.add_subplot(ax)was a thing.)jklymak commentedon 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 commentedon Sep 23, 2020
Since the
projectionoption wasn't mentioned at all above, I was curious how it impacts the proposal. IMO, I'm not convinced we should have bothprojectionandaxes_clsoptions. This complicates things for the user by having kwargs with similar functionality.I also would not be a fan of deprecating
projectionseeing as it's used in pretty much every piece of code out there using CartoPy.anntzer commentedon 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 ofprojection="polar"/"3d"in the wild anyways), but just to addaxes_cls(name up to bikeshedding) as another kwarg with similar functionality (and which cannot be passed at the same time asprojection), except that it directly takes a class object rather than a string. (And again, if I had to choose betweenprojectionandaxes_cls, I would certainly rather keepprojectionas it's much more commonly used -- but I think we can have both).As a concrete example, an axisartist example that currently reads
would now read
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
(or even
expanding
projectionto directly support classes) would be OK, but again the name of the kwarg would be pretty confusing...jklymak commentedon Sep 23, 2020
Do we mind aliases? if so I'd do
and add an alias kwarg
axes_clsthat is just an alias ofprojection....timhoffm commentedon Sep 23, 2020
To summarize:
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.
polar=True(equal toprojection='polar') and the more generalprojectionparameter covering a subset of the proposed functionality. Unfortunately their naming was chosen too narrow andprojection=axisartist.HostAxeswould indeed be confusing. We cannot reuse the existing parameters.axes_cls(naming t.b.d.) parameter is the clean way. We probably must leaveprojectionandpolarfor backward compatibility. The redundancy is ugly but the lesser evil. Docs of both existing parameters should state that they are other ways of definingaxes_cls. Giving more than on is an error and should fail.I'm not convinced
axes_clsshould be an alias ofprojection. 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 confusingprojection=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.axes_class? That's already used inaxesgrid1.axes_clsfeels a bit like an unnecessary abbreviation.projection, so probably both a class and a str should be supported.jklymak commentedon Sep 23, 2020
I don't think that
projection=axisartist.HostAxesis 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 doprojection=axisartist.HostAxeswhat is the harm?I prefer
axes_classtoaxes_cls.dopplershift commentedon Sep 23, 2020
axes_classax = 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 throughprojection.anntzer commentedon Nov 1, 2020
closed by #18573.