Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/gridspec.py : 21%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1r"""
2:mod:`~matplotlib.gridspec` contains classes that help to layout multiple
3`~axes.Axes` in a grid-like pattern within a figure.
5The `GridSpec` specifies the overall grid structure. Individual cells within
6the grid are referenced by `SubplotSpec`\s.
8See the tutorial :ref:`sphx_glr_tutorials_intermediate_gridspec.py` for a
9comprehensive usage guide.
10"""
12import copy
13import logging
15import numpy as np
17import matplotlib as mpl
18from matplotlib import _pylab_helpers, cbook, tight_layout, rcParams
19from matplotlib.transforms import Bbox
20import matplotlib._layoutbox as layoutbox
22_log = logging.getLogger(__name__)
25class GridSpecBase:
26 """
27 A base class of GridSpec that specifies the geometry of the grid
28 that a subplot will be placed.
29 """
31 def __init__(self, nrows, ncols, height_ratios=None, width_ratios=None):
32 """
33 Parameters
34 ----------
35 nrows, ncols : int
36 The number of rows and columns of the grid.
37 width_ratios : array-like of length *ncols*, optional
38 Defines the relative widths of the columns. Each column gets a
39 relative width of ``width_ratios[i] / sum(width_ratios)``.
40 If not given, all columns will have the same width.
41 height_ratios : array-like of length *nrows*, optional
42 Defines the relative heights of the rows. Each column gets a
43 relative height of ``height_ratios[i] / sum(height_ratios)``.
44 If not given, all rows will have the same height.
45 """
46 self._nrows, self._ncols = nrows, ncols
47 self.set_height_ratios(height_ratios)
48 self.set_width_ratios(width_ratios)
50 def __repr__(self):
51 height_arg = (', height_ratios=%r' % (self._row_height_ratios,)
52 if self._row_height_ratios is not None else '')
53 width_arg = (', width_ratios=%r' % (self._col_width_ratios,)
54 if self._col_width_ratios is not None else '')
55 return '{clsname}({nrows}, {ncols}{optionals})'.format(
56 clsname=self.__class__.__name__,
57 nrows=self._nrows,
58 ncols=self._ncols,
59 optionals=height_arg + width_arg,
60 )
62 nrows = property(lambda self: self._nrows,
63 doc="The number of rows in the grid.")
64 ncols = property(lambda self: self._ncols,
65 doc="The number of columns in the grid.")
67 def get_geometry(self):
68 """
69 Return a tuple containing the number of rows and columns in the grid.
70 """
71 return self._nrows, self._ncols
73 def get_subplot_params(self, figure=None):
74 # Must be implemented in subclasses
75 pass
77 def new_subplotspec(self, loc, rowspan=1, colspan=1):
78 """
79 Create and return a `.SubplotSpec` instance.
81 Parameters
82 ----------
83 loc : (int, int)
84 The position of the subplot in the grid as
85 ``(row_index, column_index)``.
86 rowspan, colspan : int, default: 1
87 The number of rows and columns the subplot should span in the grid.
88 """
89 loc1, loc2 = loc
90 subplotspec = self[loc1:loc1+rowspan, loc2:loc2+colspan]
91 return subplotspec
93 def set_width_ratios(self, width_ratios):
94 """
95 Set the relative widths of the columns.
97 *width_ratios* must be of length *ncols*. Each column gets a relative
98 width of ``width_ratios[i] / sum(width_ratios)``.
99 """
100 if width_ratios is not None and len(width_ratios) != self._ncols:
101 raise ValueError('Expected the given number of width ratios to '
102 'match the number of columns of the grid')
103 self._col_width_ratios = width_ratios
105 def get_width_ratios(self):
106 """
107 Return the width ratios.
109 This is *None* if no width ratios have been set explicitly.
110 """
111 return self._col_width_ratios
113 def set_height_ratios(self, height_ratios):
114 """
115 Set the relative heights of the rows.
117 *height_ratios* must be of length *nrows*. Each row gets a relative
118 height of ``height_ratios[i] / sum(height_ratios)``.
119 """
120 if height_ratios is not None and len(height_ratios) != self._nrows:
121 raise ValueError('Expected the given number of height ratios to '
122 'match the number of rows of the grid')
123 self._row_height_ratios = height_ratios
125 def get_height_ratios(self):
126 """
127 Return the height ratios.
129 This is *None* if no height ratios have been set explicitly.
130 """
131 return self._row_height_ratios
133 def get_grid_positions(self, fig, raw=False):
134 """
135 Return the positions of the grid cells in figure coordinates.
137 Parameters
138 ----------
139 fig : `~matplotlib.figure.Figure`
140 The figure the grid should be applied to. The subplot parameters
141 (margins and spacing between subplots) are taken from *fig*.
142 raw : bool, default: False
143 If *True*, the subplot parameters of the figure are not taken
144 into account. The grid spans the range [0, 1] in both directions
145 without margins and there is no space between grid cells. This is
146 used for constrained_layout.
148 Returns
149 -------
150 bottoms, tops, lefts, rights : array
151 The bottom, top, left, right positions of the grid cells in
152 figure coordinates.
153 """
154 nrows, ncols = self.get_geometry()
156 if raw:
157 left = 0.
158 right = 1.
159 bottom = 0.
160 top = 1.
161 wspace = 0.
162 hspace = 0.
163 else:
164 subplot_params = self.get_subplot_params(fig)
165 left = subplot_params.left
166 right = subplot_params.right
167 bottom = subplot_params.bottom
168 top = subplot_params.top
169 wspace = subplot_params.wspace
170 hspace = subplot_params.hspace
171 tot_width = right - left
172 tot_height = top - bottom
174 # calculate accumulated heights of columns
175 cell_h = tot_height / (nrows + hspace*(nrows-1))
176 sep_h = hspace * cell_h
177 if self._row_height_ratios is not None:
178 norm = cell_h * nrows / sum(self._row_height_ratios)
179 cell_heights = [r * norm for r in self._row_height_ratios]
180 else:
181 cell_heights = [cell_h] * nrows
182 sep_heights = [0] + ([sep_h] * (nrows-1))
183 cell_hs = np.cumsum(np.column_stack([sep_heights, cell_heights]).flat)
185 # calculate accumulated widths of rows
186 cell_w = tot_width / (ncols + wspace*(ncols-1))
187 sep_w = wspace * cell_w
188 if self._col_width_ratios is not None:
189 norm = cell_w * ncols / sum(self._col_width_ratios)
190 cell_widths = [r * norm for r in self._col_width_ratios]
191 else:
192 cell_widths = [cell_w] * ncols
193 sep_widths = [0] + ([sep_w] * (ncols-1))
194 cell_ws = np.cumsum(np.column_stack([sep_widths, cell_widths]).flat)
196 fig_tops, fig_bottoms = (top - cell_hs).reshape((-1, 2)).T
197 fig_lefts, fig_rights = (left + cell_ws).reshape((-1, 2)).T
198 return fig_bottoms, fig_tops, fig_lefts, fig_rights
200 def __getitem__(self, key):
201 """Create and return a `.SubplotSpec` instance."""
202 nrows, ncols = self.get_geometry()
204 def _normalize(key, size, axis): # Includes last index.
205 orig_key = key
206 if isinstance(key, slice):
207 start, stop, _ = key.indices(size)
208 if stop > start:
209 return start, stop - 1
210 raise IndexError("GridSpec slice would result in no space "
211 "allocated for subplot")
212 else:
213 if key < 0:
214 key = key + size
215 if 0 <= key < size:
216 return key, key
217 elif axis is not None:
218 raise IndexError(f"index {orig_key} is out of bounds for "
219 f"axis {axis} with size {size}")
220 else: # flat index
221 raise IndexError(f"index {orig_key} is out of bounds for "
222 f"GridSpec with size {size}")
224 if isinstance(key, tuple):
225 try:
226 k1, k2 = key
227 except ValueError:
228 raise ValueError("unrecognized subplot spec")
229 num1, num2 = np.ravel_multi_index(
230 [_normalize(k1, nrows, 0), _normalize(k2, ncols, 1)],
231 (nrows, ncols))
232 else: # Single key
233 num1, num2 = _normalize(key, nrows * ncols, None)
235 return SubplotSpec(self, num1, num2)
238class GridSpec(GridSpecBase):
239 """
240 A grid layout to place subplots within a figure.
242 The location of the grid cells is determined in a similar way to
243 `~.figure.SubplotParams` using *left*, *right*, *top*, *bottom*, *wspace*
244 and *hspace*.
245 """
246 def __init__(self, nrows, ncols, figure=None,
247 left=None, bottom=None, right=None, top=None,
248 wspace=None, hspace=None,
249 width_ratios=None, height_ratios=None):
250 """
251 Parameters
252 ----------
253 nrows, ncols : int
254 The number of rows and columns of the grid.
256 figure : `~.figure.Figure`, optional
257 Only used for constrained layout to create a proper layoutbox.
259 left, right, top, bottom : float, optional
260 Extent of the subplots as a fraction of figure width or height.
261 Left cannot be larger than right, and bottom cannot be larger than
262 top. If not given, the values will be inferred from a figure or
263 rcParams at draw time. See also `GridSpec.get_subplot_params`.
265 wspace : float, optional
266 The amount of width reserved for space between subplots,
267 expressed as a fraction of the average axis width.
268 If not given, the values will be inferred from a figure or
269 rcParams when necessary. See also `GridSpec.get_subplot_params`.
271 hspace : float, optional
272 The amount of height reserved for space between subplots,
273 expressed as a fraction of the average axis height.
274 If not given, the values will be inferred from a figure or
275 rcParams when necessary. See also `GridSpec.get_subplot_params`.
277 width_ratios : array-like of length *ncols*, optional
278 Defines the relative widths of the columns. Each column gets a
279 relative width of ``width_ratios[i] / sum(width_ratios)``.
280 If not given, all columns will have the same width.
282 height_ratios : array-like of length *nrows*, optional
283 Defines the relative heights of the rows. Each column gets a
284 relative height of ``height_ratios[i] / sum(height_ratios)``.
285 If not given, all rows will have the same height.
287 """
288 self.left = left
289 self.bottom = bottom
290 self.right = right
291 self.top = top
292 self.wspace = wspace
293 self.hspace = hspace
294 self.figure = figure
296 GridSpecBase.__init__(self, nrows, ncols,
297 width_ratios=width_ratios,
298 height_ratios=height_ratios)
300 if self.figure is None or not self.figure.get_constrained_layout():
301 self._layoutbox = None
302 else:
303 self.figure.init_layoutbox()
304 self._layoutbox = layoutbox.LayoutBox(
305 parent=self.figure._layoutbox,
306 name='gridspec' + layoutbox.seq_id(),
307 artist=self)
308 # by default the layoutbox for a gridspec will fill a figure.
309 # but this can change below if the gridspec is created from a
310 # subplotspec. (GridSpecFromSubplotSpec)
312 _AllowedKeys = ["left", "bottom", "right", "top", "wspace", "hspace"]
314 def __getstate__(self):
315 state = self.__dict__
316 try:
317 state.pop('_layoutbox')
318 except KeyError:
319 pass
320 return state
322 def __setstate__(self, state):
323 self.__dict__ = state
324 # layoutboxes don't survive pickling...
325 self._layoutbox = None
327 def update(self, **kwargs):
328 """
329 Update the subplot parameters of the grid.
331 Parameters that are not explicitly given are not changed. Setting a
332 parameter to *None* resets it to :rc:`figure.subplot.*`.
334 Parameters
335 ----------
336 left, right, top, bottom : float or None, optional
337 Extent of the subplots as a fraction of figure width or height.
338 wspace, hspace : float, optional
339 Spacing between the subplots as a fraction of the average subplot
340 width / height.
341 """
342 for k, v in kwargs.items():
343 if k in self._AllowedKeys:
344 setattr(self, k, v)
345 else:
346 raise AttributeError(f"{k} is an unknown keyword")
347 for figmanager in _pylab_helpers.Gcf.figs.values():
348 for ax in figmanager.canvas.figure.axes:
349 # copied from Figure.subplots_adjust
350 if not isinstance(ax, mpl.axes.SubplotBase):
351 # Check if sharing a subplots axis
352 if isinstance(ax._sharex, mpl.axes.SubplotBase):
353 if ax._sharex.get_subplotspec().get_gridspec() == self:
354 ax._sharex.update_params()
355 ax._set_position(ax._sharex.figbox)
356 elif isinstance(ax._sharey, mpl.axes.SubplotBase):
357 if ax._sharey.get_subplotspec().get_gridspec() == self:
358 ax._sharey.update_params()
359 ax._set_position(ax._sharey.figbox)
360 else:
361 ss = ax.get_subplotspec().get_topmost_subplotspec()
362 if ss.get_gridspec() == self:
363 ax.update_params()
364 ax._set_position(ax.figbox)
366 def get_subplot_params(self, figure=None):
367 """
368 Return the `~.SubplotParams` for the GridSpec.
370 In order of precedence the values are taken from
372 - non-*None* attributes of the GridSpec
373 - the provided *figure*
374 - :rc:`figure.subplot.*`
375 """
376 if figure is None:
377 kw = {k: rcParams["figure.subplot."+k] for k in self._AllowedKeys}
378 subplotpars = mpl.figure.SubplotParams(**kw)
379 else:
380 subplotpars = copy.copy(figure.subplotpars)
382 subplotpars.update(**{k: getattr(self, k) for k in self._AllowedKeys})
384 return subplotpars
386 def locally_modified_subplot_params(self):
387 """
388 Return a list of the names of the subplot parameters explicitly set
389 in the GridSpec.
391 This is a subset of the attributes of `.SubplotParams`.
392 """
393 return [k for k in self._AllowedKeys if getattr(self, k)]
395 def tight_layout(self, figure, renderer=None,
396 pad=1.08, h_pad=None, w_pad=None, rect=None):
397 """
398 Adjust subplot parameters to give specified padding.
400 Parameters
401 ----------
402 pad : float
403 Padding between the figure edge and the edges of subplots, as a
404 fraction of the font-size.
405 h_pad, w_pad : float, optional
406 Padding (height/width) between edges of adjacent subplots.
407 Defaults to *pad*.
408 rect : tuple of 4 floats, optional
409 (left, bottom, right, top) rectangle in normalized figure
410 coordinates that the whole subplots area (including labels) will
411 fit into. Default is (0, 0, 1, 1).
412 """
414 subplotspec_list = tight_layout.get_subplotspec_list(
415 figure.axes, grid_spec=self)
416 if None in subplotspec_list:
417 cbook._warn_external("This figure includes Axes that are not "
418 "compatible with tight_layout, so results "
419 "might be incorrect.")
421 if renderer is None:
422 renderer = tight_layout.get_renderer(figure)
424 kwargs = tight_layout.get_tight_layout_figure(
425 figure, figure.axes, subplotspec_list, renderer,
426 pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
427 if kwargs:
428 self.update(**kwargs)
431class GridSpecFromSubplotSpec(GridSpecBase):
432 """
433 GridSpec whose subplot layout parameters are inherited from the
434 location specified by a given SubplotSpec.
435 """
436 def __init__(self, nrows, ncols,
437 subplot_spec,
438 wspace=None, hspace=None,
439 height_ratios=None, width_ratios=None):
440 """
441 The number of rows and number of columns of the grid need to
442 be set. An instance of SubplotSpec is also needed to be set
443 from which the layout parameters will be inherited. The wspace
444 and hspace of the layout can be optionally specified or the
445 default values (from the figure or rcParams) will be used.
446 """
447 self._wspace = wspace
448 self._hspace = hspace
449 self._subplot_spec = subplot_spec
450 GridSpecBase.__init__(self, nrows, ncols,
451 width_ratios=width_ratios,
452 height_ratios=height_ratios)
453 # do the layoutboxes
454 subspeclb = subplot_spec._layoutbox
455 if subspeclb is None:
456 self._layoutbox = None
457 else:
458 # OK, this is needed to divide the figure.
459 self._layoutbox = subspeclb.layout_from_subplotspec(
460 subplot_spec,
461 name=subspeclb.name + '.gridspec' + layoutbox.seq_id(),
462 artist=self)
464 def get_subplot_params(self, figure=None):
465 """Return a dictionary of subplot layout parameters.
466 """
467 hspace = (self._hspace if self._hspace is not None
468 else figure.subplotpars.hspace if figure is not None
469 else rcParams["figure.subplot.hspace"])
470 wspace = (self._wspace if self._wspace is not None
471 else figure.subplotpars.wspace if figure is not None
472 else rcParams["figure.subplot.wspace"])
474 figbox = self._subplot_spec.get_position(figure)
475 left, bottom, right, top = figbox.extents
477 return mpl.figure.SubplotParams(left=left, right=right,
478 bottom=bottom, top=top,
479 wspace=wspace, hspace=hspace)
481 def get_topmost_subplotspec(self):
482 """
483 Return the topmost `.SubplotSpec` instance associated with the subplot.
484 """
485 return self._subplot_spec.get_topmost_subplotspec()
488class SubplotSpec:
489 """
490 Specifies the location of a subplot in a `GridSpec`.
492 .. note::
494 Likely, you'll never instantiate a `SubplotSpec` yourself. Instead you
495 will typically obtain one from a `GridSpec` using item-access.
497 Parameters
498 ----------
499 gridspec : `~matplotlib.gridspec.GridSpec`
500 The GridSpec, which the subplot is referencing.
501 num1, num2 : int
502 The subplot will occupy the num1-th cell of the given
503 gridspec. If num2 is provided, the subplot will span between
504 num1-th cell and num2-th cell *inclusive*.
506 The index starts from 0.
507 """
508 def __init__(self, gridspec, num1, num2=None):
509 self._gridspec = gridspec
510 self.num1 = num1
511 self.num2 = num2
512 if gridspec._layoutbox is not None:
513 glb = gridspec._layoutbox
514 # So note that here we don't assign any layout yet,
515 # just make the layoutbox that will contain all items
516 # associated w/ this axis. This can include other axes like
517 # a colorbar or a legend.
518 self._layoutbox = layoutbox.LayoutBox(
519 parent=glb,
520 name=glb.name + '.ss' + layoutbox.seq_id(),
521 artist=self)
522 else:
523 self._layoutbox = None
525 # num2 is a property only to handle the case where it is None and someone
526 # mutates num1.
528 @property
529 def num2(self):
530 return self.num1 if self._num2 is None else self._num2
532 @num2.setter
533 def num2(self, value):
534 self._num2 = value
536 def __getstate__(self):
537 state = self.__dict__
538 try:
539 state.pop('_layoutbox')
540 except KeyError:
541 pass
542 return state
544 def __setstate__(self, state):
545 self.__dict__ = state
546 # layoutboxes don't survive pickling...
547 self._layoutbox = None
549 def get_gridspec(self):
550 return self._gridspec
552 def get_geometry(self):
553 """
554 Return the subplot geometry as tuple ``(n_rows, n_cols, start, stop)``.
556 The indices *start* and *stop* define the range of the subplot within
557 the `GridSpec`. *stop* is inclusive (i.e. for a single cell
558 ``start == stop``).
559 """
560 rows, cols = self.get_gridspec().get_geometry()
561 return rows, cols, self.num1, self.num2
563 def get_rows_columns(self):
564 """
565 Return the subplot row and column numbers as a tuple
566 ``(n_rows, n_cols, row_start, row_stop, col_start, col_stop)``.
567 """
568 gridspec = self.get_gridspec()
569 nrows, ncols = gridspec.get_geometry()
570 row_start, col_start = divmod(self.num1, ncols)
571 row_stop, col_stop = divmod(self.num2, ncols)
572 return nrows, ncols, row_start, row_stop, col_start, col_stop
574 @property
575 def rowspan(self):
576 """The rows spanned by this subplot, as a `range` object."""
577 ncols = self.get_gridspec().ncols
578 return range(self.num1 // ncols, self.num2 // ncols + 1)
580 @property
581 def colspan(self):
582 """The columns spanned by this subplot, as a `range` object."""
583 ncols = self.get_gridspec().ncols
584 return range(self.num1 % ncols, self.num2 % ncols + 1)
586 def get_position(self, figure, return_all=False):
587 """
588 Update the subplot position from ``figure.subplotpars``.
589 """
590 gridspec = self.get_gridspec()
591 nrows, ncols = gridspec.get_geometry()
592 rows, cols = np.unravel_index([self.num1, self.num2], (nrows, ncols))
593 fig_bottoms, fig_tops, fig_lefts, fig_rights = \
594 gridspec.get_grid_positions(figure)
596 fig_bottom = fig_bottoms[rows].min()
597 fig_top = fig_tops[rows].max()
598 fig_left = fig_lefts[cols].min()
599 fig_right = fig_rights[cols].max()
600 figbox = Bbox.from_extents(fig_left, fig_bottom, fig_right, fig_top)
602 if return_all:
603 return figbox, rows[0], cols[0], nrows, ncols
604 else:
605 return figbox
607 def get_topmost_subplotspec(self):
608 """
609 Return the topmost `SubplotSpec` instance associated with the subplot.
610 """
611 gridspec = self.get_gridspec()
612 if hasattr(gridspec, "get_topmost_subplotspec"):
613 return gridspec.get_topmost_subplotspec()
614 else:
615 return self
617 def __eq__(self, other):
618 """
619 Two SubplotSpecs are considered equal if they refer to the same
620 position(s) in the same `GridSpec`.
621 """
622 # other may not even have the attributes we are checking.
623 return ((self._gridspec, self.num1, self.num2)
624 == (getattr(other, "_gridspec", object()),
625 getattr(other, "num1", object()),
626 getattr(other, "num2", object())))
628 def __hash__(self):
629 return hash((self._gridspec, self.num1, self.num2))
631 def subgridspec(self, nrows, ncols, **kwargs):
632 """
633 Create a GridSpec within this subplot.
635 The created `.GridSpecFromSubplotSpec` will have this `SubplotSpec` as
636 a parent.
638 Parameters
639 ----------
640 nrows : int
641 Number of rows in grid.
643 ncols : int
644 Number or columns in grid.
646 Returns
647 -------
648 gridspec : `.GridSpecFromSubplotSpec`
650 Other Parameters
651 ----------------
652 **kwargs
653 All other parameters are passed to `.GridSpecFromSubplotSpec`.
655 See Also
656 --------
657 matplotlib.pyplot.subplots
659 Examples
660 --------
661 Adding three subplots in the space occupied by a single subplot::
663 fig = plt.figure()
664 gs0 = fig.add_gridspec(3, 1)
665 ax1 = fig.add_subplot(gs0[0])
666 ax2 = fig.add_subplot(gs0[1])
667 gssub = gs0[2].subgridspec(1, 3)
668 for i in range(3):
669 fig.add_subplot(gssub[0, i])
670 """
671 return GridSpecFromSubplotSpec(nrows, ncols, self, **kwargs)