Skip to content

Commit 86b9928

Browse files
authored
Merge branch 'matplotlib:main' into fixSpineBounds
2 parents 72e83eb + 192b7c2 commit 86b9928

File tree

9 files changed

+73
-19
lines changed

9 files changed

+73
-19
lines changed

lib/matplotlib/__init__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -743,12 +743,11 @@ def __setitem__(self, key, val):
743743
and val is rcsetup._auto_backend_sentinel
744744
and "backend" in self):
745745
return
746+
valid_key = _api.check_getitem(
747+
self.validate, rcParam=key, _error_cls=KeyError
748+
)
746749
try:
747-
cval = self.validate[key](val)
748-
except KeyError as err:
749-
raise KeyError(
750-
f"{key} is not a valid rc parameter (see rcParams.keys() for "
751-
f"a list of valid parameters)") from err
750+
cval = valid_key(val)
752751
except ValueError as ve:
753752
raise ValueError(f"Key {key}: {ve}") from None
754753
self._set(key, cval)

lib/matplotlib/_api/__init__.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1111
"""
1212

13+
import difflib
1314
import functools
1415
import itertools
1516
import pathlib
@@ -174,12 +175,17 @@ def check_shape(shape, /, **kwargs):
174175
)
175176

176177

177-
def check_getitem(mapping, /, **kwargs):
178+
def check_getitem(mapping, /, _error_cls=ValueError, **kwargs):
178179
"""
179180
*kwargs* must consist of a single *key, value* pair. If *key* is in
180181
*mapping*, return ``mapping[value]``; else, raise an appropriate
181182
ValueError.
182183
184+
Parameters
185+
----------
186+
_error_cls :
187+
Class of error to raise.
188+
183189
Examples
184190
--------
185191
>>> _api.check_getitem({"foo": "bar"}, arg=arg)
@@ -190,9 +196,14 @@ def check_getitem(mapping, /, **kwargs):
190196
try:
191197
return mapping[v]
192198
except KeyError:
193-
raise ValueError(
194-
f"{v!r} is not a valid value for {k}; supported values are "
195-
f"{', '.join(map(repr, mapping))}") from None
199+
if len(mapping) > 5:
200+
if len(best := difflib.get_close_matches(v, mapping.keys(), cutoff=0.5)):
201+
suggestion = f"Did you mean one of {best}?"
202+
else:
203+
suggestion = ""
204+
else:
205+
suggestion = f"Supported values are {', '.join(map(repr, mapping))}"
206+
raise _error_cls(f"{v!r} is not a valid value for {k}. {suggestion}") from None
196207

197208

198209
def caching_module_getattr(cls):

