Skip to content

Commit 270a08b

Browse files
committed
Deprecate SubplotSpec.get_rows_columns.
and simplify constrained_layout by using rowspan/colspan instead.
1 parent 70d59ae commit 270a08b

File tree

4 files changed

+141
-203
lines changed

4 files changed

+141
-203
lines changed

doc/api/next_api_changes/deprecations.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,8 @@ The following related APIs are also deprecated:
236236
``matplotlib.test(recursionlimit=...)``
237237
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
238238
The *recursionlimit* parameter of ``matplotlib.test`` is deprecated.
239+
240+
``SubplotSpec.get_rows_columns``
241+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
242+
This method is deprecated. Use the ``GridSpec.nrows``, ``GridSpec.ncols``,
243+
``SubplotSpec.rowspan``, and ``SubplotSpec.colspan`` properties instead.

lib/matplotlib/_constrained_layout.py

Lines changed: 113 additions & 165 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,109 @@ 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+
widths = []
329+
heights = []
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+
widths.append(sum(width_ratios[colspan.start:colspan.stop]))
338+
heights.append(sum(height_ratios[rowspan.start:rowspan.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+
width0, height0 = widths[idx0], heights[idx0]
357347
alignleft = False
358348
alignright = False
359349
alignbot = False
360350
aligntop = False
361351
alignheight = False
362352
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],
353+
for idx1 in range(idx0 + 1, len(axs)):
354+
ax1 = axs[idx1]
355+
rowspan1 = rowspans[idx1]
356+
colspan1 = colspans[idx1]
357+
width1 = widths[idx1]
358+
height1 = heights[idx1]
359+
# Horizontally align axes spines if they have the same min or max:
360+
if not alignleft and colspan0.start == colspan1.start:
361+
_log.debug('same start columns; line up layoutbox lefts')
362+
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
374363
'left')
375364
alignleft = True
376-
if not alignright and colnum0max == colnumCmax:
377-
# line up right sides of _poslayoutbox
378-
layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
365+
if not alignright and colspan0.stop == colspan1.stop:
366+
_log.debug('same stop columns; line up layoutbox rights')
367+
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
379368
'right')
380369
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],
370+
# Vertically align axes spines if they have the same min or max:
371+
if not aligntop and rowspan0.start == rowspan1.start:
372+
_log.debug('same start rows; line up layoutbox tops')
373+
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
387374
'top')
388375
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],
376+
if not alignbot and rowspan0.stop == rowspan1.stop:
377+
_log.debug('same stop rows; line up layoutbox bottoms')
378+
layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
393379
'bottom')
394380
alignbot = True
395-
###########
381+
396382
# Now we make the widths and heights of position boxes
397383
# 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.
384+
# This allows vertically stacked subplots to have different sizes
385+
# if they occupy different amounts of the gridspec: i.e.
401386
# gs = gridspec.GridSpec(3, 1)
402387
# ax1 = gs[0, :]
403388
# ax2 = gs[1:, :]
404389
# then drows0 = 1, and drowsC = 2, and ax2
405390
# should be at least twice as large as ax1.
406391
# But it can be more than twice as large because
407392
# 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)
393+
394+
# For height, this only needs to be done if the subplots share a
395+
# column.
396+
if not alignheight and len(rowspan0) == len(rowspan1):
397+
ax0._poslayoutbox.constrain_height(
398+
ax1._poslayoutbox.height * height0 / height1)
421399
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)
400+
elif _spans_overlap(colspan0, colspan1):
401+
if height0 > height1:
402+
ax0._poslayoutbox.constrain_height_min(
403+
ax1._poslayoutbox.height * height0 / height1)
427404
# these constraints stop the smaller axes from
428405
# 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)
406+
ax1._poslayoutbox.constrain_height_min(
407+
ax0._poslayoutbox.height * height1 / (height0*1.8))
408+
elif height0 < height1:
409+
ax1._poslayoutbox.constrain_height_min(
410+
ax0._poslayoutbox.height * height1 / height0)
411+
ax0._poslayoutbox.constrain_height_min(
412+
ax0._poslayoutbox.height * height0 / (height1*1.8))
413+
# For width, if they share a row.
414+
if not alignwidth and len(colspan0) == len(colspan1):
415+
ax0._poslayoutbox.constrain_width(
416+
ax1._poslayoutbox.width * width0 / width1)
442417
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))
418+
elif _spans_overlap(rowspan0, rowspan1):
419+
if width0 > width1:
420+
ax0._poslayoutbox.constrain_width_min(
421+
ax1._poslayoutbox.width * width0 / width1)
422+
ax1._poslayoutbox.constrain_width_min(
423+
ax0._poslayoutbox.width * width1 / (width0*1.8))
424+
elif width0 < width1:
425+
ax1._poslayoutbox.constrain_width_min(
426+
ax0._poslayoutbox.width * width1 / width0)
427+
ax0._poslayoutbox.constrain_width_min(
428+
ax1._poslayoutbox.width * width0 / (width1*1.8))
457429

