From 149e7fbf281541039b1b5a79d6efa7ab99f582f0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 15 Jul 2020 02:36:07 -0400 Subject: [PATCH 1/5] Fix errorbar property cycling to match plot. It would not cycle if a color were specified. However, this does not match `plot`, which does not advance the cycle only if _all_ properties in the cycle are specified. Notably, this means if your property cycle was for line style, specifying a color would ignore the cycle in `errorbar`, but not in `plot`. Fixes #7074. --- .../next_api_changes/behavior/17930-ES.rst | 8 ++++++ .../2020-07-15-errorbar-cycling.rst | 23 ++++++++++++++++ lib/matplotlib/axes/_axes.py | 11 +++----- .../test_axes/errorbar_with_prop_cycle.png | Bin 16795 -> 0 bytes lib/matplotlib/tests/test_axes.py | 25 +++++++++++++----- 5 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/17930-ES.rst create mode 100644 doc/users/next_whats_new/2020-07-15-errorbar-cycling.rst delete mode 100644 lib/matplotlib/tests/baseline_images/test_axes/errorbar_with_prop_cycle.png diff --git a/doc/api/next_api_changes/behavior/17930-ES.rst b/doc/api/next_api_changes/behavior/17930-ES.rst new file mode 100644 index 000000000000..c42b95b6f52f --- /dev/null +++ b/doc/api/next_api_changes/behavior/17930-ES.rst @@ -0,0 +1,8 @@ +``Axes.errorbar`` cycles non-color properties correctly +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Formerly, `.Axes.errorbar` incorrectly skipped the Axes property cycle if a +color was explicitly specified, even if the property cycler was for other +properties (such as line style). Now, `.Axes.errorbar` will advance the Axes +property cycle as done for `.Axes.plot`, i.e., as long as all properties in the +cycler are not explicitly passed. diff --git a/doc/users/next_whats_new/2020-07-15-errorbar-cycling.rst b/doc/users/next_whats_new/2020-07-15-errorbar-cycling.rst new file mode 100644 index 000000000000..23d3e327da0a --- /dev/null +++ b/doc/users/next_whats_new/2020-07-15-errorbar-cycling.rst @@ -0,0 +1,23 @@ +``Axes.errorbar`` cycles non-color properties correctly +------------------------------------------------------- + +Formerly, `.Axes.errorbar` incorrectly skipped the Axes property cycle if a +color was explicitly specified, even if the property cycler was for other +properties (such as line style). Now, `.Axes.errorbar` will advance the Axes +property cycle as done for `.Axes.plot`, i.e., as long as all properties in the +cycler are not explicitly passed. + +For example, the following will cycle through the line styles: + +.. plot:: + :include-source: True + + x = np.arange(0.1, 4, 0.5) + y = np.exp(-x) + offsets = [0, 1] + + plt.rcParams['axes.prop_cycle'] = plt.cycler('linestyle', ['-', '--']) + + fig, ax = plt.subplots() + for offset in offsets: + ax.errorbar(x, y + offset, xerr=0.1, yerr=0.3, fmt='tab:blue') diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index af3f5f738995..c0c22fb32ae4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3329,13 +3329,10 @@ def errorbar(self, x, y, yerr=None, xerr=None, # Remove alpha=0 color that _process_plot_format returns fmt_style_kwargs.pop('color') - if ('color' in kwargs or 'color' in fmt_style_kwargs): - base_style = {} - if 'color' in kwargs: - base_style['color'] = kwargs.pop('color') - else: - base_style = next(self._get_lines.prop_cycler) - + base_style = self._get_lines._getdefaults( + set(), {**fmt_style_kwargs, **kwargs}) + if 'color' in kwargs: + base_style['color'] = kwargs.pop('color') base_style['label'] = '_nolegend_' base_style.update(fmt_style_kwargs) if 'color' not in base_style: diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_with_prop_cycle.png b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_with_prop_cycle.png deleted file mode 100644 index c6852c150ebedbbac75ac5ed95f662b50ae32c29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16795 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 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 45fdcf5d6182..898539929f12 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3147,14 +3147,25 @@ def test_errobar_nonefmt(): assert np.all(errbar.get_color() == mcolors.to_rgba('C0')) -@image_comparison(['errorbar_with_prop_cycle.png'], - style='mpl20', remove_text=True) -def test_errorbar_with_prop_cycle(): - _cycle = cycler(ls=['--', ':'], marker=['s', 's'], mfc=['k', 'w']) +@check_figures_equal(extensions=['png']) +def test_errorbar_with_prop_cycle(fig_test, fig_ref): + ax = fig_ref.subplots() + ax.errorbar(x=[2, 4, 10], y=[0, 1, 2], yerr=0.5, + ls='--', marker='s', mfc='k') + ax.errorbar(x=[2, 4, 10], y=[2, 3, 4], yerr=0.5, color='tab:green', + ls=':', marker='s', mfc='y') + ax.errorbar(x=[2, 4, 10], y=[4, 5, 6], yerr=0.5, fmt='tab:blue', + ls='-.', marker='o', mfc='c') + ax.set_xlim(1, 11) + + _cycle = cycler(ls=['--', ':', '-.'], marker=['s', 's', 'o'], + mfc=['k', 'y', 'c'], color=['b', 'g', 'r']) plt.rc("axes", prop_cycle=_cycle) - fig, ax = plt.subplots() - ax.errorbar(x=[2, 4, 10], y=[3, 2, 4], yerr=0.5) - ax.errorbar(x=[2, 4, 10], y=[6, 4, 2], yerr=0.5) + ax = fig_test.subplots() + ax.errorbar(x=[2, 4, 10], y=[0, 1, 2], yerr=0.5) + ax.errorbar(x=[2, 4, 10], y=[2, 3, 4], yerr=0.5, color='tab:green') + ax.errorbar(x=[2, 4, 10], y=[4, 5, 6], yerr=0.5, fmt='tab:blue') + ax.set_xlim(1, 11) @check_figures_equal() From 0543e9d41aaf595339aff18fef66f326e93d88f6 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 18 Jul 2020 01:27:11 -0400 Subject: [PATCH 2/5] Re-group some bits of errorbar. Move iterable checks before working on styling. In the styling section, the lines and caps dictionary modifications are intermixed, making them a bit confusing. Also do the same marker style removal from the cap style, which would normally be replaced later. --- lib/matplotlib/axes/_axes.py | 56 ++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c0c22fb32ae4..f8bb47b4d7a4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3315,6 +3315,25 @@ def errorbar(self, x, y, yerr=None, xerr=None, self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs) + # Make sure all the args are iterable; use lists not arrays to preserve + # units. + if not np.iterable(x): + x = [x] + + if not np.iterable(y): + y = [y] + + if len(x) != len(y): + raise ValueError("'x' and 'y' must have the same size") + + if xerr is not None: + if not np.iterable(xerr): + xerr = [xerr] * len(x) + + if yerr is not None: + if not np.iterable(yerr): + yerr = [yerr] * len(y) + plot_line = (fmt.lower() != 'none') label = kwargs.pop("label", None) @@ -3339,24 +3358,6 @@ def errorbar(self, x, y, yerr=None, xerr=None, base_style['color'] = 'C0' if ecolor is None: ecolor = base_style['color'] - # make sure all the args are iterable; use lists not arrays to - # preserve units - if not np.iterable(x): - x = [x] - - if not np.iterable(y): - y = [y] - - if len(x) != len(y): - raise ValueError("'x' and 'y' must have the same size") - - if xerr is not None: - if not np.iterable(xerr): - xerr = [xerr] * len(x) - - if yerr is not None: - if not np.iterable(yerr): - yerr = [yerr] * len(y) # make the style dict for the 'normal' plot line plot_line_style = { @@ -3366,9 +3367,14 @@ def errorbar(self, x, y, yerr=None, xerr=None, kwargs['zorder'] + .1), } - # make the style dict for the line collections (the bars) + # Make the style dict for the line collections (the bars), ejecting any + # marker information from format string. eb_lines_style = dict(base_style) eb_lines_style.pop('marker', None) + eb_lines_style.pop('markersize', None) + eb_lines_style.pop('markerfacecolor', None) + eb_lines_style.pop('markeredgewidth', None) + eb_lines_style.pop('markeredgecolor', None) eb_lines_style.pop('linestyle', None) eb_lines_style['color'] = ecolor @@ -3381,14 +3387,14 @@ def errorbar(self, x, y, yerr=None, xerr=None, if key in kwargs: eb_lines_style[key] = kwargs[key] - # set up cap style dictionary + # Make the style dict for the caps, ejecting any marker information + # from format string. eb_cap_style = dict(base_style) - # eject any marker information from format string eb_cap_style.pop('marker', None) - eb_lines_style.pop('markerfacecolor', None) - eb_lines_style.pop('markeredgewidth', None) - eb_lines_style.pop('markeredgecolor', None) - eb_cap_style.pop('ls', None) + eb_cap_style.pop('markersize', None) + eb_cap_style.pop('markerfacecolor', None) + eb_cap_style.pop('markeredgewidth', None) + eb_cap_style.pop('markeredgecolor', None) eb_cap_style['linestyle'] = 'none' if capsize is None: capsize = rcParams["errorbar.capsize"] From defa63c3be91eabeb6dcb881686373b842d7bc35 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 18 Jul 2020 01:55:24 -0400 Subject: [PATCH 3/5] Return used kwargs from _process_plot_var_args._plot_args. --- lib/matplotlib/axes/_base.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 282517cba817..cd4ff5edd27b 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -310,7 +310,7 @@ def _makeline(self, x, y, kw, kwargs): default_dict = self._getdefaults(set(), kw) self._setdefaults(default_dict, kw) seg = mlines.Line2D(x, y, **kw) - return seg + return seg, kw def _makefill(self, x, y, kw, kwargs): # Polygon doesn't directly support unitized inputs. @@ -362,9 +362,9 @@ def _makefill(self, x, y, kw, kwargs): fill=kwargs.get('fill', True), closed=kw['closed']) seg.set(**kwargs) - return seg + return seg, kwargs - def _plot_args(self, tup, kwargs): + def _plot_args(self, tup, kwargs, return_kwargs=False): if len(tup) > 1 and isinstance(tup[-1], str): linestyle, marker, color = _process_plot_format(tup[-1]) tup = tup[:-1] @@ -415,8 +415,12 @@ def _plot_args(self, tup, kwargs): ncx, ncy = x.shape[1], y.shape[1] if ncx > 1 and ncy > 1 and ncx != ncy: raise ValueError(f"x has {ncx} columns but y has {ncy} columns") - return [func(x[:, j % ncx], y[:, j % ncy], kw, kwargs) - for j in range(max(ncx, ncy))] + result = (func(x[:, j % ncx], y[:, j % ncy], kw, kwargs) + for j in range(max(ncx, ncy))) + if return_kwargs: + return list(result) + else: + return [l[0] for l in result] @cbook._define_aliases({"facecolor": ["fc"]}) From 0782c743c0823bc823c16c3d66cea0d42df5fe93 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 18 Jul 2020 03:11:20 -0400 Subject: [PATCH 4/5] Make errorbar's main Line2D closer to plot. We can't directly use `self.plot` or `self._get_lines` because they would do `self._process_unit_info` and/or *data* keyword argument processing. --- lib/matplotlib/axes/_axes.py | 55 ++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f8bb47b4d7a4..a1ac62b80c3d 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3334,39 +3334,39 @@ def errorbar(self, x, y, yerr=None, xerr=None, if not np.iterable(yerr): yerr = [yerr] * len(y) - plot_line = (fmt.lower() != 'none') label = kwargs.pop("label", None) + kwargs['label'] = '_nolegend_' + + # Create the main line and determine overall kwargs for child artists. + # We avoid calling self.plot() directly, or self._get_lines(), because + # that would call self._process_unit_info again, and do other indirect + # data processing. + (data_line, base_style), = self._get_lines._plot_args( + (x, y) if fmt == '' else (x, y, fmt), kwargs, return_kwargs=True) + + # Do this after creating `data_line` to avoid modifying `base_style`. + if barsabove: + data_line.set_zorder(kwargs['zorder'] - .1) + else: + data_line.set_zorder(kwargs['zorder'] + .1) - if fmt == '': - fmt_style_kwargs = {} + # Add line to plot, or throw it away and use it to determine kwargs. + if fmt.lower() != 'none': + self.add_line(data_line) else: - fmt_style_kwargs = {k: v for k, v in - zip(('linestyle', 'marker', 'color'), - _process_plot_format(fmt)) - if v is not None} - if fmt == 'none': - # Remove alpha=0 color that _process_plot_format returns - fmt_style_kwargs.pop('color') - - base_style = self._get_lines._getdefaults( - set(), {**fmt_style_kwargs, **kwargs}) - if 'color' in kwargs: - base_style['color'] = kwargs.pop('color') - base_style['label'] = '_nolegend_' - base_style.update(fmt_style_kwargs) + data_line = None + # Remove alpha=0 color that _get_lines._plot_args returns for + # 'none' format, and replace it with user-specified color, if + # supplied. + base_style.pop('color') + if 'color' in kwargs: + base_style['color'] = kwargs.pop('color') + if 'color' not in base_style: base_style['color'] = 'C0' if ecolor is None: ecolor = base_style['color'] - # make the style dict for the 'normal' plot line - plot_line_style = { - **base_style, - **kwargs, - 'zorder': (kwargs['zorder'] - .1 if barsabove else - kwargs['zorder'] + .1), - } - # Make the style dict for the line collections (the bars), ejecting any # marker information from format string. eb_lines_style = dict(base_style) @@ -3411,11 +3411,6 @@ def errorbar(self, x, y, yerr=None, xerr=None, eb_cap_style[key] = kwargs[key] eb_cap_style['color'] = ecolor - data_line = None - if plot_line: - data_line = mlines.Line2D(x, y, **plot_line_style) - self.add_line(data_line) - barcols = [] caplines = [] From da8d510139667f56ceb3d1e67cf472901e007604 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 14 Aug 2020 21:28:01 -0400 Subject: [PATCH 5/5] Simplify creating style dict for errorbar bars/caps. --- lib/matplotlib/axes/_axes.py | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index a1ac62b80c3d..dfe7db4ea318 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3367,16 +3367,17 @@ def errorbar(self, x, y, yerr=None, xerr=None, if ecolor is None: ecolor = base_style['color'] - # Make the style dict for the line collections (the bars), ejecting any - # marker information from format string. - eb_lines_style = dict(base_style) - eb_lines_style.pop('marker', None) - eb_lines_style.pop('markersize', None) - eb_lines_style.pop('markerfacecolor', None) - eb_lines_style.pop('markeredgewidth', None) - eb_lines_style.pop('markeredgecolor', None) - eb_lines_style.pop('linestyle', None) - eb_lines_style['color'] = ecolor + # Eject any marker information from line format string, as it's not + # needed for bars or caps. + base_style.pop('marker', None) + base_style.pop('markersize', None) + base_style.pop('markerfacecolor', None) + base_style.pop('markeredgewidth', None) + base_style.pop('markeredgecolor', None) + base_style.pop('linestyle', None) + + # Make the style dict for the line collections (the bars). + eb_lines_style = {**base_style, 'color': ecolor} if elinewidth: eb_lines_style['linewidth'] = elinewidth @@ -3387,15 +3388,8 @@ def errorbar(self, x, y, yerr=None, xerr=None, if key in kwargs: eb_lines_style[key] = kwargs[key] - # Make the style dict for the caps, ejecting any marker information - # from format string. - eb_cap_style = dict(base_style) - eb_cap_style.pop('marker', None) - eb_cap_style.pop('markersize', None) - eb_cap_style.pop('markerfacecolor', None) - eb_cap_style.pop('markeredgewidth', None) - eb_cap_style.pop('markeredgecolor', None) - eb_cap_style['linestyle'] = 'none' + # Make the style dict for the caps. + eb_cap_style = {**base_style, 'linestyle': 'none'} if capsize is None: capsize = rcParams["errorbar.capsize"] if capsize > 0: