| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
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. |
Sorry, something went wrong.
|
Great, then I'm surprised there are issues with constrained_layout since its no different than a data-aspect constrained axes. constrained_layout only cares about the "original" size. |
Sorry, something went wrong.
Sorry, something went wrong.
|
I think it's a great idea :) ax.set_aspect(a, "data") ax.set_aspect(b, "axes") it's not too surprising if the second call overwrites the first one, whereas in ax.set_aspect(a) ax.set_box_aspect(b) ... do we want the second to overwrite the first one? do we want to add a check and error out? or whatnot. |
Sorry, something went wrong.
|
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 adjustable cannot be "box" in case of using a box aspect.) |
Sorry, something went wrong.
|
Ah, thanks for the clarification, I missed that; ignore the nonsense I wrote above. |
Sorry, something went wrong.
|
Some things to consider: What happens if the user manually sets the position and the box_aspect? ax.set_position([0.1, 0.1, 0.9, 0.2])
ax.set_box_aspect(1.)For constrained_layout, the set_position method takes the axes out of the automatic layout because we assume the user wants that position set. But I indicate that just by setting the layout boxes associated with the axes to None in ax.set_position, which you probably don't want to depend on here. 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]) |
Sorry, something went wrong.
|
The order of setting position and box_aspect isn't relevant. But I added a test to make sure that's the case. |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
|
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 fig, axs = plt.subplots(2,2, subplot_kw=dict(box_aspect=1)) |
Sorry, something went wrong.
|
I think that's a good compromise. It could be made to work by only applying it to the first axes. append_axes, add_subplot and add_axes would check if there already is one in the figure and go back to auto, but I could see why that would not be very transparent. |
Sorry, something went wrong.
|
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 Axes). |
Sorry, something went wrong.
|
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... |
Sorry, something went wrong.
|
I think this is basically good to go. |
Sorry, something went wrong.
|
I would agree that if we had .set_data_aspect, naming this .set_axes_aspect would be ideal. But given that the data aspect is by now commonly known as "the aspect", it is important to distinguish clearly. In general we have the semantics of object.set_property(...) throughout the code, which reads as "set the object's property to ...". In the case of the aspect, axes.set_aspect, needs to read "set the axes' aspect to ...". Following this, axes.set_axes_aspect would read "set the axes' axes aspect to" in which case there is little indication for that to be different from "set the axes' aspect to"; and that would be the data aspect. I therefore find .set_box_aspect much clearer: The axes has an aspect (which is related to data), and it has a box-aspect (which is related to its box). |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
There was a problem hiding this comment.
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.
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
|
Thanks @ImportanceOfBeingErnest I think this will be a pretty valuable tool in the axes shaping toolbox! |
Sorry, something went wrong.
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.
| Back | FazBrowse Home | New Git URL |
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 using adjustable="box". But it inevitably leads to problems, e.g.
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
fig5, axs = plt.subplots(2,2, sharex="col", sharey="row", gridspec_kw = dict(height_ratios=[1,3], width_ratios=[3,1])) axs[0,1].set_visible(False) axs[0,0].set_box_aspect(1/3) axs[1,0].set_box_aspect(1) axs[1,1].set_box_aspect(3/1) x,y = np.random.randn(2,400) * np.array([[.5],[180]]) axs[1,0].scatter(x,y) axs[0,0].hist(x) axs[1,1].hist(y, orientation="horizontal")F. Equal data aspect, unequal box aspect
fig6, ax = plt.subplots() ax.add_patch(plt.Circle((5,3), 1)) ax.set_aspect("equal", adjustable="datalim") ax.set_box_aspect(0.5) ax.autoscale()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 which box_aspect and adjustable are set is arbitrary, this still needs to define in which cases to error out, or silently fall back.
PR Checklist