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

Skip to content

ENH: add supxlabel and supylabel #17524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions doc/users/next_whats_new/suplabels.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
supxlabel and supylabel
-----------------------

It is possible to add x- and y-labels to a whole figure, analogous to
`.FigureBase.suptitle` using the new `.FigureBase.supxlabel` and
`.FigureBase.supylabel` methods.

.. plot::

np.random.seed(19680801)
fig, axs = plt.subplots(3, 2, figsize=(5, 5), constrained_layout=True,
sharex=True, sharey=True)

for nn, ax in enumerate(axs.flat):
ax.set_title(f'Channel {nn}')
ax.plot(np.cumsum(np.random.randn(50)))

fig.supxlabel('Time [s]')
fig.supylabel('Data [V]')
47 changes: 42 additions & 5 deletions examples/subplots_axes_and_figures/figure_title.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
"""
============
Figure title
============
=============================================
Figure labels: suptitle, supxlabel, supylabel
=============================================

Each subplot can have its own title (`.Axes.set_title`). Additionally,
`.Figure.suptitle` adds a centered title at the top of the figure.
Each axes can have a title (or actually three - one each with *loc* "left",
"center", and "right"), but is sometimes desirable to give a whole figure
(or `.SubFigure`) an overall title, using `.FigureBase.suptitle`.

We can also add figure-level x- and y-labels using `.FigureBase.supxlabel` and
`.FigureBase.supylabel`.
"""
from matplotlib.cbook import get_sample_data
import matplotlib.pyplot as plt

import numpy as np


Expand All @@ -24,4 +30,35 @@

fig.suptitle('Different types of oscillations', fontsize=16)

##############################################################################
# A global x- or y-label can be set using the `.FigureBase.supxlabel` and
# `.FigureBase.supylabel` methods.

fig, axs = plt.subplots(3, 5, figsize=(8, 5), constrained_layout=True,
sharex=True, sharey=True)

fname = get_sample_data('percent_bachelors_degrees_women_usa.csv',
asfileobj=False)
gender_degree_data = np.genfromtxt(fname, delimiter=',', names=True)

majors = ['Health Professions', 'Public Administration', 'Education',
'Psychology', 'Foreign Languages', 'English',
'Art and Performance', 'Biology',
'Agriculture', 'Business',
'Math and Statistics', 'Architecture', 'Physical Sciences',
'Computer Science', 'Engineering']

for nn, ax in enumerate(axs.flat):
ax.set_xlim(1969.5, 2011.1)
column = majors[nn]
column_rec_name = column.replace('\n', '_').replace(' ', '_')

line, = ax.plot('Year', column_rec_name, data=gender_degree_data,
lw=2.5)
ax.set_title(column, fontsize='small', loc='left')
ax.set_ylim([0, 100])
ax.grid()
fig.supxlabel('Year')
fig.supylabel('Percent Degrees Awarded To Women')

plt.show()
35 changes: 25 additions & 10 deletions lib/matplotlib/_constrained_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import numpy as np

import matplotlib.cbook as cbook
import matplotlib.transforms as mtransforms

_log = logging.getLogger(__name__)

Expand Down Expand Up @@ -276,21 +277,35 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0,
def _make_margin_suptitles(fig, renderer, *, w_pad=0, h_pad=0):
# Figure out how large the suptitle is and make the
# top level figure margin larger.

inv_trans_fig = fig.transFigure.inverted().transform_bbox
# get the h_pad and w_pad as distances in the local subfigure coordinates:
padbox = mtransforms.Bbox([[0, 0], [w_pad, h_pad]])
padbox = (fig.transFigure -
fig.transSubfigure).transform_bbox(padbox)
h_pad_local = padbox.height
w_pad_local = padbox.width

for panel in fig.subfigs:
_make_margin_suptitles(panel, renderer, w_pad=w_pad, h_pad=h_pad)

if fig._suptitle is not None and fig._suptitle.get_in_layout():
invTransFig = fig.transSubfigure.inverted().transform_bbox
parenttrans = fig.transFigure
w_pad, h_pad = (fig.transSubfigure -
parenttrans).transform((w_pad, 1 - h_pad))
w_pad, one = (fig.transSubfigure -
parenttrans).transform((w_pad, 1))
h_pad = one - h_pad
bbox = invTransFig(fig._suptitle.get_tightbbox(renderer))
p = fig._suptitle.get_position()
fig._suptitle.set_position((p[0], 1-h_pad))
fig._layoutgrid.edit_margin_min('top', bbox.height + 2 * h_pad)
fig._suptitle.set_position((p[0], 1 - h_pad_local))
bbox = inv_trans_fig(fig._suptitle.get_tightbbox(renderer))
fig._layoutgrid.edit_margin_min('top', bbox.height + 2.0 * h_pad)

if fig._supxlabel is not None and fig._supxlabel.get_in_layout():
p = fig._supxlabel.get_position()
fig._supxlabel.set_position((p[0], h_pad_local))
bbox = inv_trans_fig(fig._supxlabel.get_tightbbox(renderer))
fig._layoutgrid.edit_margin_min('bottom', bbox.height + 2.0 * h_pad)

