diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index d7a72fe4372b..6327dde85276 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2179,12 +2179,12 @@ def _convert_dx(dx, x0, xconv, convert): # removes the units from unit packages like `pint` that # wrap numpy arrays. try: - x0 = cbook._safe_first_non_none(x0) + x0 = cbook._safe_first_finite(x0) except (TypeError, IndexError, KeyError): pass try: - x = cbook._safe_first_non_none(xconv) + x = cbook._safe_first_finite(xconv) except (TypeError, IndexError, KeyError): x = xconv @@ -2813,11 +2813,11 @@ def broken_barh(self, xranges, yrange, **kwargs): """ # process the unit information if len(xranges): - xdata = cbook._safe_first_non_none(xranges) + xdata = cbook._safe_first_finite(xranges) else: xdata = None if len(yrange): - ydata = cbook._safe_first_non_none(yrange) + ydata = cbook._safe_first_finite(yrange) else: ydata = None self._process_unit_info( @@ -3459,10 +3459,10 @@ def _upcast_err(err): # safe_first_element because getitem is index-first not # location first on pandas objects so err[0] almost always # fails. - isinstance(cbook._safe_first_non_none(err), np.ndarray) + isinstance(cbook._safe_first_finite(err), np.ndarray) ): # Get the type of the first element - atype = type(cbook._safe_first_non_none(err)) + atype = type(cbook._safe_first_finite(err)) # Promote the outer container to match the inner container if atype is np.ndarray: # Converts using np.asarray, because data cannot @@ -4325,7 +4325,7 @@ def _parse_scatter_color_args(c, edgecolors, kwargs, xsize, c_is_string_or_strings = ( isinstance(c, str) or (np.iterable(c) and len(c) > 0 - and isinstance(cbook._safe_first_non_none(c), str))) + and isinstance(cbook._safe_first_finite(c), str))) def invalid_shape_exception(csize, xsize): return ValueError( diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 17d1cad3a753..171c7a6fff1f 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1703,19 +1703,29 @@ def safe_first_element(obj): This is an type-independent way of obtaining the first element, supporting both index access and the iterator protocol. """ - return _safe_first_non_none(obj, skip_none=False) + return _safe_first_finite(obj, skip_nonfinite=False) -def _safe_first_non_none(obj, skip_none=True): +def _safe_first_finite(obj, *, skip_nonfinite=True): """ - Return the first non-None element in *obj*. + Return the first non-None (and optionally finite) element in *obj*. + This is a method for internal use. This is an type-independent way of obtaining the first non-None element, supporting both index access and the iterator protocol. The first non-None element will be obtained when skip_none is True. """ - if skip_none is False: + def safe_isfinite(val): + if val is None: + return False + try: + return np.isfinite(val) if np.isscalar(val) else True + except TypeError: + # This is something that numpy can not make heads or tails + # of, assume "finite" + return True + if skip_nonfinite is False: if isinstance(obj, collections.abc.Iterator): # needed to accept `array.flat` as input. # np.flatiter reports as an instance of collections.Iterator @@ -1730,12 +1740,13 @@ def _safe_first_non_none(obj, skip_none=True): "as input") return next(iter(obj)) elif isinstance(obj, np.flatiter): + # TODO do the finite filtering on this return obj[0] elif isinstance(obj, collections.abc.Iterator): raise RuntimeError("matplotlib does not " "support generators as input") else: - return next(val for val in obj if val is not None) + return next(val for val in obj if safe_isfinite(val)) def sanitize_sequence(data): diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index f3a49759b9ff..ac1a7d03c687 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -1907,7 +1907,7 @@ def default_units(x, axis): x = x.ravel() try: - x = cbook._safe_first_non_none(x) + x = cbook._safe_first_finite(x) except (TypeError, StopIteration): pass diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a230af2ac1e0..628f9542aa42 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8109,3 +8109,27 @@ def test_get_xticklabel(): for ind in range(10): assert ax.get_xticklabels()[ind].get_text() == f'{ind}' assert ax.get_yticklabels()[ind].get_text() == f'{ind}' + + +def test_bar_leading_nan(): + + barx = np.arange(3, dtype=float) + barheights = np.array([0.5, 1.5, 2.0]) + barstarts = np.array([0.77]*3) + + barx[0] = np.NaN + + fig, ax = plt.subplots() + + bars = ax.bar(barx, barheights, bottom=barstarts) + + hbars = ax.barh(barx, barheights, left=barstarts) + + for bar_set in (bars, hbars): + # the first bar should have a nan in the location + nanful, *rest = bar_set + assert (~np.isfinite(nanful.xy)).any() + assert np.isfinite(nanful.get_width()) + for b in rest: + assert np.isfinite(b.xy).all() + assert np.isfinite(b.get_width()) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 70f2c2499418..26748d1a5798 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -602,7 +602,7 @@ def test_flatiter(): it = x.flat assert 0 == next(it) assert 1 == next(it) - ret = cbook._safe_first_non_none(it) + ret = cbook._safe_first_finite(it) assert ret == 0 assert 0 == next(it) @@ -758,7 +758,7 @@ def test_contiguous_regions(): def test_safe_first_element_pandas_series(pd): # deliberately create a pandas series with index not starting from 0 s = pd.Series(range(5), index=range(10, 15)) - actual = cbook._safe_first_non_none(s) + actual = cbook._safe_first_finite(s) assert actual == 0 @@ -893,5 +893,5 @@ def test_format_approx(): def test_safe_first_element_with_none(): datetime_lst = [date.today() + timedelta(days=i) for i in range(10)] datetime_lst[0] = None - actual = cbook._safe_first_non_none(datetime_lst) + actual = cbook._safe_first_finite(datetime_lst) assert actual is not None and actual == datetime_lst[1] diff --git a/lib/matplotlib/units.py b/lib/matplotlib/units.py index cf973034d341..2bcfcaf2eb12 100644 --- a/lib/matplotlib/units.py +++ b/lib/matplotlib/units.py @@ -197,7 +197,7 @@ def get_converter(self, x): except KeyError: pass try: # If cache lookup fails, look up based on first element... - first = cbook._safe_first_non_none(x) + first = cbook._safe_first_finite(x) except (TypeError, StopIteration): pass else: