Skip to content

Rework MaxNLocator, eliminating infinite loop; closes #6849 #6919

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 3 commits into from
Aug 22, 2016
Merged
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
11 changes: 11 additions & 0 deletions lib/matplotlib/tests/test_ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import warnings


@cleanup(style='classic')
def test_MaxNLocator():
loc = mticker.MaxNLocator(nbins=5)
test_value = np.array([20., 40., 60., 80., 100.])
Expand All @@ -26,6 +27,16 @@ def test_MaxNLocator():
assert_almost_equal(loc.tick_values(-1e15, 1e15), test_value)


@cleanup
def test_MaxNLocator_integer():
loc = mticker.MaxNLocator(nbins=5, integer=True)
test_value = np.array([-1, 0, 1, 2])
assert_almost_equal(loc.tick_values(-0.1, 1.1), test_value)

test_value = np.array([-0.25, 0, 0.25, 0.5, 0.75, 1])
assert_almost_equal(loc.tick_values(-0.1, 0.95), test_value)


def test_LinearLocator():
loc = mticker.LinearLocator(numticks=3)
test_value = np.array([-0.8, -0.3, 0.2])
Expand Down
87 changes: 45 additions & 42 deletions lib/matplotlib/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1603,7 +1603,9 @@ def __init__(self, *args, **kwargs):
e.g., [1, 2, 4, 5, 10]

*integer*
If True, ticks will take only integer values.
If True, ticks will take only integer values, provided
at least `min_n_ticks` integers are found within the
view limits.

*symmetric*
If True, autoscaling will result in a range symmetric
Expand All @@ -1613,16 +1615,16 @@ def __init__(self, *args, **kwargs):
['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.
tick of the axes above it, primarily when
`rcParams['axes.autolimit_mode']` is `'round_numbers'`.
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.

*min_n_ticks*
While the estimated number of ticks is less than the minimum,
the target value *nbins* is incremented and the ticks are
recalculated.
Relax `nbins` and `integer` constraints if necessary to
obtain this minimum number of ticks.

"""
if args:
Expand All @@ -1643,8 +1645,6 @@ def set_params(self, **kwargs):
warnings.warn(
"The 'trim' keyword has no effect since version 2.0.",
mplDeprecation)
if 'integer' in kwargs:
self._integer = kwargs['integer']
if 'symmetric' in kwargs:
self._symmetric = kwargs['symmetric']
if 'prune' in kwargs:
Expand All @@ -1662,6 +1662,13 @@ def set_params(self, **kwargs):
steps = list(steps)
steps.append(10)
self._steps = steps
# Make an extended staircase within which the needed
# step will be found. This is probably much larger
# than necessary.
flights = (0.1 * np.array(self._steps[:-1]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 to the naming

self._steps,
[10 * self._steps[1]])
self._extended_steps = np.hstack(flights)
if 'integer' in kwargs:
self._integer = kwargs['integer']
if self._integer:
Expand All @@ -1676,42 +1683,38 @@ def _raw_ticks(self, vmin, vmax):
else:
nbins = self._nbins

while True:
ticks = self._try_raw_ticks(vmin, vmax, nbins)
scale, offset = scale_range(vmin, vmax, nbins)
_vmin = vmin - offset
_vmax = vmax - offset
raw_step = (vmax - vmin) / nbins
steps = self._extended_steps * scale
istep = np.nonzero(steps >= raw_step)[0][0]

# Classic round_numbers mode may require a larger step.
if rcParams['axes.autolimit_mode'] == 'round_numbers':
for istep in range(istep, len(steps)):
step = steps[istep]
best_vmin = (_vmin // step) * step
best_vmax = best_vmin + step * nbins
if (best_vmax >= _vmax):
break

# This is an upper limit; move to smaller steps if necessary.
for i in range(istep):
step = steps[istep - i]
if (self._integer and
np.floor(_vmax) - np.ceil(_vmin) >= self._min_n_ticks - 1):
step = max(1, step)
best_vmin = (_vmin // step) * step

low = round(Base(step).le(_vmin - best_vmin) / step)
high = round(Base(step).ge(_vmax - best_vmin) / step)
ticks = np.arange(low, high + 1) * step + best_vmin + offset
nticks = ((ticks <= vmax) & (ticks >= vmin)).sum()
if nticks >= self._min_n_ticks:
break
nbins += 1

self._nbins_used = nbins # Maybe useful for troubleshooting.
return ticks

def _try_raw_ticks(self, vmin, vmax, nbins):
scale, offset = scale_range(vmin, vmax, nbins)
if self._integer:
scale = max(1, scale)
vmin = vmin - offset
vmax = vmax - offset
raw_step = (vmax - vmin) / nbins
scaled_raw_step = raw_step / scale
best_vmax = vmax
best_vmin = vmin

steps = (x for x in self._steps if x >= scaled_raw_step)
for step in steps:
step *= scale
best_vmin = vmin // step * step
best_vmax = best_vmin + step * nbins
if best_vmax >= vmax:
break

# More than nbins may be required, e.g. vmin, vmax = -4.1, 4.1 gives
# nbins=9 but 10 bins are actually required after rounding. So we just
# create the bins that span the range we need instead.
low = round(Base(step).le(vmin - best_vmin) / step)
high = round(Base(step).ge(vmax - best_vmin) / step)
return np.arange(low, high + 1) * step + best_vmin + offset

@cbook.deprecated("2.0")
def bin_boundaries(self, vmin, vmax):
return self._raw_ticks(vmin, vmax)
Expand Down
Binary file modified lib/mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.pdf
Binary file not shown.
Binary file modified lib/mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading