From bab3658cfdfdcfeb60f79d890be221c91b5659bd Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 16 Nov 2015 18:48:02 -0500 Subject: [PATCH 1/4] Fix #5488: Adjust nticks based on length of axis --- lib/matplotlib/axis.py | 28 ++++++++++++++++++++++++ lib/matplotlib/ticker.py | 46 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 3c578d431adc..084b4fbb2030 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1665,6 +1665,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 +1995,17 @@ def set_default_intervals(self): self.axes.viewLim.intervalx = xmin, xmax self.stale = True + def get_tick_space(self): + # TODO: cache this computation + 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())) + return np.floor(length / size) + class YAxis(Axis): __name__ = 'yaxis' @@ -2318,3 +2336,13 @@ def set_default_intervals(self): if not viewMutated: self.axes.viewLim.intervaly = ymin, ymax self.stale = True + + def get_tick_space(self): + # TODO: cache this computation + 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())) + return np.floor(length / size) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 450869878c14..e647c8018ce1 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -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 = self.axis.get_tick_space() + return super(AutoSpacedLocator, self).__call__() + + def decade_down(x, base=10): 'floor x to the nearest lower decade' if x == 0.0: @@ -1872,9 +1914,9 @@ def tick_values(self, vmin, vmax): return self.raise_if_exceeds(np.array(ticklocs)) -class AutoLocator(MaxNLocator): +class AutoLocator(AutoSpacedLocator): def __init__(self): - MaxNLocator.__init__(self, nbins=9, steps=[1, 2, 5, 10]) + AutoSpacedLocator.__init__(self, steps=[1, 2, 5, 10]) class AutoMinorLocator(Locator): From 7bf93704049ba9fe1d3d5438fafafef66a2e92b7 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 16 Nov 2015 20:45:56 -0500 Subject: [PATCH 2/4] Cache tick space calculation --- lib/matplotlib/axis.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 084b4fbb2030..760b48299637 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -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): @@ -1996,15 +1998,16 @@ def set_default_intervals(self): self.stale = True def get_tick_space(self): - # TODO: cache this computation - 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())) - return np.floor(length / size) + 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): @@ -2338,11 +2341,12 @@ def set_default_intervals(self): self.stale = True def get_tick_space(self): - # TODO: cache this computation - 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())) - return np.floor(length / size) + 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 From bdc95184c6e6144be63f356a864fad0fadd4a2b7 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 17 Nov 2015 18:33:28 -0500 Subject: [PATCH 3/4] Always use at least 3 bins --- 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 e647c8018ce1..3032faea021e 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1487,7 +1487,7 @@ def __init__(self, *args, **kwargs): self.set_params(**kwargs) def __call__(self): - self._nbins = self.axis.get_tick_space() + self._nbins = max(self.axis.get_tick_space(), 3) return super(AutoSpacedLocator, self).__call__() From e529f0727640440e1db89e5b0df07c53fed77d03 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 18 Nov 2015 09:05:36 -0500 Subject: [PATCH 4/4] Make behavior backward compatible --- examples/units/basic_units.py | 6 +++++- lib/matplotlib/axis.py | 5 ++++- lib/matplotlib/ticker.py | 6 ++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/units/basic_units.py b/examples/units/basic_units.py index f8425f1ec45c..fa83f7643b1a 100644 --- a/examples/units/basic_units.py +++ b/examples/units/basic_units.py @@ -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, ) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 760b48299637..333140f8c293 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -711,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()) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 3032faea021e..d9aeb0b15ae9 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1919,6 +1919,12 @@ 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]) + + class AutoMinorLocator(Locator): """ Dynamically find minor tick positions based on the positions of pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy