Skip to content

BUG: Prevent Tick params calls from overwriting visibility without being told to #12839

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

Merged
merged 6 commits into from
Feb 25, 2019

Conversation

ksunden
Copy link
Member

@ksunden ksunden commented Nov 19, 2018

PR Summary

Prevents a regression bisected to #10088, which caused subsequent calls to tick_params to overwrite the visibility of tick labels (et al.) when those calls did not set visibility themselves.

Just pops a subset of the kwargs out of the dictionary and replaces them after the call to _apply_params is made, so that they are there for other parts of the codebase (including tests).

The test included here passes on current release (v3.0.2), but fails on current master (
936855c).

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 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

for d in dicts:
if reset:
d.clear()
blacklist_dicts.append(
{k: d.pop(k) for k in blacklist if k in d.keys()}
Copy link

@42B 42B Nov 19, 2018

Choose a reason for hiding this comment

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

First, either this line seems to be indented too far or the line above isn't indented enough? Second, I'm curious about if there is a better way to write this without using .pop and iterating through d.keys() for every single lookup?

Copy link
Member Author

@ksunden ksunden Nov 19, 2018

Choose a reason for hiding this comment

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

I'll note that the iteration is only over the keys in the blacklist (total of 5 items), k in d.keys() is an O(1) check since keys view objects are set-like and hashable.

The second iteration could actually be rolled into this conditional, to prevent things from being popped then added back with updates, rather than popping indiscriminately and then iterating over the kwtrans keys.

fig, ax = plt.subplots()
plt.plot([0, 1, 2], [0, -1, 4])
plt.setp(ax.get_yticklabels(), visible=False)
ax.tick_params(axis="y", which="both", length=0)
Copy link
Member

Choose a reason for hiding this comment

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

I think this test could be easily done without an image comparison by just checking the visibility of the ticks?

Copy link
Member Author

Choose a reason for hiding this comment

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

The tricky bit is that one of the common ways in tests of checking wheter the ticks are visible was lying to you prior to this PR, (though if you went to the individual ticks, it would be correct) As such, I was looking to make sure it translated to the image generated.

Copy link
Contributor

Choose a reason for hiding this comment

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

You can instead save to svg and use some long-ish strings as labels, and check that the labels don't appear as substrings in the svg file.

for d in dicts:
if reset:
d.clear()
blacklist_dicts.append(
Copy link
Member

Choose a reason for hiding this comment

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

I find this pretty opaque, and don’t really understand why these kwargs are different than the other kwargs (other than that they control visibility). This could really use a comment explaining what it does and why. As it stands, I’m not clear at all that this is the proper solution, and maybe there is something more fundamental that is at fault.

@anntzer
Copy link
Contributor

anntzer commented Feb 7, 2019

So the bug comes from the fact that plt.setp(... visible=False) set the visibility by calling tick.set_visible(False), which used to override tick_params() which would set reset label1On to True; but after #10088 label1On maps to set_visible and thus the call to tick_params undoes the call to set_visible(False).

I think a correct fix would be somewhere along the lines of

diff --git i/lib/matplotlib/axis.py w/lib/matplotlib/axis.py
index 127c9c5b0..094cd53fd 100644
--- i/lib/matplotlib/axis.py
+++ w/lib/matplotlib/axis.py
@@ -856,26 +856,16 @@ class Axis(martist.Artist):
         For documentation of keyword arguments, see
         :meth:`matplotlib.axes.Axes.tick_params`.
         """
-        dicts = []
-        if which == 'major' or which == 'both':
-            dicts.append(self._major_tick_kw)
-        if which == 'minor' or which == 'both':
-            dicts.append(self._minor_tick_kw)
         kwtrans = self._translate_tick_kw(kw)
-        for d in dicts:
-            if reset:
-                d.clear()
-            d.update(kwtrans)
-
         if reset:
             self.reset_ticks()
         else:
             if which == 'major' or which == 'both':
                 for tick in self.majorTicks:
-                    tick._apply_params(**self._major_tick_kw)
+                    tick._apply_params(**kwtrans)
             if which == 'minor' or which == 'both':
                 for tick in self.minorTicks:
-                    tick._apply_params(**self._minor_tick_kw)
+                    tick._apply_params(**kwtrans)
             if 'labelcolor' in kwtrans:
                 self.offsetText.set_color(kwtrans['labelcolor'])
         self.stale = True

plus some wrangling with the rest of the codebase to keep track of label1On-ness.

Release critical as a regression.

@anntzer anntzer added this to the v3.1.0 milestone Feb 7, 2019
@anntzer anntzer added the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Feb 7, 2019
@ksunden
Copy link
Member Author

ksunden commented Feb 7, 2019

If I recall correctly, I had tried something like the simpler diff you presented here, but got several new test failures as a result. It may very well be easy to track those down in a cleaner way, though my inital efforts focused on fixing the problem fairily locally.

@anntzer
Copy link
Contributor

anntzer commented Feb 11, 2019

I think a simpler approach may be possible but this looks generally ok as a stopgap if nothing better is proposed before 3.1.

blacklist_dicts.append(
{
k: d.pop(k) for k in blacklist
if k in d.keys() and k not in kwtrans.keys()
Copy link
Contributor

Choose a reason for hiding this comment

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

k in d

Copy link
Contributor

@anntzer anntzer left a comment

Choose a reason for hiding this comment

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

Strongly suggest not adding a baseline image per https://github.com/matplotlib/matplotlib/pull/12839/files#r255441630.

@tacaswell
Copy link
Member

I am concerned about this fix. The underlying issue here is that the tick system has an internal set of defaults that it uses to create new ticks, but by using plt.setp you are reaching in to change the state of the ticks without propagating that back the default dictionary. This skip-list works for visibility, but will not work for color (or any other property).
Doing

ax.tick_params(axis="y", which="both", label1On=False)
ax.tick_params(axis="y", which="both", length=0)

works as expected.

I am 👎 on merging this. The correct fix is to track on the tick objects what state has been touch independently by the user and then not update that property.

I am also concerned that this will not work in the case where we generate more tick objects later so you will end up with mixed state.

Copy link
Member

@tacaswell tacaswell left a comment

Choose a reason for hiding this comment

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

I do not think this is the correct fix to the problem (which is that we do not track if users have mutated objects that the axis thinks it is managing).

@tacaswell
Copy link
Member

It might be possible to use the get methods to check what state does not match the (previous) defaults and then skip applying based on that?

@ksunden
Copy link
Member Author

ksunden commented Feb 19, 2019

To be clear, this is just taking out arguments only in the case that they were not asked for explicitly and simply not trying to change it. If the user is modifying the state using this function, those changes are propegated.

That said, I make no illusions, this is a hack, and there are certainly cleaner solutions, including changing my own library code such that the labelOn, etc. get set to match.

The point about mixed state is a good one, however, if users intentionally got a mixed state (I would tend to avoid this myself, but still) The code without this patch will override that mixed state, even when the arguments that are written have nothing to do with visibility.

The way this is implemented preserves mixed state if it exists, but makes it easy to get back to a consistent state.

@tacaswell tacaswell removed the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Feb 23, 2019
@tacaswell tacaswell modified the milestones: v3.1.0, v3.2.0 Feb 23, 2019
@tacaswell tacaswell added the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Feb 23, 2019
@tacaswell tacaswell modified the milestones: v3.2.0, v3.1.0 Feb 23, 2019
@tacaswell
Copy link
Member

Sorry, being thrashy about milestones / tags... Still thinking about this.

@tacaswell tacaswell dismissed their stale review February 23, 2019 17:55

I have now pushed a commit to this branch.

@tacaswell tacaswell requested a review from anntzer February 23, 2019 17:55
@tacaswell
Copy link
Member

I applied half of the diff suggested in the comments by @anntzer (we still want to update the default settings for new ticks) but only apply the passed-in kwargs to the existing ticks.

If I recall correctly, I had tried something like the simpler diff you presented here, but got several new test failures as a resul

@ksunden what tests failed for you locally with this change?


Went back and forth a bit on trying to understand what the intent of this method is (as it could be argued that the correct behavior is to re-apply the standard style to all of the ticks), but the presence of the reset kwargs suggests otherwise.

@ksunden
Copy link
Member Author

ksunden commented Feb 23, 2019

I am not getting any local test failures now, I recall trying this back in november, but don't recall why I ultimately moved on from it. All looks good on my end now.

Copy link
Contributor

@anntzer anntzer left a comment

Choose a reason for hiding this comment

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

Please try to not include a baseline image per https://github.com/matplotlib/matplotlib/pull/12839/files#r255441630. (I'm not 100% sure this will work, but is at least worth a try.)

@tacaswell tacaswell dismissed anntzer’s stale review February 25, 2019 20:09

Saving 15Kb on a file is not worth the complexity of checking the text of an svg (discussed on phone call)

Copy link
Contributor

@dopplershift dopplershift left a comment

Choose a reason for hiding this comment

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

I'm ok with adding another baseline image. It's only 15kb, and the alternative would actually be something out of line from the rest of the test suite and require more effort to understand what's going on.

@dopplershift dopplershift merged commit eb92f62 into matplotlib:master Feb 25, 2019
meeseeksmachine pushed a commit to meeseeksmachine/matplotlib that referenced this pull request Feb 25, 2019
@lumberbot-app
Copy link

lumberbot-app bot commented Feb 25, 2019

Something went wrong ... Please have a look at my logs.

jklymak added a commit that referenced this pull request Feb 25, 2019
…839-on-v3.1.x

Backport PR #12839 on branch v3.1.x (BUG: Prevent Tick params calls from overwriting visibility without being told to)
@ksunden ksunden deleted the tick_params branch February 26, 2019 00:05
@ksunden ksunden restored the tick_params branch March 29, 2019 16:22
has2k1 added a commit to has2k1/plotnine that referenced this pull request Jul 8, 2019
After matplotlib/matplotlib#12839
axis ticks showed up along all the edges of the plot panel
and theming of tick labels was finicky.

The solution is we have to be careful in how we toggle the
visibility of these elements. If not careful, merely setting
the tick/tick labels could switch on the visibility. This
could interfere with theme.

closes #278
@ksunden ksunden deleted the tick_params branch December 27, 2022 18:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants