14
14
and calendar differences can cause confusing differences between what
15
15
Python and mpl give as the number of days since 0001-01-01 and what other
16
16
software and databases yield. For example, the `US Naval Observatory
17
- <http://www.usno.navy.mil/USNO/astronomical-applications/data-services/jul-date>`_
17
+ <http://www.usno.navy.mil/USNO/astronomical-applications/
18
+ data-services/jul-date>`_
18
19
uses a calendar that switches from Julian to Gregorian in October, 1582.
19
20
Hence, using their calculator, the number of days between 0001-01-01 and
20
21
2006-04-01 is 732403, whereas using the Gregorian calendar via the datetime
112
113
import math
113
114
import datetime
114
115
from itertools import izip
116
+ import warnings
115
117
116
- import matplotlib
118
+
119
+ from dateutil .rrule import (rrule , MO , TU , WE , TH , FR , SA , SU , YEARLY ,
120
+ MONTHLY , WEEKLY , DAILY , HOURLY , MINUTELY ,
121
+ SECONDLY )
122
+ from dateutil .relativedelta import relativedelta
123
+ import dateutil .parser
117
124
import numpy as np
118
125
126
+
127
+ import matplotlib
119
128
import matplotlib .units as units
120
129
import matplotlib .cbook as cbook
121
130
import matplotlib .ticker as ticker
122
131
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
132
128
133
__all__ = ('date2num' , 'num2date' , 'drange' , 'epoch2num' ,
129
134
'num2epoch' , 'mx2num' , 'DateFormatter' ,
130
135
'IndexDateFormatter' , 'AutoDateFormatter' , 'DateLocator' ,
131
136
'RRuleLocator' , 'AutoDateLocator' , 'YearLocator' ,
132
137
'MonthLocator' , 'WeekdayLocator' ,
133
138
'DayLocator' , 'HourLocator' , 'MinuteLocator' ,
134
- 'SecondLocator' , 'rrule' , 'MO' , 'TU' , 'WE' , 'TH' , 'FR' ,
135
- 'SA' , 'SU' , 'YEARLY' , 'MONTHLY' , 'WEEKLY' , 'DAILY' ,
136
- 'HOURLY' , 'MINUTELY' , 'SECONDLY' , 'relativedelta' ,
139
+ 'SecondLocator' , 'MicrosecondLocator' ,
140
+ 'rrule' , 'MO' , 'TU' , 'WE' , 'TH' , 'FR' , 'SA' , 'SU' ,
141
+ 'YEARLY' , 'MONTHLY' , 'WEEKLY' , 'DAILY' ,
142
+ 'HOURLY' , 'MINUTELY' , 'SECONDLY' , 'MICROSECONDLY' , 'relativedelta' ,
137
143
'seconds' , 'minutes' , 'hours' , 'weeks' )
138
144
139
145
@@ -162,7 +168,7 @@ def _get_rc_timezone():
162
168
import pytz
163
169
return pytz .timezone (s )
164
170
165
-
171
+ MICROSECONDLY = SECONDLY + 1
166
172
HOURS_PER_DAY = 24.
167
173
MINUTES_PER_DAY = 60. * HOURS_PER_DAY
168
174
SECONDS_PER_DAY = 60. * MINUTES_PER_DAY
@@ -465,6 +471,7 @@ class AutoDateFormatter(ticker.Formatter):
465
471
30. : '%b %Y',
466
472
1.0 : '%b %d %Y',
467
473
1./24. : '%H:%M:%D',
474
+ 1. / (24. * 60.): '%H:%M:%S.%f',
468
475
}
469
476
470
477
@@ -498,17 +505,14 @@ def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
498
505
self ._tz = tz
499
506
self .defaultfmt = defaultfmt
500
507
self ._formatter = DateFormatter (self .defaultfmt , tz )
501
- self .scaled = {
502
- 365.0 : '%Y' ,
503
- 30. : '%b %Y' ,
504
- 1.0 : '%b %d %Y' ,
505
- 1. / 24. : '%H:%M:%S' ,
506
- }
508
+ self .scaled = {365.0 : '%Y' ,
509
+ 30. : '%b %Y' ,
510
+ 1.0 : '%b %d %Y' ,
511
+ 1. / 24. : '%H:%M:%S' ,
512
+ 1. / (24. * 60. ): '%H:%M:%S.%f' }
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 ):
@@ -573,6 +577,11 @@ def _get_interval(self):
573
577
return 1
574
578
575
579
def nonsingular (self , vmin , vmax ):
580
+ """
581
+ Given the proposed upper and lower extent, adjust the range
582
+ if it is too close to being singular (i.e. a range of ~0).
583
+
584
+ """
576
585
unit = self ._get_unit ()
577
586
interval = self ._get_interval ()
578
587
if abs (vmax - vmin ) < 1e-6 :
@@ -639,6 +648,7 @@ def _get_unit(self):
639
648
freq = self .rule ._rrule ._freq
640
649
return self .get_unit_generic (freq )
641
650
651
+ @staticmethod
642
652
def get_unit_generic (freq ):
643
653
if (freq == YEARLY ):
644
654
return 365.0
@@ -657,7 +667,6 @@ def get_unit_generic(freq):
657
667
else :
658
668
# error
659
669
return - 1 # or should this just return '1'?
660
- get_unit_generic = staticmethod (get_unit_generic )
661
670
662
671
def _get_interval (self ):
663
672
return self .rule ._rrule ._interval
@@ -704,11 +713,11 @@ def autoscale(self):
704
713
class AutoDateLocator (DateLocator ):
705
714
"""
706
715
On autoscale, this class picks the best
707
- :class:`MultipleDateLocator ` to set the view limits and the tick
716
+ :class:`DateLocator ` to set the view limits and the tick
708
717
locations.
709
718
"""
710
719
def __init__ (self , tz = None , minticks = 5 , maxticks = None ,
711
- interval_multiples = False ):
720
+ interval_multiples = False ):
712
721
"""
713
722
*minticks* is the minimum number of ticks desired, which is used to
714
723
select the type of ticking (yearly, monthly, etc.).
@@ -719,7 +728,7 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
719
728
individual rrule frequency constants (YEARLY, MONTHLY, etc.)
720
729
to their own maximum number of ticks. This can be used to keep
721
730
the number of ticks appropriate to the format chosen in
722
- class:`AutoDateFormatter`. Any frequency not specified in this
731
+ : class:`AutoDateFormatter`. Any frequency not specified in this
723
732
dictionary is given a default value.
724
733
725
734
*tz* is a :class:`tzinfo` instance.
@@ -735,12 +744,16 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
735
744
multiple allowed for that ticking. The default looks like this::
736
745
737
746
self.intervald = {
738
- YEARLY : [1, 2, 4, 5, 10],
747
+ YEARLY : [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
748
+ 1000, 2000, 4000, 5000, 10000],
739
749
MONTHLY : [1, 2, 3, 4, 6],
740
750
DAILY : [1, 2, 3, 7, 14],
741
751
HOURLY : [1, 2, 3, 4, 6, 12],
742
752
MINUTELY: [1, 5, 10, 15, 30],
743
- SECONDLY: [1, 5, 10, 15, 30]
753
+ SECONDLY: [1, 5, 10, 15, 30],
754
+ MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000,
755
+ 5000, 10000, 20000, 50000, 100000, 200000, 500000,
756
+ 1000000],
744
757
}
745
758
746
759
The interval is used to specify multiples that are appropriate for
@@ -754,11 +767,12 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
754
767
DateLocator .__init__ (self , tz )
755
768
self ._locator = YearLocator ()
756
769
self ._freq = YEARLY
757
- self ._freqs = [YEARLY , MONTHLY , DAILY , HOURLY , MINUTELY , SECONDLY ]
770
+ self ._freqs = [YEARLY , MONTHLY , DAILY , HOURLY , MINUTELY ,
771
+ SECONDLY , MICROSECONDLY ]
758
772
self .minticks = minticks
759
773
760
- self .maxticks = {YEARLY : 16 , MONTHLY : 12 , DAILY : 11 , HOURLY : 16 ,
761
- MINUTELY : 11 , SECONDLY : 11 }
774
+ self .maxticks = {YEARLY : 11 , MONTHLY : 12 , DAILY : 11 , HOURLY : 12 ,
775
+ MINUTELY : 11 , SECONDLY : 11 , MICROSECONDLY : 8 }
762
776
if maxticks is not None :
763
777
try :
764
778
self .maxticks .update (maxticks )
@@ -767,24 +781,35 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
767
781
# number of ticks for every frequency and create a
768
782
# dictionary for this
769
783
self .maxticks = dict (izip (self ._freqs ,
770
- [maxticks ] * len (self ._freqs )))
784
+ [maxticks ] * len (self ._freqs )))
771
785
self .interval_multiples = interval_multiples
772
786
self .intervald = {
773
- YEARLY : [1 , 2 , 4 , 5 , 10 ],
774
- MONTHLY : [1 , 2 , 3 , 4 , 6 ],
775
- DAILY : [1 , 2 , 3 , 7 , 14 ],
776
- HOURLY : [1 , 2 , 3 , 4 , 6 , 12 ],
777
- MINUTELY : [1 , 5 , 10 , 15 , 30 ],
778
- SECONDLY : [1 , 5 , 10 , 15 , 30 ]
779
- }
787
+ YEARLY : [1 , 2 , 4 , 5 , 10 , 20 , 40 , 50 , 100 , 200 , 400 , 500 ,
788
+ 1000 , 2000 , 4000 , 5000 , 10000 ],
789
+ MONTHLY : [1 , 2 , 3 , 4 , 6 ],
790
+ DAILY : [1 , 2 , 3 , 7 , 14 ],
791
+ HOURLY : [1 , 2 , 3 , 4 , 6 , 12 ],
792
+ MINUTELY : [1 , 5 , 10 , 15 , 30 ],
793
+ SECONDLY : [1 , 5 , 10 , 15 , 30 ],
794
+ MICROSECONDLY : [1 , 2 , 5 , 10 , 20 , 50 , 100 , 200 , 500 , 1000 , 2000 ,
795
+ 5000 , 10000 , 20000 , 50000 , 100000 , 200000 , 500000 ,
796
+ 1000000 ]}
780
797
self ._byranges = [None , range (1 , 13 ), range (1 , 32 ), range (0 , 24 ),
781
- range (0 , 60 ), range (0 , 60 )]
798
+ range (0 , 60 ), range (0 , 60 ), None ]
782
799
783
800
def __call__ (self ):
784
801
'Return the locations of the ticks'
785
802
self .refresh ()
786
803
return self ._locator ()
787
804
805
+ def nonsingular (self , vmin , vmax ):
806
+ # whatever is thrown at us, we can scale the unit.
807
+ # But default nonsingular date plots at an ~4 year period.
808
+ if vmin == vmax :
809
+ vmin = vmin - 365 * 2
810
+ vmax = vmax + 365 * 2
811
+ return vmin , vmax
812
+
788
813
def set_axis (self , axis ):
789
814
DateLocator .set_axis (self , axis )
790
815
self ._locator .set_axis (axis )
@@ -795,7 +820,10 @@ def refresh(self):
795
820
self ._locator = self .get_locator (dmin , dmax )
796
821
797
822
def _get_unit (self ):
798
- return RRuleLocator .get_unit_generic (self ._freq )
823
+ if self ._freq in [MICROSECONDLY ]:
824
+ return 1. / MUSECONDS_PER_DAY
825
+ else :
826
+ return RRuleLocator .get_unit_generic (self ._freq )
799
827
800
828
def autoscale (self ):
801
829
'Try to choose the view limits intelligently.'
@@ -805,7 +833,6 @@ def autoscale(self):
805
833
806
834
def get_locator (self , dmin , dmax ):
807
835
'Pick the best locator based on a distance.'
808
-
809
836
delta = relativedelta (dmax , dmin )
810
837
811
838
numYears = (delta .years * 1.0 )
@@ -814,12 +841,17 @@ def get_locator(self, dmin, dmax):
814
841
numHours = (numDays * 24.0 ) + delta .hours
815
842
numMinutes = (numHours * 60.0 ) + delta .minutes
816
843
numSeconds = (numMinutes * 60.0 ) + delta .seconds
844
+ numMicroseconds = (numSeconds * 1e6 ) + delta .microseconds
817
845
818
- nums = [numYears , numMonths , numDays , numHours , numMinutes , numSeconds ]
846
+ nums = [numYears , numMonths , numDays , numHours , numMinutes ,
847
+ numSeconds , numMicroseconds ]
848
+
849
+ use_rrule_locator = [True ] * 6 + [False ]
819
850
820
851
# 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 ]
852
+ # [unused (for year), bymonth, bymonthday, byhour, byminute,
853
+ # bysecond, unused (for microseconds)]
854
+ byranges = [None , 1 , 1 , 0 , 0 , 0 , None ]
823
855
824
856
# Loop over all the frequencies and try to find one that gives at
825
857
# least a minticks tick positions. Once this is found, look for
@@ -841,8 +873,13 @@ def get_locator(self, dmin, dmax):
841
873
if num <= interval * (self .maxticks [freq ] - 1 ):
842
874
break
843
875
else :
844
- # We went through the whole loop without breaking, default to 1
845
- interval = 1
876
+ # We went through the whole loop without breaking, default to
877
+ # the last interval in the list and raise a warning
878
+ warnings .warn ('AutoDateLocator was unable to pick an '
879
+ 'appropriate interval for this date range. '
880
+ 'It may be necessary to add an interval value '
881
+ "to the AutoDateLocator's intervald dictionary."
882
+ ' Defaulting to {0}.' .format (interval ))
846
883
847
884
# Set some parameters as appropriate
848
885
self ._freq = freq
@@ -856,22 +893,22 @@ def get_locator(self, dmin, dmax):
856
893
# We found what frequency to use
857
894
break
858
895
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 )
896
+ raise ValueError ( 'No sensible date limit could be found in the '
897
+ 'AutoDateLocator.' )
898
+
899
+ if use_rrule_locator [ i ]:
900
+ _ , bymonth , bymonthday , byhour , byminute , bysecond , _ = byranges
901
+
902
+ rrule = rrulewrapper ( self . _freq , interval = interval ,
903
+ dtstart = dmin , until = dmax ,
904
+ bymonth = bymonth , bymonthday = bymonthday ,
905
+ byhour = byhour , byminute = byminute ,
906
+ bysecond = bysecond )
907
+
908
+ locator = RRuleLocator ( rrule , self . tz )
909
+ else :
910
+ locator = MicrosecondLocator ( interval , tz = self . tz )
911
+
875
912
locator .set_axis (self .axis )
876
913
877
914
locator .set_view_interval (* self .axis .get_view_interval ())
@@ -1051,6 +1088,55 @@ def __init__(self, bysecond=None, interval=1, tz=None):
1051
1088
RRuleLocator .__init__ (self , rule , tz )
1052
1089
1053
1090
1091
+ class MicrosecondLocator (DateLocator ):
1092
+ """
1093
+ Make ticks on occurances of each microsecond.
1094
+
1095
+ """
1096
+ def __init__ (self , interval = 1 , tz = None ):
1097
+ """
1098
+ *interval* is the interval between each iteration. For
1099
+ example, if ``interval=2``, mark every second microsecond.
1100
+
1101
+ """
1102
+ self ._interval = interval
1103
+ self ._wrapped_locator = ticker .MultipleLocator (interval )
1104
+ self .tz = tz
1105
+
1106
+ def set_axis (self , axis ):
1107
+ self ._wrapped_locator .set_axis (axis )
1108
+ return DateLocator .set_axis (self , axis )
1109
+
1110
+ def set_view_interval (self , vmin , vmax ):
1111
+ self ._wrapped_locator .set_view_interval (vmin , vmax )
1112
+ return DateLocator .set_view_interval (self , vmin , vmax )
1113
+
1114
+ def set_data_interval (self , vmin , vmax ):
1115
+ self ._wrapped_locator .set_data_interval (vmin , vmax )
1116
+ return DateLocator .set_data_interval (self , vmin , vmax )
1117
+
1118
+ def __call__ (self , * args , ** kwargs ):
1119
+ vmin , vmax = self .axis .get_view_interval ()
1120
+ vmin *= MUSECONDS_PER_DAY
1121
+ vmax *= MUSECONDS_P
DA70
ER_DAY
1122
+ ticks = self ._wrapped_locator .tick_values (vmin , vmax )
1123
+ ticks = [tick / MUSECONDS_PER_DAY for tick in ticks ]
1124
+ return ticks
1125
+
1126
+ def _get_unit (self ):
1127
+ """
1128
+ Return how many days a unit of the locator is; used for
1129
+ intelligent autoscaling.
1130
+ """
1131
+ return 1. / MUSECONDS_PER_DAY
1132
+
1133
+ def _get_interval (self ):
1134
+ """
1135
+ Return the number of units for each tick.
1136
+ """
1137
+ return self ._interval
1138
+
1139
+
1054
1140
def _close_to_dt (d1 , d2 , epsilon = 5 ):
1055
1141
'Assert that datetimes *d1* and *d2* are within *epsilon* microseconds.'
1056
1142
delta = d2 - d1
0 commit comments