| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
It may be that I misunderstand something here, but shouldn't the date format be determined though the rcParams date.autoformatter.xyz parameters? |
Sorry, something went wrong.
|
The current formatters are: # date.autoformatter.year : %Y # date.autoformatter.month : %Y-%m # date.autoformatter.day : %Y-%m-%d # date.autoformatter.hour : %m-%d %H # date.autoformatter.minute : %d %H:%M # date.autoformatter.second : %H:%M:%S # date.autoformatter.microsecond : %M:%S.%f and which one gets used is set by logic inside dates.py that chooses which interval is most appropriate. But only one formatter gets used for every tick once that formatter is chosen. This PR takes into account surrounding ticks and changes the format accordingly. For instance, its extra info to repeat 2009-01- for all the ticks in the "Days" example above, or the date in the "Hours" example. Taking into account the surrounding ticks to minimize visual clutter is what this PR does. It would make sense for this PR to have a way to specify the format of the individual date elements - there are already placeholders for that. i.e. self._yearfmt = '%Y'
self._monthfmt = '%b'
self._dayfmt = '%d'
# etc...but I've not added RC params or kwargs yet. It would of course be necessary to get the old behaviour back, either by leaving it the default, or by leaving instructions: locator = mdates.LegacyAutoDateLocator()
formatter = mdates.LegacyAutoDateFormatter(locator)
ax.yaxis.set_major_locator(locator)
ax.yaxis.set_major_formatter(formatter) |
Sorry, something went wrong.
|
I see. Just note that it is very convenient to set the preferred format once through the rcParams and have every plot the way you want it. So if that is going to change, intruducing a parameter which reverts back would definitely be useful, date.autoformatter.use : old or similar. |
Sorry, something went wrong.
|
@ImportanceOfBeingErnest That makes sense to me. It sounds like we are also moving to a "startup-file" like startup sequence, so folks will be able to put slightly more sophisticated recipes in their new matplotlibrc.py, like changing the default date handler. However I'm not sure if the architecture of that file is going to just affect rcParams, or arbitrary code. I think the former, so some way for rcParams to specify the DateFormatter is what will be needed. ie. rcParams['dates.locator']=mdates.LegacayAutoDateLocator |
Sorry, something went wrong.
This seems to be something used downstream in ObsPy (e.g., on waveform plots). I have not looked at or compared either implementation though. |
Sorry, something went wrong.
|
@QuLogic I think ObPy just makes the first tick have more info, but doesn't try to be intelligent about |
Sorry, something went wrong.
|
Working through the example, I think my question may be more of what resolution does the formatter pick up? fig, ax = plt.subplots()
t0 = datetime.datetime(2009, 1, 20)
tf = datetime.datetime(2009, 1, 21)
ax.axvspan(t0, tf, facecolor="blue", alpha=0.25)
ax.set_xlim(t0 - datetime.timedelta(days=5), tf + datetime.timedelta(days=5))
ax.vlines([datetime.datetime(2009,1,17,5) +
datetime.timedelta(hours=x) for x in range(0,200,25)], 0, 1)
ax.set_ylim(0,1)
plt.show() |
Sorry, something went wrong.
|
@story645 It returns: Note this doesn't change the locator (which chooses the ticks), it sets the formatter. |
Sorry, something went wrong.
|
Actually, coming back to the comment about localization mentioned during the call:
A quick search did not yield any way to obtain localized separators or formats (especially "date without year") though :/ Not saying this should be a blocker, but just raising the issue. |
Sorry, something went wrong.
|
I'm fine w/o the dashes. Easy change. I'm personally against any formats that don't go year month day. As someone who writes hand written logs, nothing drives me nuttier than seeing "12-11-10" in a log book I know was written in November 2010, even though I would totally say 12 November 2010 in speech; written and spoken speech and scientific notation on a plot can/should be different. |
Sorry, something went wrong.
|
@jklymak thanks! This only works with the major locator? (And I know that's probably a question about the autoformatter in general...) |
Sorry, something went wrong.
|
Ummm... I think you can pass whatever formatter you want: ax.yaxis.set_minor_formatter(formatter)I think to make nicely formatted labels for minor date ticks would be a challenge given that they are already too big for normal axes sizes! I think having a decent minor locator would be a fun challenge, but one thing at a time ;-) |
Sorry, something went wrong.
|
This was largely met w/ approval on the call to turn this into AutoDateFormatter, and move the current AutoDateFormatter to a Mpl22DateFormatter so folks could still access it. But some key folks weren't on the call. @tacaswell and @efiring, or anyone else want to weigh in before I do all the work of changing it? @ImportanceOfBeingErnest I'll still try to figure out the best API for getting the old behaviour back, and ping you for your opinion. |
Sorry, something went wrong.
|
Since my opinion on this is being asked for: I would not change the name. AutoDateFormatter is the automatic formatter which labels the ticks according to the rules given in the date.autoformatter.xyz rcParams. I would leave it at this, because this is how people use it now and will still want to use it. There are all kinds of useful as well as weird standards for datetime formats and indeed as @anntzer commented, different language backgrounds need different formatting capabilities. They might have set their rcParams up to do the plots they like that way and I think one should not require them to change anything more than possibly add a single additional rcParam to get the old behaviour back. In view of that I could imagine to allow the rcParam date.formatter : auto to get the AutoDateFormatter. Concerning the new formatter: I'm not sure if I grasp this correctly. Does it not allow to specify any custom formats for individual components like days, minutes, years etc, or is it still not decided? As the overall aim of the new formatter seems to be to have nicely looking plots I could imagine to name it PrettyDateFormatter. Setting this would then be done via the rcParam date.formatter : pretty, which would also be the default if that is what people agree upon. |
Sorry, something went wrong.
|
Thanks - I'll certainly make it so the current behaviour is achievable with rcParams. As written the new formatter does not allow you to pass different formats for the elements. It'd be nice if it did. My tendancy would be to try to make it fit into one rcParam versus six or seven, but I'd have to think about whether parsing a format string is the best way to do that or passing a dict. Kind of needs @anntzer new rcParam methodology in place to see how that'll work 😉 |
Sorry, something went wrong.
| in future versions of Matplotlib. Please report any issues or | ||
| suggestions for improvement to the github repository or mailing list. | ||
|
|
||
| First, this is the default formatter. |
There was a problem hiding this comment.
Can this line be put after imports&data and right before the figure? It seems a bit odd here by itself.
Sorry, something went wrong.
|
|
||
| ############################################################################# | ||
| # If all calls to axes that have dates are to be made using this converter, | ||
| # it is probably most convenient to use the units reigistry where you do |
There was a problem hiding this comment.
typo:
| # it is probably most convenient to use the units reigistry where you do | |
| # it is probably most convenient to use the units registry where you do |
Sorry, something went wrong.
|
|
||
| ############################################################################# | ||
| # The default date formater is quite verbose, so Matplotlib has an | ||
| # alternative that can be chosen with a few extra lines of code. Note, |
There was a problem hiding this comment.
You should mention what the alternative is here; it may be in the code, but that doesn't mean it's obvious what's important.
Sorry, something went wrong.
| # manipulating one of three lists of strings. | ||
| # | ||
| # The ``formatter.formats`` list of formats is for the normal tick labels, | ||
| # There are seven levels, years, months, days, hours, minutes, seconds, and |
There was a problem hiding this comment.
| # There are seven levels, years, months, days, hours, minutes, seconds, and | |
| # There are seven levels: years, months, days, hours, minutes, seconds, and |
Sorry, something went wrong.
| # ticks that are "zeros". These are tick values that are either the first of | ||
| # the year, month, or day of month, or the zeroth hour, minute, or second. | ||
| # These are usually the same as the format of | ||
| # the ticks a level above. e.g. if the axis limts mean the ticks are mostly |
There was a problem hiding this comment.
Reflow.
Also, "e.g." cannot start a sentence; use "For example".
typo: limts
Sorry, something went wrong.
| self._formatter = DateFormatter(fmt, self._tz) | ||
| tickdate = np.array([num2date(tick).timetuple()[:6] | ||
| for tick in ticks]) | ||
| tickdatetime = [num2date(tick) for tick in ticks] |
There was a problem hiding this comment.
If you flip the order of these two lines, you won't need to call num2date twice and create duplicate datetime objects, I think?
| tickdatetime = [num2date(tick) for tick in ticks] | |
| tickdatetime = [num2date(tick) for tick in ticks] | |
| tickdate = np.array([tick.timetuple()[:6] | |
| for tick in tickdatetime]) |
Sorry, something went wrong.
| else: | ||
| fmt = fmts[level] | ||
| else: | ||
| # special handling for sconds + microseconds |
There was a problem hiding this comment.
| # special handling for sconds + microseconds | |
| # special handling for seconds + microseconds |
Sorry, something went wrong.
| # now loop through and decide the actual ticklabels | ||
| zerovals = [0, 1, 1, 0, 0, 0, 0] | ||
| ticknew = [] | ||
| for nn in range(len(tickdate)): |
There was a problem hiding this comment.
These could be zipd, no?
Sorry, something went wrong.
There was a problem hiding this comment.
Not sure what you mean...
Sorry, something went wrong.
There was a problem hiding this comment.
I mean, you don't seem to need nn: for something, something_else in zip(tickdate, tickdatetime): (I'm just not sure what to name those two.)
Sorry, something went wrong.
There was a problem hiding this comment.
Gotcha, but I find that actually makes me have to read the code more than what this does. However, to make things better, I got rid of the append and pre-allocate the list of string first (and hence need the index to know which string to populate).
Sorry, something went wrong.
| Return the :class:`~matplotlib.units.AxisInfo` for *unit*. | ||
|
|
||
| *unit* is a tzinfo instance or None. | ||
| The *axis* argument is required but not used. |
There was a problem hiding this comment.
Huh?
Sorry, something went wrong.
There was a problem hiding this comment.
Yeah, well... Thats just copied over from DateConverter 😉
Sorry, something went wrong.
| return sts | ||
|
|
||
| d1 = datetime.datetime(1997, 1, 1) | ||
| results = ([datetime.timedelta(weeks=52 * 200), |
There was a problem hiding this comment.
Using test parametrization seems the better option here?
Sorry, something went wrong.
There was a problem hiding this comment.
Maybe, but all the other similar tests are written this way, and I', not going to change all of them, particularly given my lack of comfort with parameterized tests...
Sorry, something went wrong.
There was a problem hiding this comment.
It's a fairly straightforward rearrangement of the for-loop, essentially:
@pytest.mark.parametrize(
't_delta expected',
# Here put the list/tuple of things that was in `results`.
)
def test_concise_formatter(t_delta, expected):
d1 = datetime.datetime(1997, 1, 1)
d2 = d1 + t_delta
# And then put the rest of `_create_auto_date_locator`, but without the function.You could do more fancy things with ids or pytest.param, but it's not really required here.
Sorry, something went wrong.
There was a problem hiding this comment.
That doesn't seem any more readable to me. I guess there is some advantage to parameterizing tests from the point of view of running them all rather than having them fail sequentially, but....
Sorry, something went wrong.
|
Thanks @QuLogic - one of your comments I didn't grok, and a couple of them I think can be left to future efforts (parameterizing the tests).... |
Sorry, something went wrong.
|
Pinging for this to be merged... |
Sorry, something went wrong.
| locator_unit_scale = 1.0 | ||
| ticks = self._locator() | ||
| if pos is not None: | ||
| if not np.array_equal(ticks, self._oldticks): |
There was a problem hiding this comment.
If I get this correctly, _oldticks/_oldlabels is an optimization to avoid recomputing all the ticks when the formatter gets called with pos=0, pos=1, pos=2, etc.
But that also means that if one assigns to formats/offset_formats/zero_formats, the labels don't get invalidated, so the assignment has no effect... until one changes the axes limits enough to make the ticks change.
I guess the options are either to make formats/... properties such that assigning to them invalidates _oldlabels, or also keep track of _oldformats, or use some helper that goes through functools.lru_cache(1), or just not worry about the performance cost of doing the formatting multiple times.
(FWIW I think the formatter API is not optimal here; transforming tick positions to tick labels should be done as a single method call that takes all tick positions at the same time and returns all labels.)
Sorry, something went wrong.
There was a problem hiding this comment.
OK, it works w properties. OTOH, the API is not what I'd have expected. The "setter" doesn't get called if I do something like:
formatter.format[0] = '%y'and instead you must do:
fmt = formatter.format
fmt[0] = '%y'
formatter.format = fmtwhich I guess makes sense, but is not what I think would happen if it was just a bare class variable.
So, should I just track the old formats? Thats a bit ugly, and at that point I'm not convinced its not just as fast to reformat everything for each call.
Sorry, something went wrong.
There was a problem hiding this comment.
Yeah, I failed to notice the problem with mutability, which means you need to track the old formats (in a local copy, as the lists can get modified in place...).
I agree with just redoing the formatting n times for now and change that if someone ever comes up with a use case where the performance cost matters...
The setter doesn't get called as foo.bar[0] = baz is equivalent to tmp = foo.bar; tmp[0] = baz so you call the getter, not the setter.
Sorry, something went wrong.
There was a problem hiding this comment.
I guess the other thing we could do is add set_formats, get_formats etc and make self.formats private. Then it will be clear that you have to pass the whole list of formats.
Sorry, something went wrong.
There was a problem hiding this comment.
But that just seems to make the API more awkward to work around what may not even be a real performance issue.
Sorry, something went wrong.
|
On 2018/11/29 10:05 AM, Antony Lee wrote:
I agree with just redoing the formatting /n/ times for now and change
that if someone ever comes up with a use case where the performance cost
matters...
That strikes me as unspeakably ugly... Surely there is a clean and
simple way to do it once.
|
Sorry, something went wrong.
|
It's not particularly hard (keep track of the various foo_formats in copies and see whether things have been invalidated); the question is just whether the whole thing is worth it. Of course the datetime formatters already break the underlying assumption that formatters operate independently of locators and format each value independently of the others: the formatter proposed by this PR needs to call (n times... even with the fix above) self._locator() to know what are all the tick values before formatting each of them (or, with the fix above, to see whether invalidation occurred). Which again argues that formatters should just grow a format_tick_labels(self, ticks) (name up to bikeshedding) method that computes all the labels in one go. Actually now that I think of it, it's not even that hard to do the API change while providing a deprecation period for third-party formatters (if there's no format_tick_labels, then fallback on calling __call__ n times after emitting a DeprecationWarning). |
Sorry, something went wrong.
|
Added the check. It wasn't the hardness of it that I was worried about, it was just the performance. But the algorithm itself is a bit of a pig, so I think its worth adding the checks. |
Sorry, something went wrong.
FIX: allow non-unit aware tickers FIX: make str list length more strict DOC FIX: add kwargs to ConciseDateConverter FIX: fix cursor formatter FIX: cleanup loop logic FIX: cleanup append
|
Rebased. I think this is OK to merge assuming it all passes CI |
Sorry, something went wrong.
|
This has two approvals - would someone do the honours and merge it? |
Sorry, something went wrong.
|
Let's roll. |
Sorry, something went wrong.
|
Thanks @anntzer (ahem, particularly as you don't use date times very much 😉) |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
PR Summary
Update 22 Sep 2018: New localization mechanism.
This can now be localized. Its a bit verbose because there are basically 3*7 = 21 formatting strings. 7 for the normal ticks, 7 for the ticks that are major time divisions (i.e. if ticks are 22 Mar, 1 Apr, 8 Apr, the tick labels will be "22", "Apr", "8". ). I think the default is good, and ISO compliant. The verbose lists of strings allow localization with a bit of work. I include an example of how to create a trivial subclass to encapsulate that customization. Maybe someone more clever than me can come up with a better way to do this.
..its described in detail at https://13776-1385122-gh.circle-artifacts.com/0/home/circleci/project/doc/build/html/gallery/ticks_and_spines/date_concise_formatter.html#sphx-glr-gallery-ticks-and-spines-date-concise-formatter-py
[x] still needs tests
ping @anntzer and @ImportanceOfBeingErnest who were both interested in how this would localize.
Update 4 July 2018
Renamed ConciseDateFormatter and included examples of how to use it.
I think mechanisms or dicussion of whether it shoudl be the defautl can be deferred. Certainly its nice to be able to set the date format manually as pointed out by @ImportanceOfBeingErnest below, and the current method allows this, whereas this Formatter has no localization like that.
...
This is a proposed new date formatter. I find the Automatic one less than useful, though I appreciate the functional flexibility of the default one.
I'd argue the default should be something like this, but apprecaite we are conservative on such matters.
This isn't ready for serious review attention yet, but thoughts on the API or the result are welcome...
I'll note a couple fo flaws (in my opinion) of the default locator. As per #9801, interval_multiples=True should be the default. Secondly, it would be nice if the locator only located the first and 15th of the month rather than 1, 15, 29.
EDIT: added extra info in the "offset" region of the axes.
Before:
After:
PR Checklist