8000 Added AutoDateFormatter and AutoDateLocator submitted · matplotlib/matplotlib@5f9468d · GitHub
[go: up one dir, main page]

Skip to content

Commit 5f9468d

Browse files
committed
Added AutoDateFormatter and AutoDateLocator submitted
by James Evans. Try the load_converter.py example for a demo. - ADS svn path=/trunk/matplotlib/; revision=2142
1 parent 953128b commit 5f9468d

File tree

3 files changed

+239
-14
lines changed

3 files changed

+239
-14
lines changed

CHANGELOG

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
2006-03-12 Added AutoDateFormatter and AutoDateLocator submitted
2+
by James Evans. Try the load_converter.py example for a
3+
demo. - ADS
4+
15
2006-03-11 Added subprocess module from python-2.4 - DSD
26

37
2006-03-11 Fixed landscape orientation support with the usetex

lib/matplotlib/axes.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
import matplotlib
4747

4848
if matplotlib._havedate:
49-
from dates import date_ticker_factory
49+
from dates import AutoDateFormatter, AutoDateLocator
5050

5151
def delete_masked_points(*args):
5252
"""
@@ -3807,8 +3807,8 @@ def xaxis_date(self, tz=None):
38073807
tz is the time zone to use in labeling dates. Defaults to rc value.
38083808
"""
38093809

3810-
span = self.dataLim.intervalx().span()
3811-
locator, formatter = date_ticker_factory(span, tz)
3810+
locator = AutoDateLocator(tz)
3811+
formatter = AutoDateFormatter(locator)
38123812
self.xaxis.set_major_locator(locator)
38133813
self.xaxis.set_major_formatter(formatter)
38143814

@@ -3818,8 +3818,8 @@ def yaxis_date(self, tz=None):
38183818
tz is the time zone to use in labeling dates. Defaults to rc value.
38193819
"""
38203820

3821-
span = self.dataLim.intervaly().span()
3822-
locator, formatter = date_ticker_factory(span, tz)
3821+
locator = AutoDateLocator(tz)
3822+
formatter = AutoDateFormatter(locator)
38233823
self.yaxis.set_major_locator(locator)
38243824
self.yaxis.set_major_formatter(formatter)
38253825

lib/matplotlib/dates.py

Lines changed: 230 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,14 @@
8282

8383
try: import datetime
8484
except ImportError:
85-
raise ValueError('matplotlib %s date handling requires python2.3' % matplotlib.__version__)
85+
raise ValueError('matplotlib %s date handling requires python2.3'
86+
% matplotlib.__version__)
8687

8788
from cbook import iterable, is_string_like
8889
from pytz import timezone
8990
from numerix import arange, asarray
9091
from ticker import Formatter, Locator, Base
91-
from dateutil.rrule import rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY,\
92+
from dateutil.rrule import rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY, \
9293
MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY
9394
from dateutil.relativedelta import relativedelta
9495
import dateutil.parser
@@ -108,7 +109,8 @@ def _get_rc_timezone():
108109
SEC_PER_HOUR = 3600
109110
SEC_PER_DAY = SEC_PER_HOUR * 24
110111
SEC_PER_WEEK = SEC_PER_DAY * 7
111-
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = MO, TU, WE, TH, FR, SA, SU
112+
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = (
113+
MO, TU, WE, TH, FR, SA, SU)
112114
WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY)
113115

114116
__all__ = ['date2num', 'num2date', 'drange', 'HOURS_PER_DAY',
@@ -156,8 +158,9 @@ def _from_ordinalf(x, tz=None):
156158
second, remainder = divmod(60*remainder, 1)
157159
microsecond = int(1e6*remainder)
158160
if microsecond<10: microsecond=0 # compensate for rounding errors
159-
dt = datetime.datetime(dt.year, dt.month, dt.day, int(hour), int(minute), int(second), microsecond, tzinfo=UTC).astimezone(tz)
160-
161+
dt = datetime.datetime(
162+
dt.year, dt.month, dt.day, int(hour), int(minute), int(second),
163+
microsecond, tzinfo=UTC).astimezone(tz)
161164

162165
if microsecond>999990: # compensate for rounding errors
163166
dt += datetime.timedelta(microseconds=1e6-microsecond)
@@ -207,7 +210,8 @@ def drange(dstart, dend, delta):
207210
Return a date range as float gregorian ordinals. dstart and dend
208211
are datetime instances. delta is a datetime.timedelta instance
209212
"""
210-
step = delta.days + delta.seconds/SECONDS_PER_DAY + delta.microseconds/MUSECONDS_PER_DAY
213+
step = (delta.days + delta.seconds/SECONDS_PER_DAY +
214+
delta.microseconds/MUSECONDS_PER_DAY)
211215
f1 = _to_ordinalf(dstart)
212216
f2 = _to_ordinalf(dend)
213217
return arange(f1, f2, step)
@@ -321,6 +325,50 @@ def __call__(self, x, pos=0):
321325

