As a user, I was always confused by the which parameter of set_position and under what circumstance one might set the active position. The docstring indicates that the original and active positions are usually the same unless a fixed aspect ratio is set.
Looking through the code of apply_aspect, I think it always updates the active position. Since apply_aspect gets called within draw, get_tightbbox and get_position, it's not clear to me that the user can ever effectively set the active position directly, unless they made an axes subclass that replaced apply_aspect.
apply_aspect itself uses a private _set_position that was factored out of set_position at #9082. So maybe the existence of the which parameter in the public function is a hangover from before that and is no longer needed.
Proposed fix
Either deprecate the which parameter in the public method so it only sets the original position, or make it clear in the docstring that setting the active position will almost never be effective.
changed the title [-][MNT]: deprecate *which* parameter of `Axes.set_position`?[/-][+][MNT]: deprecate _which_ parameter of `Axes.set_position`?[/+]on Mar 7, 2026
changed the title [-][MNT]: deprecate _which_ parameter of `Axes.set_position`?[/-][+][MNT]: deprecate "which" parameter of `Axes.set_position`?[/+]on Mar 7, 2026
Deprecations always introduce API changes. That is their purpose.
I meant that clarifying the docstring could be a lower-impact first step, since it avoids starting a deprecation cycle immediately. If the parameter is indeed no longer meaningful in practice, then deprecating it would also make sense.
I'm not quite clear when or why one would use the different values of the parameter. I think thinking this through exactly is the prerequisite for both documenting and deprecating (the latter because we must be sure we don't remove a valid use case).
Semi-related: The apply_aspect(position=) parameter is related to the topic and also not useful. We might be able to improve on that as well.
This one appears to be about colorbar placement. Of the four tests that fail, the only one obviously using colorbars is an image test with this reference.
The colorbar one seems to be only used for cases where the colorbar isn't drawn. The line right above sets the axes to invisible. The line in question is setting the axes to be tiny and in the corner of the figure. Perhaps that was necessary at the time to prevent the invisible axes affecting bbox_inches='tight'? These days setting invisible is enough to ensure an axes doesn't participate in layout questions.
In the parasite axes code, apply_aspect gets called right after set_position. Parasite axes does not appear to have its own apply_aspect, so I think the active position will immediate be overwritten by matplotlib.Axes.apply_aspect, as described in the OP.
Secondary axes and axes3d both have their own apply_aspect but again they always set the active position
I think deprecating this makes sense. We don't honour "active", so it doesn't make sense to offer it.
Long term removal may take some effort as there are various axes parameters that have the active and original positions, but I don't think that should change whether set_postion should make it seem like it has control over what type of position it sets.
Is this correct? - At its core the original/active concept is done because the Axes is originally placed somewhere. The eventually drawn active position may be different because of additional constraints (at least aspect handling, maybe also layout managers?). Once calculated, only the active position is relevant. It is used for drawing and also the default for get_position(). We only need the original position to be able to recalculate the active position if constraints change.
What does the user need?
setting and getting the original position - to be able to redesign the original position/arrangement of Axes
getting the active position - to know where the Axes is effectively so that they can put something next to it
Not needed: Setting the active position - this should always be calculated from the original position and the constraints.
On the one hand, both parts are needed, we can't make them go away. On the other hand, this feels a bit bolted on. Would it make more sense to have two separate functions? If I would redesign this from scratch, I'd likely use set_position()/get_position() only for the original position, and additionally introduce get_active_position() (likely with a better name).
The problem is that this is not compatible with the current defaults. In particular get_position returns the active position by default, but set_position by default sets both. Well, I suppose effectively only the original as the active should be recomputed, but t.b.d.
While we may be able to remove the parameter from set_position and reduce it effectively to set_position(..., which="original") in a compatible way for default usage. This deepens the inconsistency that set_position would apply to original, but get_position would apply to active. I have no good solution for that, maybe it's still the way to go, but requires very good documentation.
I think the history here is that set_position used to be used internally. We moved the actual function down to private and made set_position a wrapper that takes also the axes out of the layout management. Probably when that happened we should have removed the which parameter or started the deprecation.
Activity
Chirag3841 commented on Mar 8, 2026
@rcomer Clarifying the behavior in the docstring might be the safer option, since deprecating the which parameter could introduce an API change.
rcomer commented on Mar 8, 2026
Deprecations always introduce API changes. That is their purpose.
Chirag3841 commented on Mar 8, 2026
I meant that clarifying the docstring could be a lower-impact first step, since it avoids starting a deprecation cycle immediately. If the parameter is indeed no longer meaningful in practice, then deprecating it would also make sense.
timhoffm commented on Mar 8, 2026
I'm not quite clear when or why one would use the different values of the parameter. I think thinking this through exactly is the prerequisite for both documenting and deprecating (the latter because we must be sure we don't remove a valid use case).
Semi-related: The apply_aspect(position=) parameter is related to the topic and also not useful. We might be able to improve on that as well.
rcomer commented on Mar 8, 2026
I tried adding the deprecation locally and found it is used twice in toolkits
matplotlib/lib/mpl_toolkits/axes_grid1/axes_grid.py
Lines 557 to 558 in 829a9cc
This one appears to be about colorbar placement. Of the four tests that fail, the only one obviously using colorbars is an image test with this reference.
matplotlib/lib/mpl_toolkits/axes_grid1/parasite_axes.py
Line 132 in 829a9cc
This one caused eight test failures, four of which are image comparison tests.
No tests fail if I simply remove those set_position calls. So it seems like they are redundant, but I do not know the axes_grid modules very well.
rcomer commented on Mar 8, 2026
Looking again at those toolkits cases
Secondary axes and axes3d both have their own apply_aspect but again they always set the active position
matplotlib/lib/matplotlib/axes/_secondary_axes.py
Lines 144 to 147 in 829a9cc
matplotlib/lib/mpl_toolkits/mplot3d/axes3d.py
Lines 433 to 448 in 829a9cc
rcomer commented on Mar 8, 2026
I found a third party case where a subclass clobbers apply_aspect, so I guess setting the active position could work with that. set_position does not appear in that repo though.
https://github.com/AudioSceneDescriptionFormat/splines/blob/4473d4b3acc1c9abeb22197c50ec942566c7696a/doc/rotation/helper.py#L279-L280
jklymak commented on Mar 8, 2026
I think deprecating this makes sense. We don't honour "active", so it doesn't make sense to offer it.
Long term removal may take some effort as there are various axes parameters that have the active and original positions, but I don't think that should change whether set_postion should make it seem like it has control over what type of position it sets.
timhoffm commented on Mar 9, 2026
Is this correct? - At its core the original/active concept is done because the Axes is originally placed somewhere. The eventually drawn active position may be different because of additional constraints (at least aspect handling, maybe also layout managers?). Once calculated, only the active position is relevant. It is used for drawing and also the default for get_position(). We only need the original position to be able to recalculate the active position if constraints change.
What does the user need?
On the one hand, both parts are needed, we can't make them go away. On the other hand, this feels a bit bolted on. Would it make more sense to have two separate functions? If I would redesign this from scratch, I'd likely use set_position()/get_position() only for the original position, and additionally introduce get_active_position() (likely with a better name).
The problem is that this is not compatible with the current defaults. In particular get_position returns the active position by default, but set_position by default sets both. Well, I suppose effectively only the original as the active should be recomputed, but t.b.d.
While we may be able to remove the parameter from set_position and reduce it effectively to set_position(..., which="original") in a compatible way for default usage. This deepens the inconsistency that set_position would apply to original, but get_position would apply to active. I have no good solution for that, maybe it's still the way to go, but requires very good documentation.
jklymak commented on Mar 9, 2026
I think the history here is that set_position used to be used internally. We moved the actual function down to private and made set_position a wrapper that takes also the axes out of the layout management. Probably when that happened we should have removed the which parameter or started the deprecation.
DOC: Document that set_aspect applies the aspect lazily
DOC: Document that set_aspect applies the aspect lazily
DOC: Document that set_aspect applies the aspect lazily