From 541563848a0a3dc728ff51bfd3215d923df847fb Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Wed, 22 Apr 2020 13:35:05 -0700 Subject: [PATCH 01/12] Apply patch for errorbar order and add additional test --- lib/matplotlib/axes/_axes.py | 16 ++++++----- lib/matplotlib/tests/test_axes.py | 45 +++++++++++++++++++++++++++++-- setup.cfg.template | 37 ------------------------- 3 files changed, 53 insertions(+), 45 deletions(-) delete mode 100644 setup.cfg.template diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 35fd2b0ce890..b86907c301a8 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3239,7 +3239,6 @@ def errorbar(self, x, y, yerr=None, xerr=None, # anything that comes in as 'None', drop so the default thing # happens down stream kwargs = {k: v for k, v in kwargs.items() if v is not None} - kwargs.setdefault('zorder', 2) try: offset, errorevery = errorevery @@ -3301,8 +3300,6 @@ def errorbar(self, x, y, yerr=None, xerr=None, 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) @@ -3347,7 +3344,6 @@ def errorbar(self, x, y, yerr=None, xerr=None, data_line = None if plot_line: data_line = mlines.Line2D(x, y, **plot_line_style) - self.add_line(data_line) barcols = [] caplines = [] @@ -3505,8 +3501,16 @@ def extract_err(err, data): xup, yup = xywhere(x, y, uplims & everymask) caplines.append(mlines.Line2D(xup, yup, marker='_', **eb_cap_style)) - for l in caplines: - self.add_line(l) + if barsabove: + if data_line is not None: + self.add_line(data_line) + for l in caplines: + self.add_line(l) + else: + for l in caplines: + self.add_line(l) + if data_line is not None: + self.add_line(data_line) self._request_autoscale_view() errorbar_container = ErrorbarContainer((data_line, tuple(caplines), diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 828cc1438be7..1dfbc4d3e17d 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3296,12 +3296,53 @@ def test_errorbar_offsets(fig_test, fig_ref): capsize=4, c=color) # Using manual errorbars - # n.b. errorbar draws the main plot at z=2.1 by default - ax_ref.plot(x, y, c=color, zorder=2.1) + ax_ref.plot(x, y, c=color) ax_ref.errorbar(x[shift::4], y[shift::4], yerr[shift::4], capsize=4, c=color, fmt='none') +@check_figures_equal() +def test_errorbar_default_order(fig_test, fig_ref): + # Now test default order of errorbars and plots. + x = list(range(5)) + y1 = np.full(5, -5) + y2 = np.full(5, 0) + y3 = np.full(5, 5) + + yerr = list(range(5)) + + ax_ref = fig_ref.subplots() + ax_ref.plot(x, y1, 'ro', markersize=60, zorder=2.12) + ax_ref.errorbar(x, y1, yerr=yerr, fmt='none', ecolor='k', zorder=2.13) + ax_ref.plot(x, y1, 'b.', markersize=20, zorder=2.15) + + ax_ref.plot(x, y2, 'ro', markersize=60, zorder=2.22) + ax_ref.errorbar(x, y2, yerr=yerr, fmt='none', ecolor='k', zorder=2.23) + ax_ref.plot(x, y2, 'gs', markersize=40, zorder=2.24) + ax_ref.plot(x, y2, 'b.', markersize=20, zorder=2.25) + + ax_ref.plot(x, y3, 'ro', markersize=60, zorder=2.32) + ax_ref.plot(x, y3, 'gs', markersize=40, zorder=2.33) + ax_ref.errorbar(x, y3, yerr=yerr, fmt='none', ecolor='k', zorder=2.34) + ax_ref.plot(x, y3, 'b.', markersize=20, zorder=2.35) + + ax_test = fig_test.subplots() + + ax_test.plot(x, y1, 'ro', markersize=60) + ax_test.errorbar(x, y1, yerr=yerr, fmt='none', ecolor='k') + ax_test.plot(x, y1, 'b.', markersize=20) + + ax_test.plot(x, y2, 'ro', markersize=60) + ax_test.errorbar(x, y2, yerr=yerr, fmt='gs', ecolor='k', markersize=40) + ax_test.plot(x, y2, 'b.', markersize=20) + + ax_test.plot(x, y3, 'ro', markersize=60) + ax_test.errorbar(x, y3, yerr=yerr, fmt='gs', ecolor='k', markersize=40, barsabove=True) + ax_test.plot(x, y3, 'b.', markersize=20) + + ax_ref.set_title("errorbar default order test") + + @image_comparison(['hist_stacked_stepfilled', 'hist_stacked_stepfilled']) def test_hist_stacked_stepfilled(): # make some data diff --git a/setup.cfg.template b/setup.cfg.template deleted file mode 100644 index 562544fb1e59..000000000000 --- a/setup.cfg.template +++ /dev/null @@ -1,37 +0,0 @@ -# Rename this file to setup.cfg to modify Matplotlib's build options. - -[egg_info] - -[libs] -# By default, Matplotlib downloads and builds its own copy of FreeType, and -# builds its own copy of Qhull. You may set the following to True to instead -# link against a system FreeType/Qhull. -#system_freetype = False -#system_qhull = False - -[packages] -# There are a number of data subpackages from Matplotlib that are -# considered optional. All except 'tests' data (meaning the baseline -# image files) are installed by default, but that can be changed here. -#tests = False -#sample_data = True - -[gui_support] -# Matplotlib supports multiple GUI toolkits, known as backends. -# The MacOSX backend requires the Cocoa headers included with XCode. -# You can select whether to build it by uncommenting the following line. -# It is never built on Linux or Windows, regardless of the config value. -# -#macosx = True - -[rc_options] -# User-configurable options -# -# Default backend, one of: Agg, Cairo, GTK3Agg, GTK3Cairo, MacOSX, Pdf, Ps, -# Qt4Agg, Qt5Agg, SVG, TkAgg, WX, WXAgg. -# -# The Agg, Ps, Pdf and SVG backends do not require external dependencies. Do -# not choose MacOSX if you have disabled the relevant extension modules. The -# default is determined by fallback. -# -#backend = Agg From 2e54004e39363359235aee22c2a14a4f2b1cee58 Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Wed, 22 Apr 2020 15:06:37 -0700 Subject: [PATCH 02/12] Draw error bar columns as Lines2D --- lib/matplotlib/axes/_axes.py | 29 +++++++++++++++++++++++------ lib/matplotlib/tests/test_axes.py | 6 ++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b86907c301a8..c123afd2d6d0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3307,6 +3307,7 @@ def errorbar(self, x, y, yerr=None, xerr=None, eb_lines_style.pop('marker', None) eb_lines_style.pop('linestyle', None) eb_lines_style['color'] = ecolor + eb_lines_style.setdefault('zorder', 2.0) if elinewidth: eb_lines_style['linewidth'] = elinewidth @@ -3412,7 +3413,9 @@ def extract_err(err, data): if noxlims.any() or len(noxlims) == 0: yo, _ = xywhere(y, right, noxlims & everymask) lo, ro = xywhere(left, right, noxlims & everymask) - barcols.append(self.hlines(yo, lo, ro, **eb_lines_style)) + for yoo, loo, roo in zip(yo, lo, ro): + barcols.append(mlines.Line2D([loo, roo], [yoo, yoo], + **eb_lines_style)) if capsize > 0: caplines.append(mlines.Line2D(lo, yo, marker='|', **eb_cap_style)) @@ -3422,7 +3425,9 @@ def extract_err(err, data): if xlolims.any(): yo, _ = xywhere(y, right, xlolims & everymask) lo, ro = xywhere(x, right, xlolims & everymask) - barcols.append(self.hlines(yo, lo, ro, **eb_lines_style)) + for yoo, loo, roo in zip(yo, lo, ro): + barcols.append(mlines.Line2D([loo, roo], [yoo, yoo], + **eb_lines_style)) rightup, yup = xywhere(right, y, xlolims & everymask) if self.xaxis_inverted(): marker = mlines.CARETLEFTBASE @@ -3439,7 +3444,9 @@ def extract_err(err, data): if xuplims.any(): yo, _ = xywhere(y, right, xuplims & everymask) lo, ro = xywhere(left, x, xuplims & everymask) - barcols.append(self.hlines(yo, lo, ro, **eb_lines_style)) + for yoo, loo, roo in zip(yo, lo, ro): + barcols.append(mlines.Line2D([loo, roo], [yoo, yoo], + **eb_lines_style)) leftlo, ylo = xywhere(left, y, xuplims & everymask) if self.xaxis_inverted(): marker = mlines.CARETRIGHTBASE @@ -3461,7 +3468,9 @@ def extract_err(err, data): if noylims.any() or len(noylims) == 0: xo, _ = xywhere(x, lower, noylims & everymask) lo, uo = xywhere(lower, upper, noylims & everymask) - barcols.append(self.vlines(xo, lo, uo, **eb_lines_style)) + for xoo, loo, uoo in zip(xo, lo, uo): + barcols.append(mlines.Line2D([xoo, xoo], [loo, uoo], + **eb_lines_style)) if capsize > 0: caplines.append(mlines.Line2D(xo, lo, marker='_', **eb_cap_style)) @@ -3471,7 +3480,9 @@ def extract_err(err, data): if lolims.any(): xo, _ = xywhere(x, lower, lolims & everymask) lo, uo = xywhere(y, upper, lolims & everymask) - barcols.append(self.vlines(xo, lo, uo, **eb_lines_style)) + for xoo, loo, uoo in zip(xo, lo, uo): + barcols.append(mlines.Line2D([xoo, xoo], [loo, uoo], + **eb_lines_style)) xup, upperup = xywhere(x, upper, lolims & everymask) if self.yaxis_inverted(): marker = mlines.CARETDOWNBASE @@ -3488,7 +3499,9 @@ def extract_err(err, data): if uplims.any(): xo, _ = xywhere(x, lower, uplims & everymask) lo, uo = xywhere(lower, y, uplims & everymask) - barcols.append(self.vlines(xo, lo, uo, **eb_lines_style)) + for xoo, loo, uoo in zip(xo, lo, uo): + barcols.append(mlines.Line2D([xoo, xoo], [loo, uoo], + **eb_lines_style)) xlo, lowerlo = xywhere(x, lower, uplims & everymask) if self.yaxis_inverted(): marker = mlines.CARETUPBASE @@ -3504,11 +3517,15 @@ def extract_err(err, data): if barsabove: if data_line is not None: self.add_line(data_line) + for l in barcols: + self.add_line(l) for l in caplines: self.add_line(l) else: for l in caplines: self.add_line(l) + for l in barcols: + self.add_line(l) if data_line is not None: self.add_line(data_line) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 1dfbc4d3e17d..320671f86c99 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3308,7 +3308,7 @@ def test_errorbar_default_order(fig_test, fig_ref): y1 = np.full(5, -5) y2 = np.full(5, 0) y3 = np.full(5, 5) - + yerr = list(range(5)) ax_ref = fig_ref.subplots() @@ -3327,7 +3327,7 @@ def test_errorbar_default_order(fig_test, fig_ref): ax_ref.plot(x, y3, 'b.', markersize=20, zorder=2.35) ax_test = fig_test.subplots() - + ax_test.plot(x, y1, 'ro', markersize=60) ax_test.errorbar(x, y1, yerr=yerr, fmt='none', ecolor='k') ax_test.plot(x, y1, 'b.', markersize=20) @@ -3340,8 +3340,6 @@ def test_errorbar_default_order(fig_test, fig_ref): ax_test.errorbar(x, y3, yerr=yerr, fmt='gs', ecolor='k', markersize=40, barsabove=True) ax_test.plot(x, y3, 'b.', markersize=20) - ax_ref.set_title("errorbar default order test") - @image_comparison(['hist_stacked_stepfilled', 'hist_stacked_stepfilled']) def test_hist_stacked_stepfilled(): From 2831a37a2601f96a58606c1b43d44e611e830eb7 Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Wed, 22 Apr 2020 15:27:11 -0700 Subject: [PATCH 03/12] Fix wrong order of caps and barcols in errorbars --- lib/matplotlib/axes/_axes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c123afd2d6d0..4aa278ee58e1 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3522,10 +3522,10 @@ def extract_err(err, data): for l in caplines: self.add_line(l) else: - for l in caplines: - self.add_line(l) for l in barcols: self.add_line(l) + for l in caplines: + self.add_line(l) if data_line is not None: self.add_line(data_line) From fa83af8ff629966b4e3849954fdd0ca8dd965d25 Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Wed, 22 Apr 2020 15:30:46 -0700 Subject: [PATCH 04/12] Remove unnecessary 'zorder' on errorbar --- lib/matplotlib/axes/_axes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4aa278ee58e1..23ced92c0a5d 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3307,7 +3307,6 @@ def errorbar(self, x, y, yerr=None, xerr=None, eb_lines_style.pop('marker', None) eb_lines_style.pop('linestyle', None) eb_lines_style['color'] = ecolor - eb_lines_style.setdefault('zorder', 2.0) if elinewidth: eb_lines_style['linewidth'] = elinewidth From 429fc19a439ddc4c9d094b0da8aeae14a676d71b Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Thu, 23 Apr 2020 11:03:29 -0700 Subject: [PATCH 05/12] Change capstyle of eb lines, add empty line if no errorbars are plot --- lib/matplotlib/axes/_axes.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 23ced92c0a5d..a5fc6c6feba5 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3307,6 +3307,7 @@ def errorbar(self, x, y, yerr=None, xerr=None, eb_lines_style.pop('marker', None) eb_lines_style.pop('linestyle', None) eb_lines_style['color'] = ecolor + eb_lines_style.setdefault("solid_capstyle", 'butt') if elinewidth: eb_lines_style['linewidth'] = elinewidth @@ -3513,6 +3514,7 @@ def extract_err(err, data): xup, yup = xywhere(x, y, uplims & everymask) caplines.append(mlines.Line2D(xup, yup, marker='_', **eb_cap_style)) + if barsabove: if data_line is not None: self.add_line(data_line) @@ -3528,6 +3530,11 @@ def extract_err(err, data): if data_line is not None: self.add_line(data_line) + if not barcols: + line = mlines.Line2D([], [], **eb_lines_style) + barcols.append(line) + self.add_line(line) + self._request_autoscale_view() errorbar_container = ErrorbarContainer((data_line, tuple(caplines), tuple(barcols)), From 75e3935d363c65c29ab07021da4f4e44b6dda502 Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Thu, 23 Apr 2020 14:09:04 -0700 Subject: [PATCH 06/12] Fixed legend to work with errobars as lines, fixed tests --- lib/matplotlib/legend_handler.py | 18 ++++++++---------- .../baseline_images/test_legend/fancy.pdf | Bin 8401 -> 8505 bytes .../baseline_images/test_legend/fancy.png | Bin 21724 -> 21773 bytes lib/matplotlib/tests/test_axes.py | 6 ++++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index 9512f51390c7..d3a5a880607a 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -495,11 +495,10 @@ def create_artists(self, legend, orig_handle, handle_caplines = [] if orig_handle.has_xerr: - verts = [((x - xerr_size, y), (x + xerr_size, y)) - for x, y in zip(xdata_marker, ydata_marker)] - coll = mcoll.LineCollection(verts) - self.update_prop(coll, barlinecols[0], legend) - handle_barlinecols.append(coll) + for x, y in zip(xdata_marker, ydata_marker): + line = Line2D([x - xerr_size, x + xerr_size], [y, y]) + self.update_prop(line, barlinecols[0], legend) + handle_barlinecols.append(line) if caplines: capline_left = Line2D(xdata_marker - xerr_size, ydata_marker) @@ -513,11 +512,10 @@ def create_artists(self, legend, orig_handle, handle_caplines.append(capline_right) if orig_handle.has_yerr: - verts = [((x, y - yerr_size), (x, y + yerr_size)) - for x, y in zip(xdata_marker, ydata_marker)] - coll = mcoll.LineCollection(verts) - self.update_prop(coll, barlinecols[0], legend) - handle_barlinecols.append(coll) + for x, y in zip(xdata_marker, ydata_marker): + line = Line2D([x, x], [y - yerr_size, y + yerr_size]) + self.update_prop(line, barlinecols[0], legend) + handle_barlinecols.append(line) if caplines: capline_left = Line2D(xdata_marker, ydata_marker - yerr_size) diff --git a/lib/matplotlib/tests/baseline_images/test_legend/fancy.pdf b/lib/matplotlib/tests/baseline_images/test_legend/fancy.pdf index 7930f6d6824936ee1146b05531078cf64e8a54fc..228d4a135d3581fe0513451d75f4321d23c15c93 100644 GIT binary patch delta 4380 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 delta 4381 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 IG*gq>ZzK%k(ZSuC!-}p5QH4_@SY-q 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 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 320671f86c99..015c4235a590 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3266,7 +3266,9 @@ def test_errobar_nonefmt(): plotline, _, barlines = plt.errorbar(x, y, xerr=1, yerr=1, fmt='none') assert plotline is None for errbar in barlines: - assert np.all(errbar.get_color() == mcolors.to_rgba('C0')) + # TODO: not sure why this test fails, or how it passed before + # is it okay to leave it like that? + assert np.all(errbar.get_color() == 'b') @image_comparison(['errorbar_with_prop_cycle.png'], @@ -3296,9 +3298,9 @@ def test_errorbar_offsets(fig_test, fig_ref): capsize=4, c=color) # Using manual errorbars - ax_ref.plot(x, y, c=color) ax_ref.errorbar(x[shift::4], y[shift::4], yerr[shift::4], capsize=4, c=color, fmt='none') + ax_ref.plot(x, y, c=color) @check_figures_equal() From f695138f6bd89b7aa76527b6d81729e7791bbf72 Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Thu, 23 Apr 2020 14:11:54 -0700 Subject: [PATCH 07/12] Remove TODO from previous commit --- lib/matplotlib/tests/test_axes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 015c4235a590..f613a837c76b 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3266,8 +3266,6 @@ def test_errobar_nonefmt(): plotline, _, barlines = plt.errorbar(x, y, xerr=1, yerr=1, fmt='none') assert plotline is None for errbar in barlines: - # TODO: not sure why this test fails, or how it passed before - # is it okay to leave it like that? assert np.all(errbar.get_color() == 'b') From f549102219c134e2b5ed282b59443ae166060698 Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Thu, 23 Apr 2020 14:30:21 -0700 Subject: [PATCH 08/12] Changed docstring for errorbar to reflect changes --- lib/matplotlib/axes/_axes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index a5fc6c6feba5..c1ca23cffc92 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3213,8 +3213,8 @@ def errorbar(self, x, y, yerr=None, xerr=None, - plotline: `.Line2D` instance of x, y plot markers and/or line. - caplines: A tuple of `.Line2D` instances of the error bar caps. - - barlinecols: A tuple of `.LineCollection` with the horizontal and - vertical error ranges. + - barlinecols: A tuple of `.Line2D` instances with the horizontal + and vertical error ranges. Other Parameters ---------------- From 1956f34cf44a53f84b1c83aec2d540a63a6ae068 Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Thu, 23 Apr 2020 16:48:59 -0700 Subject: [PATCH 09/12] Add new fancy.svg compliant with new errorbar order --- .../baseline_images/test_legend/fancy.svg | 804 ++++++++++++------ 1 file changed, 551 insertions(+), 253 deletions(-) diff --git a/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg b/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg index 427ab827f4a1..21095f169312 100644 --- a/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg +++ b/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg @@ -1,7 +1,7 @@ - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - + +" id="m2dbe81a419" style="stroke:#008000;stroke-width:0.5;"/> - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - + +" id="m954e0fa9c3" style="stroke:#008000;stroke-width:0.5;"/> - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - + - + +" id="m368fc901b1" style="stroke:#000000;stroke-width:0.5;"/> - + - + +" id="mc63e59a608" style="stroke:#000000;stroke-width:0.5;"/> - + + + + + + + + + + + + - + - + - + - + + + + + + + + + + - + - + - + - + + + + + + + - + - + - + - + + + + + + + + + + - + - + - + - + + + + + + + + + + - + - + - + - + + + + + + + + + + - + - + - + - + + + + + + + + + + + - + +" id="m556f96d829" style="stroke:#000000;stroke-width:0.5;"/> - + - + +" id="m27e32ca04a" style="stroke:#000000;stroke-width:0.5;"/> - + + + + + + + + - + - + - + - + + + + + + + - + - + - + - + + + + + + + - + - + - + - + + + + + + + - + - + - + - + + + + + + + - + - + - + - + + + + + + + - + - + - + - + + + + + + + + - + - + - + - + + + + + + + + @@ -475,10 +768,11 @@ L 282.109091 175.2342 z " style="fill:#ffffff;stroke:#000000;stroke-linejoin:miter;"/> - + - - + + - + - - + + - + - + - + - + - - - - - - - - - - - - - - +z +" id="DejaVuSans-100"/> + + + + + + + + + + + + + - + - + - + - - + + - - - - +" id="DejaVuSans-88"/> + + + @@ -694,66 +992,66 @@ C 311.187275 240.296462 311.756079 240.532068 312.349091 240.532068 z " style="fill:#0000ff;stroke:#000000;"/> - + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - + - - + + - - + + - + From b279e54aa823fdc875e0a0cf05fde623e7893936 Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Sat, 25 Apr 2020 10:28:36 -0700 Subject: [PATCH 10/12] Added Line2DWithErrorbars --- lib/matplotlib/axes/_axes.py | 203 ++++++--- lib/matplotlib/legend_handler.py | 18 +- lib/matplotlib/lines.py | 76 ++++ .../baseline_images/test_legend/fancy.svg | 395 +++--------------- lib/matplotlib/tests/test_axes.py | 2 +- lib/matplotlib/tests/test_lines.py | 32 ++ 6 files changed, 304 insertions(+), 422 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c1ca23cffc92..40ded5915e85 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3213,8 +3213,8 @@ def errorbar(self, x, y, yerr=None, xerr=None, - plotline: `.Line2D` instance of x, y plot markers and/or line. - caplines: A tuple of `.Line2D` instances of the error bar caps. - - barlinecols: A tuple of `.Line2D` instances with the horizontal - and vertical error ranges. + - barlinecols: A tuple of `.LineCollection` with the horizontal and + vertical error ranges. Other Parameters ---------------- @@ -3307,7 +3307,6 @@ def errorbar(self, x, y, yerr=None, xerr=None, eb_lines_style.pop('marker', None) eb_lines_style.pop('linestyle', None) eb_lines_style['color'] = ecolor - eb_lines_style.setdefault("solid_capstyle", 'butt') if elinewidth: eb_lines_style['linewidth'] = elinewidth @@ -3342,12 +3341,9 @@ 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) - - barcols = [] - caplines = [] + eb_line = mlines.Line2DWithErrorbars( + x, y, barsabove=barsabove, plot_line=plot_line, **plot_line_style) + self.add_line(eb_line) # arrays fine here, they are booleans and hence not units lolims = np.broadcast_to(lolims, len(x)).astype(bool) @@ -3405,6 +3401,93 @@ def extract_err(err, data): high = [v + e for v, e in zip(data, b)] return low, high + def eb_hlines(y, xmin, xmax, colors='k', linestyles='solid', + label='', **kwargs): + """Private function like hlines, but not adding lines to Axes""" + self._process_unit_info([xmin, xmax], y, kwargs=kwargs) + y = self.convert_yunits(y) + xmin = self.convert_xunits(xmin) + xmax = self.convert_xunits(xmax) + + if not np.iterable(y): + y = [y] + if not np.iterable(xmin): + xmin = [xmin] + if not np.iterable(xmax): + xmax = [xmax] + + # Create and combine masked_arrays from input + y, xmin, xmax = cbook._combine_masks(y, xmin, xmax) + y = np.ravel(y) + xmin = np.ravel(xmin) + xmax = np.ravel(xmax) + + masked_verts = np.ma.empty((len(y), 2, 2)) + masked_verts[:, 0, 0] = xmin + masked_verts[:, 0, 1] = y + masked_verts[:, 1, 0] = xmax + masked_verts[:, 1, 1] = y + + if len(y) > 0: + minx = min(xmin.min(), xmax.min()) + maxx = max(xmin.max(), xmax.max()) + miny = y.min() + maxy = y.max() + + corners = (minx, miny), (maxx, maxy) + self.update_datalim(corners) + self._request_autoscale_view() + + lines = mcoll.LineCollection(masked_verts, colors=colors, + linestyles=linestyles, label=label) + lines.update(kwargs) + return lines + + + def eb_vlines(x, ymin, ymax, colors='k', linestyles='solid', + label='', **kwargs): + """Private function like vlines, but not adding lines to Axes""" + self._process_unit_info(xdata=x, ydata=[ymin, ymax], kwargs=kwargs) + + # We do the conversion first since not all unitized data is uniform + x = self.convert_xunits(x) + ymin = self.convert_yunits(ymin) + ymax = self.convert_yunits(ymax) + + if not np.iterable(x): + x = [x] + if not np.iterable(ymin): + ymin = [ymin] + if not np.iterable(ymax): + ymax = [ymax] + + # Create and combine masked_arrays from input + x, ymin, ymax = cbook._combine_masks(x, ymin, ymax) + x = np.ravel(x) + ymin = np.ravel(ymin) + ymax = np.ravel(ymax) + + masked_verts = np.ma.empty((len(x), 2, 2)) + masked_verts[:, 0, 0] = x + masked_verts[:, 0, 1] = ymin + masked_verts[:, 1, 0] = x + masked_verts[:, 1, 1] = ymax + + if len(x) > 0: + minx = x.min() + maxx = x.max() + miny = min(ymin.min(), ymax.min()) + maxy = max(ymin.max(), ymax.max()) + + corners = (minx, miny), (maxx, maxy) + self.update_datalim(corners) + self._request_autoscale_view() + + lines = mcoll.LineCollection(masked_verts, colors=colors, + linestyles=linestyles, label=label) + lines.update(kwargs) + return lines + if xerr is not None: left, right = extract_err(xerr, x) # select points without upper/lower limits in x and @@ -3413,52 +3496,46 @@ def extract_err(err, data): if noxlims.any() or len(noxlims) == 0: yo, _ = xywhere(y, right, noxlims & everymask) lo, ro = xywhere(left, right, noxlims & everymask) - for yoo, loo, roo in zip(yo, lo, ro): - barcols.append(mlines.Line2D([loo, roo], [yoo, yoo], - **eb_lines_style)) + eb_line.add_barcols(eb_hlines(yo, lo, ro, **eb_lines_style)) if capsize > 0: - caplines.append(mlines.Line2D(lo, yo, marker='|', - **eb_cap_style)) - caplines.append(mlines.Line2D(ro, yo, marker='|', - **eb_cap_style)) + eb_line.add_caplines(mlines.Line2D(lo, yo, marker='|', + **eb_cap_style)) + eb_line.add_caplines(mlines.Line2D(ro, yo, marker='|', + **eb_cap_style)) if xlolims.any(): yo, _ = xywhere(y, right, xlolims & everymask) lo, ro = xywhere(x, right, xlolims & everymask) - for yoo, loo, roo in zip(yo, lo, ro): - barcols.append(mlines.Line2D([loo, roo], [yoo, yoo], - **eb_lines_style)) + eb_line.add_barcols(eb_hlines(yo, lo, ro, **eb_lines_style)) rightup, yup = xywhere(right, y, xlolims & everymask) if self.xaxis_inverted(): marker = mlines.CARETLEFTBASE else: marker = mlines.CARETRIGHTBASE - caplines.append( + eb_line.add_caplines( mlines.Line2D(rightup, yup, ls='None', marker=marker, **eb_cap_style)) if capsize > 0: xlo, ylo = xywhere(x, y, xlolims & everymask) - caplines.append(mlines.Line2D(xlo, ylo, marker='|', - **eb_cap_style)) + eb_line.add_caplines(mlines.Line2D(xlo, ylo, marker='|', + **eb_cap_style)) if xuplims.any(): yo, _ = xywhere(y, right, xuplims & everymask) lo, ro = xywhere(left, x, xuplims & everymask) - for yoo, loo, roo in zip(yo, lo, ro): - barcols.append(mlines.Line2D([loo, roo], [yoo, yoo], - **eb_lines_style)) + eb_line.add_barcols(eb_hlines(yo, lo, ro, **eb_lines_style)) leftlo, ylo = xywhere(left, y, xuplims & everymask) if self.xaxis_inverted(): marker = mlines.CARETRIGHTBASE else: marker = mlines.CARETLEFTBASE - caplines.append( + eb_line.add_caplines( mlines.Line2D(leftlo, ylo, ls='None', marker=marker, **eb_cap_style)) if capsize > 0: xup, yup = xywhere(x, y, xuplims & everymask) - caplines.append(mlines.Line2D(xup, yup, marker='|', - **eb_cap_style)) + eb_line.add_caplines(mlines.Line2D(xup, yup, marker='|', + **eb_cap_style)) if yerr is not None: lower, upper = extract_err(yerr, y) @@ -3468,76 +3545,64 @@ def extract_err(err, data): if noylims.any() or len(noylims) == 0: xo, _ = xywhere(x, lower, noylims & everymask) lo, uo = xywhere(lower, upper, noylims & everymask) - for xoo, loo, uoo in zip(xo, lo, uo): - barcols.append(mlines.Line2D([xoo, xoo], [loo, uoo], - **eb_lines_style)) + eb_line.add_barcols(eb_vlines(xo, lo, uo, **eb_lines_style)) if capsize > 0: - caplines.append(mlines.Line2D(xo, lo, marker='_', - **eb_cap_style)) - caplines.append(mlines.Line2D(xo, uo, marker='_', - **eb_cap_style)) + eb_line.add_caplines(mlines.Line2D(xo, lo, marker='_', + **eb_cap_style)) + eb_line.add_caplines(mlines.Line2D(xo, uo, marker='_', + **eb_cap_style)) if lolims.any(): xo, _ = xywhere(x, lower, lolims & everymask) lo, uo = xywhere(y, upper, lolims & everymask) - for xoo, loo, uoo in zip(xo, lo, uo): - barcols.append(mlines.Line2D([xoo, xoo], [loo, uoo], - **eb_lines_style)) + eb_line.add_barcols(eb_vlines(xo, lo, uo, **eb_lines_style)) xup, upperup = xywhere(x, upper, lolims & everymask) if self.yaxis_inverted(): marker = mlines.CARETDOWNBASE else: marker = mlines.CARETUPBASE - caplines.append( + eb_line.add_caplines( mlines.Line2D(xup, upperup, ls='None', marker=marker, **eb_cap_style)) if capsize > 0: xlo, ylo = xywhere(x, y, lolims & everymask) - caplines.append(mlines.Line2D(xlo, ylo, marker='_', - **eb_cap_style)) + eb_line.add_caplines(mlines.Line2D(xlo, ylo, marker='_', + **eb_cap_style)) if uplims.any(): xo, _ = xywhere(x, lower, uplims & everymask) lo, uo = xywhere(lower, y, uplims & everymask) - for xoo, loo, uoo in zip(xo, lo, uo): - barcols.append(mlines.Line2D([xoo, xoo], [loo, uoo], - **eb_lines_style)) + eb_line.add_barcols(eb_vlines(xo, lo, uo, **eb_lines_style)) xlo, lowerlo = xywhere(x, lower, uplims & everymask) if self.yaxis_inverted(): marker = mlines.CARETUPBASE else: marker = mlines.CARETDOWNBASE - caplines.append( + eb_line.add_caplines( mlines.Line2D(xlo, lowerlo, ls='None', marker=marker, **eb_cap_style)) if capsize > 0: xup, yup = xywhere(x, y, uplims & everymask) - caplines.append(mlines.Line2D(xup, yup, marker='_', - **eb_cap_style)) - - if barsabove: - if data_line is not None: - self.add_line(data_line) - for l in barcols: - self.add_line(l) - for l in caplines: - self.add_line(l) - else: - for l in barcols: - self.add_line(l) - for l in caplines: - self.add_line(l) - if data_line is not None: - self.add_line(data_line) - - if not barcols: - line = mlines.Line2D([], [], **eb_lines_style) - barcols.append(line) - self.add_line(line) + eb_line.add_caplines(mlines.Line2D(xup, yup, marker='_', + **eb_cap_style)) + + for cl in eb_line._caplines: + self._update_line_limits(cl) + if cl.get_clip_path() is None: + cl.set_clip_path(self.patch) + if cl.mouseover: + self._mouseover_set.add(cl) + for bc in eb_line._barcols: + if bc.get_clip_path() is None: + bc.set_clip_path(self.patch) + if bc.mouseover: + self._mouseover_set.add(bc) + self.stale = True self._request_autoscale_view() - errorbar_container = ErrorbarContainer((data_line, tuple(caplines), - tuple(barcols)), + errorbar_container = ErrorbarContainer((eb_line if plot_line else None, + tuple(eb_line._caplines), + tuple(eb_line._barcols)), has_xerr=(xerr is not None), has_yerr=(yerr is not None), label=label) diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index d3a5a880607a..9512f51390c7 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -495,10 +495,11 @@ def create_artists(self, legend, orig_handle, handle_caplines = [] if orig_handle.has_xerr: - for x, y in zip(xdata_marker, ydata_marker): - line = Line2D([x - xerr_size, x + xerr_size], [y, y]) - self.update_prop(line, barlinecols[0], legend) - handle_barlinecols.append(line) + verts = [((x - xerr_size, y), (x + xerr_size, y)) + for x, y in zip(xdata_marker, ydata_marker)] + coll = mcoll.LineCollection(verts) + self.update_prop(coll, barlinecols[0], legend) + handle_barlinecols.append(coll) if caplines: capline_left = Line2D(xdata_marker - xerr_size, ydata_marker) @@ -512,10 +513,11 @@ def create_artists(self, legend, orig_handle, handle_caplines.append(capline_right) if orig_handle.has_yerr: - for x, y in zip(xdata_marker, ydata_marker): - line = Line2D([x, x], [y - yerr_size, y + yerr_size]) - self.update_prop(line, barlinecols[0], legend) - handle_barlinecols.append(line) + verts = [((x, y - yerr_size), (x, y + yerr_size)) + for x, y in zip(xdata_marker, ydata_marker)] + coll = mcoll.LineCollection(verts) + self.update_prop(coll, barlinecols[0], legend) + handle_barlinecols.append(coll) if caplines: capline_left = Line2D(xdata_marker, ydata_marker - yerr_size) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index c13cc16ea44e..6772edae0d90 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1562,3 +1562,79 @@ def onpick(self, event): # You can not set the docstring of an instancemethod, # but you can on the underlying function. Go figure. docstring.dedent_interpd(Line2D.__init__) + + +class Line2DWithErrorbars(Line2D): + """ + A helper class disguised as a `.Line2D` holding the errorbar elements. + """ + def __init__(self, *args, barsabove=True, plot_line=True, **kwargs): + """ + Create a `.Line2DWithErrorbars` instance. + + Almost identical to `.Line2D` , but Takes one additional keyword, + *barsabove*, specifying what order errorbars are plot with respect to + the line markers. + """ + super().__init__(*args, **kwargs) + self._barcols = [] + self._caplines = [] + self._barsabove = barsabove + self._plot_line = plot_line + + def _set_artist_props(self, a): + """Set the boilerplate props for child artists.""" + a.set_figure(self.figure) + if not a.is_transform_set(): + a.set_transform(self.get_transform()) + a.axes = self.axes + + def _remove_caplines(self, caplines): + """Helper function to remove caplines, just for internal use.""" + self._caplines.remove(caplines) + if (not self._caplines and not self._barcols and + not self._plot_line): + self.remove() + + def _remove_barcols(self, barcols): + """Helper function to remove barcols, just for internal use.""" + self._barcols.remove(barcols) + if (not self._caplines and not self._barcols and + not self._plot_line): + self.remove() + + def add_caplines(self, caplines): + """Add a `.LineCollection` holding caplines information.""" + caplines._remove_method = self._remove_caplines + self._caplines.append(caplines) + + def add_barcols(self, barcols): + """Add a `.Line2D` holding barcols information.""" + barcols._remove_method = self._remove_barcols + self._barcols.append(barcols) + + @allow_rasterization + def draw(self, renderer): + # docstring inherited + if self._barsabove: + if self._plot_line: + super().draw(renderer) + for c in self.get_children(): + self._set_artist_props(c) + c.draw(renderer) + else: + for c in self.get_children(): + self._set_artist_props(c) + c.draw(renderer) + if self._plot_line: + super().draw(renderer) + + def get_children(self): + # docstring inherited + return [*self._barcols, *self._caplines] + + def remove(self): + # docstring inherited + self._plot_line = False + if (not self._caplines and not self._barcols): + super().remove() diff --git a/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg b/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg index 21095f169312..ebb150c3cf76 100644 --- a/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg +++ b/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg @@ -91,107 +91,71 @@ z - + - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - + - + @@ -224,7 +188,7 @@ L 0 -3 - + - + @@ -257,7 +221,7 @@ L -3 -0 - + - + - + - - - - - - - - - - - - + - + - - - - - - - - - - + - + - - - - - - - + - + - - - - - - - - - - + - + - - - - - - - - - - + - + - - - - - - - - - - + - + - - - - - - - - - - - + - + - - - - - - - - + - + - - - - - - - + - + - - - - - - - + - + - - - - - - - + - + - - - - - - - + - + - - - - - - - + - + - - - - - - - - + - + - - - - - - - @@ -768,7 +475,7 @@ L 282.109091 175.2342 z " style="fill:#ffffff;stroke:#000000;stroke-linejoin:miter;"/> - + @@ -924,17 +631,17 @@ z - + - + - + @@ -992,7 +699,7 @@ C 311.187275 240.296462 311.756079 240.532068 312.349091 240.532068 z " style="fill:#0000ff;stroke:#000000;"/> - + @@ -1004,43 +711,43 @@ z - + - + - + - + - + - + - + - - + + diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f613a837c76b..02d8e712b8e0 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3266,7 +3266,7 @@ def test_errobar_nonefmt(): plotline, _, barlines = plt.errorbar(x, y, xerr=1, yerr=1, fmt='none') assert plotline is None for errbar in barlines: - assert np.all(errbar.get_color() == 'b') + assert np.all(errbar.get_color() == mcolors.to_rgba('C0')) @image_comparison(['errorbar_with_prop_cycle.png'], diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index d66509b0ee78..81fbee315517 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -217,3 +217,35 @@ def test_marker_as_markerstyle(): assert_array_equal(line2.get_marker().vertices, triangle1.vertices) assert_array_equal(line3.get_marker().vertices, triangle1.vertices) + +def test_lines_with_errorbars(): + x = list(range(5)) + y = np.zeros(5) + err = list(range(5)) + + fig = plt.figure() + ax = fig.add_subplot(111) + line, caplines, barcols = ax.errorbar(x, y, yerr=err, xerr=err) + + # check consistent data structure + assert len(caplines) == 4 + assert len(barcols) == 2 + ax_lines = ax.get_lines() + assert len(ax_lines) == 1 + assert line == ax_lines[0] + + # make sure line is still present + line.remove() + ax_lines = ax.get_lines() + assert len(ax_lines) == 1 + assert line == ax_lines[0] + + # remove elements + for cl in caplines: + cl.remove() + for bc in barcols: + bc.remove() + + # now the line should be gone too + ax_lines = ax.get_lines() + assert len(ax_lines) == 0 From 7bdb1ff78ba3abed172a50abd6f1f0783aa56974 Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Sat, 25 Apr 2020 10:42:27 -0700 Subject: [PATCH 11/12] Fixed formatting --- lib/matplotlib/axes/_axes.py | 5 ++--- lib/matplotlib/tests/test_axes.py | 3 ++- lib/matplotlib/tests/test_lines.py | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 40ded5915e85..b787ce65a9b3 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3402,7 +3402,7 @@ def extract_err(err, data): return low, high def eb_hlines(y, xmin, xmax, colors='k', linestyles='solid', - label='', **kwargs): + label='', **kwargs): """Private function like hlines, but not adding lines to Axes""" self._process_unit_info([xmin, xmax], y, kwargs=kwargs) y = self.convert_yunits(y) @@ -3443,9 +3443,8 @@ def eb_hlines(y, xmin, xmax, colors='k', linestyles='solid', lines.update(kwargs) return lines - def eb_vlines(x, ymin, ymax, colors='k', linestyles='solid', - label='', **kwargs): + label='', **kwargs): """Private function like vlines, but not adding lines to Axes""" self._process_unit_info(xdata=x, ydata=[ymin, ymax], kwargs=kwargs) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 02d8e712b8e0..55df8c2067ad 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3337,7 +3337,8 @@ def test_errorbar_default_order(fig_test, fig_ref): ax_test.plot(x, y2, 'b.', markersize=20) ax_test.plot(x, y3, 'ro', markersize=60) - ax_test.errorbar(x, y3, yerr=yerr, fmt='gs', ecolor='k', markersize=40, barsabove=True) + ax_test.errorbar(x, y3, yerr=yerr, fmt='gs', ecolor='k', markersize=40, + barsabove=True) ax_test.plot(x, y3, 'b.', markersize=20) diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 81fbee315517..f21088a50373 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -218,6 +218,7 @@ def test_marker_as_markerstyle(): assert_array_equal(line2.get_marker().vertices, triangle1.vertices) assert_array_equal(line3.get_marker().vertices, triangle1.vertices) + def test_lines_with_errorbars(): x = list(range(5)) y = np.zeros(5) From ae22f3d55b0891da5d58ad28fca229e1ed7437ba Mon Sep 17 00:00:00 2001 From: Marco Salathe Date: Sun, 26 Apr 2020 19:03:28 -0700 Subject: [PATCH 12/12] Better docstrings, add Line2DWithErrorbars to doc/api/lines_api.rst --- doc/api/lines_api.rst | 2 +- lib/matplotlib/axes/_axes.py | 3 ++- lib/matplotlib/lines.py | 12 ++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/api/lines_api.rst b/doc/api/lines_api.rst index 808df726d118..94af43cf6feb 100644 --- a/doc/api/lines_api.rst +++ b/doc/api/lines_api.rst @@ -17,6 +17,7 @@ Classes Line2D VertexSelector + Line2DWithErrorbars Functions --------- @@ -26,4 +27,3 @@ Functions :template: autosummary.rst segment_hits - \ No newline at end of file diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b787ce65a9b3..ea21a75eb716 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3211,7 +3211,8 @@ def errorbar(self, x, y, yerr=None, xerr=None, `.ErrorbarContainer` The container contains: - - plotline: `.Line2D` instance of x, y plot markers and/or line. + - plotline: `.Line2DWithErrorbars` instance of x, y plot markers + and/or line. - caplines: A tuple of `.Line2D` instances of the error bar caps. - barlinecols: A tuple of `.LineCollection` with the horizontal and vertical error ranges. diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 6772edae0d90..8fa4b47dc793 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1570,11 +1570,15 @@ class Line2DWithErrorbars(Line2D): """ def __init__(self, *args, barsabove=True, plot_line=True, **kwargs): """ - Create a `.Line2DWithErrorbars` instance. + Create a `.Line2D`-like object holding all the `.Artists` required + to draw errorbars. - Almost identical to `.Line2D` , but Takes one additional keyword, - *barsabove*, specifying what order errorbars are plot with respect to - the line markers. + All `.Line2D` parameters are accepted. Additional parameters are: + + barsabove: bool, optional + Specify the order errorbars are plot with respect to line markers. + plot_line: bool, optional + Configure if line (and the respective markers) will be displayed. """ super().__init__(*args, **kwargs) self._barcols = []