From e223f3095e0aba4b478986fd629ac912240a016b Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 1 Nov 2021 13:52:55 +0100 Subject: [PATCH] Make _request_autoscale_view more generalizable to 3D. The API of _request_autoscale_view() was originally modelled to match the one of autoscale_view() -- hence the scalex/scaley/scalez kwargs -- but just taking an axis name (or "all") as single arg makes generalization to 3D easier. (In bxp(), the autoscale request does not need to be protected by checking whether the autoscale state is on, as autoscale_view is a noop for non-autoscaling axises.) --- lib/matplotlib/axes/_axes.py | 18 +++++--- lib/matplotlib/axes/_base.py | 68 +++++++++++++++--------------- lib/mpl_toolkits/mplot3d/axes3d.py | 62 +++------------------------ 3 files changed, 53 insertions(+), 95 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 91badadf088e..0511b89602fd 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -737,7 +737,8 @@ def axhline(self, y=0, xmin=0, xmax=1, **kwargs): trans = self.get_yaxis_transform(which='grid') l = mlines.Line2D([xmin, xmax], [y, y], transform=trans, **kwargs) self.add_line(l) - self._request_autoscale_view(scalex=False, scaley=scaley) + if scaley: + self._request_autoscale_view("y") return l @docstring.dedent_interpd @@ -804,7 +805,8 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs): trans = self.get_xaxis_transform(which='grid') l = mlines.Line2D([x, x], [ymin, ymax], transform=trans, **kwargs) self.add_line(l) - self._request_autoscale_view(scalex=scalex, scaley=False) + if scalex: + self._request_autoscale_view("x") return l @staticmethod @@ -934,7 +936,7 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): p = mpatches.Polygon(verts, **kwargs) p.set_transform(self.get_yaxis_transform(which="grid")) self.add_patch(p) - self._request_autoscale_view(scalex=False) + self._request_autoscale_view("y") return p @docstring.dedent_interpd @@ -991,7 +993,7 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs): p.set_transform(self.get_xaxis_transform(which="grid")) p.get_path()._interpolation_steps = 100 self.add_patch(p) - self._request_autoscale_view(scaley=False) + self._request_autoscale_view("x") return p @_preprocess_data(replace_names=["y", "xmin", "xmax", "colors"], @@ -1627,7 +1629,10 @@ def plot(self, *args, scalex=True, scaley=True, data=None, **kwargs): lines = [*self._get_lines(*args, data=data, **kwargs)] for line in lines: self.add_line(line) - self._request_autoscale_view(scalex=scalex, scaley=scaley) + if scalex: + self._request_autoscale_view("x") + if scaley: + self._request_autoscale_view("y") return lines @_preprocess_data(replace_names=["x", "y"], label_namer="y") @@ -4045,8 +4050,7 @@ def do_patch(xs, ys, **kwargs): axis.set_major_formatter(formatter) formatter.seq = [*formatter.seq, *datalabels] - self._request_autoscale_view( - scalex=self._autoscaleXon, scaley=self._autoscaleYon) + self._request_autoscale_view() return dict(whiskers=whiskers, caps=caps, boxes=boxes, medians=medians, fliers=fliers, means=means) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index ac7dd97e3b5b..3e0d3a04e338 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -781,24 +781,26 @@ def viewLim(self): self._unstale_viewLim() return self._viewLim - # API could be better, right now this is just to match the old calls to - # autoscale_view() after each plotting method. - def _request_autoscale_view(self, tight=None, **kwargs): - # kwargs are "scalex", "scaley" (& "scalez" for 3D) and default to True - want_scale = {name: True for name in self._axis_names} - for k, v in kwargs.items(): # Validate args before changing anything. - if k.startswith("scale"): - name = k[5:] - if name in want_scale: - want_scale[name] = v - continue - raise TypeError( - f"_request_autoscale_view() got an unexpected argument {k!r}") + def _request_autoscale_view(self, axis="all", tight=None): + """ + Mark a single axis, or all of them, as stale wrt. autoscaling. + + No computation is performed until the next autoscaling; thus, separate + calls to control individual axises incur negligible performance cost. + + Parameters + ---------- + axis : str, default: "all" + Either an element of ``self._axis_names``, or "all". + tight : bool or None, default: None + """ + axis_names = _api.check_getitem( + {**{k: [k] for k in self._axis_names}, "all": self._axis_names}, + axis=axis) + for name in axis_names: + self._stale_viewlims[name] = True if tight is not None: self._tight = tight - for k, v in want_scale.items(): - if v: - self._stale_viewlims[k] = True # Else keep old state. def _set_lim_and_transforms(self): """ @@ -2425,8 +2427,7 @@ def _unit_change_handler(self, axis_name, event=None): for line in self.lines: line.recache_always() self.relim() - self._request_autoscale_view(scalex=(axis_name == "x"), - scaley=(axis_name == "y")) + self._request_autoscale_view(axis_name) def relim(self, visible_only=False): """ @@ -2634,7 +2635,7 @@ def set_xmargin(self, m): if m <= -0.5: raise ValueError("margin must be greater than -0.5") self._xmargin = m - self._request_autoscale_view(scalex=True, scaley=False) + self._request_autoscale_view("x") self.stale = True def set_ymargin(self, m): @@ -2657,7 +2658,7 @@ def set_ymargin(self, m): if m <= -0.5: raise ValueError("margin must be greater than -0.5") self._ymargin = m - self._request_autoscale_view(scalex=False, scaley=True) + self._request_autoscale_view("y") self.stale = True def margins(self, *margins, x=None, y=None, tight=True): @@ -2774,7 +2775,8 @@ def autoscale(self, enable=True, axis='both', tight=None): True turns autoscaling on, False turns it off. None leaves the autoscaling state unchanged. axis : {'both', 'x', 'y'}, default: 'both' - Which axis to operate on. + The axis on which to operate. (For 3D axes, *axis* can also be set + to 'z', and 'both' refers to all three axes.) tight : bool or None, default: None If True, first set the margins to zero. Then, this argument is forwarded to `autoscale_view` (regardless of its value); see the @@ -2796,7 +2798,10 @@ def autoscale(self, enable=True, axis='both', tight=None): self._xmargin = 0 if tight and scaley: self._ymargin = 0 - self._request_autoscale_view(tight=tight, scalex=scalex, scaley=scaley) + if scalex: + self._request_autoscale_view("x", tight=tight) + if scaley: + self._request_autoscale_view("y", tight=tight) def autoscale_view(self, tight=None, scalex=True, scaley=True): """ @@ -3312,8 +3317,8 @@ def locator_params(self, axis='both', tight=None, **kwargs): Parameters ---------- axis : {'both', 'x', 'y'}, default: 'both' - The axis on which to operate. - + The axis on which to operate. (For 3D axes, *axis* can also be + set to 'z', and 'both' refers to all three axes.) tight : bool or None, optional Parameter passed to `~.Axes.autoscale_view`. Default is None, for no change. @@ -3335,15 +3340,12 @@ def locator_params(self, axis='both', tight=None, **kwargs): ax.locator_params(tight=True, nbins=4) """ - _api.check_in_list(['x', 'y', 'both'], axis=axis) - update_x = axis in ['x', 'both'] - update_y = axis in ['y', 'both'] - if update_x: - self.xaxis.get_major_locator().set_params(**kwargs) - if update_y: - self.yaxis.get_major_locator().set_params(**kwargs) - self._request_autoscale_view(tight=tight, - scalex=update_x, scaley=update_y) + _api.check_in_list([*self._axis_names, "both"], axis=axis) + for name in self._axis_names: + if axis in [name, "both"]: + loc = self._get_axis_map()[name].get_major_locator() + loc.set_params(**kwargs) + self._request_autoscale_view(name, tight=tight) self.stale = True def tick_params(self, axis='both', **kwargs): diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index a29b075e030d..a9846d4d584e 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -476,17 +476,6 @@ def get_axis_position(self): zhigh = tc[0][2] > tc[2][2] return xhigh, yhigh, zhigh - def _unit_change_handler(self, axis_name, event=None): - # docstring inherited - if event is None: # Allow connecting `self._unit_change_handler(name)` - return functools.partial( - self._unit_change_handler, axis_name, event=object()) - _api.check_in_list(self._get_axis_map(), axis_name=axis_name) - self.relim() - self._request_autoscale_view(scalex=(axis_name == "x"), - scaley=(axis_name == "y"), - scalez=(axis_name == "z")) - def update_datalim(self, xys, **kwargs): pass @@ -514,24 +503,6 @@ def set_autoscalez_on(self, b): """ self._autoscaleZon = b - def set_xmargin(self, m): - # docstring inherited - scalez = self._stale_viewlims["z"] - super().set_xmargin(m) - # Superclass is 2D and will call _request_autoscale_view with defaults - # for unknown Axis, which would be scalez=True, but it shouldn't be for - # this call, so restore it. - self._stale_viewlims["z"] = scalez - - def set_ymargin(self, m): - # docstring inherited - scalez = self._stale_viewlims["z"] - super().set_ymargin(m) - # Superclass is 2D and will call _request_autoscale_view with defaults - # for unknown Axis, which would be scalez=True, but it shouldn't be for - # this call, so restore it. - self._stale_viewlims["z"] = scalez - def set_zmargin(self, m): """ Set padding of Z data limits prior to autoscaling. @@ -544,7 +515,7 @@ def set_zmargin(self, m): if m < 0 or m > 1: raise ValueError("margin must be in range 0 to 1") self._zmargin = m - self._request_autoscale_view(scalex=False, scaley=False, scalez=True) + self._request_autoscale_view("z") self.stale = True def margins(self, *margins, x=None, y=None, z=None, tight=True): @@ -638,8 +609,12 @@ def autoscale(self, enable=True, axis='both', tight=None): self._autoscaleZon = scalez = bool(enable) else: scalez = False - self._request_autoscale_view(tight=tight, scalex=scalex, scaley=scaley, - scalez=scalez) + if scalex: + self._request_autoscale_view("x", tight=tight) + if scaley: + self._request_autoscale_view("y", tight=tight) + if scalez: + self._request_autoscale_view("z", tight=tight) def auto_scale_xyz(self, X, Y, Z=None, had_data=None): # This updates the bounding boxes as to keep a record as to what the @@ -1343,29 +1318,6 @@ def grid(self, visible=True, **kwargs): self._draw_grid = visible self.stale = True - def locator_params(self, axis='both', tight=None, **kwargs): - """ - Convenience method for controlling tick locators. - - See :meth:`matplotlib.axes.Axes.locator_params` for full - documentation. Note that this is for Axes3D objects, - therefore, setting *axis* to 'both' will result in the - parameters being set for all three axes. Also, *axis* - can also take a value of 'z' to apply parameters to the - z axis. - """ - _x = axis in ['x', 'both'] - _y = axis in ['y', 'both'] - _z = axis in ['z', 'both'] - if _x: - self.xaxis.get_major_locator().set_params(**kwargs) - if _y: - self.yaxis.get_major_locator().set_params(**kwargs) - if _z: - self.zaxis.get_major_locator().set_params(**kwargs) - self._request_autoscale_view(tight=tight, scalex=_x, scaley=_y, - scalez=_z) - def tick_params(self, axis='both', **kwargs): """ Convenience method for changing the appearance of ticks and