Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Move major/minor tick overstrike logic to Axis.#13314

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

Merged
jklymak merged 5 commits intomatplotlib:masterfromanntzer:minoroverstrike
Feb 23, 2019

Conversation

anntzer
Copy link
Contributor

@anntzeranntzer commentedJan 29, 2019
edited
Loading

PR Summary

The Axis knows what the major and the minor ticker are so it makes sense
to let it handle the deduplication logic.

Revert some heuristic deduplication logic from the Locators themselves
(which effectively tests that the new code works). (#11762,#13126)

Closes#11575 (which has a few duplicates).

Goes on top of#12909 (which has already three approvals). [edit: that got merged]

PR Checklist

  • Has Pytest style unit tests
  • Code isFlake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@anntzer
Copy link
ContributorAuthor

anntzer commentedJan 29, 2019
edited
Loading

The test_tightlayout.py::test_outward_ticks failure is explicitly testing that one can overlay a minor tick on top of a major tick, so I'll probably just remove that test (after looking into whether it is testing anything else relevant) (The patch below does not help because the test even requires amajor label at x=0 that's overstruck by a minor tick:

diff --git i/lib/matplotlib/tests/test_tightlayout.py w/lib/matplotlib/tests/test_tightlayout.pyindex 5898c1a94..a8f833f07 100644--- i/lib/matplotlib/tests/test_tightlayout.py+++ w/lib/matplotlib/tests/test_tightlayout.py@@ -167,16 +167,14 @@ def test_outward_ticks():     'Test automatic use of tight_layout'     fig = plt.figure()     ax = fig.add_subplot(221)-    ax.xaxis.set_tick_params(tickdir='out', length=16, width=3)-    ax.yaxis.set_tick_params(tickdir='out', length=16, width=3)-    ax.xaxis.set_tick_params(-        tickdir='out', length=32, width=3, tick1On=True, which='minor')-    ax.yaxis.set_tick_params(-        tickdir='out', length=32, width=3, tick1On=True, which='minor')-    # The following minor ticks are not labelled, and they-    # are drawn over the major ticks and labels--ugly!-    ax.xaxis.set_ticks([0], minor=True)-    ax.yaxis.set_ticks([0], minor=True)+    for axis in [ax.xaxis, ax.yaxis]:+        axis.set_ticks([0.2, 0.4, 0.6, 0.8, 1.0])+        axis.set_tick_params(tickdir='out', length=16, width=3)+        axis.set_ticks([0], minor=True)+        # The following minor ticks are not labelled, and they+        # are drawn over the major ticks and labels--ugly!+        axis.set_tick_params(+            which='minor', tick1On=True, tickdir='out', length=32, width=3)     ax = fig.add_subplot(222)     ax.xaxis.set_tick_params(tickdir='in', length=32, width=3)     ax.yaxis.set_tick_params(tickdir='in', length=32, width=3)

)

The test_scale.py::test_logscales failure is (I believe?) due to the fact that the Symlog minor locator previously did not implement overstriking avoidance, so we ended up with overstruck major and minor ticks, which the svg rasterizer would pick up (one can examine the svg output to confirm); with this PR, the overstruck minor tick is absent, causing a tiny change in rasterization. Again, may be worth just killing the test if it's not testing anything else (after proper investigation).

Edit: test_logscales was introduced in#1247 to check that ax{v,h}line works with (sym)log scales. Rewrote it to use check_figures_equal.

@jklymak
Copy link
Member

Overall 👍 But another approach would have been to add a remove_ticks kwarg to locator.call. Not sure which approach is better but this way the locator could decide what to do with the ticks to remove. It’s also possible that the locator logic could do something interesting with the major ticks info.

@anntzer
Copy link
ContributorAuthor

Adding a remove_ticks=... to__call__ means going through a deprecation to not break third-party locators (ugh), and likely a bunch of locators will need to basically use the same code anyways (well, that can always be factored out in a helper method, but still...)

OTOH it does allow for really weird cases if youreally want your custom locator to overstrike a major tick with a minor tick, not that I think there's a realistic use case... (the test_outwards_tick test is completely artificial.)

@jklymak
Copy link
Member

Oh, duh, you are right - I thought callingfoo(unknown_kwarg='Boo') just ignored the kwarg, but thats only if the call signature has**kwargs whichlocator.__call__ doesn't have.

Now that I think of it, allowing API changes to have better backwards compatibility is actually a pretty good reason to have signatures with**kwargs.

@jklymak
Copy link
Member

For the "weird" cases, I could imagine knowing the major ticks could change the minor tick intervals. i.e. for dates, if you know the major ticks are days, making the minor ticks hours is useful. No doubt we already have logic for that, but it would be made easier by knowing the major ticks.

I guess compatibility could be maintained by atry block, but that is inelegant.

@anntzer
Copy link
ContributorAuthor

Now that I think of it, allowing API changes to have better backwards compatibility is actually a pretty good reason to have signatures with**kwargs.

That doesn't really help: for our own locators we can update them as desired, for third party locators we can't really force them to accept**kwargs as they would work fine without that.
A try: block introspecting the signature is probably doable, just a minor pain.

For the "weird" cases, I could imagine knowing the major ticks could change the minor tick intervals. i.e. for dates, if you know the major ticks are days, making the minor ticks hours is useful. No doubt we already have logic for that, but it would be made easier by knowing the major ticks.

Should probably have a look at what adhoc mechanisms are currently used and how this PR could or could not subsume them.

@anntzer
Copy link
ContributorAuthor

@jklymak I skimmed through dates.py and didn't see anything noticeable. Do you know of any example with interesting interactions between major and minor dates locators?

@jklymak
Copy link
Member

No I don’t think so, but you could imagine doing something like that....

@anntzer
Copy link
ContributorAuthor

Then I think I'd rather leave this PR as it is and worry about changing the signature of__call__ when an actual use case shows up...

@anntzer
Copy link
ContributorAuthor

Edited the other failing test (initially added in#5683) to check the axes positions instead.

@anntzer
Copy link
ContributorAuthor

Missed the fact that test_symlog{,2} suffers from the same overstriking problem as test_logscales. Dropped the png and svg tests, and kept the pdf baseline, as ghostscript appears not to pick up the overstriking, so the fundamental functionality (of symlog scales) is still tested.

@jklymak
Copy link
Member

I don't think there is a compelling need to have those tests make pdfs and svgs. OTOH, I bet the png still won't match because of anti-aliasing so maybe update the baseline image?

@anntzer
Copy link
ContributorAuthor

anntzer commentedJan 29, 2019
edited
Loading

the png also fails due to antialiasing (likely), it is thepdf test that I kept; likely it works because ghostscript's rasterizer is not as good and does not perform the antialiasing correctly? :p

PS: I find it pretty funny that github's UI includes the svg files in the +/- lines count :)

@anntzer
Copy link
ContributorAuthor

Thinking again about adding remove_ticks to__call__ as a mechanism for inter-locator communication, I think that basically assumes (*) that the minor locator would always be used with the "corresponding" major locator (so that the minor locator can meaningfully interpret what is pass down to it). It is true that in general, a minor loglocator is used with a major loglocator (for example). But if you look at the issue that I mentioned in this PR, some of them come from people forcing major tick locations (effectively using a FixedLocator under the hood) and expecting the minor ticker to know not to put a tick there, so the assumption (*) does not hold.

If a minor locator really wants to know what a major locator did, it should probably(?) do something like 1) check that itself is the minor locator (self is self.axis.minor.locator) and then 2) call the major locator again to see what it outputs (self.axis.major.locator()).

@jklymak
Copy link
Member

I don't follow that argument. Why would it assume anything about what kind of locator it is or if its been used with a major locator? Its just saying: don't use these possible tick values, and maybe being clever about what to do between those tick values.

If you like, we could call itmajor_ticks and then that isthe signal that the locator is a minor locator.

@anntzer
Copy link
ContributorAuthor

anntzer commentedJan 30, 2019
edited
Loading

Again, I think we should have an actual example where passing the major ticks down is useful before going through the API change pain.
Also,self is self.axis.minor.locator is such an easy way to check whether you're the minor locator that I wonder why we missed it before [edit: I guess because that doesn't work for locators not associated with an axis, but I'm not sure we really care about that case...].

@jklymak
Copy link
Member

OK, getting in the weeds, but why does the locator care about its axis? We pass stuff around on a somewhat ad-hoc basis and this is one case where I'm not sure what the benefit is.

@anntzer
Copy link
ContributorAuthor

anntzer commentedJan 30, 2019
edited
Loading

Right now, the locators need to know what the axis is because when you calllocator(), they need to look up the axis limits; they also use it to consultaxis.get_tick_space() (for determining the number of ticks).

The formatters need to know what the axis is to consult the axis limits.

I would like to make tickers independent of the axis (and always pass the axis down as argument) to make them reusable over multiple axises, but that's not going to happen here.

jklymak reacted with thumbs up emoji

Copy link
Member

@jklymakjklymak left a comment

Choose a reason for hiding this comment

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

Just commenting - these aren't necessarily blockers from my point of view, but should be discussed before I approve....

return self.minor.locator()
"""Get the array of minor tick locations in data coordinates."""
# Remove minor ticks duplicating major ticks.
major_locs = self.major.locator()
Copy link
Member

Choose a reason for hiding this comment

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

why not pass themajor_locs here, if we don't want to do it in the locator? Agree that its an API change, but I suspect a minor one; this method is not used elsewhere, except in the tests...

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Because if anything I think the main API change here is that one cannot callaxis.minor.locator() to get the minor tick locations. The main API to do that is nowaxis.get_minorticklocs(); making itaxis.get_minorticklocs(major_locs=axis.get_majorticklocs()) seems a bit of a pain (Also, what aboutaxis.get_ticklocs(minor=True)? Would you now have to pass the major_locs kwarg toget_ticklocs() but only ifminor=True? Looks a bit awkward.).

"""Get the array of minor tick locations in data coordinates."""
# Remove minor ticks duplicating major ticks.
major_locs = self.major.locator()
minor_locs = self.minor.locator()
Copy link
Member

Choose a reason for hiding this comment

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

aren't we now calling both locators twice? Seems inefficient to me.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Yes (per the above).
Frankly, would not worry as I doubt it's anywhere close to being a bottleneck, but we could factor outget_minorticklocs to a private helper that takes majorlocs as parameter if this is really an issue...

Copy link
Member

Choose a reason for hiding this comment

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

OK, actually I'm confused - why are we calling the locator at all here? Do we not cache the existing tick locations in some way? Sorry if I'm being dumb.

Copy link
ContributorAuthor

@anntzeranntzerFeb 1, 2019
edited
Loading

Choose a reason for hiding this comment

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

I... don't think so? Note that the previous implementation also called the locator.
Obviously we could inspect what are thecurrent tick positions, but I don't know if there's any invalidation mechanism in place right now (e.g. what happens when you pan/zoom?).
IOW, this uses the same structure as before the PR; havingget_{major,minor}ticklocs handle cache invalidation could be future work ( :) ).

@anntzer
Copy link
ContributorAuthor

anntzer commentedFeb 12, 2019
edited
Loading

Tentatively milestoning as 3.1 as this already has one positive review (and fixes a longstanding bug), feel free to remilestone if it doesn't make it.

lo, hi = sorted(transform.transform(self.get_view_interval()))
# Use the transformed view limits as scale. 1e-5 is the default rtol
# for np.isclose.
tol = (hi - lo) * 1e-5
Copy link
Member

Choose a reason for hiding this comment

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

"transformed view limits" means this is now in axes coordinates, right? If so, this is the right thing to do.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It's some linear transform away from axes coordinates, so the relative check is equivalent to being done in axes coordinates.

timhoffm reacted with thumbs up emoji
The Axis knows what the major and the minor ticker are so it makes senseto let it handle the deduplication logic.Revert some heuristic deduplication logic from the Locators themselves(which effectively tests that the new code works).
Its original intent was to check that ax{v,h}line works in (sym)logscales, and the baseline svg changed with the removal of minor tickoverstriking.
@anntzer
Copy link
ContributorAuthor

rebased

@timhoffm
Copy link
Member

Anyone can merge after CI pass.

@tacaswell
Copy link
Member

azure failure was download related, restarting.

@jklymakjklymak merged commit2658eef intomatplotlib:masterFeb 23, 2019
meeseeksmachine pushed a commit to meeseeksmachine/matplotlib that referenced this pull requestFeb 23, 2019
timhoffm added a commit that referenced this pull requestFeb 23, 2019
…314-on-v3.1.xBackport PR#13314 on branch v3.1.x (Move major/minor tick overstrike logic to Axis.)
@anntzeranntzer deleted the minoroverstrike branchFebruary 23, 2019 21:11
tacaswell added a commit to tacaswell/matplotlib that referenced this pull requestApr 1, 2019
Inmatplotlib#13363 when `iter_ticks` was deprecated the in-lined logicdid not account for the updates frommatplotlib#13314.
tacaswell added a commit to tacaswell/matplotlib that referenced this pull requestApr 9, 2019
Inmatplotlib#13363 when `iter_ticks` was deprecated the in-lined logicdid not account for the updates frommatplotlib#13314.
tacaswell added a commit to tacaswell/matplotlib that referenced this pull requestApr 9, 2019
If Axis.remove_overlapping_locs is set to False the `Axis` object willnot attempt to remove locations in the minor ticker that overlap withlocations in the major ticker.   This is a follow up tomatplotlib#13314 whichun-conditionally removed the overlap.closesmatplotlib#13618
tacaswell added a commit to tacaswell/matplotlib that referenced this pull requestApr 12, 2019
Inmatplotlib#13363 when `iter_ticks` was deprecated the in-lined logicdid not account for the updates frommatplotlib#13314.
tacaswell added a commit to tacaswell/matplotlib that referenced this pull requestApr 12, 2019
If Axis.remove_overlapping_locs is set to False the `Axis` object willnot attempt to remove locations in the minor ticker that overlap withlocations in the major ticker.   This is a follow up tomatplotlib#13314 whichun-conditionally removed the overlap.closesmatplotlib#13618
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@jklymakjklymakjklymak approved these changes

@timhoffmtimhoffmtimhoffm approved these changes

Assignees
No one assigned
Projects
None yet
Milestone
v3.1.0
Development

Successfully merging this pull request may close these issues.

Setting axis ticks in log scale produces duplicate tick labels.
4 participants
@anntzer@jklymak@timhoffm@tacaswell

[8]ページ先頭

©2009-2025 Movatter.jp