Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 54 additions & 88 deletions lib/matplotlib/dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down