Skip to content

Commit 041c598

Browse files
committed
Use SubplotSpec.row/colspans more, and deprecate get_rows_columns.
1 parent 071da48 commit 041c598

File tree

4 files changed

+150
-205
lines changed

4 files changed

+150
-205
lines changed

doc/api/next_api_changes/deprecations.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,8 @@ In :mod:`mpl_toolkits.axes_grid1.axes_rgb`, ``imshow_rgb`` is deprecated (use
333333
``ax.imshow(np.dstack([r, g, b]))`` instead); ``RGBAxesBase`` is deprecated
334334
(use ``RGBAxes`` instead); ``RGBAxes.add_RGB_to_figure`` is deprecated (it was
335335
an internal helper).
336+
337+
``SubplotSpec.get_rows_columns``
338+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
339+
This method is deprecated. Use the ``GridSpec.nrows``, ``GridSpec.ncols``,
340+
``SubplotSpec.rowspan``, and ``SubplotSpec.colspan`` properties instead.

lib/matplotlib/_constrained_layout.py

Lines changed: 122 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,8 @@
5555
_log = logging.getLogger(__name__)
5656

5757

58-
def _in_same_column(colnum0min, colnum0max, colnumCmin, colnumCmax):
59-
return (colnumCmin <= colnum0min <= colnumCmax
60-
or colnumCmin <= colnum0max <= colnumCmax)
61-
62-
63-
def _in_same_row(rownum0min, rownum0max, rownumCmin, rownumCmax):
64-
return (rownumCmin <= rownum0min <= rownumCmax
65-
or rownumCmin <= rownum0max <= rownumCmax)
58+
def _spans_overlap(span0, span1):
59+
return span0.start in span1 or span1.start in span0
6660

6761

6862
def _axes_all_finite_sized(fig):
@@ -155,7 +149,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
155149
# fill in any empty gridspec slots w/ ghost axes...
156150
_make_ghost_gridspec_slots(fig, gs)
157151

158-
for nnn in range(2):
152+
for _ in range(2):
159153
# do the algorithm twice. This has to be done because decorators
160154
# change size after the first re-position (i.e. x/yticklabels get
161155
# larger/smaller). This second reposition tends to be much milder,
@@ -329,131 +323,110 @@ def _align_spines(fig, gs):
329323
if (hasattr(ax, 'get_subplotspec')
330324
and ax._layoutbox is not None
331325
and ax.get_subplotspec().get_gridspec() == gs)]
332-
rownummin = np.zeros(len(axs), dtype=np.int8)
333-
rownummax = np.zeros(len(axs), dtype=np.int8)
334-
colnummin = np.zeros(len(axs), dtype=np.int8)
335-
colnummax = np.zeros(len(axs), dtype=np.int8)
336-
width = np.zeros(len(axs))
337-
height = np.zeros(len(axs))
326+
rowspans = []
327+
colspans = []
328+
heights = []
329+
widths = []
338330

