From bfb9cc798d712d5b574a5ae7ed7cace245558303 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 12 Mar 2020 19:06:32 +0100 Subject: [PATCH] Make cursor text precision actually correspond to pointing precision. Currently, the cursor text (x=..., y=...) is typically displayed with much, much more precision than the mouse cursor has (typically, one pixel); IOW the last digits displayed are typically meaningless. Make ScalarFormatter.format_data_short convert the inter-pixel distance to data space and compute the corresponding number of significant digits to use (and don't drop the final zeroes if any, as they are now, well, significant). (A similar change could be applied to other formatters, e.g. LogFormatter.) --- .../2020-03-15-cursor-sigdigits.rst | 6 +++ lib/matplotlib/tests/test_ticker.py | 10 +++++ lib/matplotlib/ticker.py | 38 +++++++++++++++++-- 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 doc/users/next_whats_new/2020-03-15-cursor-sigdigits.rst diff --git a/doc/users/next_whats_new/2020-03-15-cursor-sigdigits.rst b/doc/users/next_whats_new/2020-03-15-cursor-sigdigits.rst new file mode 100644 index 000000000000..d340a3111d5a --- /dev/null +++ b/doc/users/next_whats_new/2020-03-15-cursor-sigdigits.rst @@ -0,0 +1,6 @@ +Cursor text now uses a number of significant digits matching pointing precision +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, the x/y position displayed by the cursor text would usually include +far more significant digits than the mouse pointing precision (typically one +pixel). This is now fixed for linear scales. diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 1495a1877e65..ae60b28267ac 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -558,6 +558,16 @@ def test_scilimits(self, sci_type, scilimits, lim, orderOfMag, fewticks): tmp_form.set_locs(ax.yaxis.get_majorticklocs()) assert orderOfMag == tmp_form.orderOfMagnitude + def test_cursor_precision(self): + fig, ax = plt.subplots() + ax.set_xlim(-1, 1) # Pointing precision of 0.001. + fmt = ax.xaxis.get_major_formatter().format_data_short + assert fmt(0.) == "0.000" + assert fmt(0.0123) == "0.012" + assert fmt(0.123) == "0.123" + assert fmt(1.23) == "1.230" + assert fmt(12.3) == "12.300" + class FakeAxis: """Allow Formatter to be called without having a "full" plot set up.""" diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index ff7bf99010d0..857c25c2fb8e 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -168,6 +168,7 @@ import logging import locale import math +from numbers import Integral import numpy as np @@ -587,11 +588,42 @@ def set_powerlimits(self, lims): def format_data_short(self, value): # docstring inherited + if isinstance(value, np.ma.MaskedArray) and value.mask: + return "" + if isinstance(value, Integral): + fmt = "%d" + else: + if self.axis.__name__ in ["xaxis", "yaxis"]: + if self.axis.__name__ == "xaxis": + axis_trf = self.axis.axes.get_xaxis_transform() + axis_inv_trf = axis_trf.inverted() + screen_xy = axis_trf.transform((value, 0)) + neighbor_values = axis_inv_trf.transform( + screen_xy + [[-1, 0], [+1, 0]])[:, 0] + else: # yaxis: + axis_trf = self.axis.axes.get_yaxis_transform() + axis_inv_trf = axis_trf.inverted() + screen_xy = axis_trf.transform((0, value)) + neighbor_values = axis_inv_trf.transform( + screen_xy + [[0, -1], [0, +1]])[:, 1] + delta = abs(neighbor_values - value).max() + else: + # Rough approximation: no more than 1e4 pixels. + delta = self.axis.get_view_interval() / 1e4 + # If e.g. value = 45.67 and delta = 0.02, then we want to round to + # 2 digits after the decimal point (floor(log10(0.02)) = -2); + # 45.67 contributes 2 digits before the decimal point + # (floor(log10(45.67)) + 1 = 2): the total is 4 significant digits. + # A value of 0 contributes 1 "digit" before the decimal point. + sig_digits = max( + 0, + (math.floor(math.log10(abs(value))) + 1 if value else 1) + - math.floor(math.log10(delta))) + fmt = f"%-#.{sig_digits}g" return ( - "" if isinstance(value, np.ma.MaskedArray) and value.mask else self.fix_minus( - locale.format_string("%-12g", (value,)) if self._useLocale else - "%-12g" % value)) + locale.format_string(fmt, (value,)) if self._useLocale else + fmt % value)) def format_data(self, value): # docstring inherited