Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
ENH: box aspect for axes#14917
Uh oh!
There was an error while loading.Please reload this page.
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 caseF.. (It's just that |
anntzer commentedJul 30, 2019 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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
tocdd2843
CompareThe order of setting position and box_aspect isn't relevant. But I added a test to make sure that's the case. |
3b19944
to0e2fdb9
CompareJust 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. |
ImportanceOfBeingErnest commentedAug 9, 2019 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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
tof4cd76e
Compare458ff92
tof40a5b7
ComparePower 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
todb45c2f
Comparedb45c2f
to20134e2
CompareI 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 theactive
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 thatwe do not try to adjust the aspect during the draw (because we havetemporarily de-coupled the reported figure size from the transformswhich results in the being distorted). Previously we did not have away to fix the aspect ratio in screen space of the Axes (only theaspect ratio in dataspace) however in 3.3 we gained this ability forboth 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 witha no-op function and then restore it when we are done. Previously wewould set the aspect to "auto" and restore it in the same places.closesmatplotlib#16463.
The way that bbox_inches='tight' is implemented we need to ensure thatwe do not try to adjust the aspect during the draw (because we havetemporarily de-coupled the reported figure size from the transformswhich results in the being distorted). Previously we did not have away to fix the aspect ratio in screen space of the Axes (only theaspect ratio in dataspace) however in 3.3 we gained this ability forboth 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 witha no-op function and then restore it when we are done. Previously wewould set the aspect to "auto" and restore it in the same places.closesmatplotlib#16463.
Uh oh!
There was an error while loading.Please reload this page.
PR Summary
Matplotlib axes'
aspect
refers to thedata, 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 theaxes 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 alreadypointed out, there might be a problem with layout managers, and indeed at least caseE. from above fails with contrained_layout.(fixed byFIX 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