From 529d37ccacdf985a2ae9adb08c56a78a218d354d Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Tue, 10 Feb 2026 14:07:46 +0530 Subject: [PATCH 01/10] Fix relim() to update limits for scatter PathCollection and add regression test --- lib/matplotlib/axes/_base.py | 5 +++++ lib/matplotlib/tests/test_axes.py | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index f89c231815dc..64faf463a014 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -29,6 +29,7 @@ import matplotlib.text as mtext import matplotlib.ticker as mticker import matplotlib.transforms as mtransforms +import matplotlib.collections as mcollections _log = logging.getLogger(__name__) @@ -2605,6 +2606,10 @@ def relim(self, visible_only=False): self._update_patch_limits(artist) elif isinstance(artist, mimage.AxesImage): self._update_image_limits(artist) + elif isinstance(artist, mcollections.Collection): + offsets = artist.get_offsets() + if offsets is not None and len(offsets): + self.update_datalim(offsets) 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 46843841fe93..59050e59d3be 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -10159,3 +10159,26 @@ def test_animated_artists_not_drawn_by_default(): mocked_im_draw.assert_not_called() mocked_ln_draw.assert_not_called() + +def test_relim_updates_scatter_offsets(): + import numpy as np + import matplotlib.pyplot as plt + + 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 From defd13eaa16f88b59192dd20aa762270022c3c0d Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Tue, 10 Feb 2026 16:45:48 +0530 Subject: [PATCH 02/10] Refactor: use _update_collection_limits for scatter handling in relim --- lib/matplotlib/axes/_base.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 64faf463a014..9e515a7a8aa0 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2416,6 +2416,12 @@ def _update_image_limits(self, image): xmin, xmax, ymin, ymax = image.get_extent() self.axes.update_datalim(((xmin, ymin), (xmax, ymax))) + def _update_collection_limits(self, collection): + offsets = collection.get_offsets() + if offsets is not None and len(offsets): + self.update_datalim(offsets) + + def add_line(self, line): """ Add a `.Line2D` to the Axes; return the line. @@ -2607,9 +2613,7 @@ def relim(self, visible_only=False): elif isinstance(artist, mimage.AxesImage): self._update_image_limits(artist) elif isinstance(artist, mcollections.Collection): - offsets = artist.get_offsets() - if offsets is not None and len(offsets): - self.update_datalim(offsets) + self._update_collection_limits(artist) def update_datalim(self, xys, updatex=True, updatey=True): """ From fbb623e7f8cf25595a4c8bc1e852f4a7d22f39ab Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Wed, 11 Feb 2026 00:37:34 +0530 Subject: [PATCH 03/10] Fix ruff E302: add missing blank line before test --- lib/matplotlib/tests/test_axes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 59050e59d3be..6e6614090311 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -10160,6 +10160,7 @@ def test_animated_artists_not_drawn_by_default(): mocked_im_draw.assert_not_called() mocked_ln_draw.assert_not_called() + def test_relim_updates_scatter_offsets(): import numpy as np import matplotlib.pyplot as plt From 5bf5273fb2648ee6644e83e59642fb9cd7235525 Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Mon, 16 Feb 2026 19:00:25 +0530 Subject: [PATCH 04/10] Add private Artist-level autoscale participation flag --- lib/matplotlib/artist.py | 23 +++++++++++++++++++++++ lib/matplotlib/axes/_base.py | 6 +++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 5b6fadfd364f..7c7b80935bb4 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -225,6 +225,7 @@ def __init__(self): self._path_effects = mpl.rcParams['path.effects'] self._sticky_edges = _XYPair([], []) self._in_layout = True + self._in_autoscale = True def __getstate__(self): d = self.__dict__.copy() @@ -902,7 +903,17 @@ def get_in_layout(self): ``fig.savefig(fname, bbox_inches='tight')``. """ return self._in_layout + + def get_in_autoscale(self): + """ + Return boolean flag, ``True`` if artist is included in autoscaling + calculations. + E.g. :ref:`autoscaling_guide` and + `.Axes.relim()`. + """ + return self._in_autoscale + def _fully_clipped_to_axes(self): """ Return a boolean flag, ``True`` if the artist is clipped to the Axes @@ -1132,6 +1143,18 @@ def set_in_layout(self, in_layout): """ self._in_layout = in_layout + def set_in_autoscale(self, in_autoscale): + """ + Set if artist is to be included in autoscaling calculations, + E.g. :ref:`autoscaling_guide` and + `.Axes.relim()`. + + Parameters + ---------- + in_autoscale : bool + """ + self._in_autoscale = in_autoscale + def get_label(self): """Return the label used for this artist in the legend.""" return self._label diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 9e515a7a8aa0..558df2d01bbd 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2606,15 +2606,15 @@ 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 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, mcollections.Collection): - self._update_collection_limits(artist) - + def update_datalim(self, xys, updatex=True, updatey=True): """ Extend the `~.Axes.dataLim` Bbox to include the given points. From 7247fa57011efe9655c67bb105a5450bab1d340a Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Thu, 19 Feb 2026 00:27:47 +0530 Subject: [PATCH 05/10] Make autoscale participation fully artist-driven and Remove scatter relim regression test (to be added in follow-up PR) --- lib/matplotlib/artist.py | 19 ++++++++----------- lib/matplotlib/axes/_base.py | 7 ++++++- lib/matplotlib/tests/test_axes.py | 26 +------------------------- 3 files changed, 15 insertions(+), 37 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 7c7b80935bb4..e922dd045aed 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -225,7 +225,7 @@ def __init__(self): self._path_effects = mpl.rcParams['path.effects'] self._sticky_edges = _XYPair([], []) self._in_layout = True - self._in_autoscale = True + self._in_autoscale = False def __getstate__(self): d = self.__dict__.copy() @@ -904,16 +904,14 @@ def get_in_layout(self): """ return self._in_layout - def get_in_autoscale(self): + def _get_in_autoscale(self): """ Return boolean flag, ``True`` if artist is included in autoscaling calculations. - E.g. :ref:`autoscaling_guide` and - `.Axes.relim()`. + E.g. `.axes.Axes.autoscale_view()`. """ - return self._in_autoscale - + return self._in_autoscale def _fully_clipped_to_axes(self): """ Return a boolean flag, ``True`` if the artist is clipped to the Axes @@ -1143,17 +1141,16 @@ def set_in_layout(self, in_layout): """ self._in_layout = in_layout - def set_in_autoscale(self, in_autoscale): + def _set_in_autoscale(self, b): """ Set if artist is to be included in autoscaling calculations, - E.g. :ref:`autoscaling_guide` and - `.Axes.relim()`. + E.g. `.axes.Axes.autoscale_view()`. Parameters ---------- - in_autoscale : bool + b : bool """ - self._in_autoscale = in_autoscale + self._in_autoscale = b def get_label(self): """Return the label used for this artist in the legend.""" diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 558df2d01bbd..00bd8debd3c4 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2317,6 +2317,7 @@ def add_artist(self, a): if a.get_clip_path() is None: a.set_clip_path(self.patch) self.stale = True + a._set_in_autoscale(False) return a def add_child_axes(self, ax): @@ -2397,6 +2398,7 @@ def add_collection(self, collection, autolim=True): self._request_autoscale_view() self.stale = True + collection._set_in_autoscale(autolim) return collection def add_image(self, image): @@ -2410,6 +2412,7 @@ def add_image(self, image): self._children.append(image) image._remove_method = self._children.remove self.stale = True + image._set_in_autoscale(True) return image def _update_image_limits(self, image): @@ -2437,6 +2440,7 @@ def add_line(self, line): self._children.append(line) line._remove_method = self._children.remove self.stale = True + line._set_in_autoscale(True) return line def _add_text(self, txt): @@ -2509,6 +2513,7 @@ def add_patch(self, p): self._update_patch_limits(p) self._children.append(p) p._remove_method = self._children.remove + p._set_in_autoscale(True) return p def _update_patch_limits(self, patch): @@ -2606,7 +2611,7 @@ 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(): + if not artist._get_in_autoscale(): continue if isinstance(artist, mlines.Line2D): self._update_line_limits(artist) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 6e6614090311..9db6eabb523d 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -10158,28 +10158,4 @@ def test_animated_artists_not_drawn_by_default(): fig.draw_without_rendering() mocked_im_draw.assert_not_called() - mocked_ln_draw.assert_not_called() - - -def test_relim_updates_scatter_offsets(): - import numpy as np - import matplotlib.pyplot as plt - - 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 + mocked_ln_draw.assert_not_called() \ No newline at end of file From b527465d6db60815c8920b86ad3bae4b9b978623 Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Thu, 19 Feb 2026 01:43:20 +0530 Subject: [PATCH 06/10] fixing CI fails --- lib/matplotlib/artist.py | 4 ++-- lib/matplotlib/axes/_base.py | 3 +-- lib/matplotlib/tests/test_axes.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index e922dd045aed..834b79d97b44 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -903,7 +903,7 @@ def get_in_layout(self): ``fig.savefig(fname, bbox_inches='tight')``. """ return self._in_layout - + def _get_in_autoscale(self): """ Return boolean flag, ``True`` if artist is included in autoscaling @@ -911,7 +911,7 @@ def _get_in_autoscale(self): E.g. `.axes.Axes.autoscale_view()`. """ - return self._in_autoscale + return self._in_autoscale def _fully_clipped_to_axes(self): """ Return a boolean flag, ``True`` if the artist is clipped to the Axes diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 00bd8debd3c4..16b41ea523fd 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -29,7 +29,6 @@ import matplotlib.text as mtext import matplotlib.ticker as mticker import matplotlib.transforms as mtransforms -import matplotlib.collections as mcollections _log = logging.getLogger(__name__) @@ -2619,7 +2618,7 @@ def relim(self, visible_only=False): self._update_patch_limits(artist) elif isinstance(artist, mimage.AxesImage): self._update_image_limits(artist) - + def update_datalim(self, xys, updatex=True, updatey=True): """ Extend the `~.Axes.dataLim` Bbox to include the given points. diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 9db6eabb523d..46843841fe93 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -10158,4 +10158,4 @@ def test_animated_artists_not_drawn_by_default(): fig.draw_without_rendering() mocked_im_draw.assert_not_called() - mocked_ln_draw.assert_not_called() \ No newline at end of file + mocked_ln_draw.assert_not_called() From 0ad2c0af104eeb1d28b77182ab8ecfef98350546 Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Thu, 26 Feb 2026 18:14:08 +0530 Subject: [PATCH 07/10] removed the set_in_autoscale from add_artist --- lib/matplotlib/axes/_base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 16b41ea523fd..152ac802e77f 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2316,7 +2316,6 @@ def add_artist(self, a): if a.get_clip_path() is None: a.set_clip_path(self.patch) self.stale = True - a._set_in_autoscale(False) return a def add_child_axes(self, ax): From 6c702b6f2684974b7d2acd33cdfc34ed1541db0f Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Thu, 26 Feb 2026 18:19:37 +0530 Subject: [PATCH 08/10] Update lib/matplotlib/artist.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/artist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 834b79d97b44..a91ea4f18cb6 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -912,6 +912,7 @@ def _get_in_autoscale(self): E.g. `.axes.Axes.autoscale_view()`. """ return self._in_autoscale + def _fully_clipped_to_axes(self): """ Return a boolean flag, ``True`` if the artist is clipped to the Axes From bcf2fc2f7a5b243e17b557e5ade16d3fc5bf54ba Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Thu, 26 Feb 2026 18:19:56 +0530 Subject: [PATCH 09/10] Update lib/matplotlib/artist.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/artist.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index a91ea4f18cb6..6e917851f91d 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -906,8 +906,7 @@ def get_in_layout(self): def _get_in_autoscale(self): """ - Return boolean flag, ``True`` if artist is included in autoscaling - calculations. + Return whether the artist is included in autoscaling calculations. E.g. `.axes.Axes.autoscale_view()`. """ From 31f59f466c3de88aac8a5e054fca7e5846f6554c Mon Sep 17 00:00:00 2001 From: Archil Jain Date: Thu, 26 Feb 2026 18:30:20 +0530 Subject: [PATCH 10/10] Removed update limit function and autolim --- lib/matplotlib/axes/_base.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 152ac802e77f..179774e2b7a2 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2396,7 +2396,6 @@ def add_collection(self, collection, autolim=True): self._request_autoscale_view() self.stale = True - collection._set_in_autoscale(autolim) return collection def add_image(self, image): @@ -2417,12 +2416,6 @@ def _update_image_limits(self, image): xmin, xmax, ymin, ymax = image.get_extent() self.axes.update_datalim(((xmin, ymin), (xmax, ymax))) - def _update_collection_limits(self, collection): - offsets = collection.get_offsets() - if offsets is not None and len(offsets): - self.update_datalim(offsets) - - def add_line(self, line): """ Add a `.Line2D` to the Axes; return the line.