82
82
83
83
try : import datetime
84
84
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__ )
86
87
87
88
from cbook import iterable , is_string_like
88
89
from pytz import timezone
89
90
from numerix import arange , asarray
90
91
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 , \
92
93
MONTHLY , WEEKLY , DAILY , HOURLY , MINUTELY , SECONDLY
93
94
from dateutil .relativedelta import relativedelta
94
95
import dateutil .parser
@@ -108,7 +109,8 @@ def _get_rc_timezone():
108
109
SEC_PER_HOUR = 3600
109
110
SEC_PER_DAY = SEC_PER_HOUR * 24
110
111
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 )
112
114
WEEKDAYS = (MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY , SUNDAY )
113
115
114
116
__all__ = ['date2num' , 'num2date' , 'drange' , 'HOURS_PER_DAY' ,
@@ -156,8 +158,9 @@ def _from_ordinalf(x, tz=None):
156
158
second , remainder = divmod (60 * remainder , 1 )
157
159
microsecond = int (1e6 * remainder )
158
160
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 )
161
164
162
165
if microsecond > 999990 : # compensate for rounding errors
163
166
dt += datetime .timedelta (microseconds = 1e6 - microsecond )
@@ -207,7 +210,8 @@ def drange(dstart, dend, delta):
207
210
Return a date range as float gregorian ordinals. dstart and dend
208
211
are datetime instances. delta is a datetime.timedelta instance
209
212
"""
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 )
211
215
f1 = _to_ordinalf (dstart )
212
216
f2 = _to_ordinalf (dend )
213
217
return arange (f1 , f2 , step )
@@ -321,6 +325,50 @@ def __call__(self, x, pos=0):
321
325
322
326
return unicode (dt .strftime (self .fmt ), locale .getpreferredencoding ())
323
327
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
+
324
372
class rrulewrapper :
325
373
326
374
def __init__ (self , freq , ** kwargs ):
@@ -393,6 +441,30 @@ def __call__(self):
393
441
dates = self .rule .between (dmin , dmax , True )
394
442
return date2num (dates )
395
443
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
+
396
468
def autoscale (self ):
397
469
"""
398
470
Set the view limits to include the data range
@@ -419,6 +491,152 @@ def autoscale(self):
419
491
return self .nonsingular (vmin , vmax )
420
492
421
493
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
+
422
640
423
641
class YearLocator (DateLocator ):
424
642
"""
@@ -529,7 +747,8 @@ def __init__(self, byweekday=1, interval=1, tz=None):
529
747
plots every second week
530
748
531
749
"""
532
- o = rrulewrapper (DAILY , byweekday = byweekday , interval = interval , ** self .hms0d )
750
+ o = rrulewrapper (DAILY , byweekday = byweekday ,
751
+ interval = interval , ** self .hms0d )
533
752
RRuleLocator .__init__ (self , o , tz )
534
753
535
754
def _get_unit (self ):
@@ -551,7 +770,8 @@ def __init__(self, bymonthday=None, interval=1, tz=None):
551
770
Default is to tick every day of the month - bymonthday=range(1,32)
552
771
"""
553
772
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 )
555
775
RRuleLocator .__init__ (self , o , tz )
556
776
557
777
def _get_unit (self ):
@@ -639,7 +859,8 @@ def _get_unit(self):
639
859
def _close_to_dt (d1 , d2 , epsilon = 5 ):
640
860
'assert that datetimes d1 and d2 are within epsilon microseconds'
641
861
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 )
643
864
assert (mus < epsilon )
644
865
645
866
def _close_to_num (o1 , o2 , epsilon = 5 ):
0 commit comments