From e7b2708598e0f0e8a8ae87a05c634b92c2e3df88 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:59:00 +0200 Subject: [PATCH] ENH: ax.add_collection(..., autolim=True) updates view limits This makes explicit calls to `autoscale_view()` or `_request_autoscale_view()` unnecessary. 3D Axes have a special `auto_scale_xyz()`, also there's a mixture of `add_collection()` and `add_collection3d()`. This needs separate sorting . I've added a private value `autolim="_datalim_only"` to keep the behavior for 3D Axes unchanged for now. That will be resolved by a follow-up PR. I believe it's getting too complicated if we fold this into the 2D change. --- .../next_api_changes/behavior/29958-TH.rst | 8 ++++++++ .../shapes_and_collections/collections.py | 7 +------ .../ellipse_collection.py | 1 - galleries/users_explain/axes/autoscale.py | 1 - lib/matplotlib/axes/_axes.py | 5 ----- lib/matplotlib/axes/_base.py | 19 ++++++++++++++++++ lib/matplotlib/axes/_base.pyi | 2 +- lib/matplotlib/colorbar.py | 4 ++-- lib/matplotlib/tests/test_backend_ps.py | 6 +++++- lib/matplotlib/tests/test_collections.py | 3 --- lib/matplotlib/tests/test_patches.py | 4 +++- lib/matplotlib/tri/_tripcolor.py | 4 +++- lib/mpl_toolkits/mplot3d/axes3d.py | 20 +++++++++---------- lib/mpl_toolkits/mplot3d/tests/test_art3d.py | 2 +- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 2 +- .../mplot3d/tests/test_legend3d.py | 6 +++--- 16 files changed, 57 insertions(+), 37 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/29958-TH.rst diff --git a/doc/api/next_api_changes/behavior/29958-TH.rst b/doc/api/next_api_changes/behavior/29958-TH.rst new file mode 100644 index 000000000000..cacaf2bac612 --- /dev/null +++ b/doc/api/next_api_changes/behavior/29958-TH.rst @@ -0,0 +1,8 @@ +``Axes.add_collection(..., autolim=True)`` updates view limits +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Axes.add_collection(..., autolim=True)`` has so far only updated the data limits. +Users needed to additionally call `.Axes.autoscale_view` to update the view limits. +View limits are now updated as well if ``autolim=True``, using a lazy internal +update mechanism, so that the costs only apply once also if you add multiple +collections. diff --git a/galleries/examples/shapes_and_collections/collections.py b/galleries/examples/shapes_and_collections/collections.py index 1f60afda1c5f..a6f4e450ab38 100644 --- a/galleries/examples/shapes_and_collections/collections.py +++ b/galleries/examples/shapes_and_collections/collections.py @@ -59,14 +59,12 @@ # but it is good enough to generate a plot that you can use # as a starting point. If you know beforehand the range of # x and y that you want to show, it is better to set them -# explicitly, leave out the *autolim* keyword argument (or set it to False), -# and omit the 'ax1.autoscale_view()' call below. +# explicitly, set the *autolim* keyword argument to False. # Make a transform for the line segments such that their size is # given in points: col.set_color(colors) -ax1.autoscale_view() # See comment above, after ax1.add_collection. ax1.set_title('LineCollection using offsets') @@ -79,7 +77,6 @@ col.set_color(colors) -ax2.autoscale_view() ax2.set_title('PolyCollection using offsets') # 7-sided regular polygons @@ -90,7 +87,6 @@ col.set_transform(trans) # the points to pixels transform ax3.add_collection(col, autolim=True) col.set_color(colors) -ax3.autoscale_view() ax3.set_title('RegularPolyCollection using offsets') @@ -114,7 +110,6 @@ col = collections.LineCollection(segs, offsets=offs) ax4.add_collection(col, autolim=True) col.set_color(colors) -ax4.autoscale_view() ax4.set_title('Successive data offsets') ax4.set_xlabel('Zonal velocity component (m/s)') ax4.set_ylabel('Depth (m)') diff --git a/galleries/examples/shapes_and_collections/ellipse_collection.py b/galleries/examples/shapes_and_collections/ellipse_collection.py index 7118e5f7abf2..52379ae06d50 100644 --- a/galleries/examples/shapes_and_collections/ellipse_collection.py +++ b/galleries/examples/shapes_and_collections/ellipse_collection.py @@ -30,7 +30,6 @@ offset_transform=ax.transData) ec.set_array((X + Y).ravel()) ax.add_collection(ec) -ax.autoscale_view() ax.set_xlabel('X') ax.set_ylabel('y') cbar = plt.colorbar(ec) diff --git a/galleries/users_explain/axes/autoscale.py b/galleries/users_explain/axes/autoscale.py index df1fbbc8aea8..ef471b29d50c 100644 --- a/galleries/users_explain/axes/autoscale.py +++ b/galleries/users_explain/axes/autoscale.py @@ -177,4 +177,3 @@ offset_transform=ax.transData, # Propagate transformations of the Axes ) ax.add_collection(collection) -ax.autoscale_view() diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 82ddc53904b3..69b55b32f3af 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3010,7 +3010,6 @@ def broken_barh(self, xranges, yrange, **kwargs): col = mcoll.PolyCollection(np.array(vertices), **kwargs) self.add_collection(col, autolim=True) - self._request_autoscale_view() return col @@ -4997,7 +4996,6 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, self.set_ymargin(0.05) self.add_collection(collection) - self._request_autoscale_view() return collection @@ -5468,7 +5466,6 @@ def quiver(self, *args, **kwargs): args = self._quiver_units(args, kwargs) q = mquiver.Quiver(self, *args, **kwargs) self.add_collection(q, autolim=True) - self._request_autoscale_view() return q # args can be some combination of X, Y, U, V, C and all should be replaced @@ -5480,7 +5477,6 @@ def barbs(self, *args, **kwargs): args = self._quiver_units(args, kwargs) b = mquiver.Barbs(self, *args, **kwargs) self.add_collection(b, autolim=True) - self._request_autoscale_view() return b # Uses a custom implementation of data-kwarg handling in @@ -5640,7 +5636,6 @@ def _fill_between_x_or_y( where=where, interpolate=interpolate, step=step, **kwargs) self.add_collection(collection) - self._request_autoscale_view() return collection def _fill_between_process_units(self, ind_dir, dep_dir, ind, dep1, dep2, **kwargs): diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 5a74845df147..7dd7a6c236ad 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2331,6 +2331,23 @@ def add_child_axes(self, ax): def add_collection(self, collection, autolim=True): """ Add a `.Collection` to the Axes; return the collection. + + Parameters + ---------- + collection : `.Collection` + The collection to add. + autolim : bool + Whether to update data and view limits. + + .. versionchanged:: 3.11 + + This now also updates the view limits, making explicit + calls to `~.Axes.autoscale_view` unnecessary. + + As an implementation detail, the value "_datalim_only" is + supported to smooth the internal transition from pre-3.11 + behavior. This is not a public interface and will be removed + again in the future. """ _api.check_isinstance(mcoll.Collection, collection=collection) if not collection.get_label(): @@ -2356,6 +2373,8 @@ def add_collection(self, collection, autolim=True): # This ensures that log scales see the correct minimum. points = np.concatenate([points, [datalim.minpos]]) self.update_datalim(points) + if autolim != "_datalim_only": + self._request_autoscale_view() self.stale = True return collection diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index 4933d0d1e236..91485dd3915a 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -234,7 +234,7 @@ class _AxesBase(martist.Artist): def add_artist(self, a: Artist) -> Artist: ... def add_child_axes(self, ax: _AxesBase) -> _AxesBase: ... def add_collection( - self, collection: Collection, autolim: bool = ... + self, collection: Collection, autolim: bool | Literal["_datalim_only"] = ... ) -> Collection: ... def add_image(self, image: AxesImage) -> AxesImage: ... def add_line(self, line: Line2D) -> Line2D: ... diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index db33698c5514..b5f8634ae8f6 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -371,7 +371,7 @@ def __init__( colors=[mpl.rcParams['axes.edgecolor']], linewidths=[0.5 * mpl.rcParams['axes.linewidth']], clip_on=False) - self.ax.add_collection(self.dividers) + self.ax.add_collection(self.dividers, autolim=False) self._locator = None self._minorlocator = None @@ -805,7 +805,7 @@ def add_lines(self, *args, **kwargs): xy = self.ax.transAxes.inverted().transform(inches.transform(xy)) col.set_clip_path(mpath.Path(xy, closed=True), self.ax.transAxes) - self.ax.add_collection(col) + self.ax.add_collection(col, autolim=False) self.stale = True def update_ticks(self): diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index f5ec85005079..9859a286e5fd 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -354,7 +354,11 @@ def test_path_collection(): sizes = [0.02, 0.04] pc = mcollections.PathCollection(paths, sizes, zorder=-1, facecolors='yellow', offsets=offsets) - ax.add_collection(pc) + # Note: autolim=False is used to keep the view limits as is for now, + # given the updated behavior of autolim=True to also update the view + # limits. It may be reasonable to test the limits handling in the future + # as well. This will require regenerating the reference image. + ax.add_collection(pc, autolim=False) ax.set_xlim(0, 1) diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 27ce8b5d69bc..ebfc098380bd 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -408,7 +408,6 @@ def test_EllipseCollection(): ww, hh, aa, units='x', offsets=XY, offset_transform=ax.transData, facecolors='none') ax.add_collection(ec) - ax.autoscale_view() def test_EllipseCollection_setter_getter(): @@ -504,7 +503,6 @@ def test_regularpolycollection_rotate(): 4, sizes=(100,), rotation=alpha, offsets=[xy], offset_transform=ax.transData) ax.add_collection(col, autolim=True) - ax.autoscale_view() @image_comparison(['regularpolycollection_scale.png'], remove_text=True) @@ -886,7 +884,6 @@ def test_blended_collection_autolim(): f, ax = plt.subplots() trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes) ax.add_collection(LineCollection(line_segs, transform=trans)) - ax.autoscale_view(scalex=True, scaley=False) np.testing.assert_allclose(ax.get_xlim(), [1., 4.]) diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 4ed9222eb95e..6190e26f1e26 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -941,7 +941,9 @@ def test_arc_in_collection(fig_test, fig_ref): arc2 = Arc([.5, .5], .5, 1, theta1=0, theta2=60, angle=20) col = mcollections.PatchCollection(patches=[arc2], facecolors='none', edgecolors='k') - fig_ref.subplots().add_patch(arc1) + ax_ref = fig_ref.subplots() + ax_ref.add_patch(arc1) + ax_ref.autoscale_view() fig_test.subplots().add_collection(col) diff --git a/lib/matplotlib/tri/_tripcolor.py b/lib/matplotlib/tri/_tripcolor.py index f3c26b0b25ff..5a5b24522d17 100644 --- a/lib/matplotlib/tri/_tripcolor.py +++ b/lib/matplotlib/tri/_tripcolor.py @@ -163,5 +163,7 @@ def tripcolor(ax, *args, alpha=1.0, norm=None, cmap=None, vmin=None, corners = (minx, miny), (maxx, maxy) ax.update_datalim(corners) ax.autoscale_view() - ax.add_collection(collection) + # TODO: check whether the above explicit limit handling can be + # replaced by autolim=True + ax.add_collection(collection, autolim=False) return collection diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 55b204022fb9..9576b299ab72 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2133,7 +2133,7 @@ def fill_between(self, x1, y1, z1, x2, y2, z2, *, polyc = art3d.Poly3DCollection(polys, facecolors=facecolors, shade=shade, axlim_clip=axlim_clip, **kwargs) - self.add_collection(polyc) + self.add_collection(polyc, autolim="_datalim_only") self.auto_scale_xyz([x1, x2], [y1, y2], [z1, z2], had_data) return polyc @@ -2332,7 +2332,7 @@ def plot_surface(self, X, Y, Z, *, norm=None, vmin=None, polys, facecolors=color, shade=shade, lightsource=lightsource, axlim_clip=axlim_clip, **kwargs) - self.add_collection(polyc) + self.add_collection(polyc, autolim="_datalim_only") self.auto_scale_xyz(X, Y, Z, had_data) return polyc @@ -2458,7 +2458,7 @@ def plot_wireframe(self, X, Y, Z, *, axlim_clip=False, **kwargs): lines = list(row_lines) + list(col_lines) linec = art3d.Line3DCollection(lines, axlim_clip=axlim_clip, **kwargs) - self.add_collection(linec) + self.add_collection(linec, autolim="_datalim_only") return linec @@ -2559,7 +2559,7 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None, verts, *args, shade=shade, lightsource=lightsource, facecolors=color, axlim_clip=axlim_clip, **kwargs) - self.add_collection(polyc) + self.add_collection(polyc, autolim="_datalim_only") self.auto_scale_xyz(tri.x, tri.y, z, had_data) return polyc @@ -2901,7 +2901,7 @@ def add_collection3d(self, col, zs=0, zdir='z', autolim=True, *, # Currently unable to do so due to issues with Patch3DCollection # See https://github.com/matplotlib/matplotlib/issues/14298 for details - collection = super().add_collection(col) + collection = super().add_collection(col, autolim="_datalim_only") return collection @_preprocess_data(replace_names=["xs", "ys", "zs", "s", @@ -3231,7 +3231,7 @@ def bar3d(self, x, y, z, dx, dy, dz, color=None, lightsource=lightsource, axlim_clip=axlim_clip, *args, **kwargs) - self.add_collection(col) + self.add_collection(col, autolim="_datalim_only") self.auto_scale_xyz((minx, maxx), (miny, maxy), (minz, maxz), had_data) @@ -3328,7 +3328,7 @@ def calc_arrows(UVW): if any(len(v) == 0 for v in input_args): # No quivers, so just make an empty collection and return early linec = art3d.Line3DCollection([], **kwargs) - self.add_collection(linec) + self.add_collection(linec, autolim="_datalim_only") return linec shaft_dt = np.array([0., length], dtype=float) @@ -3366,7 +3366,7 @@ def calc_arrows(UVW): lines = [] linec = art3d.Line3DCollection(lines, axlim_clip=axlim_clip, **kwargs) - self.add_collection(linec) + self.add_collection(linec, autolim="_datalim_only") self.auto_scale_xyz(XYZ[:, 0], XYZ[:, 1], XYZ[:, 2], had_data) @@ -3897,7 +3897,7 @@ def _extract_errs(err, data, lomask, himask): errline = art3d.Line3DCollection(np.array(coorderr).T, axlim_clip=axlim_clip, **eb_lines_style) - self.add_collection(errline) + self.add_collection(errline, autolim="_datalim_only") errlines.append(errline) coorderrs.append(coorderr) @@ -4047,7 +4047,7 @@ def stem(self, x, y, z, *, linefmt='C0-', markerfmt='C0o', basefmt='C3-', stemlines = art3d.Line3DCollection( lines, linestyles=linestyle, colors=linecolor, label='_nolegend_', axlim_clip=axlim_clip) - self.add_collection(stemlines) + self.add_collection(stemlines, autolim="_datalim_only") markerline, = self.plot(x, y, z, markerfmt, label='_nolegend_') stem_container = StemContainer((markerline, stemlines, baseline), diff --git a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py index 174c12608ae9..8ff6050443ab 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py @@ -55,7 +55,7 @@ def test_zordered_error(): fig = plt.figure() ax = fig.add_subplot(projection="3d") - ax.add_collection(Line3DCollection(lc)) + ax.add_collection(Line3DCollection(lc), autolim="_datalim_only") ax.scatter(*pc, visible=False) plt.draw() diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 79c7baba9bd1..5c60ceedd969 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1218,7 +1218,7 @@ def _test_proj_draw_axes(M, s=1, *args, **kwargs): fig, ax = plt.subplots(*args, **kwargs) linec = LineCollection(lines) - ax.add_collection(linec) + ax.add_collection(linec, autolim="_datalim_only") for x, y, t in zip(txs, tys, ['o', 'x', 'y', 'z']): ax.text(x, y, t) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py b/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py index 7fd676df1e31..091ae2c3e12f 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py @@ -47,9 +47,9 @@ def test_linecollection_scaled_dashes(): lc3 = art3d.Line3DCollection(lines3, linestyles=":", lw=.5) fig, ax = plt.subplots(subplot_kw=dict(projection='3d')) - ax.add_collection(lc1) - ax.add_collection(lc2) - ax.add_collection(lc3) + ax.add_collection(lc1, autolim="_datalim_only") + ax.add_collection(lc2, autolim="_datalim_only") + ax.add_collection(lc3, autolim="_datalim_only") leg = ax.legend([lc1, lc2, lc3], ['line1', 'line2', 'line 3']) h1, h2, h3 = leg.legend_handles 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