322326
return unicode(dt.strftime(self.fmt), locale.getpreferredencoding())
323327

328+
329+
class AutoDateFormatter(Formatter):
330+
"""
331+
This class attempt to figure out the best format to use. This is
332+
most useful when used with the AutoDateLocator.
333+
"""
334+
335+
# This can be improved by providing some user-level direction on
336+
# how to choose the best format (precedence, etc...)
337+
338+
# Perhaps a 'struct' that has a field for each time-type where a
339+
# zero would indicate "don't show" and a number would indicate
340+
# "show" with some sort of priority. Same priorities could mean
341+
# show all with the same priority.
342+
343+
# Or more simply, perhaps just a format string for each
344+
# possibility...
345+
346+
def __init__(self, locator, tz=None):
347+
self._locator = locator
348+
self._formatter = DateFormatter("%b %d %Y %H:%M:%S %Z", tz)
349+
self._tz = tz
350+
351+
def __call__(self, x, pos=0):
352+
scale = float( self._locator._get_unit() )
353+
354+
if ( < 10000 span class=pl-s1>scale == 365.0 ):
355+
self._formatter = DateFormatter("%Y", self._tz)
356+
elif ( scale == 30.0 ):
357+
self._formatter = DateFormatter("%b %Y", self._tz)
358+
elif ( (scale == 1.0) or (scale == 7.0) ):
359+
self._formatter = DateFormatter("%b %d %Y", self._tz)
360+
elif ( scale == (1.0/24.0) ):
361+
self._formatter = DateFormatter("%H:%M:%S %Z", self._tz)
362+
elif ( scale == (1.0/(24*60)) ):
363+
self._formatter = DateFormatter("%H:%M:%S %Z", self._tz)
364+
elif ( scale == (1.0/(24*3600)) ):
365+
self._formatter = DateFormatter("%H:%M:%S %Z", self._tz)
366+
else:
367+
self._formatter = DateFormatter("%b %d %Y %H:%M:%S %Z", self._tz)
368+
369+
return self._formatter(x, pos)
370+
371+
324372
class rrulewrapper:
325373

326374
def __init__(self, freq, **kwargs):
@@ -393,6 +441,30 @@ def __call__(self):
393441
dates = self.rule.between(dmin, dmax, True)
394442
return date2num(dates)
395443

