Skip to content
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

WIP: Adjust nticks based on length of axis #5494

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion examples/units/basic_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,12 @@ def axisinfo(unit, axis):
label=unit.fullname,
)
elif unit == degrees:
if rcParams['_internal.classic_mode']:
locator = ticker.ClassicAutoLocator()
else:
locator = ticker.AutoLocator()
return units.AxisInfo(
majloc=ticker.AutoLocator(),
majloc=locator,
majfmt=ticker.FormatStrFormatter(r'$%i^\circ$'),
label=unit.fullname,
)
Expand Down
37 changes: 36 additions & 1 deletion 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 @@ -710,7 +711,10 @@ def get_children(self):

def cla(self):
'clear the current axis'
self.set_major_locator(mticker.AutoLocator())
if rcParams['_internal.classic_mode']:
self.set_major_locator(mticker.ClassicAutoLocator())
else:
self.set_major_locator(mticker.AutoLocator())
self.set_major_formatter(mticker.ScalarFormatter())
self.set_minor_locator(mticker.NullLocator())
self.set_minor_formatter(mticker.NullFormatter())
Expand Down Expand Up @@ -785,6 +789,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 +1670,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 +2000,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 4:1
size = tick.label1.get_size() * 4
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 +2342,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
50 changes: 49 additions & 1 deletion lib/matplotlib/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,48 @@ def view_limits(self, dmin, dmax):
return np.take(self.bin_boundaries(dmin, dmax), [0, -1])


class AutoSpacedLocator(MaxNLocator):
"""
Behaves like a MaxNLocator, except N is automatically determined
from the length of the axis.
"""
def __init__(self, *args, **kwargs):
"""
Keyword args:

*steps*
Sequence of nice numbers starting with 1 and ending with 10;
e.g., [1, 2, 4, 5, 10]

*integer*
If True, ticks will take only integer values.

*symmetric*
If True, autoscaling will result in a range symmetric
about zero.

*prune*
['lower' | 'upper' | 'both' | None]
Remove edge ticks -- useful for stacked or ganged plots
where the upper tick of one axes overlaps with the lower
tick of the axes above it.
If prune=='lower', the smallest tick will
be removed. If prune=='upper', the largest tick will be
removed. If prune=='both', the largest and smallest ticks
will be removed. If prune==None, no ticks will be removed.

"""
if 'nbins' in kwargs:
raise ValueError(
'AutoSpacedLocator does not take nbins as an argument')
self.set_params(**self.default_params)
self.set_params(**kwargs)

def __call__(self):
self._nbins = max(self.axis.get_tick_space(), 3)
return super(AutoSpacedLocator, self).__call__()


def decade_down(x, base=10):
'floor x to the nearest lower decade'
if x == 0.0:
Expand Down Expand Up @@ -1872,7 +1914,13 @@ def tick_values(self, vmin, vmax):
return self.raise_if_exceeds(np.array(ticklocs))


class AutoLocator(MaxNLocator):
class AutoLocator(AutoSpacedLocator):
def __init__(self):
AutoSpacedLocator.__init__(self, steps=[1, 2, 5, 10])


class ClassicAutoLocator(MaxNLocator):
# Used only for classic style
def __init__(self):
MaxNLocator.__init__(self, nbins=9, steps=[1, 2, 5, 10])

Expand Down