Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Make YearLocator a subclass of RRuleLocator#19348
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
@@ -1482,52 +1510,9 @@ def __init__(self, base=1, month=1, day=1, tz=None): | |||
Mark years that are multiple of base on a given month and day | |||
(default jan 1). | |||
""" | |||
super().__init__(tz) | |||
self.base = ticker._Edge_integer(base, 0) | |||
self.replaced = {'month': month, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
overall this seems fine. I suspect you need a minor API note that saysself.replaced
has been dropped. This should probably never have been public in the first place, as I can't see why a user would do anything with it, but...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Is it worth putting in the a property what warns on get/set? On one hand, most likely no one is using this, on the other hand it is not much work to make sure we do not needlessly break anyone who is trying to access this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I believe you that YEARLY needs the special logic, but without experimenting with it, I don't quite understand why... Overall, this seems like a good idea, but lets just make sure its clear and has tests..
@@ -0,0 +1,5 @@ | |||
``dates.YearLocator`` attribute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
``dates.YearLocator`` attribute | |
Removed``dates.YearLocator.replaced`` attribute |
``dates.YearLocator`` attribute | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
The attribute ``YearLocator.replaced`` has been removed. For tick locations which |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Theattribute ``YearLocator.replaced`` has been removed. For tick locationswhich | |
`.YearLocator` is now a subclass of `RRuleLocator`, and theattribute ``YearLocator.replaced`` has been removed. For tick locationsthat |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think this is fine. I'm guessing no one used this undocumented attribute.
lib/matplotlib/dates.py Outdated
@@ -1161,10 +1161,16 @@ def nonsingular(self, vmin, vmax): | |||
class RRuleLocator(DateLocator): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Sorry, I should have noticed this the first time. You have also changed the signature of RRuleLocator. I think you need to write a new docstring for it, since it cannot simply inherit fromDateLocator
any longer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'm actually not completely happy with that signature change yet. Calling the argument 'interval_multiples' is a bit confusing if it really only applies to yearly ticks.
Taking care of the special case directly in the RRuleLocator is best for overall clarity, imo. That's why I think this change is an improvement.
But I also want to achieve easy usage of the RRuleLocator for timedeltas at the same time. It already works for all frequencies except for daily. Daily with timedeltas is a very similar special case to yearly with dates. I'd like to subclass the RRuleLocator and only override init and the method that determines start, stop as multiples of the interval.
It might be better to have the signature be different between RRuleLocator and a subclass to make everything more clear?
For example, call the argument 'yearly_interval_multiples' maybe?
It would be best to have only one RRuleLocator class for dates and timedeltas. Also because of the locator classes for specific frequencies. As it looks, HourLocator can be used for dates and timedeltas. But DayLocator cannot be used for both because that is a special case with timedeltas. Ideally, all locators can be used for both. But I see no way currently to make that happen.
This is, of course, out of scope for this PR. But I do want to consider it already to facilitate the other PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
If it really can't be done generally, then maybe a subClass make sense. In that case you could make it the same as the previous YearlyLocator, but hopefully with less repeated code because you now subclass RRuleLocator. That seems the most straightforward, and it stops RRuleLocator from having a bunch of extra logic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
That might actually be the best option. It still requires overwriting the.tick_values
method in the yearly locator. There will be some very similar but slightly different code then. But less complex than the previous YearLocator. So an improvement after all.
And it keeps the extra logic out of the RRuleLocator, that's true. And it's not changing the signature of RRuleLocator.
But it also means that this has to stay inAutoDateLocator.get_locator()
:
if (freq == YEARLY) and self.interval_multiples: locator = YearLocator(interval, tz=self.tz) elif use_rrule_locator[i]: ... locator = RRuleLocator(rrule, self.tz)
I don't particularly like this because it is not very flexible, considering an AutoTimedeltaLocator requires a different special case here. But maybe that's not a problem to solve here.
lib/matplotlib/dates.py Outdated
@@ -1175,22 +1181,28 @@ def __call__(self): | |||
return self.tick_values(dmin, dmax) | |||
def tick_values(self, vmin, vmax): | |||
delta = relativedelta(vmax, vmin) | |||
if self.mult_base: | |||
# start and stop need to be multiples of the interval |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'm not sure I follow this. Why do start and stop need to be multiples of the intervals? This either needs more comment, or cleaning up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
More comments probably? So, usually creating ticks on multiples of the interval is done by adjusting the "byranges". So for example with frequency =SECONDLY and interval = 15, the AutoDateLocator makesbyseconds = [0, 15, 30, 45]
.
But there is no "byrange" for years. Rrule can create a reoccurrence every 10 years (for example). But you cannot specify that you want that to be on every full decade only. It will be every 10 years starting with the start date. So to get multiples of the interval, start needs to be a multiple of the interval.
This is why AutoDateLocator currently returns an RRuleLocator for YEARLY when.interval_multiples = False
, but a YearLocator for when it is true.
Stop can technically be a value greater than the last desired tick.
The comment is not very clear, I agree. It should probably be something like "start needs to be a multiple of the interval for ticks on interval multiples when freq == YEARLY"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
But there is no "byrange" for years. Rrule can create a reoccurrence every 10 years (for example). But you cannot specify that you want that to be on every full decade only. It will be every 10 years starting with the start date. So to get multiples of the interval, start needs to be a multiple of the interval.
Probably something like this in the comments? That makes sense...
lib/matplotlib/dates.py Outdated
ticks.append(dt) | ||
rule = rrulewrapper(YEARLY, interval=base, bymonth=month, | ||
bymonthday=day, **self.hms0d) | ||
super().__init__(rule, tz, interval_multiples=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This needs to get hit by the tests somehow! Maybe not your fault - i.e. perhaps YEARLY wasn't in the tests really well before, but the new functionality should get a test.
Ok, some changes, sort of like you suggested. |
I played around with this some more, also in reference to#19236. This is basically the same special handling that's necessary for the DayLocator for timedeltas, just some slightly different math. |
@theOehrly this seemed reasonable. Sorry it languished. where is it at? And please do ping for reviews if needed... |
It works and the created ticks are exactly the same as with the current YearLocator in all test cases that I tried. Maybe it needs another small test added to get full coverage. |
@jklymak From my point of view it works. So I guess it can be reviewed in cases you're generally interested in having this merged. (I guess I need to use pings more?) |
start, stop = self._create_rrule(vmin, vmax) | ||
dates = self.rule.between(start, stop, True) | ||
if len(dates) == 0: | ||
return date2num([vmin, vmax]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yes can you add a test for this. Otherwise I think this is good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@jklymak done
Closer! |
@jklymak I think now everything is right. I couldn't open the log of those test runs, so I didn't kown why they were failing. Should everybody be able to see that? |
Yes I think everyone can see them but you have to download the log because it's usually too long. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Looks good to me. I think this is a clear improvement of the YearLocator.
I'll mark this as merge with single review and merge in a week if there are no further comments. Thanks! |
I do not think anything that requires an API change note should be merged on a single review. |
I guess I think that depends on the intrusiveness of the API change. In this case, |
@theOehrly Thanks and congratulations on your first contribution to Matplotlib! We hope to hear from you again. |
I think it was fine to merge this as-is, but@tacaswelldid suggest a change above. I've opened an issue#20313 to track that suggestion. |
PR Summary
This makes the YearLocator a subclass of RRuleLocator instead of DateLocator.
Advantanges:
PR Checklist
pytest
passes).flake8
on changed files to check).flake8-docstrings
and runflake8 --docstring-convention=all
).doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).