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

Skip to content

Commit dd8a598

Browse files
committed
fix polar error bar cap orientation
1 parent fbf1611 commit dd8a598

File tree

6 files changed

+76
-3
lines changed

6 files changed

+76
-3
lines changed

lib/matplotlib/axes/_axes.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
_AxesBase, _TransformedBoundsLocator, _process_plot_format)
3838
from matplotlib.axes._secondary_axes import SecondaryAxis
3939
from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer
40+
from matplotlib.transforms import ScaledRotation
4041

4142
_log = logging.getLogger(__name__)
4243

@@ -3784,13 +3785,16 @@ def apply_mask(arrays, mask):
37843785
caplines[dep_axis].append(mlines.Line2D(
37853786
x_masked, y_masked, marker=marker, **eb_cap_style))
37863787
if self.name == 'polar':
3788+
trans_shift = self.transShift
37873789
for axis in caplines:
37883790
for l in caplines[axis]:
37893791
# Rotate caps to be perpendicular to the error bars
37903792
for theta, r in zip(l.get_xdata(), l.get_ydata()):
3791-
rotation = mtransforms.Affine2D().rotate(theta)
3793+
rotation = ScaledRotation(
3794+
theta=theta,
3795+
trans_shift=trans_shift)
37923796
if axis == 'y':
3793-
rotation.rotate(-np.pi / 2)
3797+
rotation += mtransforms.Affine2D().rotate(np.pi / 2)
37943798
ms = mmarkers.MarkerStyle(marker=marker,
37953799
transform=rotation)
37963800
self.add_line(mlines.Line2D([theta], [r], marker=ms,

lib/matplotlib/tests/test_polar.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,3 +481,21 @@ def test_polar_neg_theta_lims():
481481
ax.set_thetalim(-np.pi, np.pi)
482482
labels = [l.get_text() for l in ax.xaxis.get_ticklabels()]
483483
assert labels == ['-180°', '-135°', '-90°', '-45°', '0°', '45°', '90°', '135°']
484+
485+
486+
@pytest.mark.parametrize("order", ["before", "after"])
487+
@image_comparison(baseline_images=['polar_errorbar'], remove_text=True,
488+
extensions=['png'], style='mpl20')
489+
def test_polar_errorbar(order):
490+
theta = np.arange(0, 2 * np.pi, np.pi / 8)
491+
r = theta / np.pi / 2 + 0.5
492+
fig = plt.figure(figsize=(5, 5))
493+
ax = fig.add_subplot(projection='polar')
494+
if order == "before":
495+
ax.set_theta_zero_location("N")
496+
ax.set_theta_direction(-1)
497+
ax.errorbar(theta, r, xerr=0.1, yerr=0.1, capsize=7, fmt="o", c="seagreen")
498+
else:
499+
ax.errorbar(theta, r, xerr=0.1, yerr=0.1, capsize=7, fmt="o", c="seagreen")
500+
ax.set_theta_zero_location("N")
501+
ax.set_theta_direction(-1)

lib/matplotlib/tests/test_transforms.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
import matplotlib.pyplot as plt
1010
import matplotlib.patches as mpatches
1111
import matplotlib.transforms as mtransforms
12-
from matplotlib.transforms import Affine2D, Bbox, TransformedBbox
12+
from matplotlib.transforms import Affine2D, Bbox, TransformedBbox, ScaledRotation
1313
from matplotlib.path import Path
1414
from matplotlib.testing.decorators import image_comparison, check_figures_equal
15+
from unittest.mock import MagicMock
1516

1617

1718
class TestAffine2D:
@@ -1104,3 +1105,26 @@ def test_interval_contains_open():
11041105
assert not mtransforms.interval_contains_open((0, 1), -1)
11051106
assert not mtransforms.interval_contains_open((0, 1), 2)
11061107
assert mtransforms.interval_contains_open((1, 0), 0.5)
1108+
1109+
1110+
def test_initialization():
1111+
"""Test that the ScaledRotation object is initialized correctly."""
1112+
theta = 1.0 # Arbitrary theta value for testing
1113+
trans_shift = MagicMock() # Mock the trans_shift transformation
1114+
scaled_rot = ScaledRotation(theta, trans_shift)
1115+
assert scaled_rot._theta == theta
1116+
assert scaled_rot._trans_shift == trans_shift
1117+
assert scaled_rot._mtx is None
1118+
1119+
1120+
def test_get_matrix_invalid():
1121+
"""Test get_matrix when the matrix is invalid and needs recalculation."""
1122+
theta = 1.0
1123+
trans_shift = MagicMock(transform=MagicMock(return_value=[[theta, 0]]))
1124+
scaled_rot = ScaledRotation(theta, trans_shift)
1125+
scaled_rot._invalid = True
1126+
matrix = scaled_rot.get_matrix()
1127+
trans_shift.transform.assert_called_once_with([[theta, 0]])
1128+
assert matrix is not None
1129+
expected_rotation = Affine2D().rotate(theta).get_matrix()[:2, :2]
1130+
assert (matrix[:2, :2] == expected_rotation).all()

lib/matplotlib/transforms.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from matplotlib import _api
5050
from matplotlib._path import (
5151
affine_transform, count_bboxes_overlapping_bbox, update_path_extents)
52+
from matplotlib import transforms
5253
from .path import Path
5354

5455
DEBUG = False
@@ -2685,6 +2686,27 @@ def get_matrix(self):
26852686
return self._mtx
26862687

26872688

2689+
class ScaledRotation(Affine2DBase):
2690+
"""
2691+
A transformation that applies offset and direction
2692+
based on *trans_shift*.
2693+
"""
2694+
def __init__(self, theta, trans_shift):
2695+
super().__init__()
2696+
self._theta = theta
2697+
self._trans_shift = trans_shift
2698+
self._mtx = None
2699+
2700+
def get_matrix(self):
2701+
if self._invalid:
2702+
transformed_coords = self._trans_shift.transform([[self._theta, 0]])[0]
2703+
adjusted_theta = transformed_coords[0]
2704+
rotation = transforms.Affine2D().rotate(adjusted_theta)
2705+
self._mtx = IdentityTransform._mtx.copy()
2706+
self._mtx[:2, :2] = rotation.get_matrix()[:2, :2]
2707+
return self._mtx
2708+
2709+
26882710
class AffineDeltaTransform(Affine2DBase):
26892711
r"""
26902712
A transform wrapper for transforming displacements between pairs of points.

lib/matplotlib/transforms.pyi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,8 @@ def offset_copy(
334334
y: float = ...,
335335
units: Literal["inches", "points", "dots"] = ...,
336336
) -> Transform: ...
337+
338+
339+
class ScaledRotation(Affine2DBase):
340+
def __init__(self, theta: float, trans_shift: Transform) -> None: ...
341+
def get_matrix(self) -> np.ndarray: ...

0 commit comments

Comments
 (0)