sawyl: (A self portrait)
[personal profile] sawyl
I 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:

  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 False


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 setFormat() to return yet another alternate format if the locations array is zero length, as is the case when displaying interactive values.

Profile

sawyl: (Default)
sawyl

August 2018

S M T W T F S
   123 4
5 6 7 8910 11
12131415161718
192021222324 25
262728293031 

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Feb. 4th, 2026 03:07 pm
Powered by Dreamwidth Studios