diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index acde4fb179a2..31be2cbdf04e 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1625,12 +1625,41 @@ def _check_xy(self, renderer=None): if renderer is None: renderer = self.get_figure(root=True)._get_renderer() b = self.get_annotation_clip() + not_none = (b is None and + callable(self.xycoords) and + self.xycoords(renderer) is not None) if b or (b is None and self.xycoords == "data"): # check if self.xy is inside the Axes. xy_pixel = self._get_position_xy(renderer) return self.axes.contains_point(xy_pixel) + + if not_none: + if self.xycoords(renderer).is_null(): + return False + return True + def _check_xytext(self, renderer=None): + """Check whether the annotation text at *xy_pixel* can be drawn.""" + valid = True + + if self._xytext is None: + return valid + + # transforming the coordinates + x = self.convert_xunits(self._xytext[0]) + y = self.convert_yunits(self._xytext[1]) + unitless_coords = (x, y) + coords = np.array(self.get_transform().transform(unitless_coords)) + valid = not np.isnan(coords).any() and np.isfinite(coords).all() + # DEBUG + print("###") + print(coords) + + if not valid: + raise ValueError("xytext coordinates must be finite numbers") + return valid + def draggable(self, state=None, use_blit=False): """ Set whether the annotation is draggable with the mouse. @@ -1862,6 +1891,7 @@ def transform(renderer) -> Transform xy, xycoords=xycoords, annotation_clip=annotation_clip) + self._xytext = xytext # warn about wonky input data if (xytext is None and textcoords is not None and @@ -2027,11 +2057,16 @@ def draw(self, renderer): # docstring inherited if renderer is not None: self._renderer = renderer - if not self.get_visible() or not self._check_xy(renderer): + + visible = self.get_visible() and self._check_xy(renderer) + + + if not visible: return # Update text positions before `Text.draw` would, so that the # FancyArrowPatch is correctly positioned. self.update_positions(renderer) + self._check_xytext() self.update_bbox_position_size(renderer) if self.arrow_patch is not None: # FancyArrowPatch if (self.arrow_patch.get_figure(root=False) is None and @@ -2046,7 +2081,10 @@ def get_window_extent(self, renderer=None): # docstring inherited # This block is the same as in Text.get_window_extent, but we need to # set the renderer before calling update_positions(). - if not self.get_visible() or not self._check_xy(renderer): + visible = self.get_visible() and self._check_xy(renderer) + + + if not visible: return Bbox.unit() if renderer is not None: self._renderer = renderer @@ -2056,6 +2094,7 @@ def get_window_extent(self, renderer=None): raise RuntimeError('Cannot get window extent without renderer') self.update_positions(self._renderer) + self._check_xytext() text_bbox = Text.get_window_extent(self) bboxes = [text_bbox] diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 350113c56170..774bfceabadf 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -783,6 +783,14 @@ def frozen(self): frozen_bbox._minpos = self.minpos.copy() return frozen_bbox + def is_null(self): + """ + Return True if this bbox is a 'null' bbox, i.e. + [[inf, inf], [-inf, -inf]]. + """ + return (np.isposinf(self.x0) and np.isposinf(self.y0) and + np.isneginf(self.x1) and np.isneginf(self.y1)) + @staticmethod def unit(): """Create a new unit `Bbox` from (0, 0) to (1, 1)."""