Skip to content

Commit 9b65519

Browse files
committed
updated solution to use dimensions from axis, and added autodatelocator.spacing to rcparams' as an option
1 parent 0af2379 commit 9b65519

16 files changed

+41
-27
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
``AutoDateLocator.get_locator`` now contains reduced datetime overlaps
22
````````````````````````````````````````````````````````````````````````
33

4-
Due to issue #7712, the interval frequency of datetime ticks gets reduced in order
5-
to avoid overlapping tick labels.
4+
Due to issue #7712, the interval frequency of datetime ticks gets reduced due to
5+
avoid overlapping tick labels.

lib/matplotlib/dates.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,7 +1166,7 @@ class AutoDateLocator(DateLocator):
11661166
locations.
11671167
"""
11681168
def __init__(self, tz=None, minticks=5, maxticks=None,
1169-
interval_multiples=False):
1169+
interval_multiples=False, spread=""):
11701170
"""
11711171
*minticks* is the minimum number of ticks desired, which is used to
11721172
select the type of ticking (yearly, monthly, etc.).
@@ -1208,18 +1208,17 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
12081208
The interval is used to specify multiples that are appropriate for
12091209
the frequency of ticking. For instance, every 7 days is sensible
12101210
for daily ticks, but for minutes/seconds, 15 or 30 make sense.
1211-
You can customize this dictionary by doing::
1211+
You can customize this dictionary by doing:
12121212
12131213
locator = AutoDateLocator()
12141214
locator.intervald[HOURLY] = [3] # only show every 3 hours
12151215
12161216
In order to avoid overlapping dates, another dictionary was
12171217
created to map date intervals to the format of the date used in
1218-
rcParams. In addition, the default width of figsize from rcparams
1219-
(rcParams["figure.figsize"][0]) is used to get a estimate of the
1220-
number of date ticks we can fit in the axis. This allows customization
1221-
by using rcParam's date format and figsize.
1222-
1218+
rcParams. In addition, the figsize and font is used from the axis,
1219+
and a new setting in rcparams['autodatelocator.spacing'] is added
1220+
and used to let the user decide when spacing should be used. This
1221+
was done because rotation at this point in runtime is not known.
12231222
"""
12241223
DateLocator.__init__(self, tz)
12251224

@@ -1231,7 +1230,6 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
12311230

12321231
self.maxticks = {YEARLY: 11, MONTHLY: 12, DAILY: 11, HOURLY: 12,
12331232
MINUTELY: 11, SECONDLY: 11, MICROSECONDLY: 8}
1234-
12351233
if maxticks is not None:
12361234
try:
12371235
self.maxticks.update(maxticks)
@@ -1261,6 +1259,7 @@ def __init__(self, tz=None, minticks=5, maxticks=None,
12611259
MINUTELY: rcParams['date.autoformatter.minute'],
12621260
SECONDLY: rcParams['date.autoformatter.second'],
12631261
MICROSECONDLY: rcParams['date.autoformatter.microsecond']}
1262+
self.spread = spread
12641263

12651264
self._byranges = [None, range(1, 13), range(1, 32),
12661265
range(0, 24), range(0, 60), range(0, 60), None]
@@ -1334,12 +1333,25 @@ def get_locator(self, dmin, dmax):
13341333
# bysecond, unused (for microseconds)]
13351334
byranges = [None, 1, 1, 0, 0, 0, None]
13361335

1336+
axis_name = getattr(self.axis, 'axis_name', '')
1337+
label = getattr(self.axis, 'label', None)
1338+
figure = getattr(self.axis, 'figure', None)
1339+
13371340
# estimated font ratio since our estimation
13381341
# is on font size 10
1339-
font_ratio = (rcParams['font.size'])/10
1342+
if label is not None:
1343+
font_ratio = label.get_fontsize()/10
1344+
else:
1345+
font_ratio = rcParams['font.size']/10
13401346

1341-
# a ratio of 10 date characters per inch is 'estimated'
1342-
maxwid = rcParams["figure.figsize"][0] * 10
1347+
if figure is not None:
1348+
width = figure.get_size_inches()[0]
1349+
else:
1350+
width = rcParams["figure.figsize"][0]
1351+
1352+
# a ratio of 8 date characters per inch is 'estimated'
1353+
maxwid = width * 8
1354+
apply_spread = (rcParams["autodatelocator.spacing"] == "generous")
13431355

13441356
# Loop over all the frequencies and try to find one that gives at
13451357
# least a minticks tick positions. Once this is found, look for
@@ -1356,13 +1368,9 @@ def get_locator(self, dmin, dmax):
13561368
continue
13571369

13581370
# Compute at runtime the size of date label with given format
1359-
try:
1360-
# ensure yaxis ticks are not reduced
1361-
if (self.axis.axis_name == 'x'):
1362-
date_len = len(dmin.strftime(self.eachtick[freq])) + 1
1363-
else:
1364-
date_len = 1
1365-
except AttributeError:
1371+
if (axis_name == 'x'):
1372+
date_len = len(dmin.strftime(self.eachtick[freq]))
1373+
else:
13661374
date_len = 1
13671375

13681376
# Find the first available interval that doesn't give too many
@@ -1371,7 +1379,8 @@ def get_locator(self, dmin, dmax):
13711379
if (num <= interval * (self.maxticks[freq] - 1)):
13721380
# Using an estmation of characters per inch, reduce
13731381
# intervals untill we get no overlaps
1374-
if ((num/interval) * date_len * font_ratio <= maxwid):
1382+
if (((num/interval) * date_len * font_ratio) <= maxwid or
1383+
(not apply_spread)):
13751384
break
13761385
else:
13771386
# We went through the whole loop without breaking, default to

lib/matplotlib/rcsetup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1212,7 +1212,10 @@ def _validate_linestyle(ls):
12121212
'date.autoformatter.minute': ['%d %H:%M', validate_string],
12131213
'date.autoformatter.second': ['%H:%M:%S', validate_string],
12141214
'date.autoformatter.microsecond': ['%M:%S.%f', validate_string],
1215-
1215+
1216+
# To avoid overlapping date invervals, we can set the spacing in advance
1217+
# 'generous' is set to avoid overlapping, otherwise 'tight' by default
1218+
'autodatelocator.spacing' : ['tight', validate_string],
12161219
#legend properties
12171220
'legend.fancybox': [True, validate_bool],
12181221
'legend.loc': ['best', validate_legend_loc],
Loading

lib/matplotlib/tests/test_dates.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ def test_num2timedelta(x, tdelta):
619619
def test_datetime_daily_overlap():
620620
# issue 7712 for overlapping daily dates
621621
plt.rcParams['date.autoformatter.day'] = "%Y-%m-%d"
622-
plt.rcParams['xtick.major.pad'] = 8
622+
plt.rcParams["autodatelocator.spacing"] = "generous"
623623
dates = [datetime.datetime(2018, 1, i) for i in range(1, 30)]
624624
values = list(range(1, 30))
625625
plt.plot(dates, values)
@@ -629,8 +629,8 @@ def test_datetime_daily_overlap():
629629
extensions=['png'])
630630
def test_datetime_monthly_overlap():
631631
# issue 7712 for overlapping monthly dates
632-
plt.rcParams['date.autoformatter.month'] = '%Y-%m'
633-
plt.rcParams['xtick.major.pad'] = '8'
632+
plt.rcParams['date.autoformatter.month'] = '%Y-%m'
633+
plt.rcParams["autodatelocator.spacing"] = "generous"
634634
dates = [datetime.datetime(2018, i, 1) for i in range(1, 11)]
635635
values = list(range(1, 11))
636636
plt.plot(dates, values)
@@ -641,7 +641,7 @@ def test_datetime_monthly_overlap():
641641
def test_datetime_hourly_overlap():
642642
# issue 7712 for overlapping hourly dates
643643
plt.rcParams['date.autoformatter.hour'] = '%m-%d %H'
644-
plt.rcParams['xtick.major.pad'] = '8'
644+
plt.rcParams["autodatelocator.spacing"] = "generous"
645645
dates = [datetime.datetime(2018, 1, 1, i) for i in range(1, 20)]
646646
values = list(range(1, 20))
647647
plt.plot(dates, values)
@@ -652,7 +652,7 @@ def test_datetime_hourly_overlap():
652652
def test_datetime_minutely_overlap():
653653
# issue 7712 for overlapping date ticks in minutely intervals
654654
plt.rcParams['date.autoformatter.minute'] = '%d %H:%M'
655-
plt.rcParams['xtick.major.pad'] = '8'
655+
plt.rcParams["autodatelocator.spacing"] = "generous"
656656
dates = [datetime.datetime(2018, 1, 1, 1, i) for i in range(1, 55)]
657657
values = list(range(1, 55))
658658
plt.plot(dates, values)

matplotlibrc.template

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,8 @@ backend : $TEMPLATE_BACKEND
352352
#date.autoformatter.second : %H:%M:%S
353353
#date.autoformatter.microsecond : %M:%S.%f
354354

355+
#autodatelocator.spacing : tight ## if generous, add extra spacing to avoid overlap, otherwise tight
356+
355357
#### TICKS
356358
## see http://matplotlib.org/api/axis_api.html#matplotlib.axis.Tick
357359
#xtick.top : False ## draw ticks on the top side

0 commit comments

Comments
 (0)