Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0b419ec

Browse files
committed
Improvements to DateLocators to partially address #2976576:
- Take into account "interval" when determining autorange (particularly important for expanding singular ranges) - prevent a near-infinite loop in the "dateutil.rrule.between" method (calculating a repeating event between two dates) by estimating with division the approximate number, and if this number is way more than reasonable, raise an Exception. svn path=/trunk/matplotlib/; revision=8296
1 parent f3e59a4 commit 0b419ec

1 file changed

Lines changed: 40 additions & 75 deletions

File tree

lib/matplotlib/dates.py

Lines changed: 40 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -507,11 +507,18 @@ def _get_unit(self):
507507
"""
508508
return 1
509509

510+
def _get_interval(self):
511+
"""
512+
Return the number of units for each tick.
513+
"""
514+
return 1
515+
510516
def nonsingular(self, vmin, vmax):
511517
unit = self._get_unit()
518+
interval = self._get_interval()
512519
if abs(vmax - vmin) < 1e-6:
513-
vmin -= 2*unit
514-
vmax += 2*unit
520+
vmin -= 2*unit*interval
521+
vmax += 2*unit*interval
515522
return vmin, vmax
516523

517524
class RRuleLocator(DateLocator):
@@ -542,8 +549,22 @@ def __call__(self):
542549
# The magic number!
543550
stop = _from_ordinalf( 3652059.9999999 )
544551

545-
self.rule.set(dtstart=start, until=stop)
552+
self.rule.set(dtstart=start, until=stop, count=self.MAXTICKS + 1)
553+
554+
# estimate the number of ticks very approximately so we don't
555+
# have to do a very expensive (and potentially near infinite)
556+
# 'between' calculation, only to find out it will fail.
557+
nmax, nmin = date2num((dmax, dmin))
558+
estimate = (nmax - nmin) / (self._get_unit() * self._get_interval())
559+
# This estimate is only an estimate, so be really conservative
560+
# about bailing...
561+
if estimate > self.MAXTICKS * 2:
562+
raise RuntimeError(
563+
'RRuleLocator estimated to generate %d ticks from %s to %s: exceeds Locator.MAXTICKS * 2 (%d) ' % (estimate, dmin, dmax, self.MAXTICKS * 2))
564+
546565
dates = self.rule.between(dmin, dmax, True)
566+
if len(dates) == 0:
567+
return date2num([dmin, dmax])
547568
return self.raise_if_exceeds(date2num(dates))
548569

549570
def _get_unit(self):
@@ -552,14 +573,17 @@ def _get_unit(self):
552573
intelligent autoscaling.
553574
"""
554575
freq = self.rule._rrule._freq
576+
return self.get_unit_generic(freq)
577+
578+
def get_unit_generic(freq):
555579
if ( freq == YEARLY ):
556-
return 365
580+
return 365.0
557581
elif ( freq == MONTHLY ):
558-
return 30
582+
return 30.0
559583
elif ( freq == WEEKLY ):
560-
return 7
584+
return 7.0
561585
elif ( freq == DAILY ):
562-
return 1
586+
return 1.0
563587
elif ( freq == HOURLY ):
564588
return (1.0/24.0)
565589
elif ( freq == MINUTELY ):
@@ -569,7 +593,11 @@ def _get_unit(self):
569593
else:
570594
# error
571595
return -1 #or should this just return '1'?
596+
get_unit_generic = staticmethod(get_unit_generic)
572597

