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

Skip to content

Commit decda3b

Browse files
committed
Improve formatting of imshow() cursor data independently of colorbar.
Currently, when a colorbar is present, the cursor data under imshow() is formatted using the colorbar's cursor formatter (the idea being that that formatter should be able to "smartly" take normalization limits into account); if no colorbar is present a fixed format string ("%0.3g") is used. In fact, there is a better scale that defines the number of significant digits one should display in an imshow cursor data: it arises because colormaps are discrete (usually with 256 colors, but in any case the value is available as `cmap.N`). This quantization tells us, for a given value, by how much one needs to move before the underlying color changes (at all); that step size can be used to to determine a number of significant digits to display. (Even if that's not necessarily always the best number, it should at least be reasonable *given the user's choice of normalization*.) Also, note that because ScalarFormatter has now changed to take pixel size into account when determining *its* number of significant digits, the previous approach of relying on the colorbar formatter has become a less good approximation, as that means that the number of digits displayed for an imshow() cursor could depend on the physical size of the associated colorbar (if present). Also factor out and reuse some logic to compute the number of significant digits to use in format strings for given value/error pairs, already used by the linear and polar tickers.
1 parent 1d12973 commit decda3b

File tree

5 files changed

+36
-39
lines changed

5 files changed

+36
-39
lines changed

lib/matplotlib/artist.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import matplotlib as mpl
1414
from . import _api, cbook
15+
from .cm import ScalarMappable
1516
from .path import Path
1617
from .transforms import (Bbox, IdentityTransform, Transform, TransformedBbox,
1718
TransformedPatchPath, TransformedPath)
@@ -1258,17 +1259,18 @@ def format_cursor_data(self, data):
12581259
--------
12591260
get_cursor_data
12601261
"""
1261-
if np.ndim(data) == 0 and getattr(self, "colorbar", None):
1262+
if np.ndim(data) == 0 and isinstance(self, ScalarMappable):
12621263
# This block logically belongs to ScalarMappable, but can't be
12631264
# implemented in it because most ScalarMappable subclasses inherit
12641265
# from Artist first and from ScalarMappable second, so
12651266
# Artist.format_cursor_data would always have precedence over
12661267
# ScalarMappable.format_cursor_data.
1267-
return (
1268-
"["
1269-
+ cbook.strip_math(
1270-
self.colorbar.formatter.format_data_short(data)).strip()
1271-
+ "]")
1268+
n = self.cmap.N
1269+
# Midpoints of neighboring color intervals.
1270+
neighbors = self.norm.inverse(
1271+
(int(self.norm(data) * n) + np.array([0, 1])) / n)
1272+
delta = abs(neighbors - data).max()
1273+
return "[{:-#.{}g}]".format(data, cbook._g_sig_digits(data, delta))
12721274
else:
12731275
try:
12741276
data[0]

lib/matplotlib/cbook/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import functools
1313
import gzip
1414
import itertools
15+
import math
1516
import operator
1617
import os
1718
from pathlib import Path
@@ -2201,6 +2202,23 @@ def _format_approx(number, precision):
22012202
return f'{number:.{precision}f}'.rstrip('0').rstrip('.') or '0'
22022203

22032204

2205+
def _g_sig_digits(value, delta):
2206+
"""
2207+
Return the number of significant digits to %g-format *value*, assuming that
2208+
it is known with an error of *delta*.
2209+
"""
2210+
# If e.g. value = 45.67 and delta = 0.02, then we want to round to 2 digits
2211+
# after the decimal point (floor(log10(0.02)) = -2); 45.67 contributes 2
2212+
# digits before the decimal point (floor(log10(45.67)) + 1 = 2): the total
2213+
# is 4 significant digits. A value of 0 contributes 1 "digit" before the
2214+
# decimal point.
2215+
# For inf or nan, the precision doesn't matter.
2216+
return max(
2217+
0,
2218+
(math.floor(math.log10(abs(value))) + 1 if value else 1)
2219+
- math.floor(math.log10(delta))) if math.isfinite(value) else 0
2220+
2221+
22042222
def _unikey_or_keysym_to_mplkey(unikey, keysym):
22052223
"""
22062224
Convert a Unicode key or X keysym to a Matplotlib key name.

