Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ce9c860

Browse files
authored
Merge pull request #16618 from anntzer/subplotspans
Use SubplotSpec row/colspans more, and deprecate get_rows_columns.
2 parents defce2e + 3e4c299 commit ce9c860

File tree

4 files changed

+156
-212
lines changed

4 files changed

+156
-212
lines changed

doc/api/next_api_changes/deprecations.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,3 +386,8 @@ instead.
386386
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
387387
The unused ``animation.html_args`` rcParam and ``animation.HTMLWriter.args_key``
388388
attribute are deprecated.
389+
390+
``SubplotSpec.get_rows_columns``
391+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
392+
This method is deprecated. Use the ``GridSpec.nrows``, ``GridSpec.ncols``,
393+
``SubplotSpec.rowspan``, and ``SubplotSpec.colspan`` properties instead.

lib/matplotlib/_constrained_layout.py

Lines changed: 128 additions & 174 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))
338-
339-
for n, ax in enumerate(axs):
326+
rowspans = []
327+
colspans = []
328+
heights = []
329+
widths = []
330+
331+
for ax in 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.
401-
# gs = gridspec.GridSpec(3, 1)
402-
# ax1 = gs[0, :]
403-
# ax2 = gs[1:, :]
404-
# then drows0 = 1, and drowsC = 2, and ax2
405-
# should be at least twice as large as ax1.
385+
# This allows vertically stacked subplots to have different sizes
386+
# if they occupy different amounts of the gridspec, e.g. if
387+
# gs = gridspec.GridSpec(3, 1)
388+
# ax0 = gs[0, :]
389+
# ax1 = gs[1:, :]
390+
# then len(rowspan0) = 1, and len(rowspan1) = 2,
391+
# and ax1 should be at least twice as large as ax0.
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 heights, do it if the subplots share a 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 widths, do it if the subplots 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:]
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-
####
448+
for child1 in sschildren:
449+
ss1 = child1.artist
450+
rowspan1 = ss1.rowspan
451+
colspan1 = ss1.colspan
452+
# OK, this tells us the relative layout of child0 with child1.
453+
pad = wspace / ncols
454+
if colspan0.stop <= colspan1.start:
455+
layoutbox.hstack([ss0._layoutbox, ss1._layoutbox], padding=pad)
456+
if colspan1.stop <= colspan0.start:
457+
layoutbox.hstack([ss1._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 <= rowspan1.start:
461+
layoutbox.vstack([ss0._layoutbox, ss1._layoutbox], padding=pad)
462+
if rowspan1.stop <= rowspan0.start:
463+
layoutbox.vstack([ss1._layoutbox, ss0._layoutbox], padding=pad)
501464

502465

503466
def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
@@ -560,33 +523,28 @@ def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
560523

561524

562525
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-
526+
"""
527+
Find axes covering the first and last rows and columns of a list of axes.
528+
"""
529+
startrow = startcol = np.inf
530+
stoprow = stopcol = -np.inf
531+
startax_row = startax_col = stopax_row = stopax_col = None
573532
for ax in axs:
574533
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)
534+
if subspec.rowspan.start < startrow:
535+
startrow = subspec.rowspan.start
536+
startax_row = ax
537+
if subspec.rowspan.stop > stoprow:
538+
stoprow = subspec.rowspan.stop
539+
stopax_row = ax
540+
if subspec.colspan.start < startcol:
541+
startcol = subspec.colspan.start
542+
startax_col = ax
543+
if subspec.colspan.stop > stopcol:
544+
stopcol = subspec.colspan.stop
545+
stopax_col = ax
546+
return (startrow, stoprow - 1, startax_row, stopax_row,
547+
startcol, stopcol - 1, startax_col, stopax_col)
590548

591549

592550
def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
@@ -630,18 +588,16 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
630588
# Horizontal Layout: need to check all the axes in this gridspec
631589
for ch in gslb.children:
632590
subspec = ch.artist
633-
nrows, ncols, row_start, row_stop, col_start, col_stop = \
634-
subspec.get_rows_columns()
635591
if location == 'right':
636-
if col_stop <= maxcol:
592+
if subspec.colspan.stop - 1 <= maxcol:
637593
order = [subspec._layoutbox, lb]
638594
# arrange to right of the parents
639-
if col_start > maxcol:
595+
elif subspec.colspan.start > maxcol:
640596
order = [lb, subspec._layoutbox]
641597
elif location == 'left':
642-
if col_start >= mincol:
598+
if subspec.colspan.start >= mincol:
643599
order = [lb, subspec._layoutbox]
644-
if col_stop < mincol:
600+
elif subspec.colspan.stop - 1 < mincol:
645601
order = [subspec._layoutbox, lb]
646602
layoutbox.hstack(order, padding=pad * gslb.width,
647603
strength='strong')
@@ -686,17 +642,15 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
686642
# Vertical Layout: need to check all the axes in this gridspec
687643
for ch in gslb.children:
688644
subspec = ch.artist
689-
nrows, ncols, row_start, row_stop, col_start, col_stop = \
690-
subspec.get_rows_columns()
691645
if location == 'bottom':
692-
if row_stop <= minrow:
646+
if subspec.rowspan.stop - 1 <= minrow:
693647
order = [subspec._layoutbox, lb]
694-
if row_start > maxrow:
648+
elif subspec.rowspan.start > maxrow:
695649
order = [lb, subspec._layoutbox]
696650
elif location == 'top':
697-
if row_stop < minrow:
651+
if subspec.rowspan.stop - 1 < minrow:
698652
order = [subspec._layoutbox, lb]
699-
if row_start >= maxrow:
653+
elif subspec.rowspan.start >= maxrow:
700654
order = [lb, subspec._layoutbox]
701655
layoutbox.vstack(order, padding=pad * gslb.width,
702656
strength='strong')

0 commit comments

Comments
 (0)