112112import math
113113import datetime
114114from itertools import izip
115+ import warnings
115116
116- import matplotlib
117+
118+ from dateutil .rrule import rrule , MO , TU , WE , TH , FR , SA , SU , YEARLY , \
119+ MONTHLY , WEEKLY , DAILY , HOURLY , MINUTELY , SECONDLY
120+ from dateutil .relativedelta import relativedelta
121+ import dateutil .parser
117122import numpy as np
118123
124+
125+ import matplotlib
119126import matplotlib .units as units
120127import matplotlib .cbook as cbook
121128import matplotlib .ticker as ticker
122129
123- from dateutil .rrule import rrule , MO , TU , WE , TH , FR , SA , SU , YEARLY , \
124- MONTHLY , WEEKLY , DAILY , HOURLY , MINUTELY , SECONDLY
125- from dateutil .relativedelta import relativedelta
126- import dateutil .parser
127130
128131__all__ = ('date2num' , 'num2date' , 'drange' , 'epoch2num' ,
129132 'num2epoch' , 'mx2num' , 'DateFormatter' ,
133136 'DayLocator' , 'HourLocator' , 'MinuteLocator' ,
134137 'SecondLocator' , 'rrule' , 'MO' , 'TU' , 'WE' , 'TH' , 'FR' ,
135138 'SA' , 'SU' , 'YEARLY' , 'MONTHLY' , 'WEEKLY' , 'DAILY' ,
136- 'HOURLY' , 'MINUTELY' , 'SECONDLY' , 'relativedelta' ,
139+ 'HOURLY' , 'MINUTELY' , 'SECONDLY' , 'MICROSECONDLY' , ' relativedelta' ,
137140 'seconds' , 'minutes' , 'hours' , 'weeks' )
138141
139142
@@ -162,7 +165,7 @@ def _get_rc_timezone():
162165 import pytz
163166 return pytz .timezone (s )
164167
165-
168+ MICROSECONDLY = SECONDLY + 1
166169HOURS_PER_DAY = 24.
167170MINUTES_PER_DAY = 60. * HOURS_PER_DAY
168171SECONDS_PER_DAY = 60. * MINUTES_PER_DAY
@@ -465,6 +468,8 @@ class AutoDateFormatter(ticker.Formatter):
465468 30. : '%b %Y',
466469 1.0 : '%b %d %Y',
467470 1./24. : '%H:%M:%D',
471+ 1./24. : '%H:%M:%S',
472+ 1. / 10 * (24. * 60.): '%H:%M:%S.%f',
468473 }
469474
470475
@@ -503,12 +508,11 @@ def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
503508 30. : '%b %Y' ,
504509 1.0 : '%b %d %Y' ,
505510 1. / 24. : '%H:%M:%S' ,
511+ 1. / (24. * 60. ): '%H:%M:%S.%f' ,
506512 }
507513
508514 def __call__ (self , x , pos = 0 ):
509-
510515 scale = float (self ._locator ._get_unit ())
511-
512516 fmt = self .defaultfmt
513517
514518 for k in sorted (self .scaled ):
@@ -639,6 +643,7 @@ def _get_unit(self):
639643 freq = self .rule ._rrule ._freq
640644 return self .get_unit_generic (freq )
641645
646+ @staticmethod
642647 def get_unit_generic (freq ):
643648 if (freq == YEARLY ):
644649 return 365.0
@@ -657,7 +662,6 @@ def get_unit_generic(freq):
657662 else :
658663 # error
659664 return - 1 # or should this just return '1'?
660- get_unit_generic = staticmethod (get_unit_generic )
661665
662666 def _get_interval (self ):
663667 return self .rule ._rrule ._interval
@@ -704,7 +708,7 @@ def autoscale(self):
704708class AutoDateLocator (DateLocator ):
705709 """
706710 On autoscale, this class picks the best
707- :class:`MultipleDateLocator ` to set the view limits and the tick
711+ :class:`RRuleLocator ` to set the view limits and the tick
708712 locations.
709713 """
710714 def __init__ (self , tz = None , minticks = 5 , maxticks = None ,
@@ -735,12 +739,16 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
735739 multiple allowed for that ticking. The default looks like this::
736740
737741 self.intervald = {
738- YEARLY : [1, 2, 4, 5, 10],
742+ YEARLY : [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
743+ 1000, 2000, 4000, 5000, 10000],
739744 MONTHLY : [1, 2, 3, 4, 6],
740745 DAILY : [1, 2, 3, 7, 14],
741746 HOURLY : [1, 2, 3, 4, 6, 12],
742747 MINUTELY: [1, 5, 10, 15, 30],
743- SECONDLY: [1, 5, 10, 15, 30]
748+ SECONDLY: [1, 5, 10, 15, 30],
749+ MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000,
750+ 5000, 10000, 20000, 50000, 100000, 200000, 500000,
751+ 1000000],
744752 }
745753
746754 The interval is used to specify multiples that are appropriate for
@@ -754,11 +762,12 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
754762 DateLocator .__init__ (self , tz )
755763 self ._locator = YearLocator ()
756764 self ._freq = YEARLY
757- self ._freqs = [YEARLY , MONTHLY , DAILY , HOURLY , MINUTELY , SECONDLY ]
765+ self ._freqs = [YEARLY , MONTHLY , DAILY , HOURLY , MINUTELY ,
766+ SECONDLY , MICROSECONDLY ]
758767 self .minticks = minticks
759768
760- self .maxticks = {YEARLY : 16 , MONTHLY : 12 , DAILY : 11 , HOURLY : 16 ,
761- MINUTELY : 11 , SECONDLY : 11 }
769+ self .maxticks = {YEARLY : 11 , MONTHLY : 12 , DAILY : 11 , HOURLY : 12 ,
770+ MINUTELY : 11 , SECONDLY : 11 , MICROSECONDLY : 8 }
762771 if maxticks is not None :
763772 try :
764773 self .maxticks .update (maxticks )
@@ -770,21 +779,33 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
770779 [maxticks ] * len (self ._freqs )))
771780 self .interval_multiples = interval_multiples
772781 self .intervald = {
773- YEARLY : [1 , 2 , 4 , 5 , 10 ],
782+ YEARLY : [1 , 2 , 4 , 5 , 10 , 20 , 40 , 50 , 100 , 200 , 400 , 500 ,
783+ 1000 , 2000 , 4000 , 5000 , 10000 ],
774784 MONTHLY : [1 , 2 , 3 , 4 , 6 ],
775785 DAILY : [1 , 2 , 3 , 7 , 14 ],
776786 HOURLY : [1 , 2 , 3 , 4 , 6 , 12 ],
777787 MINUTELY : [1 , 5 , 10 , 15 , 30 ],
778- SECONDLY : [1 , 5 , 10 , 15 , 30 ]
788+ SECONDLY : [1 , 5 , 10 , 15 , 30 ],
789+ MICROSECONDLY : [1 , 2 , 5 , 10 , 20 , 50 , 100 , 200 , 500 , 1000 , 2000 ,
790+ 5000 , 10000 , 20000 , 50000 , 100000 , 200000 , 500000 ,
791+ 1000000 ],
779792 }
780793 self ._byranges = [None , range (1 , 13 ), range (1 , 32 ), range (0 , 24 ),
781- range (0 , 60 ), range (0 , 60 )]
794+ range (0 , 60 ), range (0 , 60 ), None ]
782795
783796 def __call__ (self ):
784797 'Return the locations of the ticks'
785798 self .refresh ()
786799 return self ._locator ()
787800
801+ def nonsingular (self , vmin , vmax ):
802+ # whatever is thrown at us, we can scale the unit.
803+ # But default nonsigular date plots at an ~4 year period.
804+ if vmin == vmax :
805+ vmin = vmin - 365 * 2
806+ vmax = vmax + 365 * 2
807+ return vmin , vmax
808+
788809 def set_axis (self , axis ):
789810 DateLocator .set_axis (self , axis )
790811 self ._locator .set_axis (axis )
@@ -795,7 +816,10 @@ def refresh(self):
795816 self ._locator = self .get_locator (dmin , dmax )
796817
797818 def _get_unit (self ):
798- return RRuleLocator .get_unit_generic (self ._freq )
819+ if self ._freq in [MICROSECONDLY ]:
820+ return 1. / MUSECONDS_PER_DAY
821+ else :
822+ return RRuleLocator .get_unit_generic (self ._freq )
799823
800824 def autoscale (self ):
801825 'Try to choose the view limits intelligently.'
@@ -805,7 +829,6 @@ def autoscale(self):
805829
806830 def get_locator (self , dmin , dmax ):
807831 'Pick the best locator based on a distance.'
808-
809832 delta = relativedelta (dmax , dmin )
810833
811834 numYears = (delta .years * 1.0 )
@@ -814,12 +837,18 @@ def get_locator(self, dmin, dmax):
814837 numHours = (numDays * 24.0 ) + delta .hours
815838 numMinutes = (numHours * 60.0 ) + delta .minutes
816839 numSeconds = (numMinutes * 60.0 ) + delta .seconds
840+ numMicroseconds = (numSeconds * 1e6 ) + \
841+ delta .microseconds
817842
818- nums = [numYears , numMonths , numDays , numHours , numMinutes , numSeconds ]
843+ nums = [numYears , numMonths , numDays , numHours , numMinutes ,
844+ numSeconds , numMicroseconds ]
845+
846+ use_rrule_locator = [True ] * 6 + [False ]
819847
820848 # Default setting of bymonth, etc. to pass to rrule
821- # [unused (for year), bymonth, bymonthday, byhour, byminute, bysecond]
822- byranges = [None , 1 , 1 , 0 , 0 , 0 ]
849+ # [unused (for year), bymonth, bymonthday, byhour, byminute,
850+ # bysecond, unused (for microseconds)]
851+ byranges = [None , 1 , 1 , 0 , 0 , 0 , None ]
823852
824853 # Loop over all the frequencies and try to find one that gives at
825854 # least a minticks tick positions. Once this is found, look for
@@ -841,8 +870,13 @@ def get_locator(self, dmin, dmax):
841870 if num <= interval * (self .maxticks [freq ] - 1 ):
842871 break
843872 else :
844- # We went through the whole loop without breaking, default to 1
845- interval = 1
873+ # We went through the whole loop without breaking, default to
874+ # the last interval in the list and raise a warning
875+ warnings .warn ('AutoDateLocator was unable to pick an '
876+ 'appropriate interval for this date range. '
877+ 'It may be necessary to add an interval value '
878+ "to the AutoDateLocator's intervald dictionary."
879+ ' Defaulting to {0}.' .format (interval ))
846880
847881 # Set some parameters as appropriate
848882 self ._freq = freq
@@ -856,22 +890,22 @@ def get_locator(self, dmin, dmax):
856890 # We found what frequency to use
857891 break
858892 else :
859- # We couldn't find a good frequency.
860- # do what?
861- # microseconds as floats, but floats from what reference point?
862- byranges = [ None , 1 , 1 , 0 , 0 , 0 ]
863- interval = 1
864-
865- unused , bymonth , bymonthday , byhour , byminute , bysecond = byranges
866- del unused
867-
868- rrule = rrulewrapper ( self . _freq , interval = interval ,
869- dtstart = dmin , until = dmax ,
870- bymonth = bymonth , bymonthday = bymonthday ,
871- byhour = byhour , byminute = byminute ,
872- bysecond = bysecond )
873-
874- locator = RRuleLocator ( rrule , self . tz )
893+ raise ValueError ( 'No sensible date limit could be found in the '
894+ 'AutoDateLocator.' )
895+
896+ if use_rrule_locator [ i ]:
897+ _ , bymonth , bymonthday , byhour , byminute , bysecond , _ = byranges
898+
899+ rrule = rrulewrapper ( self . _freq , interval = interval ,
900+ dtstart = dmin , until = dmax ,
901+ bymonth = bymonth , bymonthday = bymonthday ,
902+ byhour = byhour , byminute = byminute ,
903+ bysecond = bysecond )
904+
905+ locator = RRuleLocator ( rrule , self . tz )
906+ else :
907+ locator = MicrosecondLocator ( interval , tz = self . tz )
908+
875909 locator .set_axis (self .axis )
876910
877911 locator .set_view_interval (* self .axis .get_view_interval ())
@@ -1051,6 +1085,54 @@ def __init__(self, bysecond=None, interval=1, tz=None):
10511085 RRuleLocator .__init__ (self , rule , tz )
10521086
10531087
1088+ class MicrosecondLocator (DateLocator ):
1089+ """
1090+ Make ticks on occurances of each second.
1091+ """
1092+ def __init__ (self , interval = 1 , tz = None ):
1093+ """
1094+ *interval* is the interval between each iteration. For
1095+ example, if ``interval=2``, mark every second miscrosecond.
1096+
1097+ """
1098+ self ._interval = interval
1099+ self ._wrapped_locator = ticker .MultipleLocator (interval )
1100+ self .tz = tz
1101+
1102+ def set_axis (self , axis ):
1103+ self ._wrapped_locator .set_axis (axis )
1104+ return DateLocator .set_axis (self , axis )
1105+
1106+ def set_view_interval (self , vmin , vmax ):
1107+ self ._wrapped_locator .set_view_interval (vmin , vmax )
1108+ return DateLocator .set_view_interval (self , vmin , vmax )
1109+
1110+ def set_data_interval (self , vmin , vmax ):
1111+ self ._wrapped_locator .set_data_interval (vmin , vmax )
1112+ return DateLocator .set_data_interval (self , vmin , vmax )
1113+
1114+ def __call__ (self , * args , ** kwargs ):
1115+ vmin , vmax = self .axis .get_view_interval ()
1116+ vmin *= MUSECONDS_PER_DAY
1117+ vmax *= MUSECONDS_PER_DAY
1118+ ticks = self ._wrapped_locator .tick_values (vmin , vmax )
1119+ ticks = [tick / MUSECONDS_PER_DAY for tick in ticks ]
1120+ return ticks
1121+
1122+ def _get_unit (self ):
1123+ """
1124+ Return how many days a unit of the locator is; used for
1125+ intelligent autoscaling.
1126+ """
1127+ return 1. / MUSECONDS_PER_DAY
1128+
1129+ def _get_interval (self ):
1130+ """
1131+ Return the number of units for each tick.
1132+ """
1133+ return self ._interval
1134+
1135+
10541136def _close_to_dt (d1 , d2 , epsilon = 5 ):
10551137 'Assert that datetimes *d1* and *d2* are within *epsilon* microseconds.'
10561138 delta = d2 - d1
0 commit comments