Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[MRG] Constrained_layout (geometry manager)#9082

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

Merged
tacaswell merged 1 commit intomatplotlib:masterfromjklymak:constrainedlayout
Feb 2, 2018

Conversation

jklymak
Copy link
Member

@jklymakjklymak commentedAug 23, 2017
edited
Loading

PR Summary

This is my implementation of the geometry manager (See#1109).

This is heavily based on exploratory work by@Tillsten using thekiwi solver, and of course ontight_layout.

Latest what's new:https://5396-1385122-gh.circle-artifacts.com/0/home/circleci/project/doc/build/html/users/next_whats_new/constrained_layout.html

Latest tutorial:https://5398-1385122-gh.circle-artifacts.com/0/home/circleci/project/doc/build/html/tutorials/intermediate/constrainedlayout_guide.html#sphx-glr-tutorials-intermediate-constrainedlayout-guide-py

Intro

tight_layout() is great for what it does, but it doesn't handle some cases generally. i.e. if we have twoGridSpecFromSubplotSpec instances, they need to be positioned manually usingtight_layout(). Colorbars for individual axes work fine, but colorbars for more than one axis doesn't work. Etc.

This PR implements "constrained_layout" viaplt.figure(constrained_layout=True) for automatic re-drawing. Currently, it works for subplots and colorbars, legends, and suptitles. Nested gridspecs are respected. Spacing is provided both as a pad around axes and as a space between subplots.

The following code gives this plot:

exampleplot

fig=plt.figure(constrained_layout=True)gs=gridspec.GridSpec(1,2,fig=fig)gsl=gridspec.GridSpecFromSubplotSpec(2,2,gs[0])gsr=gridspec.GridSpecFromSubplotSpec(1,2,gs[1])axsl= []forgsingsl:ax=fig.add_subplot(gs)axsl+= [ax]example_plot(ax,fontsize=12)ax.set_xlabel('x-label\nMultiLine')axsr= []forgsingsr:ax=fig.add_subplot(gs)axsr+= [ax]pcm=example_pcolor(ax,fontsize=12)fig.colorbar(pcm,ax=axsr,pad=0.01,location='bottom',ticks=ticker.MaxNLocator(nbins=5))

A few things to note in the above:

  • all the subplots in the left side set of plots (gsl) have the same margins. They are a bit big because the bottom-right plot has a two-line x-label.
  • the x-ticklabels for the rhs plot overrun, becauseconstrained_layout doesn't do any management at the axis level.

Installation:

In addition to the normalmatplotlib installation, you will need to involve kiwisolver:pip install kiwisolver now works.

API dfferences (so far)

In order to get this to work, there are a couple of API changes:

  1. gridspec.GridSpec now has afig keyword. If this isn't supplied thenconstrained_layout won't work.
  2. figure now has aconstrained_layout keyword. Currently, I don't have it checking fortight_layout, so they could both run at the same time. These upper level user-facing decisions probaby require input.
  3. Internal calling issue: We don't want axes that have user-calledax.set_position() to participate inconstrained_layout, presumably because the user was purposefully putting the axis where they put it. So, that means internally, we set thelayoutbox properties of this axis toNone.However,ax.set_position gets called internally (i.e. bycolorbar andaspect) and for those we presumably want to keep constrained_layout as a possibility. So I made aax._set_position() for internal calls, which the publicax.set_position() uses. Internal calls toset_position should use_set_position if the axis is to continue to participate in constrained_layout.

I think most of these changes are backwards compatible, in that if you don't want to useconstrained_layout you don't need to change any calls. Backwards compatibility introduces some awkwardness, but is probably worth it.

Implementation:

Implementation is via a new packagelayoutbox.py. Basically a nested array of layoutboxe objects are set up, attached to the figure, the gridspec(s), the subplotspecs. An axes hastwo layoutboxes: one that contains the tight bounding box of the axes, and the second that contains the "position" of the axes (i.e the rectangle that is passed toax.set_position()).

A linear constraint solver is used to decide dimensions. The trick here is that we define margins between an axes bounding box and its position. These margins, and the outer dimesnion of the plot (0,0,1,1) are what constrains the problem externally. Internally, the problem is constrained by relationships between the layoutboxes.

Tests:

These are inlib/matplotlib/tests/test_constrainedlayout.py.

PR Checklist

  • Sort out padding in a non-figure unit
  • Addlegend to constrained objects
  • Add kiwi to requirements: need a new release to make OK w/ 2.7. Current requirement works 3.6
  • Has Pytest style unit tests.
  • Code is PEP 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way
  • Addsuptitle to constrained objects
  • sharex andsharey?
  • Adjust spacing forcolorbars; respectpad.
  • Implement respect forwspace andhspace variables. Actually pretty easy.
  • Checkset_position axes
  • Spacing between gridspecs
  • gridspec width_ratios and height_ratios
  • twinx,twiny?tight_layout puts axes title below twiny xlabel #5474
  • Extend to putting labels outside tick labels and titles outside of all?

janosh reacted with thumbs up emoji
@has2k1
Copy link
Contributor

I am interested in the specifics of how the legend will be constrained. Perhaps through an easily extensible mechanism that can be used to add otherAnchoredOffsetBox objects aroundaxes and aroundgridspecs.

Also a mechanism for adding arbitrary text around theaxes orgridspecs. This would be hand in adding captions to plots.

@jklymak
Copy link
MemberAuthor

@has2k1 Thanks.

I want to make sure I understand the desired behavior. We are getting into use cases I haven't ever needed for my personal plotting needs, so this is where I need help with what people envision.

Are you hoping that if I callax.legend(loc='center left', bbox_to_anchor=(1., 0.5)) then the layout manager will automatically make the axis object to accommodate the legend inside the subplotspec? But it should also acceptax.legend(loc='center left', bbox_to_anchor=(0.5, 0.5))?

I think thats doable, but I need to think about how that gets implemented. Right now I just implement nested layouts and adjacent layouts w a pad. Arbitrarily displaced in axes co-ordinates is doable, but not implemented yet. I don't see why a subplotspec can't contain more than one child that also have displacements relative to their widths.

I need to dig intooffsetbox to see how to make it extensible to other offset boxes. Can you point me to an example of other uses beyond legend so I can test?

@jklymakjklymak changed the titleWIP: constrained_layout (geometry manager)[WIP] constrained_layout (geometry manager)Aug 24, 2017
@jklymak
Copy link
MemberAuthor

jklymak commentedAug 24, 2017
edited
Loading

share_x andshare_y are a bit complicated.

tight_layout differentiates between space between the subplots and the space around all the subplots (i.e.h_space andleft). Here I don't, I just haveleft_margin andright_margin and they are the same for all subplots. Soconstrained_layout doesn't cover the often-used case of just labelling the outside rows and columns (it works, but it introduces a lot of extra white space between subplots).

As far as I can seeshare_x andshare_y are only accessible viafigure.subplots(). So, this isn't a problem for generalgridspec-derived axes added withfigure.add_axes.

Seeoffsetbox.VPacker andoffsetbox.HPacker for ideas.

@has2k1
Copy link
Contributor

Are you hoping that if I call ax.legend(loc='center left', bbox_to_anchor=(1., 0.5)) then the layout manager will automatically make the axis object to accommodate the legend inside the subplotspec?

Yes.

But it should also accept ax.legend(loc='center left', bbox_to_anchor=(0.5, 0.5))?

Yes again. But I fear it would make the code complicated as it would mix two layout mechanisms. There are already four parameters toAnchoredOffsetbox that control the location, namely;

  • loc
  • bbox_to_anchor
  • bbox_transform
  • borderpad

And so it seems likeAnchoredOffsetbox was designed in an environment that lacked a layout manager. With a layout manager in place, perhaps a newConstrainedOffsetbox can also serve as a container for other Offsetboxes. But this course of action would conflict with the first question above. i.e does the legend still useAnchoredOffsetbox and never be accommodated inside the subplotspec? Or can it be changed to use theConstrainedOffsetbox, with appropriately deprecated parameters? I have no good answer.

Plotnine extensively [1] uses offsetboxes for the legends and it hasvery finicky parts.Here is a simplified version that demonstrates a custom legend system similar to the one in plotnine.

[1]here,here,here andhere

@tacaswelltacaswell added this to the2.2 (next next feature release) milestoneAug 25, 2017
@jklymak
Copy link
MemberAuthor

@has2k1 Thanks a lot for the examples. That helps.

I think it'll be possible to haveAnchoredOffsetbox (or a modified version) behavior the way you'd like. I can tackle this once I getsharex andsharey to work.

has2k1 reacted with thumbs up emoji

@jklymak
Copy link
MemberAuthor

jklymak commentedAug 29, 2017
edited
Loading

@has2k1 this now makes space for legends. Since it actually queries the underlying_legend_box, which is an AnchoredBox, this is easily extensible to other cases. However, given that anchoredBox objects usually seem to be buried as implementation detail in other classes, my guess is the best thing is to have the checks on those classes rather than AnchoredBox directly. i.e.Legend isn't an AnchoredBox, but it uses one for its layout.

Seelayoutbox.get_axall_tightbbox for the logic here.

legend is a bit flakey as positions get set at the draw sequence, so for perversely large legends a resize is needed to make the legend fit properly. However, I think the method often yields a decent result.

for child in ax.get_children():
if isinstance(child, Legend):
bboxn = child._legend_box.get_window_extent(renderer)
bbox = transforms.Bbox.union([bbox, bboxn])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think this would be more specific in sniffing out the legend type stuff.

possible_legends=ax.artists+ [ax.legend_]ifax.legend_else []forchildinpossible_legends:ifisinstance(child, (Legend,AnchoredOffsetbox)):        ...

However, overall this is a neat little function that can bundle up all types of adornments to the axes (at the risk of complicating it).

@has2k1
Copy link
Contributor

legend is a bit flakey as positions get set at the draw sequence, so for perversely large legends a resize is needed to make the legend fit properly. However, I think the method often yields a decent result.

I have encountered a related issue when creating an Animation class for plotnine. It may complicate animations, but on the whole specified figure sizes that are just rough estimates final sizes are a worthy price for a good layout. After all,tight_layout() has been an offender already.

@jklymak
Copy link
MemberAuthor

jklymak commentedAug 30, 2017
edited
Loading

@has2k1 Note thatOffsetBox children are already included inaxes.get_tightbbox:

for child in self.get_children():            if isinstance(child, OffsetBox) and child.get_visible():                bb.append(child.get_window_extent(renderer))

It almost seems thatLegend should be added toaxes.get_tightbbox as a possible child that would expand the bbox.

See issue:#9130

@jklymakjklymak changed the title[WIP] constrained_layout (geometry manager)Constrained_layout (geometry manager)Sep 5, 2017
@jklymak
Copy link
MemberAuthor

Removed [WIP]. I think this works, but I'm sure there are edge cases I haven't found.

@jklymak
Copy link
MemberAuthor

kiwisolver is now on pypi::pip install kiwisolver installs 1.0.0 and works here. I'm not 100% sure how to incorporate it into the matplotlib setup.py as a dependency.

@jklymak
Copy link
MemberAuthor

@has2k1 Just to follow up on your legend examplesabove. Your first two examples work fine w/o any fiddling.

figure_2

The third example, where you want a global legend doesn't work, and indeed makes the layout collapse in on itself. I'm not sure how to handle this, as you specify the anchored box in figure co-ordinates, but anchor it to one of your subplots, ax1. The constrained_layout then uses the whole area taken up by ax1 and the legend to determine the area the layout needs. That leaves no room for the other subplots, and the whole thing fails.

Right now I have "global" colorbars stacked to the right of the gridspec that contains the relevant subplot specs (or to the left, bottom etc). This is hard coded intocolorbars.py. Similarlysuptitle is stacked over the parent gridspec inside the figure container. However, it seems to me that something more generic might be a future enhancement. Maybe something likegs = GridSpec(2, 2),gs.stack_right(newartist, align='v_center'). This would take some thought, however, because what if I want to stack somethingelse to the right ofnewartist?

@jklymak
Copy link
MemberAuthor

test_pickle fails becausekiwisolver objects can't be pickled, and they are now attached to all the other objects.

@has2k1
Copy link
Contributor

The way the plot in the third example is constructed need not be how it should be done. In fact, using figure coordinates is part of the problem.

Maybe something like gs = GridSpec(2, 2), gs.stack_right(newartist, align='v_center'). This would take some thought, however, because what if I want to stack something else to the right of newartist?
This is what I have had in mind too. The ability to stackingtext andoffsetboxes to the sides of the axes and the gridspec(s).

I have looked a little at what you currently have in place with the goal of making it extendable. i.e. For the
SubplotSpec, GridSpec and Figure layoutboxes, define regions to contain additional items (text & offsetboxes) for stacking.

           [top]         ----------        |          | [left] |          | [right]        |          |        |          |         ----------          [bottom]

@jklymak
Copy link
MemberAuthor

jklymak commentedSep 6, 2017
edited
Loading

I have looked a little at what you currently have in place with the goal of making it extendable. i.e. For the SubplotSpec, GridSpec and Figure layoutboxes, define regions to contain additional items (text & offsetboxes) for stacking.

For SubplotSpec its pretty much done because it works on the difference between the spineposition and the axestightbbox, which includesAnchoredBoxes.

However, we'd need an API for theGridSpec level. So far, I've mostly just worked with the existing API. Actually implementing the stacking is pretty trivial: seesuptitle orcolorbar. I think adding an external API tolayoutbox is a reasonable way to go. I think you'd want something like

  • figlayout.gridspec_stack_right(gs, newartist)
  • figlayout.gridspec_stack_bottom(gs, newartist), etc
    However, one could also imagine adding such an API togridspec where it really belongs.

Edit: ...and do these artists then get layoutbox properties?

@jklymak
Copy link
MemberAuthor

@has2k1 How about this for an API:

Right now we can doax.add_artist(anchored_box). I'd proposed we add a method toGridSpec so we can call:gs.add_anchoredbox(anchored_box) That would entail the GridSpec having a bbox, but that doesn't seem to onerous. I'm a little fuzzy on whatbbox_transform will be required to make this relative to the gridspec box, but I think its doable.

That seems the most flexible, and consitent with an API folks will already know.

@has2k1
Copy link
Contributor

Yes an API of the the form gs.add_anchoredbox(anchored_box) is simple and straight forward. I agree with the fuzziness around some of the parameters of the anchored_box. The solution may be anew offsetbox (the 2nd point)

A GridSpec that properly encloses an offsetbox of some kind will make composing/arranging multiple plots a lot easier. Packages that use MPL will get a lot more interesting, in factthis feature would rely on it.

@has2k1
Copy link
Contributor

I am trying to get up to speed with what you have so far, but despite the plentiful comments I hit a road block on thedo_constrained_layout function.

@jklymak
Copy link
MemberAuthor

jklymak commentedSep 8, 2017
edited
Loading

@has2k1 Fair enough. I'll try and improve the comments. There may even be obsolete comments in there...

@has2k1
Copy link
Contributor

I was hoping I could add comments from a third party perspective immediately after I understood what was going on.

@jklymak
Copy link
MemberAuthor

@has2k1 That would be fabulous. I just pushed a commit that improved a bit of the doc, and removed the obsolete document bits. Let me know if you have questions, or I can point you towards tests that show how the different parts work. It was all more subtle than I originally thought it would be.

@jklymak
Copy link
MemberAuthor

docs-python27 fails with a unicode error inkiwisolver. This is now fixed innucleic/kiwi#40kiwisolver/master, so this test should run.

The pickle test is always going to fail for matplotlib instances that attachkiwisolver variables because they can't be pickled. Does anyoneuse pickling with matpotlib? I admit I liked using pickle until I found out that its not compatible frompy2.7 topy3.6 and I had to translate a bunch of old pickles.

I need to change the architecture to only attach kiwisolver variables ifconstrained_layout == True, and then just say that constrained_layout won't work with pickeled plots. Unless someone knows how to serialize kiwisolver variables.

@anntzer
Copy link
Contributor

Haven't followed the discussion at all, but re: pickling, you can perhaps remove the kiwisolver instance at pickle time (we already to that e.g. inFigure.__getstate__ (or grep for__getstate__ through the codebase to see how this is done).

jklymak reacted with thumbs up emoji

@tacaswell
Copy link
Member

Does anyone use pickling with matpotlib?

Yes, a surprising number of people. When we break this we find out rapidly.

jklymak reacted with thumbs up emoji

@jklymak
Copy link
MemberAuthor

Got it. Interesting interaction - I haven't played with interactive plotting much yet, except to change plot window size. This is clearly a bug in theset_aspect interaction. Hopefully not a big deal to fix...

@efiring
Copy link
Member

I found another interactive glitch. Make a minimal plot:

fig,ax=plt.subplots(constrained_layout=True)ax.set_xlabel('X label',fontsize=20)ax.plot([0,1])

Now click the zoom/pan button and fiddle with the view limits. Then click the home button. Now the constraint management is no longer in effect: reducing the window height by dragging up the bottom chops off the bottom of the legend.

@jklymak
Copy link
MemberAuthor

@efiring - thanks a lot - try the latest commit.

The second issue was some missed calls toax.set_position that needed to be changed toax._set_postion. Calling the former turnsconstrainedlayout off.

The first issue was that I was setting both positions when I set the position in constrained layout. Setting only the original is the way to go, and allowsset_aspect to still do its thing.

Copy link
Member

@efiringefiring left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Partial review. Nothing major.

``figure`` keyword. This is backwards compatible, in that not supplying this
will simply cause ``constrained_layout`` to not operate on the subplots
orgainzed by this ``GridSpec`` instance. Routines that use ``GridSpec`` (i.e.
``ax.subplots``) have been modified to pass the figure to ``GridSpec``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

should befig.subplots. Is that the only one? If there are others, the "i.e." should be "e.g.".

jklymak reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I can never remember the difference between i.e. and e.g. The failure of not teaching latin in high school any more...

A new method to automatically decide spacing between subplots and their
organizing ``GridSpec`` instances has been added. It is meant to
replace the venerable ``tight_layout`` method. It is invoked via
a new ``plt.figure(constrained_layout=True)`` kwarg to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Delete the "plt.figure".

jklymak reacted with thumbs up emoji
@@ -568,11 +568,17 @@ def __init__(self, fig, rect,
right=rcParams['ytick.right'] and rcParams['ytick.major.right'],
which='major')

self.layoutbox = None
self.poslayoutbox = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Should these have leading underscores? Or are they intended to be part of the public API?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Well, a) when I started, I didn't even know about_privatestuff, so this naming was in ignorance. b) afterwards, I kind of thought that there may be occasion the knowledgable user would want access to these. However, I think I agree that we don't want this to be public because it could change, so I'll set accordingly.

@@ -879,6 +887,19 @@ def set_position(self, pos, which='both'):
which : ['both' | 'active' | 'original'], optional
Determines which position variables to change.

"""
self._set_position(pos, which='both')
# because this is being called esternally to the library we
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

-> "externally"

jklymak reacted with thumbs up emoji
"""
Set the spinelayoutbox for this axis...
"""
self.poslayoutbox = layoutbox
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is there a reason for using setters instead of directly assigning to the attributes? This is related to my question about whether the attributes are part of the API.

maxn=rows*cols, num=num))
self._subplotspec = GridSpec(rows, cols)[int(num) - 1]
(
"num must be 1 <= num <= {maxn}, not {num}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The string could start on the line with the opening parenthesis.

jklymak reacted with thumbs up emoji
if self.figure.get_constrained_layout():
gridspecfig = fig
else:
gridspecfig = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't see gridspecfig being used in this function. And fig is not defined there, either. And the comment suggests that maybe this function should be deprecated, unless someone knows what the use case is.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'll remove the gridspecfig stuff. No idea about whether the fcn should be deprecated or not...

@@ -243,8 +244,9 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
ax.poslayoutbox.edit_bottom_margin_min(
-bbox.y0 + pos.y0 + h_padt)
ax.poslayoutbox.edit_top_margin_min(bbox.y1-pos.y1+h_padt)
# _log.debug('left %f' % (-bbox.x0 + pos.x0 + w_pad))
# _log.debug('right %f' % (bbox.x1 - pos.x1 + w_pad))
_log.debug('left %f' % (-bbox.x0 + pos.x0 + w_pad))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

no need to call %() yourself

@@ -1286,6 +1286,19 @@ def _validate_linestyle(ls):
'figure.subplot.hspace': [0.2, ValidateInterval(0, 1, closedmin=True,
closedmax=False)],

# do constrained_layout.
'figure.constrainedlayout.do': [False, validate_bool],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Somehow the "do" grates on my aesthetic senses. Maybe "use" or "enable" or "active"? Regardless, the matplotlibrc.template also needs to be updated, since it presently has only "figure.constrainedlayout".
Also, since the kwarg is "constrained_layout", and that form (with underscore) seems prevalent (and readable), maybe the rcParam family name should match it. Unless we can think of something shorter... It's too bad "autolayout" is already used.

jklymak reacted with thumbs up emoji
@jklymak
Copy link
MemberAuthor

  • made thelayoutbox objects private attributes of the various axes/figure/gridspec/subplotspec classes...

@jklymakjklymakforce-pushed theconstrainedlayout branch 2 times, most recently from52a53a6 to7d84999CompareJanuary 24, 2018 23:17
@jklymak
Copy link
MemberAuthor

squash + rebase

Copy link
Contributor

@has2k1has2k1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The comments are minor issues mainly about style and and clarity.

One issue I have not commented on anywhere is the name generation for the layoutboxes. It may be clearer to have the names generated in one function maybeLayoutbox.make_name. The function would have an input type of layoutbox e.g'subplotspec' then return a unique name. And if you store the type (or whatever suitable name) as a property. You can check on it withchild.type == 'subplotspec'; this would limit the instances of+seq_id() and the splitting on dots as they are largely a distraction when reading the code.

The other nitpick is just an irritant, the dots after the numbers i.e0.,1.. I'm not used to seeing them in python code.

state = self.__dict__
try:
state.pop('_layoutbox')
except:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

except KeyError

jklymak reacted with thumbs up emoji
def __getstate__(self):
# The renderer should be re-created by the figure, and then cached at
# that point.
state = super(_AxesBase, self).__getstate__()
state['_cachedRenderer'] = None
state.pop('_layoutbox')
state.pop('_poslayoutbox')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

You can usedel state['_poslayoutbox']

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is there an advantage? The other state calls are topop().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Other than the intention of the statement being clear. Apop() is effectively

defpop(self,key):value=self[key]delself[key]returnvalue

invTransFig = fig.transFigure.inverted().transform_bbox

# list of unique gridspecs that contain child axes:
gss = set([])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It does not look likegss uses any set properties.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It saves me from checking if the gridspec is already in the list.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ahh I see, axes do not have a 1:1 relationship with gridspecs. Subtle way to handle it.

sschildren = []
for child in gs.children:
name = (child.name).split('.')[-1][:-3]
if name == 'ss':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

For clarity I think hiding the naming conventions behind functions likeis_subplotspec(child) andis_gridspec(child) would help.

jklymak reacted with thumbs up emoji
self._constrained_layout_pads['w_pad'] = None
self._constrained_layout_pads['h_pad'] = None
self._constrained_layout_pads['wspace'] = None
self._constrained_layout_pads['hspace'] = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think this whole block can be contained in theset_constrained_layout method.

jklymak reacted with thumbs up emoji

padd : dict
Dictionary with possible keywords as above. If a keyword
is specified then it takes precedence over the dictionary.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

No need forpadd parameter, you can use doset_constrained_layout_pads(**constrained).

jklymak reacted with thumbs up emoji
constrained = rcParams['figure.constrained_layout.use']
self._constrained = bool(constrained)
if isinstance(constrained, dict):
self.set_constrained_layout_pads(padd=constrained)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

self.set_constrained_layout_pads(**constrained)

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yeah, I didn't know about this construct until recently. I think its a bit obscure that you can specify kwarg dictionaries this way, but perhaps you are correct that we should just make this simpler and use only this form from the start.


self.stale = True

def set_constrained_layout_pads(self, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think changing the signature of the method toset_constrained_layout_pads(self, w_pad=None, h_pad=None, ...), would make the logic below clearer.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

OK, I didn't quite get to setting those explicitly, because I wanted to have a for-loop over the possible kwargs, but I simplified as you suggested.

state = self.__dict__
try:
state.pop('_layoutbox')
except:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Usedel andexcept KeyError:

jklymak reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

.... except I keptstate.pop just for consistency. If there is a good reason todel, I can easily change.

@jklymak
Copy link
MemberAuthor

The other nitpick is just an irritant, the dots after the numbers i.e 0., 1.. I'm not used to seeing them in python code.

I think if you don't do that in some languages you force integer arithmetic, which can be very unfortunate. i.e. 3./10. neq 3./10 Nice that doesn't happen in python, I'll try and find most instances and clean up.

@jklymak
Copy link
MemberAuthor

Thanks for the review@has2k1 I think I addressed most of your suggestions. Let me know about the pop/del thing.

@jklymak
Copy link
MemberAuthor

Rebase...

@tacaswell
Copy link
Member

I'm going to make an executive decision and merge this

  • It adds a major feature we have been talking about for years
  • need to get lots of users to see where it breaks 😈
  • is strictly opt-in
  • mostly new code, unlikely to break anything

Thanks@jklymak and everyone who put effort into reviewing this (particularly@has2k1 ,@anntzer and@efiring )!

afvincent, ian-r-rose, TomDLT, mgilbir, and gokceneraslan reacted with hooray emoji

@tacaswelltacaswell merged commitaed4b74 intomatplotlib:masterFeb 2, 2018
@jklymak
Copy link
MemberAuthor

Thanks a lot everyone! Now here comes the bug reports... 🦆

@QuLogicQuLogic modified the milestones:needs sorting,v2.2.0Feb 12, 2018
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@efiringefiringefiring left review comments

@tacaswelltacaswelltacaswell left review comments

@has2k1has2k1has2k1 left review comments

@anntzeranntzeranntzer left review comments

Assignees
No one assigned
Labels
Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.topic: geometry managerLayoutEngine, Constrained layout, Tight layout
Projects
None yet
Milestone
v2.2.0
Development

Successfully merging this pull request may close these issues.

7 participants
@jklymak@has2k1@anntzer@tacaswell@choldgraf@efiring@QuLogic

[8]ページ先頭

©2009-2025 Movatter.jp