339331
for n, ax in enumerate(axs):
340332
ss0 = ax.get_subplotspec()
341-
rownummin[n], colnummin[n] = divmod(ss0.num1, ncols)
342-
rownummax[n], colnummax[n] = divmod(ss0.num2, ncols)
343-
width[n] = np.sum(
344-
width_ratios[colnummin[n]:(colnummax[n] + 1)])
345-
height[n] = np.sum(
346-
height_ratios[rownummin[n]:(rownummax[n] + 1)])
347-
348-
for nn, ax in enumerate(axs[:-1]):
349-
# now compare ax to all the axs:
350-
#
351-
# If the subplotspecs have the same colnumXmax, then line
352-
# up their right sides. If they have the same min, then
353-
# line up their left sides (and vertical equivalents).
354-
rownum0min, colnum0min = rownummin[nn], colnummin[nn]
355-
rownum0max, colnum0max = rownummax[nn], colnummax[nn]
356-
width0, height0 = width[nn], height[nn]
333+
rowspan = ss0.rowspan
334+
colspan = ss0.colspan
335+
rowspans.append(rowspan)
336+
colspans.append(colspan)
337+
heights.append(sum(height_ratios[rowspan.start:rowspan.stop]))
338+
widths.append(sum(width_ratios[colspan.start:colspan.stop]))
339+
340+
for idx0, ax0 in enumerate(axs):
341+
# Compare ax to all other axs: If the subplotspecs start (/stop) at
342+
# the same column, then line up their left (/right) sides; likewise
343+
# for rows/top/bottom.
344+
rowspan0 = rowspans[idx0]
345+
colspan0 = colspans[idx0]
346+
height0 = heights[idx0]
347+
width0 = widths[idx0]
357348
alignleft = False
358349
alignright = False
359350
alignbot = False
360351
aligntop = False
361352
alignheight = False
362353
alignwidth = False
363-
for mm in range(nn+1, len(axs)):
364-
axc = axs[mm]
365-
rownumCmin, colnumCmin = rownummin[mm], colnummin[mm]
366-
rownumCmax, colnumCmax = rownummax[mm], colnummax[mm]
367-
widthC, heightC = width[mm], height[mm]
368-
# Horizontally align axes spines if they have the
369-
# same min or max:
370-
if not alignleft and colnum0min == colnumCmin:
371-
# we want the _poslayoutboxes to line up on left
372-
# side of the axes spines...
373-
layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
354+
for idx1 in range(idx0 + 1, len(axs)):
355+
ax1 = axs[idx1]
356+
rowspan1 = rowspans[idx1]
357+
colspan1 = colspans[idx1]
358+
width1 = widths[idx1]
359+
height1 = heights[idx1]
360+
# Horizontally align axes spines if they have the same min or max:
361+
if not alignleft and colspan0.start == colspan1.start:
362+
_log.debug('same start columns; line up layoutbox lefts')
363+
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
374364
'left')
375365
alignleft = True
376-
if not alignright and colnum0max == colnumCmax:
377-
# line up right sides of _poslayoutbox
378-
layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
366+
if not alignright and colspan0.stop == colspan1.stop:
367+
_log.debug('same stop columns; line up layoutbox rights')
368+
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
379369
'right')
380370
alignright = True
381-
# Vertically align axes spines if they have the
382-
# same min or max:
383-
if not aligntop and rownum0min == rownumCmin:
384-
# line up top of _poslayoutbox
385-
_log.debug('rownum0min == rownumCmin')
386-
layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
371+
# Vertically align axes spines if they have the same min or max:
372+
if not aligntop and rowspan0.start == rowspan1.start:
373+
_log.debug('same start rows; line up layoutbox tops')
374+
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
387375
'top')
388376
aligntop = True
389-
if not alignbot and rownum0max == rownumCmax:
390-
# line up bottom of _poslayoutbox
391-
_log.debug('rownum0max == rownumCmax')
392-
layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
377+
if not alignbot and rowspan0.stop == rowspan1.stop:
378+
_log.debug('same stop rows; line up layoutbox bottoms')
379+
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
393380
'bottom')
394381
alignbot = True
395-
###########
382+
396383
# Now we make the widths and heights of position boxes
397384
# similar. (i.e the spine locations)
398-
# This allows vertically stacked subplots to have
399-
# different sizes if they occupy different amounts
400-
# of the gridspec: i.e.
385+
# This allows vertically stacked subplots to have different sizes
386+
# if they occupy different amounts of the gridspec: i.e.
401387
# gs = gridspec.GridSpec(3, 1)
402388
# ax1 = gs[0, :]
403389
# ax2 = gs[1:, :]
404390
# then drows0 = 1, and drowsC = 2, and ax2
405391
# should be at least twice as large as ax1.
406392
# But it can be more than twice as large because
407393
# it needs less room for the labeling.
408-
#
409-
# For height, this only needs to be done if the
410-
# subplots share a column. For width if they
411-
# share a row.
412-
413-
drowsC = (rownumCmax - rownumCmin + 1)
414-
drows0 = (rownum0max - rownum0min + 1)
415-
dcolsC = (colnumCmax - colnumCmin + 1)
416-
dcols0 = (colnum0max - colnum0min + 1)
417-
418-
if not alignheight and drows0 == drowsC:
419-
ax._poslayoutbox.constrain_height(
420-
axc._poslayoutbox.height * height0 / heightC)
394+
395+
# For height, this only needs to be done if the subplots share a
396+
# column.
397+
if not alignheight and len(rowspan0) == len(rowspan1):
398+
ax0._poslayoutbox.constrain_height(
399+
ax1._poslayoutbox.height * height0 / height1)
421400
alignheight = True
422-
elif _in_same_column(colnum0min, colnum0max,
423-
colnumCmin, colnumCmax):
424-
if height0 > heightC:
425-
ax._poslayoutbox.constrain_height_min(
426-
axc._poslayoutbox.height * height0 / heightC)
401+
elif _spans_overlap(colspan0, colspan1):
402+
if height0 > height1:
403+
ax0._poslayoutbox.constrain_height_min(
404+
ax1._poslayoutbox.height * height0 / height1)
427405
# these constraints stop the smaller axes from
428406
# being allowed to go to zero height...
429-
axc._poslayoutbox.constrain_height_min(
430-
ax._poslayoutbox.height * heightC /
431-
(height0*1.8))
432-
elif height0 < heightC:
433-
axc._poslayoutbox.constrain_height_min(
434-
ax._poslayoutbox.height * heightC / height0)
435-
ax._poslayoutbox.constrain_height_min(
436-
ax._poslayoutbox.height * height0 /
437-
(heightC*1.8))
438-
# widths...
439-
if not alignwidth and dcols0 == dcolsC:
440-
ax._poslayoutbox.constrain_width(
441-
axc._poslayoutbox.width * width0 / widthC)
407+
ax1._poslayoutbox.constrain_height_min(
408+
ax0._poslayoutbox.height * height1 / (height0*1.8))
409+
elif height0 < height1:
410+
ax1._poslayoutbox.constrain_height_min(
411+
ax0._poslayoutbox.height * height1 / height0)
412+
ax0._poslayoutbox.constrain_height_min(
413+
ax0._poslayoutbox.height * height0 / (height1*1.8))
414+
# For width, if they share a row.
415+
if not alignwidth and len(colspan0) == len(colspan1):
416+
ax0._poslayoutbox.constrain_width(
417+
ax1._poslayoutbox.width * width0 / width1)
442418
alignwidth = True
443-
elif _in_same_row(rownum0min, rownum0max,
444-
rownumCmin, rownumCmax):
445-
if width0 > widthC:
446-
ax._poslayoutbox.constrain_width_min(
447-
axc._poslayoutbox.width * width0 / widthC)
448-
axc._poslayoutbox.constrain_width_min(
449-
ax._poslayoutbox.width * widthC /
450-
(width0*1.8))
451-
elif width0 < widthC:
452-
axc._poslayoutbox.constrain_width_min(
453-
ax._poslayoutbox.width * widthC / width0)
454-
ax._poslayoutbox.constrain_width_min(
455-
axc._poslayoutbox.width * width0 /
456-
(widthC*1.8))
419+
elif _spans_overlap(rowspan0, rowspan1):
420+
if width0 > width1:
421+
ax0._poslayoutbox.constrain_width_min(
422+
ax1._poslayoutbox.width * width0 / width1)
423+
ax1._poslayoutbox.constrain_width_min(
424+
ax0._poslayoutbox.width * width1 / (width0*1.8))
425+
elif width0 < width1:
426+
ax1._poslayoutbox.constrain_width_min(
427+
ax0._poslayoutbox.width * width1 / width0)
428+
ax0._poslayoutbox.constrain_width_min(
429+
ax1._poslayoutbox.width * width0 / (width1*1.8))
457430

