From 190937839d810759d3459b3f862a2902227a444e Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Sat, 2 Aug 2025 20:34:44 -0400 Subject: [PATCH 01/12] check xytext --- lib/matplotlib/text.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index acde4fb179a2..8037108373ca 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -5,6 +5,7 @@ import functools import logging import math +import numbers from numbers import Real import weakref @@ -1862,6 +1863,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,7 +2029,12 @@ 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) and + self._check_xytext()) + + if not visible: return # Update text positions before `Text.draw` would, so that the # FancyArrowPatch is correctly positioned. @@ -2046,7 +2053,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) and + self._check_xytext()) + if not visible: return Bbox.unit() if renderer is not None: self._renderer = renderer @@ -2072,4 +2082,10 @@ def get_tightbbox(self, renderer=None): return super().get_tightbbox(renderer) + def _check_xytext(self, renderer=None): + """Check whether the annotation at *xy_pixel* should be drawn.""" + valid = True + if all(isinstance(xyt, numbers.Number) for xyt in self.xytext): + valid = not np.isnan(self.xytext).any() + return valid _docstring.interpd.register(Annotation=Annotation.__init__.__doc__) From 07130be50aaf53e38e284c6fd478ae0348f740f1 Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Sat, 2 Aug 2025 21:22:37 -0400 Subject: [PATCH 02/12] fix case xytext is None --- lib/matplotlib/text.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 8037108373ca..da14cb3e94aa 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -2085,6 +2085,9 @@ def get_tightbbox(self, renderer=None): def _check_xytext(self, renderer=None): """Check whether the annotation at *xy_pixel* should be drawn.""" valid = True + if self.xytext is None: + return valid + if all(isinstance(xyt, numbers.Number) for xyt in self.xytext): valid = not np.isnan(self.xytext).any() return valid From 562336443007a5f9cd76ebce12bb5b13d26913c8 Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Sat, 2 Aug 2025 21:44:58 -0400 Subject: [PATCH 03/12] check_xytext finite --- lib/matplotlib/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index da14cb3e94aa..0544d1bc6f6c 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -2089,6 +2089,6 @@ def _check_xytext(self, renderer=None): return valid if all(isinstance(xyt, numbers.Number) for xyt in self.xytext): - valid = not np.isnan(self.xytext).any() + valid = not np.isnan(self.xytext).any() and np.isfinite(self.xytext).all() return valid _docstring.interpd.register(Annotation=Annotation.__init__.__doc__) From 5c979f85c391b39649978b9b19e56970c01946fb Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Mon, 4 Aug 2025 19:22:16 -0400 Subject: [PATCH 04/12] raise ValueError for invalid xytext --- lib/matplotlib/text.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 0544d1bc6f6c..95a9ff574592 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -2030,9 +2030,9 @@ def draw(self, renderer): if renderer is not None: self._renderer = renderer - visible = (self.get_visible() and - self._check_xy(renderer) and - self._check_xytext()) + visible = self.get_visible() and self._check_xy(renderer) + + self._check_xytext() if not visible: return @@ -2053,9 +2053,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(). - visible = (self.get_visible() and - self._check_xy(renderer) and - self._check_xytext()) + visible = self.get_visible() and self._check_xy(renderer) + + self._check_xytext() + if not visible: return Bbox.unit() if renderer is not None: @@ -2085,10 +2086,16 @@ def get_tightbbox(self, renderer=None): def _check_xytext(self, renderer=None): """Check whether the annotation at *xy_pixel* should be drawn.""" valid = True + if self.xytext is None: return valid - if all(isinstance(xyt, numbers.Number) for xyt in self.xytext): - valid = not np.isnan(self.xytext).any() and np.isfinite(self.xytext).all() + coords = np.array(self.get_transform().transform(self.xytext)) + + if all(isinstance(xyt, numbers.Number) for xyt in coords): + valid = not np.isnan(coords).any() and np.isfinite(coords).all() + + if not valid: + raise ValueError("xytext coordinates must be finite numbers") return valid _docstring.interpd.register(Annotation=Annotation.__init__.__doc__) From f680e3f9f7ec2806f1a3e1b6c0f7b8a1771b5532 Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Tue, 5 Aug 2025 17:50:49 -0400 Subject: [PATCH 05/12] fix transformation error --- lib/matplotlib/text.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 95a9ff574592..ece87d6304f1 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -2084,13 +2084,16 @@ def get_tightbbox(self, renderer=None): def _check_xytext(self, renderer=None): - """Check whether the annotation at *xy_pixel* should be drawn.""" + """Check whether the annotation text at *xy_pixel* should be drawn.""" valid = True if self.xytext is None: return valid - coords = np.array(self.get_transform().transform(self.xytext)) + try: + coords = np.array(self.get_transform().transform(self.xytext)) + except TypeError: + valid = False if all(isinstance(xyt, numbers.Number) for xyt in coords): valid = not np.isnan(coords).any() and np.isfinite(coords).all() From f64a90f476ef74d4d386f8081f5e58cdbe03b3da Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Tue, 5 Aug 2025 18:19:16 -0400 Subject: [PATCH 06/12] fix unbound coords --- lib/matplotlib/text.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index ece87d6304f1..77477558fd35 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -2092,11 +2092,12 @@ def _check_xytext(self, renderer=None): try: coords = np.array(self.get_transform().transform(self.xytext)) + if all(isinstance(xyt, numbers.Number) for xyt in coords): + valid = not np.isnan(coords).any() and np.isfinite(coords).all() + except TypeError: valid = False - if all(isinstance(xyt, numbers.Number) for xyt in coords): - valid = not np.isnan(coords).any() and np.isfinite(coords).all() if not valid: raise ValueError("xytext coordinates must be finite numbers") From b03ea7bc2b2db4f5feccf115c49235f3aa0893cb Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Tue, 5 Aug 2025 19:48:56 -0400 Subject: [PATCH 07/12] fix support for units --- lib/matplotlib/text.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 77477558fd35..23a27316b532 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -2091,13 +2091,19 @@ def _check_xytext(self, renderer=None): return valid try: + # transforming the coordinates coords = np.array(self.get_transform().transform(self.xytext)) if all(isinstance(xyt, numbers.Number) for xyt in coords): valid = not np.isnan(coords).any() and np.isfinite(coords).all() - except TypeError: - valid = False - + # If transformation fails, check raw coordinates + if all(isinstance(xyt, numbers.Number) for xyt in self.xytext): + is_numerical = not np.isnan(self.xytext).any() + finite = np.isfinite(self.xytext).all() + valid = is_numerical and finite + else: + # For non-number coordinates (like units), assume valid + valid = True if not valid: raise ValueError("xytext coordinates must be finite numbers") From 8cf348aa9f7e95f3b2ef81689c6e1ff9b48ad3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Gutierrez?= <12andresgutierrez04@gmail.com> Date: Mon, 11 Aug 2025 09:30:41 -0400 Subject: [PATCH 08/12] Update lib/matplotlib/text.py Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- lib/matplotlib/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 23a27316b532..f94403af0714 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -2084,7 +2084,7 @@ def get_tightbbox(self, renderer=None): def _check_xytext(self, renderer=None): - """Check whether the annotation text at *xy_pixel* should be drawn.""" + """Check whether the annotation text at *xy_pixel* can be drawn.""" valid = True if self.xytext is None: From a80baa2715ee14eabf82545373f92e771980aa54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Gutierrez?= <12andresgutierrez04@gmail.com> Date: Sun, 17 Aug 2025 11:33:09 -0400 Subject: [PATCH 09/12] Update lib/matplotlib/text.py Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- lib/matplotlib/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index f94403af0714..796c69618038 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1863,7 +1863,7 @@ def transform(renderer) -> Transform xy, xycoords=xycoords, annotation_clip=annotation_clip) - self.xytext = xytext + self._xytext = xytext # warn about wonky input data if (xytext is None and textcoords is not None and From 48559ca3101db90c4e3cda1938bc554ebfad64fe Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Sun, 17 Aug 2025 19:22:20 -0400 Subject: [PATCH 10/12] applying suggestions --- lib/matplotlib/text.py | 57 ++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 796c69618038..c09490a9ce84 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1632,6 +1632,34 @@ def _check_xy(self, renderer=None): return self.axes.contains_point(xy_pixel) 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 + + try: + # transforming the coordinates + coords = np.array(self.get_transform().transform(self._xytext)) + valid = not np.isnan(coords).any() and np.isfinite(coords).all() + # DEBUG + print("###") + print(coords) + except TypeError: + # If transformation fails, check raw coordinates + if all(isinstance(xyt, numbers.Number) for xyt in self._xytext): + is_numerical = not np.isnan(self._xytext).any() + finite = np.isfinite(self._xytext).all() + valid = is_numerical and finite + else: + # For non-number coordinates (like units), assume valid + valid = True + + 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. @@ -2032,13 +2060,13 @@ def draw(self, renderer): visible = self.get_visible() and self._check_xy(renderer) - self._check_xytext() 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 @@ -2055,7 +2083,6 @@ def get_window_extent(self, renderer=None): # set the renderer before calling update_positions(). visible = self.get_visible() and self._check_xy(renderer) - self._check_xytext() if not visible: return Bbox.unit() @@ -2067,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] @@ -2083,29 +2111,4 @@ def get_tightbbox(self, renderer=None): return super().get_tightbbox(renderer) - 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 - - try: - # transforming the coordinates - coords = np.array(self.get_transform().transform(self.xytext)) - if all(isinstance(xyt, numbers.Number) for xyt in coords): - valid = not np.isnan(coords).any() and np.isfinite(coords).all() - except TypeError: - # If transformation fails, check raw coordinates - if all(isinstance(xyt, numbers.Number) for xyt in self.xytext): - is_numerical = not np.isnan(self.xytext).any() - finite = np.isfinite(self.xytext).all() - valid = is_numerical and finite - else: - # For non-number coordinates (like units), assume valid - valid = True - - if not valid: - raise ValueError("xytext coordinates must be finite numbers") - return valid _docstring.interpd.register(Annotation=Annotation.__init__.__doc__) From 45bea11ded423071d9b4adbc95580b38c5636ac9 Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Thu, 21 Aug 2025 19:59:47 -0400 Subject: [PATCH 11/12] replacing try-except loop to unit convertion for xytext --- lib/matplotlib/text.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index c09490a9ce84..78bcdfa07653 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -5,7 +5,6 @@ import functools import logging import math -import numbers from numbers import Real import weakref @@ -1639,22 +1638,15 @@ def _check_xytext(self, renderer=None): if self._xytext is None: return valid - try: - # transforming the coordinates - coords = np.array(self.get_transform().transform(self._xytext)) - valid = not np.isnan(coords).any() and np.isfinite(coords).all() - # DEBUG - print("###") - print(coords) - except TypeError: - # If transformation fails, check raw coordinates - if all(isinstance(xyt, numbers.Number) for xyt in self._xytext): - is_numerical = not np.isnan(self._xytext).any() - finite = np.isfinite(self._xytext).all() - valid = is_numerical and finite - else: - # For non-number coordinates (like units), assume valid - valid = True + # 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") From 27f26d2165e9f54d367706004f030db7e4e0bbe4 Mon Sep 17 00:00:00 2001 From: Andres Gutierrrez Date: Sun, 7 Sep 2025 18:43:11 -0400 Subject: [PATCH 12/12] validate if Bbox is null --- lib/matplotlib/text.py | 8 ++++++++ lib/matplotlib/transforms.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 78bcdfa07653..31be2cbdf04e 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1625,10 +1625,18 @@ 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): 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)."""