if fig._supylabel is not None and fig._supxlabel.get_in_layout():
p = fig._supylabel.get_position()
fig._supylabel.set_position((w_pad_local, p[1]))
bbox = inv_trans_fig(fig._supylabel.get_tightbbox(renderer))
fig._layoutgrid.edit_margin_min('left', bbox.width + 2.0 * w_pad)


def _match_submerged_margins(fig):
Expand Down
86 changes: 59 additions & 27 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ def __init__(self):
del self._axes

self._suptitle = None
self._supxlabel = None
self._supylabel = None

# constrained_layout:
self._layoutgrid = None
Expand All @@ -254,7 +256,6 @@ def __init__(self):
self.images = []
self.legends = []
self.subfigs = []
self._suptitle = None
self.stale = True
self.suppressComposite = None

Expand Down Expand Up @@ -369,26 +370,26 @@ def get_window_extent(self, *args, **kwargs):
"""
return self.bbox

def suptitle(self, t, **kwargs):
def _suplabels(self, t, info, **kwargs):
"""
Add a centered title to the figure.
Add a centered {name} to the figure.

Parameters
----------
t : str
The title text.
The {name} text.

x : float, default: 0.5
x : float, default: {x0}
The x location of the text in figure coordinates.

y : float, default: 0.98
y : float, default: {y0}
The y location of the text in figure coordinates.

