Skip to content

Commit 065769b

Browse files
authored
Merge pull request #16776 from anntzer/cursor-significant-digits
Make cursor text precision actually correspond to pointing precision.
2 parents 5eef7b1 + bfb9cc7 commit 065769b

File tree

3 files changed

+51
-3
lines changed

3 files changed

+51
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Cursor text now uses a number of significant digits matching pointing precision
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
Previously, the x/y position displayed by the cursor text would usually include
5+
far more significant digits than the mouse pointing precision (typically one
6+
pixel). This is now fixed for linear scales.

lib/matplotlib/tests/test_ticker.py

+10
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,16 @@ def test_scilimits(self, sci_type, scilimits, lim, orderOfMag, fewticks):
556556
tmp_form.set_locs(ax.yaxis.get_majorticklocs())
557557
assert orderOfMag == tmp_form.orderOfMagnitude
558558

559+
def test_cursor_precision(self):
560+
fig, ax = plt.subplots()
561+
ax.set_xlim(-1, 1) # Pointing precision of 0.001.
562+
fmt = ax.xaxis.get_major_formatter().format_data_short
563+
assert fmt(0.) == "0.000"
564+
assert fmt(0.0123) == "0.012"
565+
assert fmt(0.123) == "0.123"
566+
assert fmt(1.23) == "1.230"
567+
assert fmt(12.3) == "12.300"
568+
559569

560570
class FakeAxis:
561571
"""Allow Formatter to be called without having a "full" plot set up."""

lib/matplotlib/ticker.py

+35-3
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
import logging
169169
import locale
170170
import math
171+
from numbers import Integral
171172

172173
import numpy as np
173174

@@ -584,11 +585,42 @@ def set_powerlimits(self, lims):
584585

585586
def format_data_short(self, value):
586587
# docstring inherited
588+
if isinstance(value, np.ma.MaskedArray) and value.mask:
589+
return ""
590+
if isinstance(value, Integral):
591+
fmt = "%d"
592+
else:
593+
if self.axis.__name__ in ["xaxis", "yaxis"]:
594+
if self.axis.__name__ == "xaxis":
595+
axis_trf = self.axis.axes.get_xaxis_transform()
596+
axis_inv_trf = axis_trf.inverted()
597+
screen_xy = axis_trf.transform((value, 0))
598+
neighbor_values = axis_inv_trf.transform(
599+
screen_xy + [[-1, 0], [+1, 0]])[:, 0]
600+
else: # yaxis:
601+
axis_trf = self.axis.axes.get_yaxis_transform()
602+
axis_inv_trf = axis_trf.inverted()
603+
screen_xy = axis_trf.transform((0, value))
604+
neighbor_values = axis_inv_trf.transform(
605+
screen_xy + [[0, -1], [0, +1]])[:, 1]
606+
delta = abs(neighbor_values - value).max()
607+
else:
608+
# Rough approximation: no more than 1e4 pixels.
609+
delta = self.axis.get_view_interval() / 1e4
610+
# If e.g. value = 45.67 and delta = 0.02, then we want to round to
611+
# 2 digits after the decimal point (floor(log10(0.02)) = -2);
612+
# 45.67 contributes 2 digits before the decimal point
613+
# (floor(log10(45.67)) + 1 = 2): the total is 4 significant digits.
614+
# A value of 0 contributes 1 "digit" before the decimal point.
615+
sig_digits = max(
616+
0,
617+
(math.floor(math.log10(abs(value))) + 1 if value else 1)
618+
- math.floor(math.log10(delta)))
619+
fmt = f"%-#.{sig_digits}g"
587620
return (
588-
"" if isinstance(value, np.ma.MaskedArray) and value.mask else
589621
self.fix_minus(
590-
locale.format_string("%-12g", (value,)) if self._useLocale else
591-
"%-12g" % value))
622+
locale.format_string(fmt, (value,)) if self._useLocale else
623+
fmt % value))
592624

593625
def format_data(self, value):
594626
# docstring inherited

0 commit comments

Comments
 (0)