Skip to content

adding check_xytext for NaN values #30381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

AndrGutierrez
Copy link
Contributor

PR summary

Closes #29516 , I have a few things to address here, this PR fixes errors for this type of case:

import matplotlib.pyplot as plt, numpy as np

ax = plt.figure().add_subplot()
ax.annotate('', (0, 0), (np.nan, np.nan), arrowprops={})

plt.show()

It checks that the coordinates for xytext are a finite number, and if it isn't, the text and the arrow are not drawn, same behaviour as check_xy.

I wasn't able to reproduce the first error in the thread in a similar environment, and for this other piece of code:

import matplotlib.pyplot as plt

ax = plt.figure().add_subplot(projection="polar")
ax.set_ylim(.25, 1.25)
ax.annotate('', (0, .5), (0, 0), arrowprops={})

plt.show()

the error seems to be related to set_ylim, if you remove that, it works. Also, if you change the y coordinate for xy to be less than ylim's bottom, for example:

import matplotlib.pyplot as plt

ax = plt.figure().add_subplot(projection="polar")
ax.set_ylim(.25, 1.25)
ax.annotate('', (0, .2499), (0, 0), arrowprops={})

plt.show()

it seems to work for anything < ylim's bottom (.25 in this case), so I guess the error appears when the text is supposed to be visible, this behaviour is weird and the easiest solution I can think of is just to not apply/allow ylim because it doesn't do anything in polar coordinates, but I don't have enough context to know if that's a good idea or even possible so I would like feedback on that 🙂.

PR checklist

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()
Copy link
Member

Choose a reason for hiding this comment

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

To fix the polar case, you need to check these values after they are transformed to "screen space", for which you can use self.axes.transData.transform. See the Transforms Tutorial for more details.

I'm not really sure if invalid values here should make the annotation invisible or raise a more helpful error message.

@AndrGutierrez
Copy link
Contributor Author

Ok, I will take a look at it later.

And I agree that not drawing the annotation isn't the best behaviour but it's how it works for check_xy and I wanted to keep it similar, maybe an error message for both would be better

@rcomer
Copy link
Member

rcomer commented Aug 4, 2025

_check_xy only determines what happens if the annotated point lies outside the axes. This could happen frequently if you plot interactively then pan/zoom to show a different part of the plot. The behaviour for this is well-established and documented as a user choice. Here we are checking whether the text point is valid, so I do not think it necessarily needs to be consistent with _check_xy.

@AndrGutierrez
Copy link
Contributor Author

Is there a way to run the CircleCI tests before pushing the changes? I always run pytest before pushing, but for circleCI I only see that it fails after it's done

@rcomer
Copy link
Member

rcomer commented Aug 6, 2025

Yes, you can build the docs locally
https://matplotlib.org/devdocs/devel/document.html#build-the-docs

@AndrGutierrez AndrGutierrez requested a review from rcomer August 6, 2025 22:47
Copy link
Member

@rcomer rcomer left a comment

Choose a reason for hiding this comment

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

Thanks @AndrGutierrez. This will also need a unit test.

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:
Copy link
Member

Choose a reason for hiding this comment

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

Why is this in a try-except loop? Did you find examples where a TypeError is thrown?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, in galleries/examples/units/annotate_with_units.py. got something like this:

TypeError: affine_transform(): incompatible function arguments. The following argument types are supported:
    1. (points: typing.Annotated[numpy.typing.ArrayLike, numpy.float64], trans: trans_affine) -> object

Copy link
Member

@rcomer rcomer Aug 17, 2025

Choose a reason for hiding this comment

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

Ah, perhaps you need to use self.get_unitless_position which applies the axises’ converters so that the result is pure numbers.

AndrGutierrez and others added 2 commits August 11, 2025 09:30
Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com>
Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com>
@AndrGutierrez
Copy link
Contributor Author

I am having an issue with an already existing test:

FAILED lib/matplotlib/tests/test_axes.py::test_centered_bar_label_label_beyond_limits - ValueError: xytext coordinates must be finite numbers.

I made a print for debugging the xytext coordinates used and it shows this:

###
[204. 240.]
###
[452. 240.]
###
[nan nan]

the original coordinates are (0,0) , but after transformation I got those three, I guess that those nan coordinates are for testing beyond limits, and this new behaviour might be expected, but I'll wait for some guidance on this before moving forward and changing anything in this test. I will create the proper new test for nan values after that.

@rcomer
Copy link
Member

rcomer commented Aug 17, 2025

It seems to me that that failing bar_label example should never get as far as _check_xytext. Instead _check_xy should return False so that draw returns early.

The test came in at #25681. I tried reverting the change in _axes.py from that PR, so the lambda returns None again, and adding another check to _check_xy:

--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -2916,7 +2916,7 @@ class Axes(_AxesBase):
                     lambda r, b=bar:
                         mtransforms.Bbox.intersection(
                             b.get_window_extent(r), b.get_clip_box()
-                        ) or mtransforms.Bbox.null()
+                        )
                 )
             else:  # edge
                 if orientation == "vertical":
diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py
index c09490a9ce..800c4c9525 100644
--- a/lib/matplotlib/text.py
+++ b/lib/matplotlib/text.py
@@ -1630,6 +1630,8 @@ class _AnnotationBase:
             # check if self.xy is inside the Axes.
             xy_pixel = self._get_position_xy(renderer)
             return self.axes.contains_point(xy_pixel)
+        if b is None and callable(self.xycoords) and self.xycoords(renderer) is None:
+            return False
         return True

This does fix that case, and does not appear to break anything else. However, I am getting out of my depth here so I would appreciate comment from someone else.

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.

[Bug]: Cannot draw arrow from polar plot origin after setting rticks
2 participants