Locators and Formatters stash info about what axis they are attached to and often use that info when they are called (via locator()). This makes locators (and some formatters) un-usable across multiple axes.
It seems that there are a few strategies, some of which have been tried:
Lock out adding a formatter to a second axes
Pros: Easy to implement. Unambiguous to the user - just throws an error if they try and add to a second axis.
Cons: Not backwards compatible. Users cannot modify just one locator or formatter and have it apply to more than one axis.
Copy locator when assigned to an axis:
Pros: relatively easy
Cons: user doesn't know about this copy and will not be able to operate on the Locator instance they created.
modify locator() to accept axis argument
i.e. ticks = self.major_locator(axis=self)
Pros: this would allow explicitly updating the tick creation when needed.
Cons: third party locators would not have this kwarg, so we could not call this unconditionally. However, we could have a try-except clause that deprecates locators with no axis, and then falls back to the stashed axis.
I think "make a copy" is off the table as I suspect that will cause far greater confusion. I also disagree that it is obviously easy to implement. The Locators can be arbitrary Python objects so being able to reliably making a fully independent copy is not a given.
I do like option 3 as it is slowly pulling back distributed shared state.
I think we need to ask does "having the same locator (axis1.get_major_locator() is ais2.get_major_locator()) mean "we use the same locations on both axis" or "we use the same method to determine where the locations are on both axis". Currently it means the first (and we break the symmetry as to which axis is the lead by the last one it is attached to). If we change to "using the same method" as the meaning, then how can we get back the ability to have "the same tick loctations" on more than one axis? I guess we could add a MirrorLocactor(other_axis) that explicitly implements that.
I think we need to ask does "having the same locator (axis1.get_major_locator() is ais2.get_major_locator()) mean "we use the same locations on both axis" or "we use the same method to determine where the locations are on both axis".
Hmm I hadn't considered that. Is there call for the first case if the axis limits are different? Right now we have shared axes, and the limits are held the same, so if they have the same method they will get the same ticks. I guess if for some reason you wanted the same ticks, but different limits that would require something new.
copyloc kwarg: This feels a bit like a crutch. I'd be ok if this is only for a transition period. Is sharing a locator still on the table? If not, well finally arrive at
We seem to regularly have this problem where we allow passing complicated objects to methods. Is the same issue with colormaps, scales, etc. I'm not sure what the usual pythonic way of dealing with this is, but it would sure be simpler if these were passed by value rather than address
I think (consistently with above) the correct solution would be to have locators not know about the axis except at call time (locator(axis) instead of locator()), but that runs into the usual backcompat problems (it's all doable, just tedious...)
It may be worth trying to get a (non-exhaustive) list of third-party locators/formatters that we may want to contact/check if we want to do the change.
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!
Making Locators and Formatters stateless seems worth investigating.
It may be worth trying to get a (non-exhaustive) list of third-party locators/formatters that we may want to contact/check if we want to do the change.
@timhoffm Perhaps you can have a look at #28429? It's not the most elegant solution, but it effectively works around the existing design issues while keeping backcompat.
Activity
jklymak commented on Nov 25, 2020
Reproducible issue:
Summary
Locators and Formatters stash info about what axis they are attached to and often use that info when they are called (via locator()). This makes locators (and some formatters) un-usable across multiple axes.
Code for reproduction
Actual outcome
Expected outcome
Probably the same outcome as
jklymak commented on Nov 25, 2020
It seems that there are a few strategies, some of which have been tried:
Lock out adding a formatter to a second axes
Pros: Easy to implement. Unambiguous to the user - just throws an error if they try and add to a second axis.
Cons: Not backwards compatible. Users cannot modify just one locator or formatter and have it apply to more than one axis.
Copy locator when assigned to an axis:
Pros: relatively easy
Cons: user doesn't know about this copy and will not be able to operate on the Locator instance they created.
modify locator() to accept axis argument
i.e. ticks = self.major_locator(axis=self)
Pros: this would allow explicitly updating the tick creation when needed.
Cons: third party locators would not have this kwarg, so we could not call this unconditionally. However, we could have a try-except clause that deprecates locators with no axis, and then falls back to the stashed axis.
tacaswell commented on Nov 25, 2020
I think "make a copy" is off the table as I suspect that will cause far greater confusion. I also disagree that it is obviously easy to implement. The Locators can be arbitrary Python objects so being able to reliably making a fully independent copy is not a given.
I do like option 3 as it is slowly pulling back distributed shared state.
I think we need to ask does "having the same locator (axis1.get_major_locator() is ais2.get_major_locator()) mean "we use the same locations on both axis" or "we use the same method to determine where the locations are on both axis". Currently it means the first (and we break the symmetry as to which axis is the lead by the last one it is attached to). If we change to "using the same method" as the meaning, then how can we get back the ability to have "the same tick loctations" on more than one axis? I guess we could add a MirrorLocactor(other_axis) that explicitly implements that.
jklymak commented on Nov 25, 2020
Hmm I hadn't considered that. Is there call for the first case if the axis limits are different? Right now we have shared axes, and the limits are held the same, so if they have the same method they will get the same ticks. I guess if for some reason you wanted the same ticks, but different limits that would require something new.
jklymak commented on Apr 27, 2021
So, I think by default:
Should warn: locator used on two axis-es, changing state on one axis will change the locators on the other. and preserve the existing behaviour
To suppress this, the user should be able to set a kwarg:
suppresses the warning, and behaves like before
makes a new locator from the old.
If they don't want to deal with either, they could just do what they probably meant, which was:
jklymak commented on Apr 27, 2021
ping @timhoffm , @anntzer re #13482
timhoffm commented on Apr 28, 2021
My concerns re #13482 are still there #13482 (comment)
copyloc kwarg: This feels a bit like a crutch. I'd be ok if this is only for a transition period. Is sharing a locator still on the table? If not, well finally arrive at
as the only valid variant, which I'd favor.
anntzer commented on May 30, 2021
I really don't think asking users to manually copy NullLocators or FuncFormatters makes sense from a user-friendliness PoV.
jklymak commented on May 30, 2021
We seem to regularly have this problem where we allow passing complicated objects to methods. Is the same issue with colormaps, scales, etc. I'm not sure what the usual pythonic way of dealing with this is, but it would sure be simpler if these were passed by value rather than address
anntzer commented on May 31, 2021
I think (consistently with above) the correct solution would be to have locators not know about the axis except at call time (locator(axis) instead of locator()), but that runs into the usual backcompat problems (it's all doable, just tedious...)
It may be worth trying to get a (non-exhaustive) list of third-party locators/formatters that we may want to contact/check if we want to do the change.
github-actions commented on Apr 9, 2025
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!
timhoffm commented on Apr 9, 2025
Making Locators and Formatters stateless seems worth investigating.
A first attempt with still a lot of false-positive is https://github.com/search?q=%2Fc%28%3F-i%29lass+%5Cw%2B%5C%28.*Locator%5C%29%3A%2F+language%3APython+NOT+is%3Afork&type=code
anntzer commented on Apr 9, 2025
@timhoffm Perhaps you can have a look at #28429? It's not the most elegant solution, but it effectively works around the existing design issues while keeping backcompat.