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

Enh optional tick deconflict #13826

Closed

Conversation

tacaswell
Copy link
Member

The name is up for debate as is threading it through all of the __init__ methods.

Docs and tests will follow if we agree on this approach and settle on some names.

This now works as expected:

import numpy as np
import matplotlib.dates as mdates
import matplotlib.pyplot as plt

t = np.arange("2018-11-03", "2018-11-06", dtype="datetime64")
x = np.random.rand(len(t))

fig, ax = plt.subplots()
ax.plot(t,x)

ax.xaxis.set_major_locator(mdates.DayLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('\n%a'))

ax.xaxis.set_minor_locator(mdates.HourLocator((0,6,12,18),
                                              enable_deconflict=False))
ax.xaxis.set_minor_formatter(mdates.DateFormatter('%H:%M'))
plt.show()

In matplotlib#13363 when `iter_ticks` was deprecated the in-lined logic
did not account for the updates from matplotlib#13314.
@tacaswell tacaswell added the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Apr 1, 2019
@tacaswell tacaswell added this to the v3.1.0 milestone Apr 1, 2019
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.

Basically good, although the name is pretty bad (yes, that's the name we were using in the discussion last week). Perhaps "allow_major_minor_overlap" or "allow_minor_overlap"?

@timhoffm
Copy link
Member

timhoffm commented Apr 1, 2019

I feel like the logic should be the other way round: "prevent_xxx_overlap=False" because this is the actual action. Alternative names: "remove_conflicting_ticks", "remove_duplicates", "clip_overlapping_ticks".

If you are worried on adding this to __init__, I think it's fine to just use the property/setter.

locator = mdates.HourLocator((0,6,12,18))
locator.clip_overlapping_ticks = False
ax.xaxis.set_minor_locator(locator)

It will be a rarely used property, so slightly longer code is ok.

@ImportanceOfBeingErnest
Copy link
Member

This is a pretty clean and future-proof solution. I think to adress my concerns in #13618 it would equally be enough to allow people to manually set a (non-predefined) attribute on the locator (I don't know if that is bad style? But it would allow to not have to change all the tickers?)

Naming

(enable_deconflict = True) :=: (disable_conflict = True) :=: (conflict = False)
This is pretty hard to decipher (at least for non-native speakers?).

Maybe just allow_overlap? The other suggestions above are also fine. Though I'm not sure if "clip" is the right word?

@pharshalp
Copy link
Contributor

I like the option allow_major_minor_overlap=False.

@tacaswell
Copy link
Member Author

I think to adress my concerns in #13618 it would equally be enough to allow people to manually set a (non-predefined) attribute on the locator (I don't know if that is bad style? But it would allow to not have to change all the tickers?)

True, but threading it through and making it a property makes it easier to document and makes it more discover-able. I think we should at least fallback to putting a class-level placeholder in the Locator baseclass.

although the name is pretty bad

Indeed. I think it should be a 'default to True', as discussed on the call remove_overlapping is the current favorite.

@tacaswell tacaswell force-pushed the enh_optional_tick_deconflict branch from 98f9043 to 59b9308 Compare April 1, 2019 19:53
This adds the logic to check if there is an attribute on the minor
locator to control the deconfliction.
@tacaswell tacaswell force-pushed the enh_optional_tick_deconflict branch from 59b9308 to 6642990 Compare April 1, 2019 19:56
@jklymak
Copy link
Member

jklymak commented Apr 2, 2019

remove_major_ticks=False? I think the kwarg should have something about “major” in them...

@tacaswell
Copy link
Member Author

I think having 'major' in the name confuses things as it is a property of the minor locator and it affects what happens to the minor ticks. remove_major_ticks makes it sound like the minor locator is going to have priority over the major locator.

On the call we decided not put 'tick' in the name because this controls if we put in a Tick object, which provides the 'tick', the 'label', and the 'grid'.

@jklymak
Copy link
Member

jklymak commented Apr 2, 2019

exclude_major_locations? I think it should have major in the name, or the user won't have any idea what's going on...

@ImportanceOfBeingErnest
Copy link
Member

exclude_minor_locs_at_major_positions would be a very accurate description - though it's of course too long.

lib/matplotlib/axis.py Outdated Show resolved Hide resolved
@@ -1317,6 +1316,9 @@ def get_minorticklocs(self):
# Remove minor ticks duplicating major ticks.
major_locs = self.major.locator()
minor_locs = self.minor.locator()
# we do check when we set the attribute that this is a Locator
# subclass, use getattr out of an over abundance of caution
remove_overlaps = getattr(self.minor.locator, 'remove_overlaps', True)
Copy link
Member

Choose a reason for hiding this comment

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

OK, so you went w/ the attribute path? So, how does this get set? I guess this PR needs tests and examples...

@@ -1326,7 +1328,8 @@ def get_minorticklocs(self):
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.

While we are here, I think the tolerance should be smaller here. .... 1.0001 == 1.000001 with this.... I would tend to go w/ something like 1e-10.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is in pixel space though. If the goal is to prevent tick/grid/label collisions shouldn't we make this closer to 1?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is a visual issue, but rather a math one. If the user wants a major tick at 1.0 and a minor tick at 1.00001 I don't think we should stop them (or make them use a kwarg) short of numerical roundoff.

Copy link
Member Author

Choose a reason for hiding this comment

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

All of the complaints about this have been due to visual overlap.

The problem we have is that we can not tell "the user intentionally put a major and minor ticks on top of each other so we should do what we are told" (ex, @ImportanceOfBeingErnest date example) vs "the user accidentally set up locators that collide and we should do the obviously correct thing and de-conflict them"

Previously the behavior was the first (which when we made the log minor locator smarter caused problems and @ImportanceOfBeingErnest was relying on) but now we do the second. If we accept that we want to de-conflict the ticks as a primarily a display issue then we should do the filtering based on how close it comes in pixel space.

Using this flag the user can pick between the two behavior (by telling us which of the two cases they want) and we chose to default to door number 2 ("do 'the right thing' visually) as I think that is going to be the more common intention.

Copy link
Member

Choose a reason for hiding this comment

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

Thats fine, I understand your point. However:

then we should do the filtering based on how close it comes in pixel space.

I don't think we should do any deciding of what to send to the renderer in pixel space because that means a 100-dpi figure is different than 200 dpi figure. You will end up with the inconsistent situation that a pdf that has 72 dpi virtual pixels will have fewer ticks than a png with 100 dpi.

If your argument were instead to do the deconflict as a fraction of tick width, I'd be fine w/ that. linewidths are in physical units.

Anyway, this comment wasn't meant at all to be a blocker on this PR. The overall idea seems fine to me.

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 DPI dependence is a very good point. I would be comfortable tweaking the exact de-confliction criteria later.

@tacaswell
Copy link
Member Author

The test coverage drop is due to travis not firing. The tests that do not have a png version or not run on windows and azure does not report coverage yet.

@tacaswell
Copy link
Member Author

Closing in favor of #13908 which puts this functionality in the Axis object instead.

@tacaswell tacaswell closed this Apr 9, 2019
@tacaswell tacaswell removed this from the v3.1.0 milestone Apr 9, 2019
@tacaswell tacaswell removed the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Apr 9, 2019
@tacaswell tacaswell added this to the unassigned milestone Apr 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants