Extending matplotlib's DateAutoFormatter
Dec. 14th, 2012 04:56 pmI use matplotlib's AutoDateFormatter a lot. But it doesn't quite match my requirements. As well as scalable date formats I'd also like to be able to mark boundary ticks, e.g. first tick of a particular month or first month of a year, using a different format to the rest of the range markers.
Luckily, after a bit of thought, I realised that I could subclass the module, add an extra dictionary of boundary formats, and add some code to use the locator passed to the object by the caller to determine the relative positions of each tick:
This method works well when labelling the X-axis but is slightly less successful when it comes to labelling the value of the cursor in interactive sessions. However it has occurred to me that it ought to be possible to fix this by adding soemthing to
Luckily, after a bit of thought, I realised that I could subclass the module, add an extra dictionary of boundary formats, and add some code to use the locator passed to the object by the caller to determine the relative positions of each tick:
import numpy
from matplotlib.dates import *
class AutoDateBoundaryFormatter(AutoDateFormatter):
def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
# Initialise the object
AutoDateFormatter.__init__(self, locator, tz, defaultfmt)
# Define a dictionary of boundary values
self.scaled_boundaries = {
365.0 : '%b\n%Y',
30. : '%d\n%b',
1.0 : '%d\n%b',
1./24. : '%H:%M\n%a',
}
return None
def __call__(self, x, pos=0):
scale = float( self._locator._get_unit() )
fmt = self.setFormat(x, scale)
self._formatter = DateFormatter(fmt, self._tz)
return self._formatter(x, pos)
def setFormat(self, x, scale):
fmt = self.defaultfmt
for k in sorted(self.scaled):
if k>=scale:
box = k
break
if (len(self.scaled_boundaries) and
box in self.scaled_boundaries and
self.isBoundary(x, scale)):
# Apply a boundary format
fmt = self.scaled_boundaries[box]
elif box in self.scaled:
# Use one of the standard formats
fmt = self.scaled[box]
return fmt
def isBoundary(self, x, scale):
# Get a list of tick locations
locs = self._locator()
if x in locs and locs[0] != x:
# Get the index of the value in the locs array
i = numpy.where(locs==x)[0][0]
# Map x and the previous x to dates
now = num2date(x, self._tz)
prev = num2date(locs[i-1], self._tz)
if scale <= 1.0/24.0 and now.day != prev.day:
# Day has changed
return True
elif scale <= 30.0 and now.month != prev.month:
# Month has changed
return True
elif now.year != prev.year:
# Year has changed
return True
return FalseThis method works well when labelling the X-axis but is slightly less successful when it comes to labelling the value of the cursor in interactive sessions. However it has occurred to me that it ought to be possible to fix this by adding soemthing to
setFormat() to return yet another alternate format if the locations array is zero length, as is the case when displaying interactive values.