lib/matplotlib/_api/__init__.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ def check_in_list(
4242
values: Sequence[Any], /, *, _print_supported_values: bool = ..., **kwargs: Any
4343
) -> None: ...
4444
def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ...
45-
def check_getitem(mapping: Mapping[Any, Any], /, **kwargs: Any) -> Any: ...
45+
def check_getitem(
46+
mapping: Mapping[Any, _T], /, _error_cls: type[Exception], **kwargs: Any
47+
) -> _T: ...
4648
def caching_module_getattr(cls: type) -> Callable[[str], Any]: ...
4749
@overload
4850
def define_aliases(

lib/matplotlib/cm.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,8 @@ def __init__(self, cmaps):
9292
self._builtin_cmaps = tuple(cmaps)
9393

9494
def __getitem__(self, item):
95-
try:
96-
return self._cmaps[item].copy()
97-
except KeyError:
98-
raise KeyError(f"{item!r} is not a known colormap name") from None
95+
cmap = _api.check_getitem(self._cmaps, colormap=item, _error_cls=KeyError)
96+
return cmap.copy()
9997

10098
def __iter__(self):
10199
return iter(self._cmaps)

lib/matplotlib/patches.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,8 @@ def set_linewidth(self, w):
459459
w : float or None
460460
"""
461461
w = mpl._val_or_rc(w, 'patch.linewidth')
462-
self._linewidth = float(w)
462+
w = float(w)
463+
self._linewidth = w
463464
self._dash_pattern = mlines._scale_dashes(*self._unscaled_dash_pattern, w)
464465
self.stale = True
465466

lib/matplotlib/tests/test_colors.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,3 +1876,13 @@ def scaled(self):
18761876
axes[0,1].pcolor(r, colorizer=colorizer)
18771877
axes[1,0].contour(r, colorizer=colorizer)
18781878
axes[1,1].contourf(r, colorizer=colorizer)
1879+
1880+
1881+
def test_close_error_name():
1882+
with pytest.raises(
1883+
KeyError,
1884+
match=(
1885+
"'grays' is not a valid value for colormap. "
1886+
"Did you mean one of ['gray', 'Grays', 'gray_r']?"
1887+
)):
1888+
matplotlib.colormaps["grays"]

lib/matplotlib/tests/test_style.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ def test_context_with_badparam():
150150
with style.context({PARAM: other_value}):
151151
assert mpl.rcParams[PARAM] == other_value
152152
x = style.context({PARAM: original_value, 'badparam': None})
153-
with pytest.raises(KeyError):
153+
with pytest.raises(
154+
KeyError, match="\'badparam\' is not a valid value for rcParam. "
155+
):
154156
with x:
155157
pass
156158
assert mpl.rcParams[PARAM] == other_value

lib/matplotlib/tests/test_widgets.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import operator
44
from unittest import mock
55

6-
from matplotlib.backend_bases import MouseEvent
6+
from matplotlib.backend_bases import MouseEvent, DrawEvent
77
import matplotlib.colors as mcolors
88
import matplotlib.widgets as widgets
99
import matplotlib.pyplot as plt
@@ -1757,3 +1757,26 @@ def test_MultiCursor(horizOn, vertOn):
17571757
assert l.get_xdata() == (.5, .5)
17581758
for l in multi.hlines:
17591759
assert l.get_ydata() == (.25, .25)
1760+
1761+
1762+
def test_parent_axes_removal():
1763+
1764+
fig, (ax_radio, ax_checks) = plt.subplots(1, 2)
1765+
1766+
radio = widgets.RadioButtons(ax_radio, ['1', '2'], 0)
1767+
checks = widgets.CheckButtons(ax_checks, ['1', '2'], [True, False])
1768+
1769+
ax_checks.remove()
1770+
ax_radio.remove()
1771+
with io.BytesIO() as out:
1772+
# verify that saving does not raise
1773+
fig.savefig(out, format='raw')
1774+
1775+
# verify that this method which is triggered by a draw_event callback when
1776+
# blitting is enabled does not raise. Calling private methods is simpler
1777+
# than trying to force blitting to be enabled with Agg or use a GUI
1778+
# framework.
1779+
renderer = fig._get_renderer()
1780+
evt = DrawEvent('draw_event', fig.canvas, renderer)
1781+
radio._clear(evt)
1782+
checks._clear(evt)

lib/matplotlib/widgets.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ def __init__(self, ax):
117117
self.ax = ax
118118
self._cids = []
119119

120-
canvas = property(lambda self: self.ax.get_figure(root=True).canvas)
120+
canvas = property(
121+
lambda self: getattr(self.ax.get_figure(root=True), 'canvas', None)
122+
)
121123

122124
def connect_event(self, event, callback):
123125
"""
@@ -144,6 +146,10 @@ def _get_data_coords(self, event):
144146
return ((event.xdata, event.ydata) if event.inaxes is self.ax
145147
else self.ax.transData.inverted().transform((event.x, event.y)))
146148

149+
def ignore(self, event):
150+
# docstring inherited
151+
return super().ignore(event) or self.canvas is None
152+
147153

148154
class Button(AxesWidget):
149155
"""
@@ -2181,7 +2187,9 @@ def connect_default_events(self):
21812187

21822188
def ignore(self, event):
21832189
# docstring inherited
2184-
if not self.active or not self.ax.get_visible():
2190+
if super().ignore(event):
2191+
return True
2192+
if not self.ax.get_visible():
21852193
return True
21862194
# If canvas was locked
21872195
if not self.canvas.widgetlock.available(self):

0 commit comments

Comments
 (0)