diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 14e8bd5e25ff..cf12447df73f 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2409,17 +2409,9 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval, dl.extend(y_finite) bb = mtransforms.BboxBase.union(dl) - # fall back on the viewlimits if this is not finite: - vl = None - if not np.isfinite(bb.intervalx).all(): - vl = mtransforms.BboxBase.union([ax.viewLim for ax in shared]) - bb.intervalx = vl.intervalx - if not np.isfinite(bb.intervaly).all(): - if vl is None: - vl = mtransforms.BboxBase.union( - [ax.viewLim for ax in shared]) - bb.intervaly = vl.intervaly x0, x1 = getattr(bb, interval) + # If x0 and x1 are non finite, use the locator to figure out + # default limits. locator = axis.get_major_locator() x0, x1 = locator.nonsingular(x0, x1) @@ -3260,7 +3252,6 @@ def set_xscale(self, value, **kwargs): - `matplotlib.scale.SymmetricalLogScale` - `matplotlib.scale.LogitScale` - Notes ----- By default, Matplotlib supports the above mentioned scales. @@ -3268,12 +3259,19 @@ def set_xscale(self, value, **kwargs): `matplotlib.scale.register_scale`. These scales can then also be used here. """ + old_default_lims = (self.xaxis.get_major_locator() + .nonsingular(-np.inf, np.inf)) g = self.get_shared_x_axes() for ax in g.get_siblings(self): ax.xaxis._set_scale(value, **kwargs) ax._update_transScale() ax.stale = True - self._request_autoscale_view(scaley=False) + new_default_lims = (self.xaxis.get_major_locator() + .nonsingular(-np.inf, np.inf)) + if old_default_lims != new_default_lims: + # Force autoscaling now, to take advantage of the scale locator's + # nonsingular() before it possibly gets swapped out by the user. + self.autoscale_view(scaley=False) @cbook._make_keyword_only("3.2", "minor") def get_xticks(self, minor=False): @@ -3645,7 +3643,6 @@ def set_yscale(self, value, **kwargs): - `matplotlib.scale.SymmetricalLogScale` - `matplotlib.scale.LogitScale` - Notes ----- By default, Matplotlib supports the above mentioned scales. @@ -3653,12 +3650,19 @@ def set_yscale(self, value, **kwargs): `matplotlib.scale.register_scale`. These scales can then also be used here. """ + old_default_lims = (self.yaxis.get_major_locator() + .nonsingular(-np.inf, np.inf)) g = self.get_shared_y_axes() for ax in g.get_siblings(self): ax.yaxis._set_scale(value, **kwargs) ax._update_transScale() ax.stale = True - self._request_autoscale_view(scalex=False) + new_default_lims = (self.yaxis.get_major_locator() + .nonsingular(-np.inf, np.inf)) + if old_default_lims != new_default_lims: + # Force autoscaling now, to take advantage of the scale locator's + # nonsingular() before it possibly gets swapped out by the user. + self.autoscale_view(scalex=False) @cbook._make_keyword_only("3.2", "minor") def get_yticks(self, minor=False): diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index cfa7149680fc..a56caed41de8 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -1118,6 +1118,10 @@ def nonsingular(self, vmin, vmax): Given the proposed upper and lower extent, adjust the range if it is too close to being singular (i.e. a range of ~0). """ + if not np.isfinite(vmin) or not np.isfinite(vmax): + # Except if there is no data, then use 2000-2010 as default. + return (date2num(datetime.date(2000, 1, 1)), + date2num(datetime.date(2010, 1, 1))) if vmax < vmin: vmin, vmax = vmax, vmin unit = self._get_unit() @@ -1337,6 +1341,10 @@ def tick_values(self, vmin, vmax): def nonsingular(self, vmin, vmax): # whatever is thrown at us, we can scale the unit. # But default nonsingular date plots at an ~4 year period. + if not np.isfinite(vmin) or not np.isfinite(vmax): + # Except if there is no data, then use 2000-2010 as default. + return (date2num(datetime.date(2000, 1, 1)), + date2num(datetime.date(2010, 1, 1))) if vmax < vmin: vmin, vmax = vmax, vmin if vmin == vmax: diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 9190dafd6cc4..d5df04cff226 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2170,6 +2170,13 @@ def test_log_scales(): ax.set_xscale('log', basex=9.0) +def test_log_scales_no_data(): + _, ax = plt.subplots() + ax.set(xscale="log", yscale="log") + ax.xaxis.set_major_locator(mticker.MultipleLocator(1)) + assert ax.get_xlim() == ax.get_ylim() == (1, 10) + + def test_log_scales_invalid(): fig = plt.figure() ax = fig.add_subplot(1, 1, 1) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 32dc52eeebd3..f29184ba55f8 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1710,7 +1710,19 @@ def raise_if_exceeds(self, locs): return locs def nonsingular(self, v0, v1): - """Expand a range as needed to avoid singularities.""" + """ + Adjust a range as needed to avoid singularities. + + This method gets called during autoscaling, with ``(v0, v1)`` set to + the data limits on the axes if the axes contains any data, or + ``(-inf, +inf)`` if not. + + - If ``v0 == v1`` (possibly up to some floating point slop), this + method returns an expanded interval around this value. + - If ``(v0, v1) == (-inf, +inf)``, this method returns appropriate + default view limits. + - Otherwise, ``(v0, v1)`` is returned without modification. + """ return mtransforms.nonsingular(v0, v1, expander=.05) def view_limits(self, vmin, vmax):