Skip to content

BUG: Fix text appearing far outside valid axis scale range#31061

Open
scottshambaugh wants to merge 2 commits intomatplotlib:mainfrom
scottshambaugh:log_text_negative_coord
Open

BUG: Fix text appearing far outside valid axis scale range#31061
scottshambaugh wants to merge 2 commits intomatplotlib:mainfrom
scottshambaugh:log_text_negative_coord

Conversation

@scottshambaugh
Copy link
Contributor

@scottshambaugh scottshambaugh commented Feb 2, 2026

PR summary

Closes #31054

Text didn't have a get_tightbbox override yet, so this adds that with some handling for no-show cases. The _in_axes_domain check is not exclusive to text and may be useful elsewhere, so was given to Artist more broadly.

The plot in the original issue renders correctly now, and I can confirm it was broken for me before:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
y = np.sin(x) * 10

fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_yscale('log')
ax.text(5, -5, "I am at y = -5", color='red')

plt.savefig("test_gh31054.png", bbox_inches='tight')
test_gh31054

PR checklist

@scottshambaugh scottshambaugh changed the title BIG: Fix text appearing far outside valid axis scale range BUG: Fix text appearing far outside valid axis scale range Feb 2, 2026
@scottshambaugh scottshambaugh force-pushed the log_text_negative_coord branch from 0d3ef72 to 7ef9295 Compare February 2, 2026 23:26
@scottshambaugh scottshambaugh force-pushed the log_text_negative_coord branch from 7ef9295 to 4ffb8d3 Compare February 3, 2026 00:13
@scottshambaugh scottshambaugh marked this pull request as ready for review February 3, 2026 00:26
return bbox

def get_tightbbox(self, renderer=None):
# Exclude text at data coordinates outside the valid domain of the axes
Copy link
Contributor Author

@scottshambaugh scottshambaugh Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        if not self.get_visible() or self.get_text() == "":
            return Bbox.null()

I had originally included these lines, but they were changing a half dozen baseline images as plots expanded to fill space where there was empty/invisible text. Removed for now, but I think that's probably the right behavior and we can discuss including it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think that might be important, you can target that change to the text-overhaul branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean this PR or just that change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The additional change, unless it's easier for you to do the whole PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll wait for this one to merge to avoid the def get_tightbbox conflict

@scottshambaugh scottshambaugh force-pushed the log_text_negative_coord branch from b558152 to 496411d Compare February 3, 2026 16:44
@timhoffm
Copy link
Member

timhoffm commented Feb 8, 2026

Waiting on #30161. Per #31061 (comment)

@scottshambaugh
Copy link
Contributor Author

scottshambaugh commented Feb 10, 2026

This one can merge as-is, then the change discussed above that would regenerate baseline images would be in a separate PR that targets #30161.

Comment on lines +1066 to +1068
if (self._outside_axes_domain(*self.get_unitless_position())
and self.get_transform() == self.axes.transData):
return Bbox.null()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would spell it this way:

Suggested change
if (self._outside_axes_domain(*self.get_unitless_position())
and self.get_transform() == self.axes.transData):
return Bbox.null()
if (self.axes and self.get_transform() == self.axes.transData # text is a data Artist
and self._outside_axes_domain(*self.get_unitless_position())):
return Bbox.null()

Reason:

  • Logic seems clearer "is a data artist and outside the limits"
  • Most texts are non-data artists and thus can bail out early without comparably expesive numierics. ticklabel and axis labels have self.axes = None, titles have an axes set, but a different transform.

Comment on lines +390 to +391
vmin, vmax = axis.limit_range_for_scale(val, val)
if vmin != val or vmax != val:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we grow a method Scale.val_in_range(val). This seems like a logically meaningful API. ScaleBase could implement this as a generic fallback via limit_range_for_scale to support existing third-party scales. But specific scales could implement their logic much more efficiently; e.g. LinearScale would just return True, and LogScale would return val > 0`.

If we do this, probably better in a separate PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MNT]: Inconsistency in log-scale handling: bbox_inches='tight' creates massive canvas for negative text coordinates

3 participants