From 6ad9906c3add42da014c06043ca862a729500f90 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Mon, 20 Jan 2020 18:25:29 +0100 Subject: [PATCH] Enable set_{x|y|}label(loc={'left','right','center','top','bottom'}) --- .../next_whats_new/2019-12-20-set_xy_position | 7 +++ .../axis_labels_demo.py | 20 +++++++ lib/matplotlib/axes/_axes.py | 46 +++++++++++++++- lib/matplotlib/colorbar.py | 25 ++++++++- lib/matplotlib/pyplot.py | 10 ++-- lib/matplotlib/rcsetup.py | 12 ++++- lib/matplotlib/tests/test_axes.py | 53 +++++++++++++++++++ matplotlibrc.template | 6 +++ 8 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 doc/users/next_whats_new/2019-12-20-set_xy_position create mode 100644 examples/subplots_axes_and_figures/axis_labels_demo.py diff --git a/doc/users/next_whats_new/2019-12-20-set_xy_position b/doc/users/next_whats_new/2019-12-20-set_xy_position new file mode 100644 index 000000000000..ecaa7633e6b0 --- /dev/null +++ b/doc/users/next_whats_new/2019-12-20-set_xy_position @@ -0,0 +1,7 @@ +Align labels to axes edges +-------------------------- +`~.axes.Axes.set_xlabel`, `~.axes.Axes.set_ylabel` and `ColorbarBase.set_label` +support a parameter ``loc`` for simplified positioning. Supported values are +'left', 'center', 'right' The default is controlled via :rc:`xaxis.labelposition` +and :rc:`yaxis.labelposition`; the Colorbar label takes the rcParam based on its +orientation. diff --git a/examples/subplots_axes_and_figures/axis_labels_demo.py b/examples/subplots_axes_and_figures/axis_labels_demo.py new file mode 100644 index 000000000000..8b9d38240e42 --- /dev/null +++ b/examples/subplots_axes_and_figures/axis_labels_demo.py @@ -0,0 +1,20 @@ +""" +=================== +Axis Label Position +=================== + +Choose axis label position when calling `~.Axes.set_xlabel` and +`~.Axes.set_ylabel` as well as for colorbar. + +""" +import matplotlib.pyplot as plt + +fig, ax = plt.subplots() + +sc = ax.scatter([1, 2], [1, 2], c=[1, 2]) +ax.set_ylabel('YLabel', loc='top') +ax.set_xlabel('XLabel', loc='left') +cbar = fig.colorbar(sc) +cbar.set_label("ZLabel", loc='top') + +plt.show() diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4537abe60b9f..9b38fcac305b 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -188,7 +188,8 @@ def get_xlabel(self): label = self.xaxis.get_label() return label.get_text() - def set_xlabel(self, xlabel, fontdict=None, labelpad=None, **kwargs): + def set_xlabel(self, xlabel, fontdict=None, labelpad=None, *, + loc=None, **kwargs): """ Set the label for the x-axis. @@ -201,6 +202,10 @@ def set_xlabel(self, xlabel, fontdict=None, labelpad=None, **kwargs): Spacing in points from the axes bounding box including ticks and tick labels. + loc : {'left', 'center', 'right'}, default: :rc:`xaxis.labellocation` + The label position. This is a high-level alternative for passing + parameters *x* and *horizonatalalignment*. + Other Parameters ---------------- **kwargs : `.Text` properties @@ -212,6 +217,22 @@ def set_xlabel(self, xlabel, fontdict=None, labelpad=None, **kwargs): """ if labelpad is not None: self.xaxis.labelpad = labelpad + _protected_kw = ['x', 'horizontalalignment', 'ha'] + if any([k in kwargs for k in _protected_kw]): + if loc is not None: + raise TypeError('Specifying *loc* is disallowed when any of ' + 'its corresponding low level kwargs {} ' + 'are supplied as well.'.format(_protected_kw)) + loc = 'center' + else: + loc = loc if loc is not None else rcParams['xaxis.labellocation'] + cbook._check_in_list(('left', 'center', 'right'), loc=loc) + if loc == 'right': + kwargs['x'] = 1. + kwargs['horizontalalignment'] = 'right' + elif loc == 'left': + kwargs['x'] = 0. + kwargs['horizontalalignment'] = 'left' return self.xaxis.set_label_text(xlabel, fontdict, **kwargs) def get_ylabel(self): @@ -221,7 +242,8 @@ def get_ylabel(self): label = self.yaxis.get_label() return label.get_text() - def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs): + def set_ylabel(self, ylabel, fontdict=None, labelpad=None, *, + loc=None, **kwargs): """ Set the label for the y-axis. @@ -234,6 +256,10 @@ def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs): Spacing in points from the axes bounding box including ticks and tick labels. + loc : {'bottom', 'center', 'top'}, default: :rc:`yaxis.labellocation` + The label position. This is a high-level alternative for passing + parameters *y* and *horizonatalalignment*. + Other Parameters ---------------- **kwargs : `.Text` properties @@ -246,6 +272,22 @@ def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs): """ if labelpad is not None: self.yaxis.labelpad = labelpad + _protected_kw = ['y', 'horizontalalignment', 'ha'] + if any([k in kwargs for k in _protected_kw]): + if loc is not None: + raise TypeError('Specifying *loc* is disallowed when any of ' + 'its corresponding low level kwargs {} ' + 'are supplied as well.'.format(_protected_kw)) + loc = 'center' + else: + loc = loc if loc is not None else rcParams['yaxis.labellocation'] + cbook._check_in_list(('bottom', 'center', 'top'), loc=loc) + if loc == 'top': + kwargs['y'] = 1. + kwargs['horizontalalignment'] = 'right' + elif loc == 'bottom': + kwargs['y'] = 0. + kwargs['horizontalalignment'] = 'left' return self.yaxis.set_label_text(ylabel, fontdict, **kwargs) def get_legend_handles_labels(self, legend_handler_map=None): diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 34da5fc7ca6b..72f30723ae64 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -739,10 +739,31 @@ def _set_label(self): self.ax.set_xlabel(self._label, **self._labelkw) self.stale = True - def set_label(self, label, **kw): + def set_label(self, label, *, loc=None, **kwargs): """Add a label to the long axis of the colorbar.""" + _pos_xy = 'y' if self.orientation == 'vertical' else 'x' + _protected_kw = [_pos_xy, 'horizontalalignment', 'ha'] + if any([k in kwargs for k in _protected_kw]): + if loc is not None: + raise TypeError('Specifying *loc* is disallowed when any of ' + 'its corresponding low level kwargs {} ' + 'are supplied as well.'.format(_protected_kw)) + loc = 'center' + else: + if loc is None: + loc = mpl.rcParams['%saxis.labellocation' % _pos_xy] + if self.orientation == 'vertical': + cbook._check_in_list(('bottom', 'center', 'top'), loc=loc) + else: + cbook._check_in_list(('left', 'center', 'right'), loc=loc) + if loc in ['right', 'top']: + kwargs[_pos_xy] = 1. + kwargs['horizontalalignment'] = 'right' + elif loc in ['left', 'bottom']: + kwargs[_pos_xy] = 0. + kwargs['horizontalalignment'] = 'left' self._label = label - self._labelkw = kw + self._labelkw = kwargs self._set_label() def _outline(self, X, Y): diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 4606ba540800..2a33d74353ff 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2855,16 +2855,18 @@ def title(label, fontdict=None, loc=None, pad=None, **kwargs): # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @docstring.copy(Axes.set_xlabel) -def xlabel(xlabel, fontdict=None, labelpad=None, **kwargs): +def xlabel(xlabel, fontdict=None, labelpad=None, *, loc=None, **kwargs): return gca().set_xlabel( - xlabel, fontdict=fontdict, labelpad=labelpad, **kwargs) + xlabel, fontdict=fontdict, labelpad=labelpad, loc=loc, + **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @docstring.copy(Axes.set_ylabel) -def ylabel(ylabel, fontdict=None, labelpad=None, **kwargs): +def ylabel(ylabel, fontdict=None, labelpad=None, *, loc=None, **kwargs): return gca().set_ylabel( - ylabel, fontdict=fontdict, labelpad=labelpad, **kwargs) + ylabel, fontdict=fontdict, labelpad=labelpad, loc=loc, + **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 0aa45817de0f..7e94c07363ea 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -993,7 +993,13 @@ def validate_webagg_address(s): raise ValueError("'webagg.address' is not a valid IP address") -validate_axes_titlelocation = ValidateInStrings('axes.titlelocation', ['left', 'center', 'right']) +validate_axes_titlelocation = ValidateInStrings('axes.titlelocation', + ['left', 'center', 'right']) +_validate_xaxis_labellocation = ValidateInStrings('xaxis.labellocation', + ['left', 'center', 'right']) +_validate_yaxis_labellocation = ValidateInStrings('yaxis.labellocation', + ['bottom', 'center', 'top']) + # a map from key -> value, converter defaultParams = { @@ -1159,6 +1165,10 @@ def validate_webagg_address(s): # errorbar props 'errorbar.capsize': [0, validate_float], + # axis props + 'xaxis.labellocation': ['center', _validate_xaxis_labellocation], # alignment of x axis title + 'yaxis.labellocation': ['center', _validate_yaxis_labellocation], # alignment of y axis title + # axes props 'axes.axisbelow': ['line', validate_axisbelow], 'axes.facecolor': ['white', validate_color], # background color diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a21c80ac1fd8..8651076db771 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -42,6 +42,59 @@ def test_get_labels(): assert ax.get_ylabel() == 'y label' +@check_figures_equal() +def test_label_loc_vertical(fig_test, fig_ref): + ax = fig_test.subplots() + sc = ax.scatter([1, 2], [1, 2], c=[1, 2]) + ax.set_ylabel('Y Label', loc='top') + ax.set_xlabel('X Label', loc='right') + cbar = fig_test.colorbar(sc) + cbar.set_label("Z Label", loc='top') + + ax = fig_ref.subplots() + sc = ax.scatter([1, 2], [1, 2], c=[1, 2]) + ax.set_ylabel('Y Label', y=1, ha='right') + ax.set_xlabel('X Label', x=1, ha='right') + cbar = fig_ref.colorbar(sc) + cbar.set_label("Z Label", y=1, ha='right') + + +@check_figures_equal() +def test_label_loc_horizontal(fig_test, fig_ref): + ax = fig_test.subplots() + sc = ax.scatter([1, 2], [1, 2], c=[1, 2]) + ax.set_ylabel('Y Label', loc='bottom') + ax.set_xlabel('X Label', loc='left') + cbar = fig_test.colorbar(sc, orientation='horizontal') + cbar.set_label("Z Label", loc='left') + + ax = fig_ref.subplots() + sc = ax.scatter([1, 2], [1, 2], c=[1, 2]) + ax.set_ylabel('Y Label', y=0, ha='left') + ax.set_xlabel('X Label', x=0, ha='left') + cbar = fig_ref.colorbar(sc, orientation='horizontal') + cbar.set_label("Z Label", x=0, ha='left') + + +@check_figures_equal() +def test_label_loc_rc(fig_test, fig_ref): + with matplotlib.rc_context({"xaxis.labellocation": "right", + "yaxis.labellocation": "top"}): + ax = fig_test.subplots() + sc = ax.scatter([1, 2], [1, 2], c=[1, 2]) + ax.set_ylabel('Y Label') + ax.set_xlabel('X Label') + cbar = fig_test.colorbar(sc, orientation='horizontal') + cbar.set_label("Z Label") + + ax = fig_ref.subplots() + sc = ax.scatter([1, 2], [1, 2], c=[1, 2]) + ax.set_ylabel('Y Label', y=1, ha='right') + ax.set_xlabel('X Label', x=1, ha='right') + cbar = fig_ref.colorbar(sc, orientation='horizontal') + cbar.set_label("Z Label", x=1, ha='right') + + @image_comparison(['acorr.png'], style='mpl20') def test_acorr(): # Remove this line when this test image is regenerated. diff --git a/matplotlibrc.template b/matplotlibrc.template index 559a40a227cd..dba5d6160cdb 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -415,6 +415,12 @@ #polaraxes.grid : True ## display grid on polar axes #axes3d.grid : True ## display grid on 3d axes +## *************************************************************************** +## * AXIS * +## *************************************************************************** +#xaxis.labellocation : center ## alignment of the xaxis label: {left, right, center} +#yaxis.labellocation : center ## alignment of the yaxis label: {bottom, top, center} + ## *************************************************************************** ## * DATES *