diff --git a/doc/users/next_whats_new/polar_errorbar_caps.rst b/doc/users/next_whats_new/polar_errorbar_caps.rst new file mode 100644 index 000000000000..c73f0210cc71 --- /dev/null +++ b/doc/users/next_whats_new/polar_errorbar_caps.rst @@ -0,0 +1,4 @@ +Polar errorbar caps are rotated +------------------------------- +When plotting errorbars on a polar plot, the caps are now rotated so they are +perpendicular to the errorbars. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b8a7c3b2c6bb..c43c847c9946 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3439,10 +3439,11 @@ def _upcast_err(err): eb_cap_style['color'] = ecolor barcols = [] - caplines = [] + caplines = {'x': [], 'y': []} # Vectorized fancy-indexer. - def apply_mask(arrays, mask): return [array[mask] for array in arrays] + def apply_mask(arrays, mask): + return [array[mask] for array in arrays] # dep: dependent dataset, indep: independent dataset for (dep_axis, dep, err, lolims, uplims, indep, lines_func, @@ -3486,7 +3487,7 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays] line = mlines.Line2D(indep_masked, indep_masked, marker=marker, **eb_cap_style) line.set(**{f"{dep_axis}data": lh_masked}) - caplines.append(line) + caplines[dep_axis].append(line) for idx, (lims, hl) in enumerate([(lolims, high), (uplims, low)]): if not lims.any(): continue @@ -3500,15 +3501,28 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays] line = mlines.Line2D(x_masked, y_masked, marker=hlmarker, **eb_cap_style) line.set(**{f"{dep_axis}data": hl_masked}) - caplines.append(line) + caplines[dep_axis].append(line) if capsize > 0: - caplines.append(mlines.Line2D( + caplines[dep_axis].append(mlines.Line2D( x_masked, y_masked, marker=marker, **eb_cap_style)) - for l in caplines: - self.add_line(l) + for axis in caplines: + for l in caplines[axis]: + if self.name == 'polar': + # Rotate caps to be perpendicular to the error bars + for theta, r in zip(l.get_xdata(), l.get_ydata()): + rotation = theta + if axis == 'x': + rotation += np.pi / 2 + ms = mmarkers.MarkerStyle(marker=marker) + ms._transform = mtransforms.Affine2D().rotate(rotation) + self.add_line(mlines.Line2D([theta], [r], marker=ms, + **eb_cap_style)) + else: + self.add_line(l) self._request_autoscale_view() + caplines = caplines['x'] + caplines['y'] errorbar_container = ErrorbarContainer( (data_line, tuple(caplines), tuple(barcols)), has_xerr=(xerr is not None), has_yerr=(yerr is not None), diff --git a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_polar_caps.png b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_polar_caps.png new file mode 100644 index 000000000000..b11485526e6c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_polar_caps.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 5da2df1455db..cdf61b92e930 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3376,6 +3376,16 @@ def test_errorbar(): ax.set_title("Simplest errorbars, 0.2 in x, 0.4 in y") +@image_comparison(['errorbar_polar_caps'], extensions=['png']) +def test_errorbar_polar_caps(): + fig = plt.figure() + ax = plt.subplot(111, projection='polar') + theta = np.arange(0, 2*np.pi, np.pi / 4) + r = theta / np.pi / 2 + 0.5 + ax.errorbar(theta, r, xerr=0.15, yerr=0.1) + ax.set_rlim(0, 2) + + def test_errorbar_colorcycle(): f, ax = plt.subplots()