From 4d2de405f7a1a44ab4e6cd2da947a51903616e28 Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Wed, 7 Dec 2016 10:06:51 -1000 Subject: [PATCH 1/3] MaxNLocator: add validation, fix integer bug --- lib/matplotlib/tests/test_ticker.py | 2 +- lib/matplotlib/ticker.py | 46 +++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index e60ba93276ac..475042f68673 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -32,7 +32,7 @@ def test_MaxNLocator_integer(): 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]) + test_value = np.array([-0.3, 0, 0.3, 0.6, 0.9, 1.2]) assert_almost_equal(loc.tick_values(-0.1, 0.95), test_value) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 754f2158159d..9034bd89b286 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1685,6 +1685,33 @@ def __init__(self, *args, **kwargs): self.set_params(**self.default_params) self.set_params(**kwargs) + @staticmethod + def _validate_steps(steps): + if not np.iterable(steps): + raise ValueError('steps argument must be a sequence of numbers ' + 'from 1 to 10') + steps = np.asarray(steps) + if np.any(np.diff(steps) <= 0): + raise ValueError('steps argument must be uniformly increasing') + if np.any((steps > 10) | (steps < 1)): + warnings.warn('Steps argument should be a sequence of numbers\n' + 'increasing from 1 to 10, inclusive. Behavior with\n' + 'values outside this range is undefined, and will\n' + 'raise a ValueError in future versions of mpl.') + if steps[0] != 1: + steps = np.hstack((1, steps)) + if steps[-1] != 10: + steps = np.hstack((steps, 10)) + return steps + + @staticmethod + def _staircase(steps): + # Make an extended staircase within which the needed + # step will be found. This is probably much larger + # than necessary. + flights = (0.1 * steps[:-1], steps, 10 * steps[1]) + return np.hstack(flights) + def set_params(self, **kwargs): """Set parameters within this locator.""" if 'nbins' in kwargs: @@ -1706,23 +1733,16 @@ def set_params(self, **kwargs): if 'steps' in kwargs: steps = kwargs['steps'] if steps is None: - self._steps = [1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] + self._steps = np.array([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10]) else: - if int(steps[-1]) != 10: - 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]), - self._steps, - [10 * self._steps[1]]) - self._extended_steps = np.hstack(flights) + self._steps = self._validate_steps(steps) + self._extended_steps = self._staircase(self._steps) if 'integer' in kwargs: self._integer = kwargs['integer'] if self._integer: - self._steps = [n for n in self._steps if _divmod(n, 1)[1] < 0.001] + self._steps = np.array([n for n in self._steps + if _divmod(n, 1)[1] < 0.001]) + self._extended_steps = self._staircase(self._steps) if 'min_n_ticks' in kwargs: self._min_n_ticks = max(1, kwargs['min_n_ticks']) From 4bb7b6f48909c83f2d3f5f0f07be0be0cd4667be Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Wed, 7 Dec 2016 14:39:25 -1000 Subject: [PATCH 2/3] DOC: dflt_style_changes, MaxNLocator algorithm changed --- doc/users/dflt_style_changes.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/users/dflt_style_changes.rst b/doc/users/dflt_style_changes.rst index 6e08fa4e712a..147bd382451b 100644 --- a/doc/users/dflt_style_changes.rst +++ b/doc/users/dflt_style_changes.rst @@ -968,6 +968,11 @@ or create a new `~matplotlib.ticker.MaxNLocator`:: import matplotlib.ticker as mticker ax.set_major_locator(mticker.MaxNLocator(nbins=9, steps=[1, 2, 5, 10]) +The algorithm used by `~matplotlib.ticker.MaxNLocator` has been +improved, and this may change the choice of tick locations in some +cases. This also affects `~matplotlib.ticker.AutoLocator`, which +uses ``MaxNLocator`` internally. + For a log-scaled axis the default locator is the `~matplotlib.ticker.LogLocator`. Previously the maximum number of ticks was set to 15, and could not be changed. Now there is a From 3d1c2937000f7dda03dacc14d0c0c28f1c96736b Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Wed, 7 Dec 2016 20:22:44 -1000 Subject: [PATCH 3/3] simplify test for range of steps --- lib/matplotlib/ticker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 9034bd89b286..85dbd35ddcf6 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1693,7 +1693,7 @@ def _validate_steps(steps): steps = np.asarray(steps) if np.any(np.diff(steps) <= 0): raise ValueError('steps argument must be uniformly increasing') - if np.any((steps > 10) | (steps < 1)): + if steps[-1] > 10 or steps[0] < 1: warnings.warn('Steps argument should be a sequence of numbers\n' 'increasing from 1 to 10, inclusive. Behavior with\n' 'values outside this range is undefined, and will\n'