112
112
import math
113
113
import datetime
114
114
from itertools import izip
115
+ import warnings
115
116
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
117
122
import numpy as np
118
123
124
+
125
+ import matplotlib
119
126
import matplotlib .units as units
120
127
import matplotlib .cbook as cbook
121
128
import matplotlib .ticker as ticker
122
129
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
127
130
128
131
__all__ = ('date2num' , 'num2date' , 'drange' , 'epoch2num' ,
129
132
'num2epoch' , 'mx2num' , 'DateFormatter' ,
133
136
'DayLocator' , 'HourLocator' , 'MinuteLocator' ,
134
137
'SecondLocator' , 'rrule' , 'MO' , 'TU' , 'WE' , 'TH' , 'FR' ,
135
138
'SA' , 'SU' , 'YEARLY' , 'MONTHLY' , 'WEEKLY' , 'DAILY' ,
136
- 'HOURLY' , 'MINUTELY' , 'SECONDLY' , 'relativedelta' ,
139
+ 'HOURLY' , 'MINUTELY' , 'SECONDLY' , 'MICROSECONDLY' , ' relativedelta' ,
137
140
'seconds' , 'minutes' , 'hours' , 'weeks' )
138
141
139
142
@@ -162,7 +165,7 @@ def _get_rc_timezone():
162
165
import pytz
163
166
return pytz .timezone (s )
164
167
165
-
168
+ MICROSECONDLY = SECONDLY + 1
166
169
HOURS_PER_DAY = 24.
167
170
MINUTES_PER_DAY = 60. * HOURS_PER_DAY
168
171
SECONDS_PER_DAY = 60. * MINUTES_PER_DAY
@@ -465,6 +468,8 @@ class AutoDateFormatter(ticker.Formatter):
465
468
30. : '%b %Y',
466
469
1.0 : '%b %d %Y',
467
470
1./24. : '%H:%M:%D',
471
+ 1./24. : '%H:%M:%S',
472
+ 1. / 10 * (24. * 60.): '%H:%M:%S.%f',
468
473
}
469
474
470
475
@@ -503,12 +508,11 @@ def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
503
508
30. : '%b %Y' ,
504
509
1.0 : '%b %d %Y' ,
505
510
1. / 24. : '%H:%M:%S' ,
511
+ 1. / (24. * 60. ): '%H:%M:%S.%f' ,
506
512
}
507
513
508
514
def __call__ (self , x , pos = 0 ):
509
-
510
515
scale = float (self ._locator ._get_unit ())
511
-
512
516
fmt = self .defaultfmt
513
517
514
518
for k in sorted (self .scaled ):
@@ -639,6 +643,7 @@ def _get_unit(self):
639
643
freq = self .rule ._rrule ._freq
640
644
return self .get_unit_generic (freq )
641
645
646
+ @staticmethod
642
647
def get_unit_generic (freq ):
643
648
if (freq == YEARLY ):
644
649
return 365.0
@@ -657,7 +662,6 @@ def get_unit_generic(freq):
657
662
else :
658
663
# error
659
664
return - 1 # or should this just return '1'?
660
- get_unit_generic = staticmethod (get_unit_generic )
661
665
662
666
def _get_interval (self ):
663
667
return self .rule ._rrule ._interval
@@ -704,7 +708,7 @@ def autoscale(self):
704
708
class AutoDateLocator (DateLocator ):
705
709
"""
706
710
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
708
712
locations.
709
713
"""
710
714
def __init__ (self , tz = None , minticks = 5 , maxticks = None ,
@@ -735,12 +739,16 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
735
739
multiple allowed for that ticking. The default looks like this::
736
740
737
741
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],
739
744
MONTHLY : [1, 2, 3, 4, 6],
740
745
DAILY : [1, 2, 3, 7, 14],
741
746
HOURLY : [1, 2, 3, 4, 6, 12],
742
747
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],
744
752
}
745
753
746
754
The interval is used to specify multiples that are appropriate for
@@ -754,11 +762,12 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
754
762
DateLocator .__init__ (self , tz )
755
763
self ._locator = YearLocator ()
756
764
self ._freq = YEARLY
757
- self ._freqs = [YEARLY , MONTHLY , DAILY , HOURLY , MINUTELY , SECONDLY ]
765
+ self ._freqs = [YEARLY , MONTHLY , DAILY , HOURLY , MINUTELY ,
766
+ SECONDLY , MICROSECONDLY ]
758
767
self .minticks = minticks
759
768
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 }
762
771
if maxticks is not None :
763
772
try :
764
773
self .maxticks .update (maxticks )
@@ -770,21 +779,33 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
770
779
[maxticks ] * len (self ._freqs )))
771
780
self .interval_multiples = interval_multiples
772
781
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 ],
774
784
MONTHLY : [1 , 2 , 3 , 4 , 6 ],
775
785
DAILY : [1 , 2 , 3 , 7 , 14 ],
776
786
HOURLY : [1 , 2 , 3 , 4 , 6 , 12 ],
777
787
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 ],
779
792
}
780
793
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 ]
782
795
783
796
def __call__ (self ):
784
797
'Return the locations of the ticks'
785
798
self .refresh ()
786
799
return self ._locator ()
787
800
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
+
788
809
def set_axis (self , axis ):
789
810
DateLocator .set_axis (self , axis )
790
811
self ._locator .set_axis (axis )
@@ -795,7 +816,10 @@ def refresh(self):
795
816
self ._locator = self .get_locator (dmin , dmax )
796
817
797
818
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 )
799
823
800
824
def autoscale (self ):
801
825
'Try to choose the view limits intelligently.'
@@ -805,7 +829,6 @@ def autoscale(self):
805
829
806
830
def get_locator (self , dmin , dmax ):
807
831
'Pick the best locator based on a distance.'
808
-
809
832
delta = relativedelta (dmax , dmin )
810
833
811
834
numYears = (delta .years * 1.0 )
@@ -814,12 +837,18 @@ def get_locator(self, dmin, dmax):
814
837
numHours = (numDays * 24.0 ) + delta .hours
815
838
numMinutes = (numHours * 60.0 ) + delta .minutes
816
839
numSeconds = (numMinutes * 60.0 ) + delta .seconds
840
+ numMicroseconds = (numSeconds * 1e6 ) + \
841
+ delta .microseconds
817
842
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 ]
819
847
820
848
# 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 ]
823
852
824
853
# Loop over all the frequencies and try to find one that gives at
825
854
# least a minticks tick positions. Once this is found, look for
@@ -841,8 +870,13 @@ def get_locator(self, dmin, dmax):
841
870
if num <= interval * (self .maxticks [freq ] - 1 ):
842
871
break
843
872
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 ))
846
880
847
881
# Set some parameters as appropriate
848
882
self ._freq = freq
@@ -856,22 +890,22 @@ def get_locator(self, dmin, dmax):
856
890
# We found what frequency to use
857
891
break
858
892
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
+
875
909
locator .set_axis (self .axis )
876
910
877
911
locator .set_view_interval (* self .axis .get_view_interval ())
@@ -1051,6 +1085,54 @@ def __init__(self, bysecond=None, interval=1, tz=None):
1051
1085
RRuleLocator .__init__ (self , rule , tz )
1052
1086
1053
1087
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
+
1054
1136
def _close_to_dt (d1 , d2 , epsilon = 5 ):
1055
1137
'Assert that datetimes *d1* and *d2* are within *epsilon* microseconds.'
1056
1138
delta = d2 - d1
0 commit comments