Skip to content

Improve formatting of imshow() cursor data independently of colorbar. #20949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import matplotlib as mpl
from . import _api, cbook
from .cm import ScalarMappable
from .path import Path
from .transforms import (Bbox, IdentityTransform, Transform, TransformedBbox,
TransformedPatchPath, TransformedPath)
Expand Down Expand Up @@ -1258,17 +1259,18 @@ def format_cursor_data(self, data):
--------
get_cursor_data
"""
if np.ndim(data) == 0 and getattr(self, "colorbar", None):
if np.ndim(data) == 0 and isinstance(self, ScalarMappable):
# This block logically belongs to ScalarMappable, but can't be
# implemented in it because most ScalarMappable subclasses inherit
# from Artist first and from ScalarMappable second, so
# Artist.format_cursor_data would always have precedence over
# ScalarMappable.format_cursor_data.
return (
"["
+ cbook.strip_math(
self.colorbar.formatter.format_data_short(data)).strip()
+ "]")
n = self.cmap.N
# Midpoints of neighboring color intervals.
neighbors = self.norm.inverse(
(int(self.norm(data) * n) + np.array([0, 1])) / n)
delta = abs(neighbors - data).max()
return "[{:-#.{}g}]".format(data, cbook._g_sig_digits(data, delta))
else:
try:
data[0]
Expand Down
18 changes: 18 additions & 0 deletions lib/matplotlib/cbook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import functools
import gzip
import itertools
import math
import operator
import os
from pathlib import Path
Expand Down Expand Up @@ -2201,6 +2202,23 @@ def _format_approx(number, precision):
return f'{number:.{precision}f}'.rstrip('0').rstrip('.') or '0'


def _g_sig_digits(value, delta):
"""
Return the number of significant digits to %g-format *value*, assuming that
it is known with an error of *delta*.
"""
# 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.
# For inf or nan, the precision doesn't matter.
return max(
0,
(math.floor(math.log10(abs(value))) + 1 if value else 1)
- math.floor(math.log10(delta))) if math.isfinite(value) else 0


def _unikey_or_keysym_to_mplkey(unikey, keysym):
"""
Convert a Unicode key or X keysym to a Matplotlib key name.
Expand Down
14 changes: 4 additions & 10 deletions lib/matplotlib/projections/polar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1399,16 +1399,10 @@ def format_coord(self, theta, r):
# (as for linear axes), but for theta, use f-formatting as scientific
# notation doesn't make sense and the trailing dot is ugly.
def format_sig(value, delta, opt, fmt):
digits_post_decimal = math.floor(math.log10(delta))
digits_offset = (
# For "f", only count digits after decimal point.
0 if fmt == "f"
# For "g", offset by digits before the decimal point.
else math.floor(math.log10(abs(value))) + 1 if value
# For "g", 0 contributes 1 "digit" before the decimal point.
else 1)
fmt_prec = max(0, digits_offset - digits_post_decimal)
return f"{value:-{opt}.{fmt_prec}{fmt}}"
# For "f", only count digits after decimal point.
prec = (max(0, -math.floor(math.log10(delta))) if fmt == "f" else
cbook._g_sig_digits(value, delta))
return f"{value:-{opt}.{prec}{fmt}}"

return ('\N{GREEK SMALL LETTER THETA}={}\N{GREEK SMALL LETTER PI} '
'({}\N{DEGREE SIGN}), r={}').format(
Expand Down
18 changes: 5 additions & 13 deletions lib/matplotlib/tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,11 +337,11 @@ def test_cursor_data():


@pytest.mark.parametrize(
"data, text_without_colorbar, text_with_colorbar", [
([[10001, 10000]], "[1e+04]", "[10001]"),
([[.123, .987]], "[0.123]", "[0.123]"),
"data, text", [
([[10001, 10000]], "[10001.000]"),
([[.123, .987]], "[0.123]"),
])
def test_format_cursor_data(data, text_without_colorbar, text_with_colorbar):
def test_format_cursor_data(data, text):
from matplotlib.backend_bases import MouseEvent

fig, ax = plt.subplots()
Expand All @@ -350,15 +350,7 @@ def test_format_cursor_data(data, text_without_colorbar, text_with_colorbar):
xdisp, ydisp = ax.transData.transform([0, 0])
event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp)
assert im.get_cursor_data(event) == data[0][0]
assert im.format_cursor_data(im.get_cursor_data(event)) \
== text_without_colorbar

fig.colorbar(im)
fig.canvas.draw() # This is necessary to set up the colorbar formatter.

assert im.get_cursor_data(event) == data[0][0]
assert im.format_cursor_data(im.get_cursor_data(event)) \
== text_with_colorbar
assert im.format_cursor_data(im.get_cursor_data(event)) == text


@image_comparison(['image_clip'], style='mpl20')
Expand Down
11 changes: 1 addition & 10 deletions lib/matplotlib/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,16 +655,7 @@ def format_data_short(self, value):
# Rough approximation: no more than 1e4 divisions.
a, b = self.axis.get_view_interval()
delta = (b - a) / 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"
fmt = "%-#.{}g".format(cbook._g_sig_digits(value, delta))
return self._format_maybe_minus_and_locale(fmt, value)

def format_data(self, value):
Expand Down