-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
_AnnotationBase keeps copy of mutable parameter (issue #17566) #17567
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
Conversation
Addresses issue matplotlib#17566
|
Thanks for working on this! Unfortunately the fix is not this simple :( We can work with user data passed in with units attached and casting through an array like this can drop that data. I think we should ether do all of the unit work up-front (and the in the |
|
I though something like this could happen (the "point" class I mentioned in #17566). The fast way is If this helper function uses a canonical type, some code could be moved away from the part which uses So I think we have a quick way (copy.copy, 1 import, 1 line changed) and a long way (take care early, helper function, documentation, extensive testing, etc). None of this is in a critical execution path and neither a lot of memory is involved, so it depends on what is expected for this interface in future. Please tell me which way to go and I try to take care of this (in text.py only) Note that a similar issue is present in the helper class OffsetFrom |
|
Adding an import is not a problem. |
|
I updated my annotate_bug branch using copy.copy both in I will see if I find something similar in other files, but it's a huge codebase (I think |
|
Could you also add a test? This seems like a good case for https://matplotlib.org/api/testing_api.html#matplotlib.testing.decorators.check_figures_equal (make two figures that you do the same thing to and then mutate one of the inputs). I am happy to have many small PRs rather than 1 big PR. Smaller PRs are easier to review and merge. If any of the things you fix get more complicated (which is a thing that happens often when you start fixing things in the mpl codebase...) we don't want to hold up the easy / non-controversial improvements while we sort out the harder stuff. |
|
OK. I'll take care of this in the next week (I have to learn how to implement the tests). I've already found the same issue on other modules, so maybe I'll do one PRs for each file. |
|
I think this is ready for review. I will move on to another file, but I have already noted non-uniform API behavior in the sense that some interfaces are ready for data with units while others are not. I do not know if I should discuss this here in the PR on the associated bug. |
Coerce the received parameter to tuple instead of making a copy Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
|
I have tested that the coercion to tuple approach works, and I do have strong objections against the copy approach because:
To make this last point clear, I think we should avoid that code like the following render a different image each time the figure is redrawn: import matplotlib.pyplot as plt
from matplotlib.text import OffsetFrom
import numpy as np
import datetime
class mock_xy:
def __init__(self, seconds_per_trip=60):
#self._bigarray = np.linspace(0, 1000000, 300000000, dtype=np.complex256)
self._spt = int(seconds_per_trip)
pass
def __getitem__(self, index):
now = datetime.datetime.now()# we could connect to a time server here
angulo = 2.*np.pi*(now.second % self._spt + now.microsecond*1E-6)/self._spt
if index == 0:
return np.cos(angulo)+.5
elif index == 1:
return np.sin(angulo)+.5
else:
raise IndexError
fig = plt.figure("Aw[ful, esome]")
ax = fig.add_axes([0.13, 0.15, .8, .8])
ax.set_xlim(-5, 5)
ax.set_ylim(-3, 3)
clock_face = plt.Circle((0.0, 0.0), 1, color='blue')
ax.add_patch(clock_face)
xy_0 = np.array((-0, 0))
offset_from = OffsetFrom(clock_face, mock_xy(5))
ann = ax.annotate("around\nthe clock", xy=(0, 0), xycoords="data",
xytext=(0, 0), textcoords=offset_from,
va="top", ha="center",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="<-"))With the copy approach, it works as a timer that moves it is redrawn (try resizing the window and watch the timer tick). I think it should instead show the annotation at the same place every time. It's my last attempt to avoid the copy approach. If that's the path the main developers prefer, let me know and I'll adjust the code accordingly. |
|
This probably needs a rebase to get tests working again. |
|
It'd be nicer to coerce to tuple if possible. However, I'm not an expert in units handling, so I cannot judge if coercing to tuple can cause issues there. OTOH, I don't see a fundamental problem with copying. If we want to persist state, we need to make a copy of the object. It's highly unlikely that an object representing xy will consume significant resources. I consider the example somewhat artificial. By doing a copy, we isolate from future changes of the original object passed in. Here, the constructed object changes without user interaction, so it's not clear to me what the expected behavior should be. |
|
Actually I believe that casting to tuple is just fine even for unit support (and much simpler), because this simply assumes that we can iterate over |
|
Since this Pull Request has not been updated in 60 days, it has been marked "inactive." This does not mean that it will be closed, though it may be moved to a "Draft" state. This helps maintainers prioritize their reviewing efforts. You can pick the PR back up anytime - please ping us if you need a review or guidance to move the PR forward! If you do not plan on continuing the work, please let us know so that we can either find someone to take the PR over, or close it. |
Fixes #17566
PR Summary
_AnnotationBase now keeps a copy of the data passed as the xy parameter, instead of the same object. Otherwise, passing an ndarray as xy and changing its elements later unexpectedly changes the annotation.
It is a one-line change, so I have performed no extensive tests beside basic functionality yet
PR Checklist