Adjust number of ticks based on length of axis #5588

Merged
merged 13 commits into from Dec 14, 2015
View
@@ -654,6 +654,7 @@ def __init__(self, axes, pickradius=15):
# Initialize here for testing; later add API
self._major_tick_kw = dict()
self._minor_tick_kw = dict()
+ self._tick_space = None
self.cla()
self._set_scale('linear')
@@ -785,6 +786,7 @@ def set_tick_params(self, which='major', reset=False, **kw):
for tick in self.minorTicks:
tick._apply_params(**self._minor_tick_kw)
self.stale = True
+ self._tick_space = None
@staticmethod
def _translate_tick_kw(kw, to_init_kw=True):
@@ -1665,6 +1667,13 @@ def axis_date(self, tz=None):
tz = pytz.timezone(tz)
self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz))
+ def get_tick_space(self):
+ """
+ Return the estimated number of ticks that can fit on the axis.
+ """
+ # Must be overridden in the subclass
+ raise NotImplementedError()
+
class XAxis(Axis):
__name__ = 'xaxis'
@@ -1988,6 +1997,18 @@ def set_default_intervals(self):
self.axes.viewLim.intervalx = xmin, xmax
self.stale = True
+ def get_tick_space(self):
+ if self._tick_space is None:
+ ends = self.axes.transAxes.transform([[0, 0], [1, 0]])
+ length = ((ends[1][0] - ends[0][0]) / self.axes.figure.dpi) * 72.0
+ tick = self._get_tick(True)
+ # There is a heuristic here that the aspect ratio of tick text
+ # is no more than 3:1
+ size = tick.label1.get_size() * 3
+ size *= np.cos(np.deg2rad(tick.label1.get_rotation()))
+ self._tick_space = np.floor(length / size)
+ return self._tick_space
+
class YAxis(Axis):
__name__ = 'yaxis'
@@ -2318,3 +2339,14 @@ def set_default_intervals(self):
if not viewMutated:
self.axes.viewLim.intervaly = ymin, ymax
self.stale = True
+
+ def get_tick_space(self):
+ if self._tick_space is None:
+ ends = self.axes.transAxes.transform([[0, 0], [0, 1]])
+ length = ((ends[1][1] - ends[0][1]) / self.axes.figure.dpi) * 72.0
+ tick = self._get_tick(True)
+ # Having a spacing of at least 2 just looks good.
+ size = tick.label1.get_size() * 2.0
+ size *= np.cos(np.deg2rad(tick.label1.get_rotation()))
+ self._tick_space = np.floor(length / size)
+ return self._tick_space
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -4191,6 +4191,12 @@ def test_axes_margins():
assert ax.get_ybound() == (-0.5, 9.5)
+@image_comparison(baseline_images=["auto_numticks"], style='default',
+ extensions=['png'])
+def test_auto_numticks():
+ fig, axes = plt.subplots(4, 4)
+
+
if __name__ == '__main__':
import nose
import sys
View
@@ -202,6 +202,10 @@ def get_data_interval(self):
def set_data_interval(self, vmin, vmax):
self.dataLim.intervalx = vmin, vmax
+ def get_tick_space(self):
+ # Just use the long-standing default of nbins==9
+ return 9
+
class TickHelper(object):
axis = None
@@ -1349,7 +1353,9 @@ def __init__(self, *args, **kwargs):
Keyword args:
*nbins*
- Maximum number of intervals; one less than max number of ticks.
+ Maximum number of intervals; one less than max number of
+ ticks. If the string `'auto'`, the number of bins will be
+ automatically determined based on the length of the axis.
*steps*
Sequence of nice numbers starting with 1 and ending with 10;
@@ -1387,7 +1393,9 @@ def __init__(self, *args, **kwargs):
def set_params(self, **kwargs):
"""Set parameters within this locator."""
if 'nbins' in kwargs:
- self._nbins = int(kwargs['nbins'])
+ self._nbins = kwargs['nbins']
+ if self._nbins != 'auto':
+ self._nbins = int(self._nbins)
if 'trim' in kwargs:
self._trim = kwargs['trim']
if 'integer' in kwargs:
@@ -1416,6 +1424,8 @@ def set_params(self, **kwargs):
def bin_boundaries(self, vmin, vmax):
nbins = self._nbins
+ if nbins == 'auto':
+ nbins = self.axis.get_tick_space()
scale, offset = scale_range(vmin, vmax, nbins)
if self._integer:
scale = max(1, scale)
@@ -1901,7 +1911,11 @@ def tick_values(self, vmin, vmax):
class AutoLocator(MaxNLocator):
def __init__(self):
- MaxNLocator.__init__(self, nbins=9, steps=[1, 2, 5, 10])
+ if rcParams['_internal.classic_mode']:
+ nbins = 9
+ else:
+ nbins = 'auto'
+ MaxNLocator.__init__(self, nbins=nbins, steps=[1, 2, 5, 10])
class AutoMinorLocator(Locator):