Skip to content

Commit 7a29ad1

Browse files
committed
Merge pull request #5588 from mdboom/dynamic-ticking
Adjust number of ticks based on length of axis
2 parents d0e7a36 + eafa79b commit 7a29ad1

File tree

5 files changed

+55
-3
lines changed

5 files changed

+55
-3
lines changed

lib/matplotlib/axis.py

+32
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ def __init__(self, axes, pickradius=15):
654654
# Initialize here for testing; later add API
655655
self._major_tick_kw = dict()
656656
self._minor_tick_kw = dict()
657+
self._tick_space = None
657658

658659
self.cla()
659660
self._set_scale('linear')
@@ -785,6 +786,7 @@ def set_tick_params(self, which='major', reset=False, **kw):
785786
for tick in self.minorTicks:
786787
tick._apply_params(**self._minor_tick_kw)
787788
self.stale = True
789+
self._tick_space = None
788790

789791
@staticmethod
790792
def _translate_tick_kw(kw, to_init_kw=True):
@@ -1665,6 +1667,13 @@ def axis_date(self, tz=None):
16651667
tz = pytz.timezone(tz)
16661668
self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz))
16671669

1670+
def get_tick_space(self):
1671+
"""
1672+
Return the estimated number of ticks that can fit on the axis.
1673+
"""
1674+
# Must be overridden in the subclass
1675+
raise NotImplementedError()
1676+
16681677

16691678
class XAxis(Axis):
16701679
__name__ = 'xaxis'
@@ -1988,6 +1997,18 @@ def set_default_intervals(self):
19881997
self.axes.viewLim.intervalx = xmin, xmax
19891998
self.stale = True
19901999

2000+
def get_tick_space(self):
2001+
if self._tick_space is None:
2002+
ends = self.axes.transAxes.transform([[0, 0], [1, 0]])
2003+
length = ((ends[1][0] - ends[0][0]) / self.axes.figure.dpi) * 72.0
2004+
tick = self._get_tick(True)
2005+
# There is a heuristic here that the aspect ratio of tick text
2006+
# is no more than 3:1
2007+
size = tick.label1.get_size() * 3
2008+
size *= np.cos(np.deg2rad(tick.label1.get_rotation()))
2009+
self._tick_space = np.floor(length / size)
2010+
return self._tick_space
2011+
19912012

19922013
class YAxis(Axis):
19932014
__name__ = 'yaxis'
@@ -2318,3 +2339,14 @@ def set_default_intervals(self):
23182339
if not viewMutated:
23192340
self.axes.viewLim.intervaly = ymin, ymax
23202341
self.stale = True
2342+
2343+
def get_tick_space(self):
2344+
if self._tick_space is None:
2345+
ends = self.axes.transAxes.transform([[0, 0], [0, 1]])
2346+
length = ((ends[1][1] - ends[0][1]) / self.axes.figure.dpi) * 72.0
2347+
tick = self._get_tick(True)
2348+
# Having a spacing of at least 2 just looks good.
2349+
size = tick.label1.get_size() * 2.0
2350+
size *= np.cos(np.deg2rad(tick.label1.get_rotation()))
2351+
self._tick_space = np.floor(length / size)
2352+
return self._tick_space

lib/matplotlib/tests/test_axes.py

+6
Original file line numberDiff line numberDiff line change
@@ -4191,6 +4191,12 @@ def test_axes_margins():
41914191
assert ax.get_ybound() == (-0.5, 9.5)
41924192

41934193

4194+
@image_comparison(baseline_images=["auto_numticks"], style='default',
4195+
extensions=['png'])
4196+
def test_auto_numticks():
4197+
fig, axes = plt.subplots(4, 4)
4198+
4199+
41944200
if __name__ == '__main__':
41954201
import nose
41964202
import sys

lib/matplotlib/ticker.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ def get_data_interval(self):
202202
def set_data_interval(self, vmin, vmax):
203203
self.dataLim.intervalx = vmin, vmax
204204

205+
def get_tick_space(self):
206+
# Just use the long-standing default of nbins==9
207+
return 9
208+
205209

206210
class TickHelper(object):
207211
axis = None
@@ -1349,7 +1353,9 @@ def __init__(self, *args, **kwargs):
13491353
Keyword args:
13501354
13511355
*nbins*
1352-
Maximum number of intervals; one less than max number of ticks.
1356+
Maximum number of intervals; one less than max number of
1357+
ticks. If the string `'auto'`, the number of bins will be
1358+
automatically determined based on the length of the axis.
13531359
13541360
*steps*
13551361
Sequence of nice numbers starting with 1 and ending with 10;
@@ -1387,7 +1393,9 @@ def __init__(self, *args, **kwargs):
13871393
def set_params(self, **kwargs):
13881394
"""Set parameters within this locator."""
13891395
if 'nbins' in kwargs:
1390-
self._nbins = int(kwargs['nbins'])
1396+
self._nbins = kwargs['nbins']
1397+
if self._nbins != 'auto':
1398+
self._nbins = int(self._nbins)
13911399
if 'trim' in kwargs:
13921400
self._trim = kwargs['trim']
13931401
if 'integer' in kwargs:
@@ -1416,6 +1424,8 @@ def set_params(self, **kwargs):
14161424

14171425
def bin_boundaries(self, vmin, vmax):
14181426
nbins = self._nbins
1427+
if nbins == 'auto':
1428+
nbins = self.axis.get_tick_space()
14191429
scale, offset = scale_range(vmin, vmax, nbins)
14201430
if self._integer:
14211431
scale = max(1, scale)
@@ -1901,7 +1911,11 @@ def tick_values(self, vmin, vmax):
19011911

19021912
class AutoLocator(MaxNLocator):
19031913
def __init__(self):
1904-
MaxNLocator.__init__(self, nbins=9, steps=[1, 2, 5, 10])
1914+
if rcParams['_internal.classic_mode']:
1915+
nbins = 9
1916+
else:
1917+
nbins = 'auto'
1918+
MaxNLocator.__init__(self, nbins=nbins, steps=[1, 2, 5, 10])
19051919

19061920

19071921
class AutoMinorLocator(Locator):

0 commit comments

Comments
 (0)