Skip to content

Adjust number of ticks based on length of axis #5588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Dec 14, 2015
32 changes: 32 additions & 0 deletions lib/matplotlib/axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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
Binary file modified lib/matplotlib/tests/baseline_images/test_artist/default_edges.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 17 additions & 3 deletions lib/matplotlib/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down