444+
def _get_unit(self):
445+
"""
446+
Return how many days a unit of the locator is; use for
447+
intelligent autoscaling
448+
"""
449+
freq = self.rule._rrule._freq
450+
if ( freq == YEARLY ):
451+
return 365
452+
elif ( freq == MONTHLY ):
453+
return 30
454+
elif ( freq == WEEKLY ):
455+
return 7
456+
elif ( freq == DAILY ):
457+
return 1
458+
elif ( freq == HOURLY ):
459+
return (1.0/24.0)
460+
elif ( freq == MINUTELY ):
461+
return (1.0/(24*60))
462+
elif ( freq == SECONDLY ):
463+
return (1.0/(24*3600))
464+
else:
465+
# error
466+
return -1 #or should this just return '1'?
467+
396468
def autoscale(self):
397469
"""
398470
Set the view limits to include the data range
@@ -419,6 +491,152 @@ def autoscale(self):
419491
return self.nonsingular(vmin, vmax)
420492

421493

494+
class AutoDateLocator(DateLocator):
495+
"""
496+
On autoscale this class picks the best MultipleDateLocator to set the
497+
view limits and the tick locs.
498+
"""
499+
def __init__(self, tz=None):
500+
DateLocator.__init__(self, tz)
501+
self._locator = YearLocator()
502+
self._freq = YEARLY
503+
504+
def __call__(self):
505+
'Return the locations of the ticks'
506+
self.refresh()
507+
return self._locator()
508+
509+
def refresh(self):
510+
'refresh internal information based on current lim'
511+
dmin, dmax = self.viewlim_to_dt()
512+
self._locator = self.get_locator(dmin, dmax)
513+
514+
def _get_unit(self):
515+
if ( self._freq == YEARLY ):
516+
return 365.0
517+
elif ( self._freq == MONTHLY ):
518+
return 30.0
519+
elif ( self._freq == WEEKLY ):
520+
return 7.0
521+
elif ( self._freq == DAILY ):
522+
return 1.0
523+
elif ( self._freq == HOURLY ):
524+
return 1.0/24
525+
elif ( self._freq == MINUTELY ):
526+
return 1.0/(24*60)
527+
elif ( self._freq == SECONDLY ):
528+
return 1.0/(24*3600)
529+
else:
530+
# error
531+
return -1
532+
533+
def autoscale(self):
534+
'Try to choose the view limits intelligently'
535+
536+
self.verify_intervals()
537+
dmin, dmax = self.datalim_to_dt()
538+
self._locator = self.get_locator(dmin, dmax)
539+
return self._locator.autoscale()
540+
541+
def get_locator(self, dmin, dmax):
542+
'pick the best locator based on a distance'
543+
544+
delta = relativedelta(dmax, dmin)
545+
546+
numYears = (delta.years * 1.0)
547+
numMonths = (numYears * 12.0) + delta.months
548+
numDays = (numMonths * 31.0) + delta.days
549+
numHours = (numDays * 24.0) + delta.hours
550+
numMinutes = (numHours * 60.0) + delta.minutes
551+
numSeconds = (numMinutes * 60.0) + delta.seconds
552+
553+
numticks = 5
554+
555+
# self._freq = YEARLY
556+
interval = 1
557+
bymonth = 1
558+
bymonthday = 1
559+
byhour = 0
560+
byminute = 0
561+
bysecond = 0
562+
563+
if ( numYears >= numticks ):
564+
self._freq = YEARLY
565+
elif ( numMonths >= numticks ):
566+
self._freq = MONTHLY
567+
bymonth = range(1, 13)
568+
if ( (0 <= numMonths) and (numMonths <= 14) ):
569+
interval< 10000 /span> = 1 # show every month
570+
elif ( (15 <= numMonths) and (numMonths <= 29) ):
571+
interval = 3 # show every 3 months
572+
elif ( (30 <= numMonths) and (numMonths <= 44) ):
573+
interval = 4 # show every 4 months
574+
else: # 45 <= numMonths <= 59
575+
interval = 6 # show every 6 months
576+
elif ( numDays >= numticks ):
577+
self._freq = DAILY
578+
bymonth = None
579+
bymonthday = range(1, 32)
580+
if ( (0 <= numDays) and (numDays <= 49) ):
581+
interval = 1 # show every day
582+
elif ( (50 <= numDays) and (numDays <= 99) ):
583+
interval = 7 # show every 1 week
584+
else: # 100 <= numDays <= ~150
585+
interval = 14 # show every 2 weeks
586+
elif ( numHours >= numticks ):
587+
self._freq = HOURLY
588+
bymonth = None
589+
bymonthday = None
590+
byhour = range(0, 24) # show every hour
591+
if ( (0 <= numHours) and (numHours <= 14) ):
592+
interval = 1 # show every hour
593+
elif ( (15 <= numHours) and (numHours <= 30) ):
594+
interval = 2 # show every 2 hours
595+
elif ( (30 <= numHours) and (numHours <= 45) ):
596+
interval = 3 # show every 3 hours
597+
elif ( (45 <= numHours) and (numHours <= 68) ):
598+
interval = 4 # show every 4 hours
599+
elif ( (68 <= numHours) and (numHours <= 90) ):
600+
interval = 6 # show every 6 hours
601+
else: # 90 <= numHours <= 120
602+
interval = 12 # show every 12 hours
603+
elif ( numMinutes >= numticks ):
604+
self._freq = MINUTELY
605+
bymonth = None
606+
bymonthday = None
607+
byhour = None
608+
byminute = range(0, 60)
609+
if ( numMinutes > (10.0 * numticks) ):
610+
interval = 10
611+
# end if
612+
elif ( numSeconds >= numticks ):
613+
self._freq = SECONDLY
614+
bymonth = None
615+
bymonthday = None
616+
byhour = None
617+
byminute = None
618+
bysecond = range(0, 60)
619+
if ( numSeconds > (10.0 * numticks) ):
620+
interval = 10
621+
# end if
622+
else:
623+
# do what?
624+
# microseconds as floats, but floats from what reference point?
625+
pass
626+
627+
628+
rrule = rrulewrapper( self._freq, interval=interval, \
629+
dtstart=dmin, until=dmax, \
630+
bymonth=bymonth, bymonthday=bymonthday, \
631+
byhour=byhour, byminute = byminute, \
632+
bysecond=bysecond )
633+
634+
locator = RRuleLocator(rrule, self.tz)
635+
636+
locator.set_view_interval(self.viewInterval)
637+
locator.set_data_interval(self.dataInterval)
638+
return locator
639+
422640

423641
class YearLocator(DateLocator):
424642
"""
@@ -529,7 +747,8 @@ def __init__(self, byweekday=1, interval=1, tz=None):
529747
plots every second week
530748
531749
"""
532-
o = rrulewrapper(DAILY, byweekday=byweekday, interval=interval, **self.hms0d)
750+
o = rrulewrapper(DAILY, byweekday=byweekday,
751+
interval=interval, **self.hms0d)
533752
RRuleLocator.__init__(self, o, tz)
534753

535754
def _get_unit(self):
@@ -551,7 +770,8 @@ def __init__(self, bymonthday=None, interval=1, tz=None):
551770
Default is to tick every day of the month - bymonthday=range(1,32)
552771
"""
553772
if bymonthday is None: bymonthday=range(1,32)
554-
o = rrulewrapper(DAILY, bymonthday=bymonthday, interval=interval, **self.hms0d)
773+
o = rrulewrapper(DAILY, bymonthday=bymonthday,
774+
interval=interval, **self.hms0d)
555775
RRuleLocator.__init__(self, o, tz)
556776

557777
def _get_unit(self):
@@ -639,7 +859,8 @@ def _get_unit(self):
639859
def _close_to_dt(d1, d2, epsilon=5):
640860
'assert that datetimes d1 and d2 are within epsilon microseconds'
641861
delta = d2-d1
642-
mus = abs(delta.days*MUSECONDS_PER_DAY + delta.seconds*1e6 + delta.microseconds)
862+
mus = abs(delta.days*MUSECONDS_PER_DAY + delta.seconds*1e6 +
863+
delta.microseconds)
643864
assert(mus<epsilon)
644865

645866
def _close_to_num(o1, o2, epsilon=5):

0 commit comments

Comments
 (0)
0