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

Skip to content

Fix errorbar property cycling to match plot. #17930

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 5 commits into from
Aug 19, 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
8 changes: 8 additions & 0 deletions doc/api/next_api_changes/behavior/17930-ES.rst
Original file line number Diff line number Diff line change
@@ -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.
23 changes: 23 additions & 0 deletions doc/users/next_whats_new/2020-07-15-errorbar-cycling.rst
Original file line number Diff line number Diff line change
@@ -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')
102 changes: 47 additions & 55 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3315,35 +3315,8 @@ def errorbar(self, x, y, yerr=None, xerr=None,

self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs)

plot_line = (fmt.lower() != 'none')
label = kwargs.pop("label", None)

if fmt == '':
fmt_style_kwargs = {}
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')

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['label'] = '_nolegend_'
base_style.update(fmt_style_kwargs)
if 'color' not in base_style:
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
# Make sure all the args are iterable; use lists not arrays to preserve
# units.
if not np.iterable(x):
x = [x]

Expand All @@ -3361,19 +3334,50 @@ def errorbar(self, x, y, yerr=None, xerr=None,
if not np.iterable(yerr):
yerr = [yerr] * len(y)

# 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),
}
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)

# Add line to plot, or throw it away and use it to determine kwargs.
if fmt.lower() != 'none':
self.add_line(data_line)
else:
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 line collections (the bars)
eb_lines_style = dict(base_style)
eb_lines_style.pop('marker', 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
Expand All @@ -3384,15 +3388,8 @@ def errorbar(self, x, y, yerr=None, xerr=None,
if key in kwargs:
eb_lines_style[key] = kwargs[key]

# set up cap style dictionary
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['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:
Expand All @@ -3408,11 +3405,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 = []

Expand Down
14 changes: 9 additions & 5 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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"]})
Expand Down
Binary file not shown.
25 changes: 18 additions & 7 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down