Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Description
Bug summary
Padding at the edges ofGridSpecFromSubplotSpec
is double counted withlayout="constrained"
- IE, for an inner grid G1 in element E of an outer grid G2, the padding is counted once between E and the other elements of G2, and once between the edges of E and the first/last elements within G1, forcing the elements within G1 to be much smaller than they need to be (see below).
Code for reproduction
importnumpyasnpimportmatplotlib.pyplotaspltimportmatplotlib.axesimportmatplotlib._layoutgridrng=np.random.default_rng(0)defmake_demo(plot_name,pad=0.1,show_box=True):file_name="%s.png"%plot_nameprint(file_name)figure=plt.figure(figsize=[10,6],layout="constrained")figure.get_layout_engine().set(w_pad=pad,h_pad=pad)axes=figure.subplots(1,2,width_ratios=[1,2])show_random_image(axes[0])axes=add_subplots(axes[1],2,2,show_box)axes=add_subplots(axes[1],2,2,show_box)figure.suptitle(plot_name,fontsize=25)figure.savefig(file_name)defadd_subplots(axis:matplotlib.axes.Axes,nx,ny,show_box, )->list[matplotlib.axes.Axes]:axis.clear()ifshow_box:axis.get_xaxis().set_visible(False)axis.get_yaxis().set_visible(False)else:axis.set_axis_off()subplot_spec=axis.get_subplotspec()subgrid_spec=subplot_spec.subgridspec(nx,ny)axis_list=subgrid_spec.subplots().flatten().tolist()forainaxis_list:show_random_image(a)returnaxis_listdefshow_random_image(axis:matplotlib.axes.Axes):axis.pcolormesh(rng.uniform(0,1, [10,10]))axis.set_axis_off()defmain():fix_parent_space()make_demo("1_original")make_demo("2_original_no_box",show_box=False)make_demo("3_more_padding",0.2)make_demo("4_more_padding_no_box",0.2,False)fix_double_edge_padding()make_demo("5_fixed",0.2)make_demo("6_fixed_no_box",0.2,False)deffix_parent_space():importmatplotlib._constrained_layoutimportmatplotlib._layoutgridasmlayoutgriddefmake_layoutgrids_gs(layoutgrids,gs):""" Make the layoutgrid for a gridspec (and anything nested in the gridspec) """ifgsinlayoutgridsorgs.figureisNone:returnlayoutgrids# in order to do constrained_layout there has to be at least *one*# gridspec in the tree:layoutgrids['hasgrids']=Trueifnothasattr(gs,'_subplot_spec'):# normal gridspecparent=layoutgrids[gs.figure]layoutgrids[gs]=mlayoutgrid.LayoutGrid(parent=parent,parent_inner=True,name='gridspec',ncols=gs._ncols,nrows=gs._nrows,width_ratios=gs.get_width_ratios(),height_ratios=gs.get_height_ratios())else:# this is a gridspecfromsubplotspec:subplot_spec=gs._subplot_specparentgs=subplot_spec.get_gridspec()# if a nested gridspec it is possible the parent is not in there yet:ifparentgsnotinlayoutgrids:layoutgrids=make_layoutgrids_gs(layoutgrids,parentgs)subspeclb=layoutgrids[parentgs]# gridspecfromsubplotspec need an outer container:# get a unique representation:rep= (gs,'top')ifrepnotinlayoutgrids:layoutgrids[rep]=mlayoutgrid.LayoutGrid(parent=subspeclb,parent_inner=True,name='top',nrows=1,ncols=1,parent_pos=(subplot_spec.rowspan,subplot_spec.colspan))layoutgrids[gs]=mlayoutgrid.LayoutGrid(parent=layoutgrids[rep],name='gridspec',nrows=gs._nrows,ncols=gs._ncols,width_ratios=gs.get_width_ratios(),height_ratios=gs.get_height_ratios())returnlayoutgridsmatplotlib._constrained_layout.make_layoutgrids_gs=make_layoutgrids_gsdeffix_double_edge_padding():importmatplotlib._constrained_layoutimportmatplotlib.gridspecdefget_margin_from_padding(obj,*,w_pad=0,h_pad=0,hspace=0,wspace=0):ss=obj._subplotspecgs=ss.get_gridspec()ifhasattr(gs,'hspace'):_hspace= (gs.hspaceifgs.hspaceisnotNoneelsehspace)_wspace= (gs.wspaceifgs.wspaceisnotNoneelsewspace)else:_hspace= (gs._hspaceifgs._hspaceisnotNoneelsehspace)_wspace= (gs._wspaceifgs._wspaceisnotNoneelsewspace)_wspace=_wspace/2_hspace=_hspace/2nrows,ncols=gs.get_geometry()# there are two margins for each direction. The "cb"# margins are for pads and colorbars, the non-"cb" are# for the Axes decorations (labels etc).margin= {'leftcb':w_pad,'rightcb':w_pad,'bottomcb':h_pad,'topcb':h_pad,'left':0,'right':0,'top':0,'bottom':0}ifisinstance(gs,matplotlib.gridspec.GridSpecFromSubplotSpec):ifss.is_first_col():margin['leftcb']=0ifss.is_last_col():margin['rightcb']=0ifss.is_first_row():margin['topcb']=0ifss.is_last_row():margin['bottomcb']=0if_wspace/ncols>w_pad:ifss.colspan.start>0:margin['leftcb']=_wspace/ncolsifss.colspan.stop<ncols:margin['rightcb']=_wspace/ncolsif_hspace/nrows>h_pad:ifss.rowspan.stop<nrows:margin['bottomcb']=_hspace/nrowsifss.rowspan.start>0:margin['topcb']=_hspace/nrowsreturnmarginmatplotlib._constrained_layout.get_margin_from_padding=get_margin_from_paddingif__name__=="__main__":main()# filenames = [# "1_original.png",# "2_original_no_box.png",# "3_more_padding.png",# "4_more_padding_no_box.png",# "5_fixed.png",# "6_fixed_no_box.png",# ]# from jutility import util, plotting# mp = plotting.MultiPlot(# *[plotting.ImShow(util.load_image(s)) for s in filenames],# num_cols=2,# colour="grey",# )# mp.save()
Actual outcome
When padding is increased (row 1 to row 2), the elements in the inner grids become way too small (this assumes the functionfix_parent_space
which fixes#28891 has been called at the start):
Expected outcome
After my fix (functionfix_double_edge_padding
) is applied, the subplots return to a normal size, shown in row 3 above.
Additional information
My fix is shown in the functionfix_double_edge_padding
above, specifically the branch that starts withif isinstance(gs, matplotlib.gridspec.GridSpecFromSubplotSpec):
, which applies to the functionget_margin_from_padding
in_constrained_layout.py
(https://github.com/matplotlib/matplotlib/blob/v3.9.2/lib/matplotlib/_constrained_layout.py#L297 ).
Operating system
Ubuntu
Matplotlib Version
3.9.2
Matplotlib Backend
qtagg
Python version
Python 3.10.12
Jupyter version
No response
Installation
pip