Skip to content

Commit d712e61

Browse files
committed
Rotate caps on polar errorbar plots
1 parent ecab6de commit d712e61

File tree

4 files changed

+57
-11
lines changed

4 files changed

+57
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Polar errorbar caps are rotated
2+
-------------------------------
3+
When plotting errorbars on a polar plot, the caps are now rotated so they are
4+
perpendicular to the errorbars.

lib/matplotlib/axes/_axes.py

+43-11
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,17 @@ 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-
3560-
barcols.append(lines_func(
3561-
*apply_mask([indep, low, high], everymask), **eb_lines_style))
3560+
if self.name == "polar":
3561+
delta_r = 0
3562+
if dep_axis == "x":
3563+
delta_r = indep*(1-np.cos((high-low).astype(float)/2))
3564+
barcols.append(lines_func(
3565+
*apply_mask([indep+delta_r, low, high], everymask),
3566+
**eb_lines_style))
3567+
else:
3568+
barcols.append(lines_func(
3569+
*apply_mask([indep, low, high], everymask),
3570+
**eb_lines_style))
35623571
# Normal errorbars for points without upper/lower limits.
35633572
nolims = ~(lolims | uplims)
35643573
if nolims.any() and capsize > 0:
@@ -3571,7 +3580,7 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays]
35713580
line = mlines.Line2D(indep_masked, indep_masked,
35723581
marker=marker, **eb_cap_style)
35733582
line.set(**{f"{dep_axis}data": lh_masked})
3574-
caplines.append(line)
3583+
caplines[dep_axis].append(line)
35753584
for idx, (lims, hl) in enumerate([(lolims, high), (uplims, low)]):
35763585
if not lims.any():
35773586
continue
@@ -3585,15 +3594,38 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays]
35853594
line = mlines.Line2D(x_masked, y_masked,
35863595
marker=hlmarker, **eb_cap_style)
35873596
line.set(**{f"{dep_axis}data": hl_masked})
3588-
caplines.append(line)
3597+
caplines[dep_axis].append(line)
35893598
if capsize > 0:
3590-
caplines.append(mlines.Line2D(
3599+
caplines[dep_axis].append(mlines.Line2D(
35913600
x_masked, y_masked, marker=marker, **eb_cap_style))
3592-
3593-
for l in caplines:
3594-
self.add_line(l)
3601+
if self.name == 'polar':
3602+
for axis in caplines:
3603+
if not caplines[axis]:
3604+
continue
3605+
lo, hi = caplines[axis]
3606+
for (lo_theta, lo_r,
3607+
hi_theta, hi_r) in zip(
3608+
lo.get_xdata(), lo.get_ydata(),
3609+
hi.get_xdata(), hi.get_ydata()):
3610+
# Rotate caps to be perpendicular to the error bars
3611+
rotation = (lo_theta + hi_theta)/2
3612+
if axis == 'x':
3613+
lo_r += lo_r*(1-np.cos((hi_theta-lo_theta)/2))
3614+
hi_r += hi_r*(1-np.cos((hi_theta-lo_theta)/2))
3615+
rotation += np.pi / 2
3616+
ms = mmarkers.MarkerStyle(marker=marker)
3617+
ms._transform = mtransforms.Affine2D().rotate(rotation)
3618+
self.add_line(mlines.Line2D([lo_theta], [lo_r],
3619+
marker=ms, **eb_cap_style))
3620+
self.add_line(mlines.Line2D([hi_theta], [hi_r],
3621+
marker=ms, **eb_cap_style))
3622+
else:
3623+
for axis in caplines:
3624+
for l in caplines[axis]:
3625+
self.add_line(l)
35953626

35963627
self._request_autoscale_view()
3628+
caplines = caplines['x'] + caplines['y']
35973629
errorbar_container = ErrorbarContainer(
35983630
(data_line, tuple(caplines), tuple(barcols)),
35993631
has_xerr=(xerr is not None), has_yerr=(yerr is not None),

lib/matplotlib/tests/test_axes.py

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

36023602

3603+
@image_comparison(['errorbar_polar_caps'], extensions=['png'])
3604+
def test_errorbar_polar_caps():
3605+
fig = plt.figure()
3606+
ax = plt.subplot(111, projection='polar')
3607+
theta = np.arange(0, 2*np.pi, np.pi / 4)
3608+
r = theta / np.pi / 2 + 0.5
3609+
ax.errorbar(theta, r, xerr=0.15, yerr=0.1, fmt="o")
3610+
ax.set_rlim(0, 2)
3611+
3612+
36033613
def test_errorbar_colorcycle():
36043614

36053615
f, ax = plt.subplots()

0 commit comments

Comments
 (0)