Skip to content
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

dates.YearLocator doesn't handle inverted axes #3648

Merged
merged 1 commit into from Jul 17, 2015
Merged

dates.YearLocator doesn't handle inverted axes #3648

merged 1 commit into from Jul 17, 2015

Conversation

elpres
Copy link
Contributor

@elpres elpres commented Oct 15, 2014

All other Locators are derived from RRuleLocator, which has the following check inside its __call__ routine:

try:
    dmin, dmax = self.viewlim_to_dt()
except ValueError:
    return []

if dmin > dmax:
    dmax, dmin = dmin, dmax

YearLocator just gets values for dmin and dmax in the same manner as above and goes on using them without checking for ordering and swapping if necessary. This leads to only one tick on inverted axes using YearLocator.

@tacaswell tacaswell added this to the v1.4.x milestone Oct 15, 2014
@tacaswell
Copy link
Member

cc @cimarronm This looks like #3522 / #3524

@cimarronm
Copy link
Contributor

It seems to me that ideally YearLocator should be modified to use RRuleLocator to be consistent with others (RRuleLocator automatically handles the inverted axis) but there may be a reason why it cannot. I'll look into it more tonight

@pelson
Copy link
Member

pelson commented Oct 16, 2014

I'm comfortable with this change - it needs a test (which should not be a visible test)

It seems to me that ideally YearLocator should be modified to use RRuleLocator to be consistent with others (RRuleLocator automatically handles the inverted axis) but there may be a reason why it cannot. I'll look into it more tonight

That is maybe a nice to have, but is by no means necessary IMHO. Let's get this in, and then any refactoring can be done as a separate activity - the good news is that there will already be a test which exercises the desired behaviour 😉

@cimarronm
Copy link
Contributor

I'm in agreement with @pelson. Let's get this in along with a test and we can look at possibly updating in a separate PR

@tacaswell
Copy link
Member

The revision needed is a test.

@elpres
Copy link
Contributor Author

elpres commented Oct 17, 2014

I've noticed that there is another method of YearLocator (autoscale) which contains the same kind of pattern, and added an identical check to that as well. However, seeing that in all cases when the min-max pairs are recieved in YearLocator and RRuleLocator, they come from the methods DateLocator.datalim_to_dt and DateLocator.viewlim_to_dt, maybe it more sensible and elegant to include the ordering check there and remove it from the consumers. But I don't know the code well enough to make the call here and leave the decision to you guys.

@tacaswell tacaswell modified the milestones: v1.4.x, v1.4.2 Oct 17, 2014
@elpres
Copy link
Contributor Author

elpres commented Oct 21, 2014

In the previous two commits I've moved the fix to the parent class, as described before. This also makes AutoDateLocator work in the same situation, where it previously aborted with

ValueError: No sensible date limit could be found in the AutoDateLocator.

@tacaswell
Copy link
Member

This looks reasonable to me, but it still needs a test.

@cimarronm
Copy link
Contributor

I'm not sure I agree with the latest changes. It seems like you are breaking API by changing the return value of the data and view limits instead of returning what was set. I think your original method for handling it inside YearLocator was the correct way.

Note that the equivalent type of thing is done in axis e.g. axis._update_ticks to handle the scenario where the order is reversed rather than just modifying get_data_interval from returning it already in min, max order and, of course, already by RRuleLocator.

@elpres
Copy link
Contributor Author

elpres commented Oct 22, 2014

Yes, I agree that it's an API change, but the question is whether anything will notice it. From what I can tell, there are three classes on the receiving end of this change:

  • RRuleLocator test the values and reverses if necessary. The code after that point expects dmin to be <= dmax.
  • YearLocator also expects dmin and dmax to be ordered, but it doesn't check, so it breaks.
  • same for AutoDateLocator.

That means all of them expect the correct ordering, and all of them have to check and swap the values they receive. So, from their point of view, the return values of datalim_to_dt and viewlim_to_dt are in an undefined state which they each have to fix manually in the same way. Continuing to return the two values in the same order as now means that whenever somebody subclasses DateLocator in the future, they will have to be aware of the situation and remember to put this check in there too. Sorting the limit in the parent seems to me to be 1) more robust, and 2) safe.

@tacaswell tacaswell modified the milestones: v1.4.x, v1.4.3 Jan 19, 2015
@tacaswell
Copy link
Member

punting to 1.4.x as this still needs work and has been silent for 2 months.

I now agree with @cimarronm that we should not change the API, but can be convinced otherwise. If it does get changed this will need to documented in https://github.com/matplotlib/matplotlib/tree/master/doc/api/api_changes and have docstrings added to the methods which make clear they are doing the order rectification.

This still needs a test.

@elpres
Copy link
Contributor Author

elpres commented Jan 20, 2015

The API in this case doesn't seem to have been clearly defined in the first place, since the existing Locators deal with the two possible orientations of axes differently (some do check for direction, some don't). The change I've proposed removes ambiguity and code duplication, which is IMHO an improvement. It also resolves the problem at the root instead of in the branches, which I would also consider the better way. And as far as I can say, there should be not breakage coming from that.

As for a test, in a simple form it would have to look something like this:

  1. Create an isolated Axis object.
  2. Set its locator to YearLocator and the range so that it results in a reasonable amount of labels.
  3. Store the labels.
  4. Invert the axis.
  5. Make sure the labels are the same but in reversed order.
  6. Repeat for AutoDateLocator.

But I don't know the low level API enough (or at all) to know how to e.g. instantiate an Axis and make it behave the way it would when it's a part of a chart. Also, I don't know your conventions for writing tests. So I'm sorry, but at the moment I don't have the spare energy to invest into learning these things and would rather leave this to somebody with more experience.

@tacaswell tacaswell modified the milestones: 1.5.0, v1.4.x Feb 7, 2015
@tacaswell
Copy link
Member

@elpres This needs a rebase

@elpres
Copy link
Contributor Author

elpres commented Jun 24, 2015

@tacaswell This was the first time I've ever done a git rebase (I use mercurial for my own projects, and never had to do any sort of modification of this complexity), and looking at the log above I likely messed something up. If I really did, I can open a new, clean pull request, as the patch itself is rather easy to re-create.

@tacaswell
Copy link
Member

Something has gone very wrong with this rebase, it should have 2 commits, not 200+ 😉

See #2742 for some docs on how to do this right (that PR was stalled on what to call the example remotes).

@tacaswell
Copy link
Member

Don't make a new PR, just force-push to this branch when you clean it up and gh will 'do the right thing'

All other Locators are derived from RRuleLocator, which has the following check inside its `__call__` routine:

    try:
        dmin, dmax = self.viewlim_to_dt()
    except ValueError:
        return []

    if dmin > dmax:
        dmax, dmin = dmin, dmax

YearLocator just gets values for `dmin` and `dmax` in the same manner as above and goes on using them without checking for ordering and swapping if necessary. This leads to only one tick on axes using YearLocator.
@elpres
Copy link
Contributor Author

elpres commented Jun 25, 2015

Ok, this time it seems to have worked out properly (right?). Thank you for linking the rebase howto; it was very helpful.

@WeatherGod
Copy link
Member

still doesn't have a unit test. Don't want to regress this feature in the future.

tacaswell added a commit that referenced this pull request Jul 17, 2015
FIX: dates.YearLocator doesn't handle inverted axes
@tacaswell tacaswell merged commit 8a34d46 into matplotlib:master Jul 17, 2015
@tacaswell
Copy link
Member

Giving up on the unit tests for now.

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.

None yet

5 participants