diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index ecff24540690..5a1ee06d845a 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2443,6 +2443,20 @@ def _add_text(self, txt): self.stale = True return txt + def _point_in_data_domain(self, x, y): + """ + Check if the data point (x, y) is within the valid domain of the axes + scales. + + Returns False if the point is outside the data range + (e.g. negative coordinates with a log scale). + """ + for val, axis in zip([x, y], self._axis_map.values()): + vmin, vmax = axis.limit_range_for_scale(val, val) + if vmin != val or vmax != val: + return False + return True + def _update_line_limits(self, line): """ Figures out the data limit of the given line, updating `.Axes.dataLim`. diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index 551adbedbc61..46231dd70908 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -1226,3 +1226,15 @@ def test_ytick_rotation_mode(): tick.set_rotation(angle) plt.subplots_adjust(left=0.4, right=0.6, top=.99, bottom=.01) + + +def test_text_tightbbox_outside_scale_domain(): + # Test that text at positions outside the valid domain of axes scales + # (e.g., negative coordinates with log scale) returns a null bbox. + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.set_ylim(1, 100) + + invalid_text = ax.text(0, -5, 'invalid') + invalid_bbox = invalid_text.get_tightbbox(fig.canvas.get_renderer()) + assert not np.isfinite(invalid_bbox.width) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index d794cab1339b..7422affab2cb 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1060,6 +1060,15 @@ def get_window_extent(self, renderer=None, dpi=None): bbox = bbox.translated(x, y) return bbox + def get_tightbbox(self, renderer=None): + # Exclude text at data coordinates outside the valid domain of the axes + # scales (e.g., negative coordinates with a log scale). + if (self.axes + and self.get_transform() == self.axes.transData + and not self.axes._point_in_data_domain(*self.get_unitless_position())): + return Bbox.null() + return super().get_tightbbox(renderer) + def set_backgroundcolor(self, color): """ Set the background color of the text.