458431

459432
def _arrange_subplotspecs(gs, hspace=0, wspace=0):
@@ -470,34 +443,25 @@ def _arrange_subplotspecs(gs, hspace=0, wspace=0):
470443
for child0 in sschildren:
471444
ss0 = child0.artist
472445
nrows, ncols = ss0.get_gridspec().get_geometry()
473-
rowNum0min, colNum0min = divmod(ss0.num1, ncols)
474-
rowNum0max, colNum0max = divmod(ss0.num2, ncols)
446+
rowspan0 = ss0.rowspan
447+
colspan0 = ss0.colspan
475448
sschildren = sschildren[1:]
476-
for childc in sschildren:
477-
ssc = childc.artist
478-
rowNumCmin, colNumCmin = divmod(ssc.num1, ncols)
479-
rowNumCmax, colNumCmax = divmod(ssc.num2, ncols)
480-
# OK, this tells us the relative layout of ax
481-
# with axc
482-
thepad = wspace / ncols
483-
if colNum0max < colNumCmin:
484-
layoutbox.hstack([ss0._layoutbox, ssc._layoutbox],
485-
padding=thepad)
486-
if colNumCmax < colNum0min:
487-
layoutbox.hstack([ssc._layoutbox, ss0._layoutbox],
488-
padding=thepad)
489-
490-
####
449+
for child1 in sschildren:
450+
ss1 = child1.artist
451+
rowspan1 = ss1.rowspan
452+
colspan1 = ss1.colspan
453+
# OK, this tells us the relative layout of ax with ax1
454+
pad = wspace / ncols
455+
if colspan0.stop <= colspan1.start:
456+
layoutbox.hstack([ss0._layoutbox, ss1._layoutbox], padding=pad)
457+
if colspan1.stop <= colspan0.start:
458+
layoutbox.hstack([ss1._layoutbox, ss0._layoutbox], padding=pad)
491459
# vertical alignment
492-
thepad = hspace / nrows
493-
if rowNum0max < rowNumCmin:
494-
layoutbox.vstack([ss0._layoutbox,
495-
ssc._layoutbox],
496-
padding=thepad)
497-
if rowNumCmax < rowNum0min:
498-
layoutbox.vstack([ssc._layoutbox,
499-
ss0._layoutbox],
500-
padding=thepad)
460+
pad = hspace / nrows
461+
if rowspan0.stop <= rowspan1.start:
462+
layoutbox.vstack([ss0._layoutbox, ss1._layoutbox], padding=pad)
463+
if rowspan1.stop <= rowspan0.start:
464+
layoutbox.vstack([ss1._layoutbox, ss0._layoutbox], padding=pad)
501465

