Skip to content

Commit c77fc36

Browse files
committed
Curved polar errorbars
- uses _interpolation_steps - prefers transform MarkerStyle in init over _transform property - adjusted what's new - added more tests for overlapping, asymmetric and long errorbars - combine all tests to a single figure - remove overlappnig since it does not work same on all platforms
1 parent ecab6de commit c77fc36

File tree

4 files changed

+57
-9
lines changed

4 files changed

+57
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixed errorbars in polar plots
2+
------------------------------
3+
Caps and error lines are now drawn with respect to polar coordinates,
4+
when plotting errorbars on polar plots.

lib/matplotlib/axes/_axes.py

+27-9
Original file line numberDiff line numberDiff line change
@@ -3522,10 +3522,11 @@ def _upcast_err(err):
35223522
eb_cap_style['color'] = ecolor
35233523

35243524
barcols = []
3525-
caplines = []
3525+
caplines = {'x': [], 'y': []}
35263526

35273527
# Vectorized fancy-indexer.
3528-
def apply_mask(arrays, mask): return [array[mask] for array in arrays]
3528+
def apply_mask(arrays, mask):
3529+
return [array[mask] for array in arrays]
35293530

35303531
# dep: dependent dataset, indep: independent dataset
35313532
for (dep_axis, dep, err, lolims, uplims, indep, lines_func,
@@ -3556,9 +3557,12 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays]
35563557
# return dep - elow * ~lolims, dep + ehigh * ~uplims
35573558
# except that broadcast_to would strip units.
35583559
low, high = dep + np.row_stack([-(1 - lolims), 1 - uplims]) * err
3559-
35603560
barcols.append(lines_func(
35613561
*apply_mask([indep, low, high], everymask), **eb_lines_style))
3562+
if self.name == "polar" and dep_axis == "x":
3563+
for b in barcols:
3564+
for p in b.get_paths():
3565+
p._interpolation_steps = 2
35623566
# Normal errorbars for points without upper/lower limits.
35633567
nolims = ~(lolims | uplims)
35643568
if nolims.any() and capsize > 0:
@@ -3571,7 +3575,7 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays]
35713575
line = mlines.Line2D(indep_masked, indep_masked,
35723576
marker=marker, **eb_cap_style)
35733577
line.set(**{f"{dep_axis}data": lh_masked})
3574-
caplines.append(line)
3578+
caplines[dep_axis].append(line)
35753579
for idx, (lims, hl) in enumerate([(lolims, high), (uplims, low)]):
35763580
if not lims.any():
35773581
continue
@@ -3585,15 +3589,29 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays]
35853589
line = mlines.Line2D(x_masked, y_masked,
35863590
marker=hlmarker, **eb_cap_style)
35873591
line.set(**{f"{dep_axis}data": hl_masked})
3588-
caplines.append(line)
3592+
caplines[dep_axis].append(line)
35893593
if capsize > 0:
3590-
caplines.append(mlines.Line2D(
3594+
caplines[dep_axis].append(mlines.Line2D(
35913595
x_masked, y_masked, marker=marker, **eb_cap_style))
3592-
3593-
for l in caplines:
3594-
self.add_line(l)
3596+
if self.name == 'polar':
3597+
for axis in caplines:
3598+
for l in caplines[axis]:
3599+
# Rotate caps to be perpendicular to the error bars
3600+
for theta, r in zip(l.get_xdata(), l.get_ydata()):
3601+
rotation = mtransforms.Affine2D().rotate(theta)
3602+
if axis == 'y':
3603+
rotation.rotate(-np.pi / 2)
3604+
ms = mmarkers.MarkerStyle(marker=marker,
3605+
transform=rotation)
3606+
self.add_line(mlines.Line2D([theta], [r], marker=ms,
3607+
**eb_cap_style))
3608+
else:
3609+
for axis in caplines:
3610+
for l in caplines[axis]:
3611+
self.add_line(l)
35953612

35963613
self._request_autoscale_view()
3614+
caplines = caplines['x'] + caplines['y']
35973615
errorbar_container = ErrorbarContainer(
35983616
(data_line, tuple(caplines), tuple(barcols)),
35993617
has_xerr=(xerr is not None), has_yerr=(yerr is not None),

lib/matplotlib/tests/test_axes.py

+26
Original file line numberDiff line numberDiff line change
@@ -3600,6 +3600,32 @@ def test_errorbar():
36003600
ax.set_title("Simplest errorbars, 0.2 in x, 0.4 in y")
36013601

36023602

3603+
@image_comparison(['mixed_errorbar_polar_caps'], extensions=['png'],
3604+
remove_text=True)
3605+
def test_mixed_errorbar_polar_caps():
3606+
fig = plt.figure()
3607+
ax = plt.subplot(111, projection='polar')
3608+
theta = np.linspace(0, np.pi, 3, False)
3609+
r = [1.0]*3
3610+
ax.errorbar(theta, r, xerr=0.35, yerr=0.2, fmt="o")
3611+
_long_errorbar_polar_caps(ax)
3612+
_asymmetric_polar_caps(ax)
3613+
3614+
3615+
def _long_errorbar_polar_caps(ax):
3616+
theta = np.linspace(0, 2 * np.pi, 2, False) + np.pi/2
3617+
r = [1.8, 2.2]
3618+
ax.errorbar(theta, r, xerr=0.8 * np.pi, yerr=0.2, fmt="o")
3619+
3620+
3621+
def _asymmetric_polar_caps(ax):
3622+
theta = np.linspace(0, np.pi, 3, False) + np.pi
3623+
r = [1.0]*3
3624+
xerr = [[.3, .3, .2], [.2, .3, .3]]
3625+
yerr = [[.35, .5, .5], [.5, .35, .5]]
3626+
ax.errorbar(theta, r, xerr=xerr, yerr=yerr, fmt="o")
3627+
3628+
36033629
def test_errorbar_colorcycle():
36043630

36053631
f, ax = plt.subplots()

0 commit comments

Comments
 (0)