diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 131eac48093c..40523ecd9b87 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -704,101 +704,71 @@ def __init__(self, locator, tz=None, formats=None, offset_formats=None, self._locator = locator self._tz = tz self.defaultfmt = '%Y' - # there are 6 levels with each level getting a specific format - # 0: mostly years, 1: months, 2: days, - # 3: hours, 4: minutes, 5: seconds - if formats: - if len(formats) != 6: - raise ValueError('formats argument must be a list of ' - '6 format strings (or None)') - self.formats = formats - else: - self.formats = ['%Y', # ticks are mostly years - '%b', # ticks are mostly months - '%d', # ticks are mostly days - '%H:%M', # hrs - '%H:%M', # min - '%S.%f', # secs - ] - # fmt for zeros ticks at this level. These are - # ticks that should be labeled w/ info the level above. - # like 1 Jan can just be labelled "Jan". 02:02:00 can - # just be labeled 02:02. - if zero_formats: - if len(zero_formats) != 6: - raise ValueError('zero_formats argument must be a list of ' - '6 format strings (or None)') - self.zero_formats = zero_formats - elif formats: - # use the users formats for the zero tick formats - self.zero_formats = [''] + self.formats[:-1] - else: - # make the defaults a bit nicer: - self.zero_formats = [''] + self.formats[:-1] - self.zero_formats[3] = '%b-%d' - - if offset_formats: - if len(offset_formats) != 6: - raise ValueError('offsetfmts argument must be a list of ' - '6 format strings (or None)') - self.offset_formats = offset_formats - else: - self.offset_formats = ['', - '%Y', - '%Y-%b', - '%Y-%b-%d', - '%Y-%b-%d', - '%Y-%b-%d %H:%M'] + + self.formats = np.array(['%Y', '%b', '%d', '%Hh', '%M', '%S', '%fµs']) + self.separator = np.array(['-', '-', ' ', '', ':', '.']) + self.offset_string = '' self.show_offset = show_offset - + def __call__(self, x, pos=None): formatter = DateFormatter(self.defaultfmt, self._tz) return formatter(x, pos=pos) + + def format_string(self, start_level, end_level): + if start_level == end_level: + return "" + s = self.formats[start_level] + for i in range(start_level+1, end_level): + s += self.separator[i-1] + self.formats[i] + return s + + def format_ticks(self, values): + if len(values) <= 1: + return [num2date(v, tz=self._tz).strftime(self.offset_formats[-1]) + for v in values] + tickdatetime = [num2date(value, tz=self._tz) for value in values] - tickdate = np.array([tdt.timetuple()[:6] for tdt in tickdatetime]) - - # basic algorithm: - # 1) only display a part of the date if it changes over the ticks. - # 2) don't display the smaller part of the date if: - # it is always the same or if it is the start of the - # year, month, day etc. - # fmt for most ticks at this level - fmts = self.formats - # format beginnings of days, months, years, etc... - zerofmts = self.zero_formats - # offset fmt are for the offset in the upper left of the - # or lower right of the axis. - offsetfmts = self.offset_formats - - # determine the level we will label at: - # mostly 0: years, 1: months, 2: days, - # 3: hours, 4: minutes, 5: seconds, 6: microseconds - for level in range(5, -1, -1): - if len(np.unique(tickdate[:, level])) > 1: + + dates = np.array([(d.year, d.month, d.day, d.hour, d.minute, d.second, + d.microsecond) for d in tickdatetime]) + + zeros = np.array([0, 1, 1, 0, 0, 0, 0]) + + # determine the offset level + for level in range(7): + if len(np.unique(dates[:, level])) > 1: break - # level is the basic level we will label at. - # now loop through and decide the actual ticklabels - zerovals = [0, 1, 1, 0, 0, 0, 0] - labels = [''] * len(tickdate) - for nn in range(len(tickdate)): - if level < 5: - if tickdate[nn][level] == zerovals[level]: - fmt = zerofmts[level] - else: - fmt = fmts[level] - else: - # special handling for seconds + microseconds - if (tickdatetime[nn].second == tickdatetime[nn].microsecond - == 0): - fmt = zerofmts[level] - else: - fmt = fmts[level] - labels[nn] = tickdatetime[nn].strftime(fmt) + if self.show_offset: + self.offset_string = tickdatetime[0].strftime(self.format_string(0, level)) + offset = dates[0].copy() + offset[level:] = zeros[level:] + else: + offset = zeros + # Check what changes from one tick to the next + ddates = np.diff(dates, axis=0, prepend=np.array([offset])) + try: + change_start = [] + change_end = [] + for d in ddates: + z = np.nonzero(d) + change_start.append(z[0][0]) + change_end.append(z[0][-1] + 1) # do usual python range + except: + raise ValueError('2 ticks with same value') + + # First tick needs to start right where the offset left off + change_start[0] = level + + labels = [] + for i, d in enumerate(tickdatetime): + labels.append(d.strftime(self.format_string(change_start[i], change_end[i]))) + + #TODO fix this to work without relying on '.' or similar hack. # special handling of seconds and microseconds: # strip extra zeros and decimal if possible. # this is complicated by two factors. 1) we have some level-4 strings @@ -813,10 +783,6 @@ def format_ticks(self, values): if '.' in labels[nn]: labels[nn] = labels[nn][:-trailing_zeros].rstrip('.') - if self.show_offset: - # set the offset string: - self.offset_string = tickdatetime[-1].strftime(offsetfmts[level]) - return labels def get_offset(self):