502466

503467
def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
@@ -560,33 +524,28 @@ def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
560524

561525

562526
def _getmaxminrowcolumn(axs):
563-
# helper to get the min/max rows and columns of a list of axes.
564-
maxrow = -100000
565-
minrow = 1000000
566-
maxax = None
567-
minax = None
568-
maxcol = -100000
569-
mincol = 1000000
570-
maxax_col = None
571-
minax_col = None
572-
527+
"""
528+
Find axes covering the first and last rows and columns of a list of axes.
529+
"""
530+
startrow = startcol = np.inf
531+
stoprow = stopcol = -np.inf
532+
startax_row = startax_row = stopax_row = stopax_col = None
573533
for ax in axs:
574534
subspec = ax.get_subplotspec()
575-
nrows, ncols, row_start, row_stop, col_start, col_stop = \
576-
subspec.get_rows_columns()
577-
if row_stop > maxrow:
578-
maxrow = row_stop
579-
maxax = ax
580-
if row_start < minrow:
581-
minrow = row_start
582-
minax = ax
583-
if col_stop > maxcol:
584-
maxcol = col_stop
585-
maxax_col = ax
586-
if col_start < mincol:
587-
mincol = col_start
588-
minax_col = ax
589-
return (minrow, maxrow, minax, maxax, mincol, maxcol, minax_col, maxax_col)
535+
if subspec.rowspan.start < startrow:
536+
startrow = subspec.rowspan.start
537+
startax_row = ax
538+
if subspec.rowspan.stop > stoprow:
539+
stoprow = subspec.rowspan.stop
540+
stopax_row = ax
541+
if subspec.colspan.start < startcol:
542+
startcol = subspec.colspan.start
543+
startax_col = ax
544+
if subspec.colspan.stop > stopcol:
545+
stopcol = subspec.colspan.stop
546+
stopax_col = ax
547+
return (startrow, stoprow - 1, startax_row, stopax_row,
548+
startcol, stopcol - 1, startax_col, stopax_col)
590549

591550

592551
def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
@@ -630,18 +589,16 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
630589
# Horizontal Layout: need to check all the axes in this gridspec
631590
for ch in gslb.children:
632591
subspec = ch.artist
633-
nrows, ncols, row_start, row_stop, col_start, col_stop = \
634-
subspec.get_rows_columns()
635592
if location == 'right':
636-
if col_stop <= maxcol:
593+
if subspec.colspan.stop - 1 <= maxcol:
637594
order = [subspec._layoutbox, lb]
638595
# arrange to right of the parents
639-
if col_start > maxcol:
596+
elif subspec.colspan.start > maxcol:
640597
order = [lb, subspec._layoutbox]
641598
elif location == 'left':
642-
if col_start >= mincol:
599+
if subspec.colspan.start >= mincol:
643600
order = [lb, subspec._layoutbox]
644-
if col_stop < mincol:
601+
elif subspec.colspan.stop - 1 < mincol:
645602
order = [subspec._layoutbox, lb]
646603
layoutbox.hstack(order, padding=pad * gslb.width,
647604
strength='strong')
@@ -686,17 +643,15 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
686643
# Vertical Layout: need to check all the axes in this gridspec
687644
for ch in gslb.children:
688645
subspec = ch.artist
689-
nrows, ncols, row_start, row_stop, col_start, col_stop = \
690-
subspec.get_rows_columns()
691646
if location == 'bottom':
692-
if row_stop <= minrow:
647+
if subspec.rowspan.stop - 1 <= minrow:
693648
order = [subspec._layoutbox, lb]
694-
if row_start > maxrow:
649+
elif subspec.rowspan.start > maxrow:
695650
order = [lb, subspec._layoutbox]
696651
elif location == 'top':
697-
if row_stop < minrow:
652+
if subspec.rowspan.stop - 1 < minrow:
698653
order = [subspec._layoutbox, lb]
699-
if row_start >= maxrow:
654+
elif subspec.rowspan.start >= maxrow:
700655
order = [lb, subspec._layoutbox]
701656
layoutbox.vstack(order, padding=pad * gslb.width,
702657
strength='strong')

0 commit comments

Comments
 (0)