458430

459431
def _arrange_subplotspecs(gs, hspace=0, wspace=0):
@@ -470,34 +442,25 @@ def _arrange_subplotspecs(gs, hspace=0, wspace=0):
470442
for child0 in sschildren:
471443
ss0 = child0.artist
472444
nrows, ncols = ss0.get_gridspec().get_geometry()
473-
rowNum0min, colNum0min = divmod(ss0.num1, ncols)
474-
rowNum0max, colNum0max = divmod(ss0.num2, ncols)
445+
rowspan0 = ss0.rowspan
446+
colspan0 = ss0.colspan
475447
sschildren = sschildren[1:]
476448
for childc in sschildren:
477449
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-
####
450+
rowspanC = ssc.rowspan
451+
colspanC = ssc.colspan
452+
# OK, this tells us the relative layout of ax with axc
453+
pad = wspace / ncols
454+
if colspan0.stop <= colspanC.start:
455+
layoutbox.hstack([ss0._layoutbox, ssc._layoutbox], padding=pad)
456+
if colspanC.stop <= colspan0.start:
457+
layoutbox.hstack([ssc._layoutbox, ss0._layoutbox], padding=pad)
491458
# 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)
459+
pad = hspace / nrows
460+
if rowspan0.stop <= rowspanC.start:
461+
layoutbox.vstack([ss0._layoutbox, ssc._layoutbox], padding=pad)
462+
if rowspanC.stop <= rowspan0.start:
463+
layoutbox.vstack([ssc._layoutbox, ss0._layoutbox], padding=pad)
501464

502465

503466
def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
@@ -561,32 +524,21 @@ def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
561524

562525
def _getmaxminrowcolumn(axs):
563526
# 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
572527

573-
for ax in axs:
574-
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)
528+
def key(pos_ax):
529+
pos, ax = pos_ax
530+
return pos
531+
532+
minrow, minrow_ax = min(
533+
((ax.get_subplotspec().rowspan.start, ax) for ax in axs), key=key)
534+
maxrow, maxrow_ax = max(
535+
((ax.get_subplotspec().rowspan.stop - 1, ax) for ax in axs), key=key)
536+
mincol, mincol_ax = min(
537+
((ax.get_subplotspec().colspan.start, ax) for ax in axs), key=key)
538+
maxcol, maxcol_ax = max(
539+
((ax.get_subplotspec().colspan.stop - 1, ax) for ax in axs), key=key)
540+
return (minrow, maxrow, minrow_ax, maxrow_ax,
541+
mincol, maxcol, mincol_ax, maxcol_ax)
590542

591543

592544
def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
@@ -630,18 +582,16 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
630582
# Horizontal Layout: need to check all the axes in this gridspec
631583
for ch in gslb.children:
632584
subspec = ch.artist
633-
nrows, ncols, row_start, row_stop, col_start, col_stop = \
634-
subspec.get_rows_columns()
635585
if location == 'right':
636-
if col_stop <= maxcol:
586+
if subspec.colspan.stop - 1 <= maxcol:
637587
order = [subspec._layoutbox, lb]
638588
# arrange to right of the parents
639-
if col_start > maxcol:
589+
elif subspec.colspan.start > maxcol:
640590
order = [lb, subspec._layoutbox]
641591
elif location == 'left':
642-
if col_start >= mincol:
592+
if subspec.colspan.start >= mincol:
643593
order = [lb, subspec._layoutbox]
644-
if col_stop < mincol:
594+
elif subspec.colspan.stop - 1 < mincol:
645595
order = [subspec._layoutbox, lb]
646596
layoutbox.hstack(order, padding=pad * gslb.width,
647597
strength='strong')
@@ -686,17 +636,15 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
686636
# Vertical Layout: need to check all the axes in this gridspec
687637
for ch in gslb.children:
688638
subspec = ch.artist
689-
nrows, ncols, row_start, row_stop, col_start, col_stop = \
690-
subspec.get_rows_columns()
691639
if location == 'bottom':
692-
if row_stop <= minrow:
640+
if subspec.rowspan.stop - 1 <= minrow:
693641
order = [subspec._layoutbox, lb]
694-
if row_start > maxrow:
642+
elif subspec.rowspan.start > maxrow:
695643
order = [lb, subspec._layoutbox]
696644
elif location == 'top':
697-
if row_stop < minrow:
645+
if subspec.rowspan.stop - 1 < minrow:
698646
order = [subspec._layoutbox, lb]
699-
if row_start >= maxrow:
647+
elif subspec.rowspan.start >= maxrow:
700648
order = [lb, subspec._layoutbox]
701649
layoutbox.vstack(order, padding=pad * gslb.width,
702650
strength='strong')

0 commit comments

Comments
 (0)