Skip to content

Commit f3d407d

Browse files
committed
Merge pull request #2707 from pelson/callable_date_formatter
Callable date formatter
2 parents 5185368 + be0aafb commit f3d407d

File tree

3 files changed

+51
-14
lines changed

3 files changed

+51
-14
lines changed

lib/matplotlib/dates.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -517,9 +517,25 @@ class AutoDateFormatter(ticker.Formatter):
517517
dictionary by doing::
518518
519519
520-
formatter = AutoDateFormatter()
521-
formatter.scaled[1/(24.*60.)] = '%M:%S' # only show min and sec
522-
520+
>>> formatter = AutoDateFormatter()
521+
>>> formatter.scaled[1/(24.*60.)] = '%M:%S' # only show min and sec
522+
523+
Custom `FunctionFormatter`s can also be used. The following example shows
524+
how to use a custom format function to strip trailing zeros from decimal
525+
seconds and adds the date to the first ticklabel::
526+
527+
>>> def my_format_function(x, pos=None):
528+
... x = matplotlib.dates.num2date(x)
529+
... if pos == 0:
530+
... fmt = '%D %H:%M:%S.%f'
531+
... else:
532+
... fmt = '%H:%M:%S.%f'
533+
... label = x.strftime(fmt)
534+
... label = label.rstrip("0")
535+
... label = label.rstrip(".")
536+
... return label
537+
>>> from matplotlib.ticker import FuncFormatter
538+
>>> formatter.scaled[1/(24.*60.)] = FuncFormatter(my_format_function)
523539
"""
524540

525541
# This can be improved by providing some user-level direction on
@@ -535,8 +551,9 @@ class AutoDateFormatter(ticker.Formatter):
535551

536552
def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
537553
"""
538-
Autofmt the date labels. The default format is the one to use
539-
if none of the times in scaled match
554+
Autoformat the date labels. The default format is the one to use
555+
if none of the values in ``self.scaled`` are greater than the unit
556+
returned by ``locator._get_unit()``.
540557
"""
541558
self._locator = locator
542559
self._tz = tz
@@ -548,17 +565,25 @@ def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
548565
1. / 24.: '%H:%M:%S',
549566
1. / (24. * 60.): '%H:%M:%S.%f'}
550567

551-
def __call__(self, x, pos=0):
552-
scale = float(self._locator._get_unit())
568+
def __call__(self, x, pos=None):
569+
locator_unit_scale = float(self._locator._get_unit())
553570
fmt = self.defaultfmt
554571

555-
for k in sorted(self.scaled):
556-
if k >= scale:
557-
fmt = self.scaled[k]
572+
# Pick the first scale which is greater than the locator unit.
573+
for possible_scale in sorted(self.scaled):
574+
if possible_scale >= locator_unit_scale:
575+
fmt = self.scaled[possible_scale]
558576
break
559577

560-
self._formatter = DateFormatter(fmt, self._tz)
561-
return self._formatter(x, pos)
578+
if isinstance(fmt, six.string_types):
579+
self._formatter = DateFormatter(fmt, self._tz)
580+
result = self._formatter(x, pos)
581+
elif six.callable(fmt):
582+
result = fmt(x, pos)
583+
else:
584+
raise TypeError('Unexpected type passed to {!r}.'.formatter(self))
585+
586+
return result
562587

563588

564589
class rrulewrapper:

lib/matplotlib/tests/test_dates.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
import warnings
99
import tempfile
1010

11-
from nose.tools import assert_raises, assert_equal
1211
import dateutil
12+
import mock
13+
from nose.tools import assert_raises, assert_equal
1314

1415
from matplotlib.testing.decorators import image_comparison, cleanup
1516
import matplotlib.pyplot as plt
1617
import matplotlib.dates as mdates
17-
from matplotlib.dates import DayLocator
1818

1919

2020
@image_comparison(baseline_images=['date_empty'], extensions=['png'])
@@ -155,6 +155,18 @@ def test_DateFormatter():
155155
fig.autofmt_xdate()
156156

157157

158+
def test_date_formatter_callable():
159+
scale = -11
160+
locator = mock.Mock(_get_unit=mock.Mock(return_value=scale))
161+
callable_formatting_function = lambda dates, _: \
162+
[dt.strftime('%d-%m//%Y') for dt in dates]
163+
164+
formatter = mdates.AutoDateFormatter(locator)
165+
formatter.scaled[-10] = callable_formatting_function
166+
assert_equal(formatter([datetime.datetime(2014, 12, 25)]),
167+
['25-12//2014'])
168+
169+
158170
def test_drange():
159171
"""
160172
This test should check if drange works as expected, and if all the

0 commit comments

Comments
 (0)