-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
ENH ticks sublabel display is now an option #7438
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
Conversation
attn @efiring We need to find a better name for the option. |
I can't reproduce the documentation build failure. |
|
||
Parameter | ||
--------- | ||
base : float, optional, default: 10. |
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.
Got a slightly better error out of running it locally (only shows up on python2)
Exception occurred:
File "/home/tcaswell/source/p/matplotlib/lib/matplotlib/contour.py", line 18, in <module>
import matplotlib.ticker as ticker
File "/home/tcaswell/source/p/matplotlib/lib/matplotlib/ticker.py", line 811
SyntaxError: Non-ASCII character '\xc2' in file /home/tcaswell/source/p/matplotlib/lib/matplotlib/ticker.py on line 812, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
There is a non-breaking space in here:
position: 25496 of 73929 (34%), column: 14
character: (displayed as ) (codepoint 160, #o240, #xa0)
preferred charset: unicode (Unicode (ISO10646))
code point in charset: 0xA0
script: latin
syntax: . which means: punctuation
category: .:Base, b:Arabic, j:Japanese, l:Latin
to input: type "C-x 8 RET a0" or "C-x 8 RET NO-BREAK SPACE"
buffer code: #xC2 #xA0
file code: #xC2 #xA0 (encoded by coding system utf-8-unix)
display: by this font (glyph code)
xft:-simp-Hack-normal-normal-normal-*-15-*-*-*-m-0-iso10646-1 (#x2A9)
hardcoded face: nobreak-space
Character code properties: customize what to show
name: NO-BREAK SPACE
old-name: NON-BREAKING SPACE
general-category: Zs (Separator, Space)
decomposition: (noBreak 32) (noBreak ' ')
There are text properties here:
face font-lock-doc-face
fontified t
Now lets see if I can successfully push to @NelleV 's branch... So far the score on this is 0 for 2 in favor of gh. |
Great, I am now 1/3 😄 |
I had an unbreakable space in the code?!! |
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.
A few minor comments and a more major one (about the is_decade
variable).
I apologize, but I am not very used to the LogFormatter part of the basecode so I won't do much more than commenting. Nevertheless, except my is_decade
worry, it looks OK to me.
""" | ||
`base` is used to locate the decade tick, which will be the only | ||
one to be labeled if `labelOnlyBase` is ``True``. | ||
|
||
Parameter |
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.
Parameter_s_?
base of the logarithm. | ||
|
||
labelOnlyBase : bool, optional, default: False | ||
whether to only label decades ticks. |
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.
- The descriptions starts by a 5-space indent, doesn't it?
Sidenote: the last parameter has a description starting with a capital letter while the first two parameters haven't.
isDecade = is_close_to_int(fx) | ||
exponent = np.round(fx) if is_decade else np.floor(fx) |
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 normal to use is_decade
here rather than the isDecade
define just before? Besides, there is also a function called is_decade
at the module level (see l. 1781).
@@ -1016,33 +1039,29 @@ def __call__(self, x, pos=None): | |||
else: | |||
base = '%s' % b | |||
|
|||
if coeff in self.sublabel: | |||
if not is_decade and self.labelOnlyBase: |
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 guess this is_decade
and the following ones are fine (see l.1030 for defining the variable this time) but again, I am not sure it is a good thing to borrow the same name as a module level function.
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.
it's a really bad idea indead…
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.
And as @tacaswell found, I tripped over it without noticing...
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.
It's been fixed. I renamed is_decade
and isDecade
into is_x_decade
In general I find the variable names of this module very hard to understand. I'll plan on doing some clean up after 2.0
return '' | ||
|
||
if not is_decade: |
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 there an interest in not "factorizing" this if statement
with the previous one (l. 1044)? Something like
if not is_decade:
if self.labelOnlyBase:
return ''
else:
return self._non_decade_format(sign_string, base, fx, usetex)
else:
...
Looking at the class |
The naming of this module is completely off, but I'd rather wait until after 2.0 to refactor it. |
@NelleV About a new name for |
@afvincent That's already much better than sublabel_filtering |
if usetex: | ||
return (r'$%s%s^{%d}$') % (sign_string, | ||
base, | ||
nearest_long(fx)) |
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.
Misaligned.
else: | ||
return ('$%s$' % _mathdefault( | ||
'%s%s^{%d}' % | ||
(sign_string, base, nearest_long(fx)))) |
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 may be a case where using .format
is definitely clearer.
@efiring It could be great if you could have a look at this PR before wednesday |
@NelleV, what I now realize is that I don't like the present implementation of subsetting in the LogFormatter. I have a simple variation that I think works much better, in that it applies subsetting only if more than a given fraction (0.4 works well) of a decade is shown. Right now I have that threshold hardwired, but of course it could also be a kwarg, replacing label_pruning. Setting it to 1 would completely disable the subsetting. Fundamentally, there are actually 2 thresholds, so both could be exposed as kwargs. I will try to make a PR against your repo to illustrate this. |
@NelleV, I botched the attempt to open a PR against your repo, but I think that failure nevertheless leaves you with access to the one commit that I would like to see added. |
@@ -801,7 +801,7 @@ class LogFormatter(Formatter): | |||
Format values for log axis. | |||
""" | |||
def __init__(self, base=10.0, labelOnlyBase=False, | |||
label_pruning=False): | |||
minor_thresholds=(1, 0.4)): |
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 don't like the name minor_thresholds
as the LogFormatter are minor/major agnostic.
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.
Trying to do all of this in a single class may be the original sin here.
|
||
label_pruning : bool, optional, default: False | ||
when set to True, label on a subset of ticks. | ||
minor_thresholds : (subset, all), optional, default: (1, 0.4) |
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 don't understand how this option works from the documentation.
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.
-> "subset_thresholds"? I actually had that first, then changed it to "minor" because I thought that in practice it would be closer to the actual meaning.
I am going to do an attempt at improving the documentation at some point today (unless you beat me to it) |
I need to work on other things now, so it's all yours. |
if self._sublabels is not None and coeff not in self._sublabels: | ||
return '' | ||
|
||
if not is_decade: |
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.
use isDecade
above, is one of them a typo?
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, thanks for catching that. It looks like every instance of is_decade
as a scalar, as opposed to a function, should be replaced by isDecade
.
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.
It is a type. isDecade is undefined in this function. I'll fix that.
I fixed the non defined variable problem, but haven't found time to work on the documentation. |
@NelleV, I think you made the change in the wrong direction. We really need to fix the whole module to remove the shadowing of the function by the scalar, hence the need to switch to isDecade everywhere that the scalar is meant. |
@efiring I just fixed the issue of using an undefined variable so that the code runs. I haven't changed the naming. |
There are other places where the variable name is wrong (and by wrong I mean the code cannot work). |
'%s%s^{%d}' % | ||
(sign_string, base, nearest_long(fx)))) | ||
else: | ||
if self.labelOnlyBase and not is_decade: |
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.
There is a problem here as well.
|
||
def __call__(self, x, pos=None): | ||
""" | ||
Return the format for tick val `x` at position `pos`. | ||
Return the format for tick val `x`. | ||
""" | ||
b = self._base |
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.
@efiring Has this been done for performance reasons?
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.
No, I thought it was misleading. The pos
value is never used in this Formatter, and doesn't have to be specified.
It looks like you and Tom are both unhappy with "minor_thresholds"; do you want to change to "subset_thresholds", or "pruning_breakpoints", or "intermediate_thresholds", or "nonBaseLabelThresholds" (a 3-hump camel, consistent with "labelOnlyBase") or...? |
@efiring I am more confused about the tuple that we provide to the thresholding tuning, but I possibly just have to get use to it. It just seems to me that it's hard to understand what the first and second values correspond to and easy to do a mistake and provide only one value or too many values (in which case the code will crash with a probably unmeaningful error message). I am also not coming up with something better at the moment… (I am very sleep deprived). |
@NelleV, get some sleep; you can read this later. Yes, the use of a tuple here implies it is an "expert option", with the expectation it would rarely be set to other than the default. Most users don't instantiate their own Locators and Formatters. I also left out validation with the idea that it could be added once we are sure we want this API at all, and when we get around to it; we are far from validating every argument to every function, so lack of validation here is not egregious. The present algorithm takes two parameters that are both thresholds applied to the same variable, so there is some natural appeal to packaging them up in a tuple; it saves having to come up with two names. But it would be easy to switch to using two variables, e.g., "label_all_non_base" and "label_half_non_base". They could be described as upper thresholds applied to the axis range expressed in powers of the base. |
@dopplershift @tacaswell That patch is ready. |
LGTM 👍 |
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 don't know much about how we want the sublabels to behave, but a couple of clarifications below.
This is normally True for major ticks and False for | ||
minor ticks. | ||
|
||
minor_thresholds : (subset, all), optional, default: (1, 0.4) |
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.
Since all
is the lower bound, would it make sense to swap the order?
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.
It could go either way; I chose this because it corresponds to the order in which the thresholds are checked: first see if any minors are labeled, and if so, see if all of them are. The most common case is expected to be that none are labeled.
``labelOnlyBase=True`` to turn off minor ticks. | ||
Parameters | ||
---------- | ||
labelOnlyBase : bool, optional, default: 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 is not optional.
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.
Good catch.
if subs is None: # consistency with previous bad API | ||
self._subs = 'auto' | ||
elif cbook.is_string_like(subs): | ||
# TODO: validation ('all', 'auto') |
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.
Seems fixable now?
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.
Fixed.
self._linthresh = linthresh | ||
else: | ||
raise ValueError("Either transform or linthresh " | ||
"and base must be provided.") |
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 a small clarification because it's not in the docstring and it can be interpreted as either "(transform or linthresh) and base" or "transform or (linthresh and base)".
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.
Good point. Fixed.
Can someone review this? |
\o/ |
Just staring at it a bit longer.... |
I suspect that we will still find some weird corner-cases, but that is because it turns out log labeling and ticking is hard. Thanks to everyone who worked on this PR! |
# Add labels between bases at log-spaced coefficients | ||
c = np.logspace(0, 1, (4 - int(numdec)) + 1, base=b) | ||
self.sublabel = set(np.round(c)) | ||
self._sublabels = set(np.linspace(1, b, b)) |
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 this third argument supposed to be b
? It's a float, which is starting to cause warnings with new NumPy.
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.
Good point. Yes, it is supposed to be b
, but it should be an integer value, and it can be cast to an int. If it is not an integer then we shouldn't be trying to put in sublabels at all. I will fix it.
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.
See #7583 for a fix. Contrary to my comment above, I decided it is OK to allow sublabels when b is not an integer, though I expect this will be extremely rare in practice.
This is work in progress.
fixes #7202 and supersedes #7211
Here how the defaults behave:
Using the formatter's directly yields the expected behaviour, even on colorbar:

Here is the code to reproduce those plots: