"""
|
|
:mod:`~matplotlib.gridspec` is a module which specifies the location
|
|
of the subplot in the figure.
|
|
|
|
`GridSpec`
|
|
specifies the geometry of the grid that a subplot will be
|
|
placed. The number of rows and number of columns of the grid
|
|
need to be set. Optionally, the subplot layout parameters
|
|
(e.g., left, right, etc.) can be tuned.
|
|
|
|
`SubplotSpec`
|
|
specifies the location of the subplot in the given `GridSpec`.
|
|
|
|
"""
|
|
|
|
import copy
|
|
import logging
|
|
import warnings
|
|
|
|
import numpy as np
|
|
|
|
import matplotlib as mpl
|
|
from matplotlib import _pylab_helpers, cbook, tight_layout, rcParams
|
|
from matplotlib.transforms import Bbox
|
|
import matplotlib._layoutbox as layoutbox
|
|
|
|
_log = logging.getLogger(__name__)
|
|
|
|
|
|
class GridSpecBase(object):
|
|
"""
|
|
A base class of GridSpec that specifies the geometry of the grid
|
|
that a subplot will be placed.
|
|
"""
|
|
|
|
def __init__(self, nrows, ncols, height_ratios=None, width_ratios=None):
|
|
"""
|
|
The number of rows and number of columns of the grid need to
|
|
be set. Optionally, the ratio of heights and widths of rows and
|
|
columns can be specified.
|
|
"""
|
|
self._nrows, self._ncols = nrows, ncols
|
|
self.set_height_ratios(height_ratios)
|
|
self.set_width_ratios(width_ratios)
|
|
|
|
def __repr__(self):
|
|
height_arg = (', height_ratios=%r' % self._row_height_ratios
|
|
if self._row_height_ratios is not None else '')
|
|
width_arg = (', width_ratios=%r' % self._col_width_ratios
|
|
if self._col_width_ratios is not None else '')
|
|
return '{clsname}({nrows}, {ncols}{optionals})'.format(
|
|
clsname=self.__class__.__name__,
|
|
nrows=self._nrows,
|
|
ncols=self._ncols,
|
|
optionals=height_arg + width_arg,
|
|
)
|
|
|
|
def get_geometry(self):
|
|
'get the geometry of the grid, e.g., 2,3'
|
|
return self._nrows, self._ncols
|
|
|
|
def get_subplot_params(self, figure=None, fig=None):
|
|
pass
|
|
|
|
def new_subplotspec(self, loc, rowspan=1, colspan=1):
|
|
"""
|
|
create and return a SuplotSpec instance.
|
|
"""
|
|
loc1, loc2 = loc
|
|
subplotspec = self[loc1:loc1+rowspan, loc2:loc2+colspan]
|
|
return subplotspec
|
|
|
|
def set_width_ratios(self, width_ratios):
|
|
if width_ratios is not None and len(width_ratios) != self._ncols:
|
|
raise ValueError('Expected the given number of width ratios to '
|
|
'match the number of columns of the grid')
|
|
self._col_width_ratios = width_ratios
|
|
|
|
def get_width_ratios(self):
|
|
return self._col_width_ratios
|
|
|
|
def set_height_ratios(self, height_ratios):
|
|
if height_ratios is not None and len(height_ratios) != self._nrows:
|
|
raise ValueError('Expected the given number of height ratios to '
|
|
'match the number of rows of the grid')
|
|
self._row_height_ratios = height_ratios
|
|
|
|
def get_height_ratios(self):
|
|
return self._row_height_ratios
|
|
|
|
def get_grid_positions(self, fig, raw=False):
|
|
"""
|
|
return lists of bottom and top position of rows, left and
|
|
right positions of columns.
|
|
|
|
If raw=True, then these are all in units relative to the container
|
|
with no margins. (used for constrained_layout).
|
|
"""
|
|
nrows, ncols = self.get_geometry()
|
|
|
|
if raw:
|
|
left = 0.
|
|
right = 1.
|
|
bottom = 0.
|
|
top = 1.
|
|
wspace = 0.
|
|
hspace = 0.
|
|
else:
|
|
subplot_params = self.get_subplot_params(fig)
|
|
left = subplot_params.left
|
|
right = subplot_params.right
|
|
bottom = subplot_params.bottom
|
|
top = subplot_params.top
|
|
wspace = subplot_params.wspace
|
|
hspace = subplot_params.hspace
|
|
tot_width = right - left
|
|
tot_height = top - bottom
|
|
|
|
# calculate accumulated heights of columns
|
|
cell_h = tot_height / (nrows + hspace*(nrows-1))
|
|
sep_h = hspace * cell_h
|
|
if self._row_height_ratios is not None:
|
|
norm = cell_h * nrows / sum(self._row_height_ratios)
|
|
cell_heights = [r * norm for r in self._row_height_ratios]
|
|
else:
|
|
cell_heights = [cell_h] * nrows
|
|
sep_heights = [0] + ([sep_h] * (nrows-1))
|
|
cell_hs = np.cumsum(np.column_stack([sep_heights, cell_heights]).flat)
|
|
|
|
# calculate accumulated widths of rows
|
|
cell_w = tot_width / (ncols + wspace*(ncols-1))
|
|
sep_w = wspace * cell_w
|
|
if self._col_width_ratios is not None:
|
|
norm = cell_w * ncols / sum(self._col_width_ratios)
|
|
cell_widths = [r * norm for r in self._col_width_ratios]
|
|
else:
|
|
cell_widths = [cell_w] * ncols
|
|
sep_widths = [0] + ([sep_w] * (ncols-1))
|
|
cell_ws = np.cumsum(np.column_stack([sep_widths, cell_widths]).flat)
|
|
|
|
fig_tops, fig_bottoms = (top - cell_hs).reshape((-1, 2)).T
|
|
fig_lefts, fig_rights = (left + cell_ws).reshape((-1, 2)).T
|
|
return fig_bottoms, fig_tops, fig_lefts, fig_rights
|
|
|
|
def __getitem__(self, key):
|
|
"""Create and return a SuplotSpec instance.
|
|
"""
|
|
nrows, ncols = self.get_geometry()
|
|
|
|
def _normalize(key, size): # Includes last index.
|
|
if isinstance(key, slice):
|
|
start, stop, _ = key.indices(size)
|
|
if stop > start:
|
|
return start, stop - 1
|
|
else:
|
|
if key < 0:
|
|
key += size
|
|
if 0 <= key < size:
|
|
return key, key
|
|
raise IndexError("invalid index")
|
|
|
|
if isinstance(key, tuple):
|
|
try:
|
|
k1, k2 = key
|
|
except ValueError:
|
|
raise ValueError("unrecognized subplot spec")
|
|
num1, num2 = np.ravel_multi_index(
|
|
[_normalize(k1, nrows), _normalize(k2, ncols)], (nrows, ncols))
|
|
else: # Single key
|
|
num1, num2 = _normalize(key, nrows * ncols)
|
|
|
|
return SubplotSpec(self, num1, num2)
|
|
|
|
|
|
class GridSpec(GridSpecBase):
|
|
"""
|
|
A class that specifies the geometry of the grid that a subplot
|
|
will be placed. The location of grid is determined by similar way
|
|
as the SubplotParams.
|
|
"""
|
|
|
|
def __init__(self, nrows, ncols, figure=None,
|
|
left=None, bottom=None, right=None, top=None,
|
|
wspace=None, hspace=None,
|
|
width_ratios=None, height_ratios=None):
|
|
"""
|
|
The number of rows and number of columns of the grid need to be set.
|
|
Optionally, the subplot layout parameters (e.g., left, right, etc.)
|
|
can be tuned.
|
|
|
|
Parameters
|
|
----------
|
|
nrows : int
|
|
Number of rows in grid.
|
|
|
|
ncols : int
|
|
Number or columns in grid.
|
|
|
|
figure : ~.figure.Figure, optional
|
|
|
|
left, right, top, bottom : float
|
|
Extent of the subplots as a fraction of figure width or height.
|
|
Left cannot be larger than right, and bottom cannot be larger than
|
|
top.
|
|
|
|
wspace : float
|
|
The amount of width reserved for space between subplots,
|
|
expressed as a fraction of the average axis width.
|
|
|
|
hspace : float
|
|
The amount of height reserved for space between subplots,
|
|
expressed as a fraction of the average axis height.
|
|
|
|
Notes
|
|
-----
|
|
See `~.figure.SubplotParams` for descriptions of the layout parameters.
|
|
"""
|
|
self.left = left
|
|
self.bottom = bottom
|
|
self.right = right
|
|
self.top = top
|
|
self.wspace = wspace
|
|
self.hspace = hspace
|
|
self.figure = figure
|
|
|
|
GridSpecBase.__init__(self, nrows, ncols,
|
|
width_ratios=width_ratios,
|
|
height_ratios=height_ratios)
|
|
|
|
if self.figure is None or not self.figure.get_constrained_layout():
|
|
self._layoutbox = None
|
|
else:
|
|
self.figure.init_layoutbox()
|
|
self._layoutbox = layoutbox.LayoutBox(
|
|
parent=self.figure._layoutbox,
|
|
name='gridspec' + layoutbox.seq_id(),
|
|
artist=self)
|
|
# by default the layoutbox for a gridsepc will fill a figure.
|
|
# but this can change below if the gridspec is created from a
|
|
# subplotspec. (GridSpecFromSubplotSpec)
|
|
|
|
_AllowedKeys = ["left", "bottom", "right", "top", "wspace", "hspace"]
|
|
|
|
def __getstate__(self):
|
|
state = self.__dict__
|
|
try:
|
|
state.pop('_layoutbox')
|
|
except KeyError:
|
|
pass
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
self.__dict__ = state
|
|
# layoutboxes don't survive pickling...
|
|
self._layoutbox = None
|
|
|
|
def update(self, **kwargs):
|
|
"""
|
|
Update the current values. If any kwarg is None, default to
|
|
the current value, if set, otherwise to rc.
|
|
"""
|
|
|
|
for k, v in kwargs.items():
|
|
if k in self._AllowedKeys:
|
|
setattr(self, k, v)
|
|
else:
|
|
raise AttributeError("%s is unknown keyword" % (k,))
|
|
|
|
for figmanager in _pylab_helpers.Gcf.figs.values():
|
|
for ax in figmanager.canvas.figure.axes:
|
|
# copied from Figure.subplots_adjust
|
|
if not isinstance(ax, mpl.axes.SubplotBase):
|
|
# Check if sharing a subplots axis
|
|
if isinstance(ax._sharex, mpl.axes.SubplotBase):
|
|
if ax._sharex.get_subplotspec().get_gridspec() == self:
|
|
ax._sharex.update_params()
|
|
ax._set_position(ax._sharex.figbox)
|
|
elif isinstance(ax._sharey, mpl.axes.SubplotBase):
|
|
if ax._sharey.get_subplotspec().get_gridspec() == self:
|
|
ax._sharey.update_params()
|
|
ax._set_position(ax._sharey.figbox)
|
|
else:
|
|
ss = ax.get_subplotspec().get_topmost_subplotspec()
|
|
if ss.get_gridspec() == self:
|
|
ax.update_params()
|
|
ax._set_position(ax.figbox)
|
|
|
|
def get_subplot_params(self, figure=None, fig=None):
|
|
"""
|
|
Return a dictionary of subplot layout parameters. The default
|
|
parameters are from rcParams unless a figure attribute is set.
|
|
"""
|
|
if fig is not None:
|
|
cbook.warn_deprecated("2.2", "fig", obj_type="keyword argument",
|
|
alternative="figure")
|
|
if figure is None:
|
|
figure = fig
|
|
|
|
if figure is None:
|
|
kw = {k: rcParams["figure.subplot."+k] for k in self._AllowedKeys}
|
|
subplotpars = mpl.figure.SubplotParams(**kw)
|
|
else:
|
|
subplotpars = copy.copy(figure.subplotpars)
|
|
|
|
subplotpars.update(**{k: getattr(self, k) for k in self._AllowedKeys})
|
|
|
|
return subplotpars
|
|
|
|
def locally_modified_subplot_params(self):
|
|
return [k for k in self._AllowedKeys if getattr(self, k)]
|
|
|
|
def tight_layout(self, figure, renderer=None,
|
|
pad=1.08, h_pad=None, w_pad=None, rect=None):
|
|
"""
|
|
Adjust subplot parameters to give specified padding.
|
|
|
|
Parameters
|
|
----------
|
|
|
|
pad : float
|
|
Padding between the figure edge and the edges of subplots, as a
|
|
fraction of the font-size.
|
|
h_pad, w_pad : float, optional
|
|
Padding (height/width) between edges of adjacent subplots.
|
|
Defaults to ``pad_inches``.
|
|
rect : tuple of 4 floats, optional
|
|
(left, bottom, right, top) rectangle in normalized figure
|
|
coordinates that the whole subplots area (including labels) will
|
|
fit into. Default is (0, 0, 1, 1).
|
|
"""
|
|
|
|
subplotspec_list = tight_layout.get_subplotspec_list(
|
|
figure.axes, grid_spec=self)
|
|
if None in subplotspec_list:
|
|
warnings.warn("This figure includes Axes that are not compatible "
|
|
"with tight_layout, so results might be incorrect.")
|
|
|
|
if renderer is None:
|
|
renderer = tight_layout.get_renderer(figure)
|
|
|
|
kwargs = tight_layout.get_tight_layout_figure(
|
|
figure, figure.axes, subplotspec_list, renderer,
|
|
pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
|
|
if kwargs:
|
|
self.update(**kwargs)
|
|
|
|
|
|
class GridSpecFromSubplotSpec(GridSpecBase):
|
|
"""
|
|
GridSpec whose subplot layout parameters are inherited from the
|
|
location specified by a given SubplotSpec.
|
|
"""
|
|
def __init__(self, nrows, ncols,
|
|
subplot_spec,
|
|
wspace=None, hspace=None,
|
|
height_ratios=None, width_ratios=None):
|
|
"""
|
|
The number of rows and number of columns of the grid need to
|
|
be set. An instance of SubplotSpec is also needed to be set
|
|
from which the layout parameters will be inherited. The wspace
|
|
and hspace of the layout can be optionally specified or the
|
|
default values (from the figure or rcParams) will be used.
|
|
"""
|
|
self._wspace = wspace
|
|
self._hspace = hspace
|
|
self._subplot_spec = subplot_spec
|
|
GridSpecBase.__init__(self, nrows, ncols,
|
|
width_ratios=width_ratios,
|
|
height_ratios=height_ratios)
|
|
# do the layoutboxes
|
|
subspeclb = subplot_spec._layoutbox
|
|
if subspeclb is None:
|
|
self._layoutbox = None
|
|
else:
|
|
# OK, this is needed to divide the figure.
|
|
self._layoutbox = subspeclb.layout_from_subplotspec(
|
|
subplot_spec,
|
|
name=subspeclb.name + '.gridspec' + layoutbox.seq_id(),
|
|
artist=self)
|
|
|
|
def get_subplot_params(self, figure=None, fig=None):
|
|
"""Return a dictionary of subplot layout parameters.
|
|
"""
|
|
if fig is not None:
|
|
cbook.warn_deprecated("2.2", "fig", obj_type="keyword argument",
|
|
alternative="figure")
|
|
if figure is None:
|
|
figure = fig
|
|
|
|
hspace = (self._hspace if self._hspace is not None
|
|
else figure.subplotpars.hspace if figure is not None
|
|
else rcParams["figure.subplot.hspace"])
|
|
wspace = (self._wspace if self._wspace is not None
|
|
else figure.subplotpars.wspace if figure is not None
|
|
else rcParams["figure.subplot.wspace"])
|
|
|
|
figbox = self._subplot_spec.get_position(figure)
|
|
left, bottom, right, top = figbox.extents
|
|
|
|
return mpl.figure.SubplotParams(left=left, right=right,
|
|
bottom=bottom, top=top,
|
|
wspace=wspace, hspace=hspace)
|
|
|
|
def get_topmost_subplotspec(self):
|
|
"""Get the topmost SubplotSpec instance associated with the subplot."""
|
|
return self._subplot_spec.get_topmost_subplotspec()
|
|
|
|
|
|
class SubplotSpec(object):
|
|
"""Specifies the location of the subplot in the given `GridSpec`.
|
|
"""
|
|
|
|
def __init__(self, gridspec, num1, num2=None):
|
|
"""
|
|
The subplot will occupy the num1-th cell of the given
|
|
gridspec. If num2 is provided, the subplot will span between
|
|
num1-th cell and num2-th cell.
|
|
|
|
The index starts from 0.
|
|
"""
|
|
self._gridspec = gridspec
|
|
self.num1 = num1
|
|
self.num2 = num2
|
|
if gridspec._layoutbox is not None:
|
|
glb = gridspec._layoutbox
|
|
# So note that here we don't assign any layout yet,
|
|
# just make the layoutbox that will conatin all items
|
|
# associated w/ this axis. This can include other axes like
|
|
# a colorbar or a legend.
|
|
self._layoutbox = layoutbox.LayoutBox(
|
|
parent=glb,
|
|
name=glb.name + '.ss' + layoutbox.seq_id(),
|
|
artist=self)
|
|
else:
|
|
self._layoutbox = None
|
|
|
|
def __getstate__(self):
|
|
state = self.__dict__
|
|
try:
|
|
state.pop('_layoutbox')
|
|
except KeyError:
|
|
pass
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
self.__dict__ = state
|
|
# layoutboxes don't survive pickling...
|
|
self._layoutbox = None
|
|
|
|
def get_gridspec(self):
|
|
return self._gridspec
|
|
|
|
def get_geometry(self):
|
|
"""
|
|
Get the subplot geometry (``n_rows, n_cols, start, stop``).
|
|
|
|
start and stop are the index of the start and stop of the
|
|
subplot.
|
|
"""
|
|
rows, cols = self.get_gridspec().get_geometry()
|
|
return rows, cols, self.num1, self.num2
|
|
|
|
def get_rows_columns(self):
|
|
"""
|
|
Get the subplot row and column numbers:
|
|
(``n_rows, n_cols, row_start, row_stop, col_start, col_stop``)
|
|
"""
|
|
gridspec = self.get_gridspec()
|
|
nrows, ncols = gridspec.get_geometry()
|
|
row_start, col_start = divmod(self.num1, ncols)
|
|
if self.num2 is not None:
|
|
row_stop, col_stop = divmod(self.num2, ncols)
|
|
else:
|
|
row_stop = row_start
|
|
col_stop = col_start
|
|
return nrows, ncols, row_start, row_stop, col_start, col_stop
|
|
|
|
def get_position(self, figure, return_all=False):
|
|
"""Update the subplot position from ``figure.subplotpars``.
|
|
"""
|
|
gridspec = self.get_gridspec()
|
|
nrows, ncols = gridspec.get_geometry()
|
|
rows, cols = np.unravel_index(
|
|
[self.num1] if self.num2 is None else [self.num1, self.num2],
|
|
(nrows, ncols))
|
|
fig_bottoms, fig_tops, fig_lefts, fig_rights = \
|
|
gridspec.get_grid_positions(figure)
|
|
|
|
fig_bottom = fig_bottoms[rows].min()
|
|
fig_top = fig_tops[rows].max()
|
|
fig_left = fig_lefts[cols].min()
|
|
fig_right = fig_rights[cols].max()
|
|
figbox = Bbox.from_extents(fig_left, fig_bottom, fig_right, fig_top)
|
|
|
|
if return_all:
|
|
return figbox, rows[0], cols[0], nrows, ncols
|
|
else:
|
|
return figbox
|
|
|
|
def get_topmost_subplotspec(self):
|
|
'get the topmost SubplotSpec instance associated with the subplot'
|
|
gridspec = self.get_gridspec()
|
|
if hasattr(gridspec, "get_topmost_subplotspec"):
|
|
return gridspec.get_topmost_subplotspec()
|
|
else:
|
|
return self
|
|
|
|
def __eq__(self, other):
|
|
# other may not even have the attributes we are checking.
|
|
return ((self._gridspec, self.num1, self.num2)
|
|
== (getattr(other, "_gridspec", object()),
|
|
getattr(other, "num1", object()),
|
|
getattr(other, "num2", object())))
|
|
|
|
def __hash__(self):
|
|
return hash((self._gridspec, self.num1, self.num2))
|
|
|
|
def subgridspec(self, nrows, ncols, **kwargs):
|
|
"""
|
|
Return a `.GridSpecFromSubplotSpec` that has this subplotspec as
|
|
a parent.
|
|
|
|
Parameters
|
|
----------
|
|
nrows : int
|
|
Number of rows in grid.
|
|
|
|
ncols : int
|
|
Number or columns in grid.
|
|
|
|
Returns
|
|
-------
|
|
gridspec : `.GridSpec`
|
|
|
|
Other Parameters
|
|
----------------
|
|
**kwargs
|
|
All other parameters are passed to `.GridSpec`.
|
|
|
|
See Also
|
|
--------
|
|
matplotlib.pyplot.subplots
|
|
|
|
Examples
|
|
--------
|
|
Adding three subplots in the space occupied by a single subplot::
|
|
|
|
fig = plt.figure()
|
|
gs0 = fig.add_gridspec(3, 1)
|
|
ax1 = fig.add_subplot(gs0[0])
|
|
ax2 = fig.add_subplot(gs0[1])
|
|
gssub = gs0[2].subgridspec(1, 3)
|
|
for i in range(3):
|
|
fig.add_subplot(gssub[0, i])
|
|
"""
|
|
|
|
return GridSpecFromSubplotSpec(nrows, ncols, self, **kwargs)
|