diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index d2bc7bc7a281..189949638100 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -1764,6 +1764,14 @@ def test_latex(self, is_latex, usetex, expected): with mpl.rc_context(rc={'text.usetex': usetex}): assert fmt.format_pct(50, 100) == expected + def test_call_without_axis(self): + # With explicit decimals the axis view interval is not needed, so the + # formatter should format a value even when it is not attached to an + # axis, instead of raising an AttributeError. + with mpl.rc_context(rc={'text.usetex': False}): + assert mticker.PercentFormatter(xmax=1.0, decimals=1)(0.5) == '50.0%' + assert mticker.PercentFormatter(xmax=100, decimals=0)(50) == '50%' + def _impl_locale_comma(): try: diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index c82cd3bd0419..391e24dd2a06 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1638,8 +1638,15 @@ def __init__(self, xmax=100, decimals=None, symbol='%', is_latex=False): def __call__(self, x, pos=None): """Format the tick as a percentage with the appropriate scaling.""" - ax_min, ax_max = self.axis.get_view_interval() - display_range = abs(ax_max - ax_min) + if self.decimals is None: + # The display range of the axis is only needed to pick the number + # of decimals automatically; when ``decimals`` is set explicitly we + # avoid touching ``self.axis`` so the formatter also works when it + # is not attached to an axis. + ax_min, ax_max = self.axis.get_view_interval() + display_range = abs(ax_max - ax_min) + else: + display_range = None return self.fix_minus(self.format_pct(x, display_range)) def format_pct(self, x, display_range):