horizontalalignment, ha : {'center', 'left', right'}, default: 'center'
horizontalalignment, ha : {{'center', 'left', 'right'}}, default: {ha}
The horizontal alignment of the text relative to (*x*, *y*).

verticalalignment, va : {'top', 'center', 'bottom', 'baseline'}, \
default: 'top'
verticalalignment, va : {{'top', 'center', 'bottom', 'baseline'}}, \
default: {va}
The vertical alignment of the text relative to (*x*, *y*).

fontsize, size : default: :rc:`figure.titlesize`
Expand All @@ -401,8 +402,8 @@ def suptitle(self, t, **kwargs):

Returns
-------
`.Text`
The instance of the title.
text
The `.Text` instance of the {name}.

Other Parameters
----------------
Expand All @@ -415,19 +416,20 @@ def suptitle(self, t, **kwargs):
**kwargs
Additional kwargs are `matplotlib.text.Text` properties.

Examples
--------
>>> fig.suptitle('This is the figure title', fontsize=12)
"""

manual_position = ('x' in kwargs or 'y' in kwargs)
suplab = getattr(self, info['name'])

x = kwargs.pop('x', 0.5)
y = kwargs.pop('y', 0.98)
x = kwargs.pop('x', info['x0'])
y = kwargs.pop('y', info['y0'])

if 'horizontalalignment' not in kwargs and 'ha' not in kwargs:
kwargs['horizontalalignment'] = 'center'
kwargs['horizontalalignment'] = info['ha']
if 'verticalalignment' not in kwargs and 'va' not in kwargs:
kwargs['verticalalignment'] = 'top'
kwargs['verticalalignment'] = info['va']
if 'rotation' not in kwargs:
kwargs['rotation'] = info['rotation']

if 'fontproperties' not in kwargs:
if 'fontsize' not in kwargs and 'size' not in kwargs:
Expand All @@ -436,19 +438,46 @@ def suptitle(self, t, **kwargs):
kwargs['weight'] = mpl.rcParams['figure.titleweight']

sup = self.text(x, y, t, **kwargs)
if self._suptitle is not None:
self._suptitle.set_text(t)
self._suptitle.set_position((x, y))
self._suptitle.update_from(sup)
if suplab is not None:
suplab.set_text(t)
suplab.set_position((x, y))
suplab.update_from(sup)
sup.remove()
else:
self._suptitle = sup

suplab = sup
if manual_position:
self._suptitle.set_in_layout(False)

suplab.set_in_layout(False)
setattr(self, info['name'], suplab)
self.stale = True
return self._suptitle
return suplab

@docstring.Substitution(x0=0.5, y0=0.98, name='suptitle', ha='center',
va='top')
@docstring.copy(_suplabels)
def suptitle(self, t, **kwargs):
# docstring from _suplabels...
info = {'name': '_suptitle', 'x0': 0.5, 'y0': 0.98,
'ha': 'center', 'va': 'top', 'rotation': 0}
return self._suplabels(t, info, **kwargs)

@docstring.Substitution(x0=0.5, y0=0.01, name='supxlabel', ha='center',
va='bottom')
@docstring.copy(_suplabels)
def supxlabel(self, t, **kwargs):
# docstring from _suplabels...
info = {'name': '_supxlabel', 'x0': 0.5, 'y0': 0.01,
'ha': 'center', 'va': 'bottom', 'rotation': 0}
return self._suplabels(t, info, **kwargs)

@docstring.Substitution(x0=0.02, y0=0.5, name='supylabel', ha='left',
va='center')
@docstring.copy(_suplabels)
def supylabel(self, t, **kwargs):
# docstring from _suplabels...
info = {'name': '_supylabel', 'x0': 0.02, 'y0': 0.5,
'ha': 'left', 'va': 'center', 'rotation': 'vertical',
'rotation_mode': 'anchor'}
return self._suplabels(t, info, **kwargs)

def get_edgecolor(self):
"""Get the edge color of the Figure rectangle."""
Expand Down Expand Up @@ -2811,6 +2840,9 @@ def clf(self, keep_observers=False):
if not keep_observers:
self._axobservers = cbook.CallbackRegistry()
self._suptitle = None
self._supxlabel = None
self._supylabel = None

if self.get_constrained_layout():
self.init_layoutgrid()
self.stale = True
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 45 additions & 2 deletions lib/matplotlib/tests/test_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ def test_reused_gridspec():
savefig_kwarg={'facecolor': 'teal'},
remove_text=False)
def test_subfigure():
np.random.seed(19680808)
np.random.seed(19680801)
fig = plt.figure(constrained_layout=True)
sub = fig.subfigures(1, 2)

Expand All @@ -862,7 +862,7 @@ def test_subfigure():
remove_text=False)
def test_subfigure_ss():
# test assigning the subfigure via subplotspec
np.random.seed(19680808)
np.random.seed(19680801)
fig = plt.figure(constrained_layout=True)
gs = fig.add_gridspec(1, 2)

Expand All @@ -879,3 +879,46 @@ def test_subfigure_ss():
ax.set_title('Axes')

fig.suptitle('Figure suptitle', fontsize='xx-large')


@image_comparison(['test_subfigure_double.png'], style='mpl20',
savefig_kwarg={'facecolor': 'teal'},
remove_text=False)
def test_subfigure_double():
# test assigning the subfigure via subplotspec
np.random.seed(19680801)

fig = plt.figure(constrained_layout=True, figsize=(10, 8))

fig.suptitle('fig')

subfigs = fig.subfigures(1, 2, wspace=0.07)

subfigs[0].set_facecolor('coral')
subfigs[0].suptitle('subfigs[0]')

subfigs[1].set_facecolor('coral')
subfigs[1].suptitle('subfigs[1]')

subfigsnest = subfigs[0].subfigures(2, 1, height_ratios=[1, 1.4])
subfigsnest[0].suptitle('subfigsnest[0]')
subfigsnest[0].set_facecolor('r')
axsnest0 = subfigsnest[0].subplots(1, 2, sharey=True)
for ax in axsnest0:
fontsize = 12
pc = ax.pcolormesh(np.random.randn(30, 30), vmin=-2.5, vmax=2.5)
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_ylabel('y-label', fontsize=fontsize)
ax.set_title('Title', fontsize=fontsize)

subfigsnest[0].colorbar(pc, ax=axsnest0)

subfigsnest[1].suptitle('subfigsnest[1]')
subfigsnest[1].set_facecolor('g')
axsnest1 = subfigsnest[1].subplots(3, 1, sharex=True)
for nn, ax in enumerate(axsnest1):
ax.set_ylabel(f'ylabel{nn}')
subfigsnest[1].supxlabel('supxlabel')
subfigsnest[1].supylabel('supylabel')

axsRight = subfigs[1].subplots(2, 2)
20 changes: 15 additions & 5 deletions lib/matplotlib/tight_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,30 @@ def auto_adjust_subplotpars(
if not margin_left:
margin_left = (max(hspaces[:, 0].max(), 0)
+ pad_inches / fig_width_inch)
suplabel = fig._supylabel
if suplabel and suplabel.get_in_layout():
rel_width = fig.transFigure.inverted().transform_bbox(
suplabel.get_window_extent(renderer)).width
margin_left += rel_width + pad_inches / fig_width_inch

if not margin_right:
margin_right = (max(hspaces[:, -1].max(), 0)
+ pad_inches / fig_width_inch)
if not margin_top:
margin_top = (max(vspaces[0, :].max(), 0)
+ pad_inches / fig_height_inch)
suptitle = fig._suptitle
if suptitle and suptitle.get_in_layout():
rel_suptitle_height = fig.transFigure.inverted().transform_bbox(
suptitle.get_window_extent(renderer)).height
margin_top += rel_suptitle_height + pad_inches / fig_height_inch
if fig._suptitle and fig._suptitle.get_in_layout():
rel_height = fig.transFigure.inverted().transform_bbox(
fig._suptitle.get_window_extent(renderer)).height
margin_top += rel_height + pad_inches / fig_height_inch
if not margin_bottom:
margin_bottom = (max(vspaces[-1, :].max(), 0)
+ pad_inches / fig_height_inch)
suplabel = fig._supxlabel
if suplabel and suplabel.get_in_layout():
rel_height = fig.transFigure.inverted().transform_bbox(
suplabel.get_window_extent(renderer)).height
margin_bottom += rel_height + pad_inches / fig_height_inch

if margin_left + margin_right >= 1:
cbook._warn_external('Tight layout not applied. The left and right '
Expand Down