Skip to content

Commit 6e5a541

Browse files
authored
Fix BoundaryNorm cursor data output (matplotlib#22835)
* Fix matplotlib#21915 * Add test for BoundaryNorm cursor data output * Use slicing for cleaner code * Use resampled colormaps * Test against explicit, predefined labels
1 parent 50aaa7c commit 6e5a541

File tree

2 files changed

+178
-4
lines changed

2 files changed

+178
-4
lines changed

lib/matplotlib/artist.py

Lines changed: 15 additions & 4 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 .colors import BoundaryNorm
1516
from .cm import ScalarMappable
1617
from .path import Path
1718
from .transforms import (Bbox, IdentityTransform, Transform, TransformedBbox,
@@ -1305,10 +1306,20 @@ def format_cursor_data(self, data):
13051306
return "[]"
13061307
normed = self.norm(data)
13071308
if np.isfinite(normed):
1308-
# Midpoints of neighboring color intervals.
1309-
neighbors = self.norm.inverse(
1310-
(int(self.norm(data) * n) + np.array([0, 1])) / n)
1311-
delta = abs(neighbors - data).max()
1309+
if isinstance(self.norm, BoundaryNorm):
1310+
# not an invertible normalization mapping
1311+
cur_idx = np.argmin(np.abs(self.norm.boundaries - data))
1312+
neigh_idx = max(0, cur_idx - 1)
1313+
# use max diff to prevent delta == 0
1314+
delta = np.diff(
1315+
self.norm.boundaries[neigh_idx:cur_idx + 2]
1316+
).max()
1317+
1318+
else:
1319+
# Midpoints of neighboring color intervals.
1320+
neighbors = self.norm.inverse(
1321+
(int(normed * n) + np.array([0, 1])) / n)
1322+
delta = abs(neighbors - data).max()
13121323
g_sig_digits = cbook._g_sig_digits(data, delta)
13131324
else:
13141325
g_sig_digits = 3 # Consistent with default below.

lib/matplotlib/tests/test_artist.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import pytest
77

8+
from matplotlib import cm
9+
import matplotlib.colors as mcolors
810
import matplotlib.pyplot as plt
911
import matplotlib.patches as mpatches
1012
import matplotlib.lines as mlines
@@ -372,3 +374,164 @@ class MyArtist4(MyArtist3):
372374
pass
373375

374376
assert MyArtist4.set is MyArtist3.set
377+
378+
379+
def test_format_cursor_data_BoundaryNorm():
380+
"""Test if cursor data is correct when using BoundaryNorm."""
381+
X = np.empty((3, 3))
382+
X[0, 0] = 0.9
383+
X[0, 1] = 0.99
384+
X[0, 2] = 0.999
385+
X[1, 0] = -1
386+
X[1, 1] = 0
387+
X[1, 2] = 1
388+
X[2, 0] = 0.09
389+
X[2, 1] = 0.009
390+
X[2, 2] = 0.0009
391+
392+
# map range -1..1 to 0..256 in 0.1 steps
393+
fig, ax = plt.subplots()
394+
fig.suptitle("-1..1 to 0..256 in 0.1")
395+
norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 20), 256)
396+
img = ax.imshow(X, cmap='RdBu_r', norm=norm)
397+
398+
labels_list = [
399+
"[0.9]",
400+
"[1.]",
401+
"[1.]",
402+
"[-1.0]",
403+
"[0.0]",
404+
"[1.0]",
405+
"[0.09]",
406+
"[0.009]",
407+
"[0.0009]",
408+
]
409+
for v, label in zip(X.flat, labels_list):
410+
# label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.1))
411+
assert img.format_cursor_data(v) == label
412+
413+
plt.close()
414+
415+
# map range -1..1 to 0..256 in 0.01 steps
416+
fig, ax = plt.subplots()
417+
fig.suptitle("-1..1 to 0..256 in 0.01")
418+
cmap = cm.get_cmap('RdBu_r', 200)
419+
norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 200), 200)
420+
img = ax.imshow(X, cmap=cmap, norm=norm)
421+
422+
labels_list = [
423+
"[0.90]",
424+
"[0.99]",
425+
"[1.0]",
426+
"[-1.00]",
427+
"[0.00]",
428+
"[1.00]",
429+
"[0.09]",
430+
"[0.009]",
431+
"[0.0009]",
432+
]
433+
for v, label in zip(X.flat, labels_list):
434+
# label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.01))
435+
assert img.format_cursor_data(v) == label
436+
437+
plt.close()
438+
439+
# map range -1..1 to 0..256 in 0.01 steps
440+
fig, ax = plt.subplots()
441+
fig.suptitle("-1..1 to 0..256 in 0.001")
442+
cmap = cm.get_cmap('RdBu_r', 2000)
443+
norm = mcolors.BoundaryNorm(np.linspace(-1, 1, 2000), 2000)
444+
img = ax.imshow(X, cmap=cmap, norm=norm)
445+
446+
labels_list = [
447+
"[0.900]",
448+
"[0.990]",
449+
"[0.999]",
450+
"[-1.000]",
451+
"[0.000]",
452+
"[1.000]",
453+
"[0.090]",
454+
"[0.009]",
455+
"[0.0009]",
456+
]
457+
for v, label in zip(X.flat, labels_list):
458+
# label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.001))
459+
assert img.format_cursor_data(v) == label
460+
461+
plt.close()
462+
463+
# different testing data set with
464+
# out of bounds values for 0..1 range
465+
X = np.empty((7, 1))
466+
X[0] = -1.0
467+
X[1] = 0.0
468+
X[2] = 0.1
469+
X[3] = 0.5
470+
X[4] = 0.9
471+
X[5] = 1.0
472+
X[6] = 2.0
473+
474+
labels_list = [
475+
"[-1.0]",
476+
"[0.0]",
477+
"[0.1]",
478+
"[0.5]",
479+
"[0.9]",
480+
"[1.0]",
481+
"[2.0]",
482+
]
483+
484+
fig, ax = plt.subplots()
485+
fig.suptitle("noclip, neither")
486+
norm = mcolors.BoundaryNorm(
487+
np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='neither')
488+
img = ax.imshow(X, cmap='RdBu_r', norm=norm)
489+
for v, label in zip(X.flat, labels_list):
490+
# label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
491+
assert img.format_cursor_data(v) == label
492+
493+
plt.close()
494+
495+
fig, ax = plt.subplots()
496+
fig.suptitle("noclip, min")
497+
norm = mcolors.BoundaryNorm(
498+
np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='min')
499+
img = ax.imshow(X, cmap='RdBu_r', norm=norm)
500+
for v, label in zip(X.flat, labels_list):
501+
# label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
502+
assert img.format_cursor_data(v) == label
503+
504+
plt.close()
505+
506+
fig, ax = plt.subplots()
507+
fig.suptitle("noclip, max")
508+
norm = mcolors.BoundaryNorm(
509+
np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='max')
510+
img = ax.imshow(X, cmap='RdBu_r', norm=norm)
511+
for v, label in zip(X.flat, labels_list):
512+
# label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
513+
assert img.format_cursor_data(v) == label
514+
515+
plt.close()
516+
517+
fig, ax = plt.subplots()
518+
fig.suptitle("noclip, both")
519+
norm = mcolors.BoundaryNorm(
520+
np.linspace(0, 1, 4, endpoint=True), 256, clip=False, extend='both')
521+
img = ax.imshow(X, cmap='RdBu_r', norm=norm)
522+
for v, label in zip(X.flat, labels_list):
523+
# label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
524+
assert img.format_cursor_data(v) == label
525+
526+
plt.close()
527+
528+
fig, ax = plt.subplots()
529+
fig.suptitle("clip, neither")
530+
norm = mcolors.BoundaryNorm(
531+
np.linspace(0, 1, 4, endpoint=True), 256, clip=True, extend='neither')
532+
img = ax.imshow(X, cmap='RdBu_r', norm=norm)
533+
for v, label in zip(X.flat, labels_list):
534+
# label = "[{:-#.{}g}]".format(v, cbook._g_sig_digits(v, 0.33))
535+
assert img.format_cursor_data(v) == label
536+
537+
plt.close()

0 commit comments

Comments
 (0)