lib/matplotlib/projections/polar.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,16 +1399,10 @@ def format_coord(self, theta, r):
13991399
# (as for linear axes), but for theta, use f-formatting as scientific
14001400
# notation doesn't make sense and the trailing dot is ugly.
14011401
def format_sig(value, delta, opt, fmt):
1402-
digits_post_decimal = math.floor(math.log10(delta))
1403-
digits_offset = (
1404-
# For "f", only count digits after decimal point.
1405-
0 if fmt == "f"
1406-
# For "g", offset by digits before the decimal point.
1407-
else math.floor(math.log10(abs(value))) + 1 if value
1408-
# For "g", 0 contributes 1 "digit" before the decimal point.
1409-
else 1)
1410-
fmt_prec = max(0, digits_offset - digits_post_decimal)
1411-
return f"{value:-{opt}.{fmt_prec}{fmt}}"
1402+
# For "f", only count digits after decimal point.
1403+
prec = (max(0, -math.floor(math.log10(delta))) if fmt == "f" else
1404+
cbook._g_sig_digits(value, delta))
1405+
return f"{value:-{opt}.{prec}{fmt}}"
14121406

14131407
return ('\N{GREEK SMALL LETTER THETA}={}\N{GREEK SMALL LETTER PI} '
14141408
'({}\N{DEGREE SIGN}), r={}').format(

lib/matplotlib/tests/test_image.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -337,11 +337,11 @@ def test_cursor_data():
337337

338338

339339
@pytest.mark.parametrize(
340-
"data, text_without_colorbar, text_with_colorbar", [
341-
([[10001, 10000]], "[1e+04]", "[10001]"),
342-
([[.123, .987]], "[0.123]", "[0.123]"),
340+
"data, text", [
341+
([[10001, 10000]], "[10001.000]"),
342+
([[.123, .987]], "[0.123]"),
343343
])
344-
def test_format_cursor_data(data, text_without_colorbar, text_with_colorbar):
344+
def test_format_cursor_data(data, text):
345345
from matplotlib.backend_bases import MouseEvent
346346

347347
fig, ax = plt.subplots()
@@ -350,15 +350,7 @@ def test_format_cursor_data(data, text_without_colorbar, text_with_colorbar):
350350
xdisp, ydisp = ax.transData.transform([0, 0])
351351
event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
352352
assert im.get_cursor_data(event) == data[0][0]
353-
assert im.format_cursor_data(im.get_cursor_data(event)) \
354-
== text_without_colorbar
355-
356-
fig.colorbar(im)
357-
fig.canvas.draw() # This is necessary to set up the colorbar formatter.
358-
359-
assert im.get_cursor_data(event) == data[0][0]
360-
assert im.format_cursor_data(im.get_cursor_data(event)) \
361-
== text_with_colorbar
353+
assert im.format_cursor_data(im.get_cursor_data(event)) == text
362354

363355

364356
@image_comparison(['image_clip'], style='mpl20')

lib/matplotlib/ticker.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -655,16 +655,7 @@ def format_data_short(self, value):
655655
# Rough approximation: no more than 1e4 divisions.
656656
a, b = self.axis.get_view_interval()
657657
delta = (b - a) / 1e4
658-
# If e.g. value = 45.67 and delta = 0.02, then we want to round to
659-
# 2 digits after the decimal point (floor(log10(0.02)) = -2);
660-
# 45.67 contributes 2 digits before the decimal point
661-
# (floor(log10(45.67)) + 1 = 2): the total is 4 significant digits.
662-
# A value of 0 contributes 1 "digit" before the decimal point.
663-
sig_digits = max(
664-
0,
665-
(math.floor(math.log10(abs(value))) + 1 if value else 1)
666-
- math.floor(math.log10(delta)))
667-
fmt = f"%-#.{sig_digits}g"
658+
fmt = "%-#.{}g".format(cbook._g_sig_digits(value, delta))
668659
return self._format_maybe_minus_and_locale(fmt, value)
669660

670661
def format_data(self, value):

0 commit comments

Comments
 (0)