-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
ENH: box aspect for axes #14917
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
ENH: box aspect for axes #14917
Conversation
Seems cool. But I think we need to sit down and think about all the axes sizing and sharing options a bit more holistically. I think if you change the non-original position then you can keep constrained layout working. |
I would have thought that is what I'm doing in line 1527. Will need to check this at some later point.
Sure, that's why I put it out in its current state, to see what is possible and to define some working use cases. In general what's still missing is a sharing of geometries (as opposed to sharing of data); or maybe call it a twinning across subplots. |
Great, then I'm surprised there are issues with |
I think it's a great idea :)
it's not too surprising if the second call overwrites the first one, whereas in
... do we want the second to overwrite the first one? do we want to add a check and error out? or whatnot. |
No, the usual (data-)aspect and the box aspect are not mutually exclusive. In fact using both may make perfect sense, as shown in case F.. (It's just that |
Ah, thanks for the clarification, I missed that; ignore the nonsense I wrote above. |
Some things to consider: What happens if the user manually sets the position and the ax.set_position([0.1, 0.1, 0.9, 0.2])
ax.set_box_aspect(1.) For constrained_layout, the I'm not sure which behaviour you want here, but you'll need to decide which call trumps which here. Even more tricky is ax.set_box_aspect(1.)
ax.set_position([0.1, 0.1, 0.9, 0.2]) |
25da964
to
cdd2843
Compare
The order of setting position and box_aspect isn't relevant. But I added a test to make sure that's the case. |
3b19944
to
0e2fdb9
Compare
Just so it doesn't get lost from #15010 , I would really like if it was possible to choose this behaviour as a default setting in rcParams. |
I mentionned my problem with an rc parameter for this in #15001 (comment):
I could imagine though to let this be taken as argument by the axes' init, to allow for something like
|
I think that's a good compromise. It could be made to work by only applying it to the first axes. |
68af0c2
to
f4cd76e
Compare
458ff92
to
f40a5b7
Compare
Power cycled to try to re-run against master and then belated realized this won't help with circle... Half of the warnings were from the required target not being hit, pushed a commit making sure they are generated (we do not auto-doc |
ok, I understand the other half of the errors. we auto-generate and insert .. table::
:class: property-table
======================================================================================= =====================================================================================================
Property Description
======================================================================================= =====================================================================================================
:meth:`adjustable <matplotlib.axes._base._AxesBase.set_adjustable>` {'box', 'datalim'}
:meth:`agg_filter <matplotlib.artist.Artist.set_agg_filter>` a filter function, which takes a (m, n, 3) float array and a dpi value, and returns a (m, n, 3) array
:meth:`alpha <matplotlib.artist.Artist.set_alpha>` float or None
:meth:`anchor <matplotlib.axes._base._AxesBase.set_anchor>` 2-tuple of floats or {'C', 'SW', 'S', 'SE', ...}
:meth:`animated <matplotlib.artist.Artist.set_animated>` bool
:meth:`aspect <matplotlib.axes._base._AxesBase.set_aspect>` {'auto', 'equal'} or num
:meth:`autoscale_on <matplotlib.axes._base._AxesBase.set_autoscale_on>` bool
:meth:`autoscalex_on <matplotlib.axes._base._AxesBase.set_autoscalex_on>` bool
:meth:`autoscaley_on <matplotlib.axes._base._AxesBase.set_autoscaley_on>` bool
:meth:`axes_locator <matplotlib.axes._base._AxesBase.set_axes_locator>` Callable[[Axes, Renderer], Bbox]
:meth:`axisbelow <matplotlib.axes._base._AxesBase.set_axisbelow>` bool or 'line'
:meth:`box_aspect <matplotlib.axes._base._AxesBase.set_box_aspect>` None, or a number
:meth:`clip_box <matplotlib.artist.Artist.set_clip_box>` `.Bbox`
:meth:`clip_on <matplotlib.artist.Artist.set_clip_on>` bool
:meth:`clip_path <matplotlib.artist.Artist.set_clip_path>` [(`~matplotlib.path.Path`, `.Transform`) | `.Patch` | None]
:meth:`contains <matplotlib.artist.Artist.set_contains>` callable
:meth:`facecolor <matplotlib.axes._base._AxesBase.set_facecolor>` color
:meth:`fc <matplotlib.axes._base._AxesBase.set_facecolor>` color
:meth:`figure <matplotlib.axes._base._AxesBase.set_figure>` `.Figure`
:meth:`frame_on <matplotlib.axes._base._AxesBase.set_frame_on>` bool
:meth:`gid <matplotlib.artist.Artist.set_gid>` str
:meth:`in_layout <matplotlib.artist.Artist.set_in_layout>` bool
:meth:`label <matplotlib.artist.Artist.set_label>` object
:meth:`navigate <matplotlib.axes._base._AxesBase.set_navigate>` bool
:meth:`navigate_mode <matplotlib.axes._base._AxesBase.set_navigate_mode>` unknown
:meth:`path_effects <matplotlib.artist.Artist.set_path_effects>` `.AbstractPathEffect`
:meth:`picker <matplotlib.artist.Artist.set_picker>` None or bool or float or callable
:meth:`position <matplotlib.axes._base._AxesBase.set_position>` [left, bottom, width, height] or `~matplotlib.transforms.Bbox`
:meth:`prop_cycle <matplotlib.axes._base._AxesBase.set_prop_cycle>` unknown
:meth:`rasterization_zorder <matplotlib.axes._base._AxesBase.set_rasterization_zorder>` float or None
:meth:`rasterized <matplotlib.artist.Artist.set_rasterized>` bool or None
:meth:`sketch_params <matplotlib.artist.Artist.set_sketch_params>` (scale: float, length: float, randomness: float)
:meth:`snap <matplotlib.artist.Artist.set_snap>` bool or None
:meth:`title <matplotlib.axes._axes.Axes.set_title>` str
:meth:`transform <matplotlib.artist.Artist.set_transform>` `.Transform`
:meth:`url <matplotlib.artist.Artist.set_url>` str
:meth:`visible <matplotlib.artist.Artist.set_visible>` bool
:meth:`xbound <matplotlib.axes._base._AxesBase.set_xbound>` unknown
:meth:`xlabel <matplotlib.axes._axes.Axes.set_xlabel>` str
:meth:`xlim <matplotlib.axes._base._AxesBase.set_xlim>` (left: float, right: float)
:meth:`xmargin <matplotlib.axes._base._AxesBase.set_xmargin>` float greater than -0.5
:meth:`xscale <matplotlib.axes._base._AxesBase.set_xscale>` {"linear", "log", "symlog", "logit", ...}
:meth:`xticklabels <matplotlib.axes._base._AxesBase.set_xticklabels>` List[str]
:meth:`xticks <matplotlib.axes._base._AxesBase.set_xticks>` unknown
:meth:`ybound <matplotlib.axes._base._AxesBase.set_ybound>` unknown
:meth:`ylabel <matplotlib.axes._axes.Axes.set_ylabel>` str
:meth:`ylim <matplotlib.axes._base._AxesBase.set_ylim>` (bottom: float, top: float)
:meth:`ymargin <matplotlib.axes._base._AxesBase.set_ymargin>` float greater than -0.5
:meth:`yscale <matplotlib.axes._base._AxesBase.set_yscale>` {"linear", "log", "symlog", "logit", ...}
:meth:`yticklabels <matplotlib.axes._base._AxesBase.set_yticklabels>` List[str]
:meth:`yticks <matplotlib.axes._base._AxesBase.set_yticks>` unknown
:meth:`zorder <matplotlib.artist.Artist.set_zorder>` float
======================================================================================= =====================================================================================================
into a bunch of docstrings which points to the base class (because it asks the methods what class they belong to). Will push a commit 'fixing' this soon... |
fb8ddd7
to
db45c2f
Compare
db45c2f
to
20134e2
Compare
I think this is basically good to go. |
I would agree that if we had In general we have the semantics of I therefore find |
OK, I'll check later, but what if you set two axes to have different aspect ratio, and use a layout manager? I'm just a little concerned there are all sorts of edge cases here that will cause problems, but I may be completely incorrect. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually because this works on the active
position of the axes, it works fine w/ the layout managers. Or at least I couldn't easily break it.
I'll merge tomorrow, if no one else does, or @ImportanceOfBeingErnest you can self-merge if we give this a day or so to make sure there are no further objections. |
Thanks @ImportanceOfBeingErnest I think this will be a pretty valuable tool in the axes shaping toolbox! |
The way that bbox_inches='tight' is implemented we need to ensure that we do not try to adjust the aspect during the draw (because we have temporarily de-coupled the reported figure size from the transforms which results in the being distorted). Previously we did not have a way to fix the aspect ratio in screen space of the Axes (only the aspect ratio in dataspace) however in 3.3 we gained this ability for both Axes (matplotlib#14917) and Axes3D (matplotlib#8896 / matplotlib#16472). Rather than add an aspect value to `set_aspect` to handle this case, in the tight_bbox code we monkey-patch the `apply_aspect` method with a no-op function and then restore it when we are done. Previously we would set the aspect to "auto" and restore it in the same places. closes matplotlib#16463.
The way that bbox_inches='tight' is implemented we need to ensure that we do not try to adjust the aspect during the draw (because we have temporarily de-coupled the reported figure size from the transforms which results in the being distorted). Previously we did not have a way to fix the aspect ratio in screen space of the Axes (only the aspect ratio in dataspace) however in 3.3 we gained this ability for both Axes (matplotlib#14917) and Axes3D (matplotlib#8896 / matplotlib#16472). Rather than add an aspect value to `set_aspect` to handle this case, in the tight_bbox code we monkey-patch the `apply_aspect` method with a no-op function and then restore it when we are done. Previously we would set the aspect to "auto" and restore it in the same places. closes matplotlib#16463.
PR Summary
Matplotlib axes'
aspect
refers to the data, i.e. it defines the ratio of vertical vs. horizontal data units. However, it seems people often (mis)use it in order to set the aspect of the axes box, which is of course possible by knowing the limits of the data and usingadjustable="box"
. But it inevitably leads to problems, e.g.axes_grid1
toolkit is able to solve natively.In short, sometimes people just want to make a square plot.
So it seems useful to introduce a
box_aspect
parameter, which sets the aspect (i.e. the ratio between height and width) of the axes box, independent of the data.Some usecases:
A. A square axes, independent of data
B. Shared square axes
C. Square twin axes
D. Normal plot next to image (works with constrained_layout)
E. Square joint/marginal plot
F. Equal data aspect, unequal box aspect
Issues:
As @jklymak already pointed out, there might be a problem with layout managers, and indeed at least case E. from above fails with contrained_layout.(fixed by FIX constrained_layout w/ hidden axes #14919)Obviously a fixed box_aspect only makes sense with
adjustable="datalim"
. Since the order in whichbox_aspect
andadjustable
are set is arbitrary, this still needs to define in which cases to error out, or silently fall back.PR Checklist