598+
def _get_interval(self):
599+
return self.rule._rrule._interval
600+
573601
def autoscale(self):
574602
"""
575603
Set the view limits to include the data range.
@@ -702,23 +730,7 @@ def refresh(self):
702730
self._locator = self.get_locator(dmin, dmax)
703731

704732
def _get_unit(self):
705-
if ( self._freq == YEARLY ):
706-
return 365.0
707-
elif ( self._freq == MONTHLY ):
708-
return 30.0
709-
elif ( self._freq == WEEKLY ):
710-
return 7.0
711-
elif ( self._freq == DAILY ):
712-
return 1.0
713-
elif ( self._freq == HOURLY ):
714-
return 1.0/24
715-
elif ( self._freq == MINUTELY ):
716-
return 1.0/(24*60)
717-
elif ( self._freq == SECONDLY ):
718-
return 1.0/(24*3600)
719-
else:
720-
# error
721-
return -1
733+
return RRuleLocator.get_unit_generic(self._freq)
722734

723735
def autoscale(self):
724736
'Try to choose the view limits intelligently.'
@@ -828,14 +840,6 @@ def __init__(self, base=1, month=1, day=1, tz=None):
828840
'tzinfo' : tz
829841
}
830842

831-
832-
def _get_unit(self):
833-
"""
834-
Return how many days a unit of the locator is; used for
835-
intelligent autoscaling.
836-
"""
837-
return 365
838-
839843
def __call__(self):
840844
dmin, dmax = self.viewlim_to_dt()
841845
ymin = self.base.le(dmin.year)
@@ -864,6 +868,7 @@ def autoscale(self):
864868
vmax = date2num(vmax)
865869
return self.nonsingular(vmin, vmax)
866870

871+
867872
class MonthLocator(RRuleLocator):
868873
"""
869874
Make ticks on occurances of each month month, eg 1, 3, 12.
@@ -881,13 +886,6 @@ def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None):
881886
interval=interval, **self.hms0d)
882887
RRuleLocator.__init__(self, o, tz)
883888

884-
def _get_unit(self):
885-
"""
886-
Return how many days a unit of the locator is; used for
887-
intelligent autoscaling.
888-
"""
889-
return 30
890-
891889

892890
class WeekdayLocator(RRuleLocator):
893891
"""
@@ -909,13 +907,6 @@ def __init__(self, byweekday=1, interval=1, tz=None):
909907
interval=interval, **self.hms0d)
910908
RRuleLocator.__init__(self, o, tz)
911909

912-
def _get_unit(self):
913-
"""
914-
return how many days a unit of the locator is; used for
915-
intelligent autoscaling.
916-
"""
917-
return 7
918-
919910

920911
class DayLocator(RRuleLocator):
921912
"""
@@ -934,13 +925,7 @@ def __init__(self, bymonthday=None, interval=1, tz=None):
934925
interval=interval, **self.hms0d)
935926
RRuleLocator.__init__(self, o, tz)
936927

937-
def _get_unit(self):
938-
"""
939-
Return how many days a unit of the locator is; used for
940-
intelligent autoscaling.
941-
"""
942-
return 1
943-
928+
944929
class HourLocator(RRuleLocator):
945930
"""
946931
Make ticks on occurances of each hour.
@@ -958,13 +943,7 @@ def __init__(self, byhour=None, interval=1, tz=None):
958943
byminute=0, bysecond=0)
959944
RRuleLocator.__init__(self, rule, tz)
960945

961-
def _get_unit(self):
962-
"""
963-
return how many days a unit of the locator is; use for
964-
intelligent autoscaling
965-
"""
966-
return 1/24.
967-
946+
968947
class MinuteLocator(RRuleLocator):
969948
"""
970949
Make ticks on occurances of each minute.
@@ -982,13 +961,7 @@ def __init__(self, byminute=None, interval=1, tz=None):
982961
bysecond=0)
983962
RRuleLocator.__init__(self, rule, tz)
984963

985-
def _get_unit(self):
986-
"""
987-
Return how many days a unit of the locator is; used for
988-
intelligent autoscaling.
989-
"""
990-
return 1./(24*60)
991-
964+
992965
class SecondLocator(RRuleLocator):
993966
"""
994967
Make ticks on occurances of each second.
@@ -1006,14 +979,6 @@ def __init__(self, bysecond=None, interval=1, tz=None):
1006979
rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval)
1007980
RRuleLocator.__init__(self, rule, tz)
1008981

1009-
def _get_unit(self):
1010-
"""
1011-
Return how many days a unit of the locator is; used for
1012-
intelligent autoscaling.
1013-
"""
1014-
return 1./(24*60*60)
1015-
1016-
1017982

1018983
def _close_to_dt(d1, d2, epsilon=5):
1019984
'Assert that datetimes *d1* and *d2* are within *epsilon* microseconds.'

0 commit comments

Comments
 (0)