diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 947457100730..31aadff5bd9f 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2394,35 +2394,34 @@ def add_collection(self, collection, autolim=True): collection.set_clip_path(self.patch) if autolim: - # Make sure viewLim is not stale (mostly to match - # pre-lazy-autoscale behavior, which is not really better). - self._unstale_viewLim() - datalim = collection.get_datalim(self.transData) - points = datalim.get_points() - if not np.isinf(datalim.minpos).all(): - # By definition, if minpos (minimum positive value) is set - # (i.e., non-inf), then min(points) <= minpos <= max(points), - # and minpos would be superfluous. However, we add minpos to - # the call so that self.dataLim will update its own minpos. - # This ensures that log scales see the correct minimum. - points = np.concatenate([points, [datalim.minpos]]) - # only update the dataLim for x/y if the collection uses transData - # in this direction. - x_is_data, y_is_data = (collection.get_transform() - .contains_branch_separately(self.transData)) - ox_is_data, oy_is_data = (collection.get_offset_transform() - .contains_branch_separately(self.transData)) - self.update_datalim( - points, - updatex=x_is_data or ox_is_data, - updatey=y_is_data or oy_is_data, - ) + self._update_collection_limits(collection) + if autolim != "_datalim_only": - self._request_autoscale_view() + self._request_autoscale_view() self.stale = True return collection + def _update_collection_limits(self, collection): + self._unstale_viewLim() + + datalim = collection.get_datalim(self.transData) + points = datalim.get_points() + + if not np.isinf(datalim.minpos).all(): + points = np.concatenate([points, [datalim.minpos]]) + + transform = collection.get_transform() + offset_trf = collection.get_offset_transform() + + x_is_data, y_is_data = transform.contains_branch_separately(self.transData) + ox_is_data, oy_is_data = offset_trf.contains_branch_separately(self.transData) + + updatex = x_is_data or ox_is_data + updatey = y_is_data or oy_is_data + + self.update_datalim(points, updatex=updatex, updatey=updatey) + def add_image(self, image): """ Add an `.AxesImage` to the Axes; return the image. @@ -2641,14 +2640,16 @@ def relim(self, visible_only=False): for artist in self._children: if not visible_only or artist.get_visible(): - if not artist._get_in_autoscale(): - continue + if hasattr(artist, "get_in_autoscale") and not artist.get_in_autoscale(): # noqa: E501 + continue if isinstance(artist, mlines.Line2D): self._update_line_limits(artist) elif isinstance(artist, mpatches.Patch): self._update_patch_limits(artist) elif isinstance(artist, mimage.AxesImage): self._update_image_limits(artist) + elif isinstance(artist, mcoll.Collection): + self._update_collection_limits(artist) def update_datalim(self, xys, updatex=True, updatey=True): """ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 45e9cc75fec0..624396ade49f 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -10189,3 +10189,24 @@ def test_errorbar_uses_rcparams(): assert_allclose([cap.get_markeredgewidth() for cap in caplines], 2.5) for barcol in barlinecols: assert_allclose(barcol.get_linewidths(), 1.75) + + +def test_relim_updates_scatter_offsets(): + fig, ax = plt.subplots() + + xs = np.linspace(0, 10, 100) + ys = np.sin(xs) + scatter = ax.scatter(xs, ys) + + # Shift scatter upward + new_ys = np.sin(xs) + 5 + scatter.set_offsets(np.column_stack((xs, new_ys))) + + ax.relim() + ax.autoscale_view() + + ymin, ymax = ax.get_ylim() + + # New limits should reflect shifted data + assert ymin > 3 + assert ymax > 5