Skip to content

ENH: ax.add_collection(..., autolim=True) updates view limits #29958

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/api/next_api_changes/behavior/29958-TH.rst
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 1 addition & 6 deletions galleries/examples/shapes_and_collections/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')


Expand All @@ -79,7 +77,6 @@
col.set_color(colors)


ax2.autoscale_view()
ax2.set_title('PolyCollection using offsets')

# 7-sided regular polygons
Expand All @@ -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')


Expand All @@ -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)')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion galleries/users_explain/axes/autoscale.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,3 @@
offset_transform=ax.transData, # Propagate transformations of the Axes
)
ax.add_collection(collection)
ax.autoscale_view()
5 changes: 0 additions & 5 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down
19 changes: 19 additions & 0 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/axes/_base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are decorations on the colorbar and should not influence its limits.


self._locator = None
self._minorlocator = None
Expand Down Expand Up @@ -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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well: These are decorations on the colorbar and should not influence its limits.

self.stale = True

def update_ticks(self):
Expand Down
6 changes: 5 additions & 1 deletion lib/matplotlib/tests/test_backend_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
3 changes: 0 additions & 3 deletions lib/matplotlib/tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.])


Expand Down
4 changes: 3 additions & 1 deletion lib/matplotlib/tests/test_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
4 changes: 3 additions & 1 deletion lib/matplotlib/tri/_tripcolor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment on lines +166 to +168
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is an explicit update_datalim(); autoscale_view() above, autolim=True has no effect. Setting autolim=False for now to make that clear. We may whether the explicit handling can be removed in favor of autolim=True, but I'll leave that for later.

return collection
20 changes: 10 additions & 10 deletions lib/mpl_toolkits/mplot3d/axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion lib/mpl_toolkits/mplot3d/tests/test_art3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion lib/mpl_toolkits/mplot3d/tests/test_axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
6 changes: 3 additions & 3 deletions lib/mpl_toolkits/mplot3d/tests/test_legend3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading