Skip to content

Commit e5b0541

Browse files
committed
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.
1 parent 492a478 commit e5b0541

File tree

16 files changed

+57
-37
lines changed

16 files changed

+57
-37
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
``Axes.add_collection(..., autolim=True)`` updates view limits
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
``Axes.add_collection(..., autolim=True)`` has so far only updated the data limits.
5+
Users needed to additionally call `.Axes.autoscale_view` to update the view limits.
6+
View limits are now updated as well if ``autolim=True``, using a lazy internal
7+
update mechanism, so that the costs only apply once also if you add multiple
8+
collections.

galleries/examples/shapes_and_collections/collections.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,12 @@
5959
# but it is good enough to generate a plot that you can use
6060
# as a starting point. If you know beforehand the range of
6161
# x and y that you want to show, it is better to set them
62-
# explicitly, leave out the *autolim* keyword argument (or set it to False),
63-
# and omit the 'ax1.autoscale_view()' call below.
62+
# explicitly, set the *autolim* keyword argument to False.
6463

6564
# Make a transform for the line segments such that their size is
6665
# given in points:
6766
col.set_color(colors)
6867

69-
ax1.autoscale_view() # See comment above, after ax1.add_collection.
7068
ax1.set_title('LineCollection using offsets')
7169

7270

@@ -79,7 +77,6 @@
7977
col.set_color(colors)
8078

8179

82-
ax2.autoscale_view()
8380
ax2.set_title('PolyCollection using offsets')
8481

8582
# 7-sided regular polygons
@@ -90,7 +87,6 @@
9087
col.set_transform(trans) # the points to pixels transform
9188
ax3.add_collection(col, autolim=True)
9289
col.set_color(colors)
93-
ax3.autoscale_view()
9490
ax3.set_title('RegularPolyCollection using offsets')
9591

9692

@@ -114,7 +110,6 @@
114110
col = collections.LineCollection(segs, offsets=offs)
115111
ax4.add_collection(col, autolim=True)
116112
col.set_color(colors)
117-
ax4.autoscale_view()
118113
ax4.set_title('Successive data offsets')
119114
ax4.set_xlabel('Zonal velocity component (m/s)')
120115
ax4.set_ylabel('Depth (m)')

galleries/examples/shapes_and_collections/ellipse_collection.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
offset_transform=ax.transData)
3131
ec.set_array((X + Y).ravel())
3232
ax.add_collection(ec)
33-
ax.autoscale_view()
3433
ax.set_xlabel('X')
3534
ax.set_ylabel('y')
3635
cbar = plt.colorbar(ec)

galleries/users_explain/axes/autoscale.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,3 @@
177177
offset_transform=ax.transData, # Propagate transformations of the Axes
178178
)
179179
ax.add_collection(collection)
180-
ax.autoscale_view()

lib/matplotlib/axes/_axes.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3010,7 +3010,6 @@ def broken_barh(self, xranges, yrange, **kwargs):
30103010

30113011
col = mcoll.PolyCollection(np.array(vertices), **kwargs)
30123012
self.add_collection(col, autolim=True)
3013-
self._request_autoscale_view()
30143013

30153014
return col
30163015

@@ -4997,7 +4996,6 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None,
49974996
self.set_ymargin(0.05)
49984997

49994998
self.add_collection(collection)
5000-
self._request_autoscale_view()
50014999

50025000
return collection
50035001

@@ -5468,7 +5466,6 @@ def quiver(self, *args, **kwargs):
54685466
args = self._quiver_units(args, kwargs)
54695467
q = mquiver.Quiver(self, *args, **kwargs)
54705468
self.add_collection(q, autolim=True)
5471-
self._request_autoscale_view()
54725469
return q
54735470

54745471
# 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):
54805477
args = self._quiver_units(args, kwargs)
54815478
b = mquiver.Barbs(self, *args, **kwargs)
54825479
self.add_collection(b, autolim=True)
5483-
self._request_autoscale_view()
54845480
return b
54855481

54865482
# Uses a custom implementation of data-kwarg handling in
@@ -5640,7 +5636,6 @@ def _fill_between_x_or_y(
56405636
where=where, interpolate=interpolate, step=step, **kwargs)
56415637

56425638
self.add_collection(collection)
5643-
self._request_autoscale_view()
56445639
return collection
56455640

56465641
def _fill_between_process_units(self, ind_dir, dep_dir, ind, dep1, dep2, **kwargs):

lib/matplotlib/axes/_base.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2331,6 +2331,23 @@ def add_child_axes(self, ax):
23312331
def add_collection(self, collection, autolim=True):
23322332
"""
23332333
Add a `.Collection` to the Axes; return the collection.
2334+
2335+
Parameters
2336+
----------
2337+
collection : `.Collection`
2338+
The collection to add.
2339+
autolim : bool
2340+
Whether to update data and view limits.
2341+
2342+
.. versionchanged:: 3.11
2343+
2344+
This now also updates the view limits, making explicit
2345+
calls to `~.Axes.autoscale_view` unnecessary.
2346+
2347+
As an implementation detail, the value "_datalim_only" is
2348+
supported to smooth the internal transition from pre-3.11
2349+
behavior. This is not a public interface and will be removed
2350+
again in the future.
23342351
"""
23352352
_api.check_isinstance(mcoll.Collection, collection=collection)
23362353
if not collection.get_label():
@@ -2356,6 +2373,8 @@ def add_collection(self, collection, autolim=True):
23562373
# This ensures that log scales see the correct minimum.
23572374
points = np.concatenate([points, [datalim.minpos]])
23582375
self.update_datalim(points)
2376+
if autolim != "_datalim_only":
2377+
self._request_autoscale_view()
23592378

23602379
self.stale = True
23612380
return collection

lib/matplotlib/axes/_base.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ class _AxesBase(martist.Artist):
234234
def add_artist(self, a: Artist) -> Artist: ...
235235
def add_child_axes(self, ax: _AxesBase) -> _AxesBase: ...
236236
def add_collection(
237-
self, collection: Collection, autolim: bool = ...
237+
self, collection: Collection, autolim: bool | Literal["_data_only"] = ...
238238
) -> Collection: ...
239239
def add_image(self, image: AxesImage) -> AxesImage: ...
240240
def add_line(self, line: Line2D) -> Line2D: ...

lib/matplotlib/colorbar.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ def __init__(
371371
colors=[mpl.rcParams['axes.edgecolor']],
372372
linewidths=[0.5 * mpl.rcParams['axes.linewidth']],
373373
clip_on=False)
374-
self.ax.add_collection(self.dividers)
374+
self.ax.add_collection(self.dividers, autolim=False)
375375

376376
self._locator = None
377377
self._minorlocator = None
@@ -805,7 +805,7 @@ def add_lines(self, *args, **kwargs):
805805
xy = self.ax.transAxes.inverted().transform(inches.transform(xy))
806806
col.set_clip_path(mpath.Path(xy, closed=True),
807807
self.ax.transAxes)
808-
self.ax.add_collection(col)
808+
self.ax.add_collection(col, autolim=False)
809809
self.stale = True
810810

811811
def update_ticks(self):

lib/matplotlib/tests/test_backend_ps.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,11 @@ def test_path_collection():
354354
sizes = [0.02, 0.04]
355355
pc = mcollections.PathCollection(paths, sizes, zorder=-1,
356356
facecolors='yellow', offsets=offsets)
357-
ax.add_collection(pc)
357+
# Note: autolim=False is used to keep the view limits as is for now,
358+
# given the updated behavior of autolim=True to also update the view
359+
# limits. It may be reasonable to test the limits handling in the future
360+
# as well. This will require regenerating the reference image.
361+
ax.add_collection(pc, autolim=False)
358362
ax.set_xlim(0, 1)
359363

360364

lib/matplotlib/tests/test_collections.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,6 @@ def test_EllipseCollection():
408408
ww, hh, aa, units='x', offsets=XY, offset_transform=ax.transData,
409409
facecolors='none')
410410
ax.add_collection(ec)
411-
ax.autoscale_view()
412411

413412

414413
def test_EllipseCollection_setter_getter():
@@ -504,7 +503,6 @@ def test_regularpolycollection_rotate():
504503
4, sizes=(100,), rotation=alpha,
505504
offsets=[xy], offset_transform=ax.transData)
506505
ax.add_collection(col, autolim=True)
507-
ax.autoscale_view()
508506

509507

510508
@image_comparison(['regularpolycollection_scale.png'], remove_text=True)
@@ -886,7 +884,6 @@ def test_blended_collection_autolim():
886884
f, ax = plt.subplots()
887885
trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
888886
ax.add_collection(LineCollection(line_segs, transform=trans))
889-
ax.autoscale_view(scalex=True, scaley=False)
890887
np.testing.assert_allclose(ax.get_xlim(), [1., 4.])
891888

892889

lib/matplotlib/tests/test_patches.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,9 @@ def test_arc_in_collection(fig_test, fig_ref):
941941
arc2 = Arc([.5, .5], .5, 1, theta1=0, theta2=60, angle=20)
942942
col = mcollections.PatchCollection(patches=[arc2], facecolors='none',
943943
edgecolors='k')
944-
fig_ref.subplots().add_patch(arc1)
944+
ax_ref = fig_ref.subplots()
945+
ax_ref.add_patch(arc1)
946+
ax_ref.autoscale_view()
945947
fig_test.subplots().add_collection(col)
946948

947949

lib/matplotlib/tri/_tripcolor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,5 +163,7 @@ def tripcolor(ax, *args, alpha=1.0, norm=None, cmap=None, vmin=None,
163163
corners = (minx, miny), (maxx, maxy)
164164
ax.update_datalim(corners)
165165
ax.autoscale_view()
166-
ax.add_collection(collection)
166+
# TODO: check whether the above explicit limit handling can be
167+
# replaced by autolim=True
168+
ax.add_collection(collection, autolim=False)
167169
return collection

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,7 +2133,7 @@ def fill_between(self, x1, y1, z1, x2, y2, z2, *,
21332133

21342134
polyc = art3d.Poly3DCollection(polys, facecolors=facecolors, shade=shade,
21352135
axlim_clip=axlim_clip, **kwargs)
2136-
self.add_collection(polyc)
2136+
self.add_collection(polyc, autolim="_datalim_only")
21372137

21382138
self.auto_scale_xyz([x1, x2], [y1, y2], [z1, z2], had_data)
21392139
return polyc
@@ -2332,7 +2332,7 @@ def plot_surface(self, X, Y, Z, *, norm=None, vmin=None,
23322332
polys, facecolors=color, shade=shade, lightsource=lightsource,
23332333
axlim_clip=axlim_clip, **kwargs)
23342334

2335-
self.add_collection(polyc)
2335+
self.add_collection(polyc, autolim="_datalim_only")
23362336
self.auto_scale_xyz(X, Y, Z, had_data)
23372337

23382338
return polyc
@@ -2458,7 +2458,7 @@ def plot_wireframe(self, X, Y, Z, *, axlim_clip=False, **kwargs):
24582458

24592459
lines = list(row_lines) + list(col_lines)
24602460
linec = art3d.Line3DCollection(lines, axlim_clip=axlim_clip, **kwargs)
2461-
self.add_collection(linec)
2461+
self.add_collection(linec, autolim="_datalim_only")
24622462

24632463
return linec
24642464

@@ -2559,7 +2559,7 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None,
25592559
verts, *args, shade=shade, lightsource=lightsource,
25602560
facecolors=color, axlim_clip=axlim_clip, **kwargs)
25612561

2562-
self.add_collection(polyc)
2562+
self.add_collection(polyc, autolim="_datalim_only")
25632563
self.auto_scale_xyz(tri.x, tri.y, z, had_data)
25642564

25652565
return polyc
@@ -2901,7 +2901,7 @@ def add_collection3d(self, col, zs=0, zdir='z', autolim=True, *,
29012901
# Currently unable to do so due to issues with Patch3DCollection
29022902
# See https://github.com/matplotlib/matplotlib/issues/14298 for details
29032903

2904-
collection = super().add_collection(col)
2904+
collection = super().add_collection(col, autolim="_datalim_only")
29052905
return collection
29062906

29072907
@_preprocess_data(replace_names=["xs", "ys", "zs", "s",
@@ -3231,7 +3231,7 @@ def bar3d(self, x, y, z, dx, dy, dz, color=None,
32313231
lightsource=lightsource,
32323232
axlim_clip=axlim_clip,
32333233
*args, **kwargs)
3234-
self.add_collection(col)
3234+
self.add_collection(col, autolim="_datalim_only")
32353235

32363236
self.auto_scale_xyz((minx, maxx), (miny, maxy), (minz, maxz), had_data)
32373237

@@ -3328,7 +3328,7 @@ def calc_arrows(UVW):
33283328
if any(len(v) == 0 for v in input_args):
33293329
# No quivers, so just make an empty collection and return early
33303330
linec = art3d.Line3DCollection([], **kwargs)
3331-
self.add_collection(linec)
3331+
self.add_collection(linec, autolim="_datalim_only")
33323332
return linec
33333333

33343334
shaft_dt = np.array([0., length], dtype=float)
@@ -3366,7 +3366,7 @@ def calc_arrows(UVW):
33663366
lines = []
33673367

33683368
linec = art3d.Line3DCollection(lines, axlim_clip=axlim_clip, **kwargs)
3369-
self.add_collection(linec)
3369+
self.add_collection(linec, autolim="_datalim_only")
33703370

33713371
self.auto_scale_xyz(XYZ[:, 0], XYZ[:, 1], XYZ[:, 2], had_data)
33723372

@@ -3897,7 +3897,7 @@ def _extract_errs(err, data, lomask, himask):
38973897
errline = art3d.Line3DCollection(np.array(coorderr).T,
38983898
axlim_clip=axlim_clip,
38993899
**eb_lines_style)
3900-
self.add_collection(errline)
3900+
self.add_collection(errline, autolim="_datalim_only")
39013901
errlines.append(errline)
39023902
coorderrs.append(coorderr)
39033903

@@ -4047,7 +4047,7 @@ def stem(self, x, y, z, *, linefmt='C0-', markerfmt='C0o', basefmt='C3-',
40474047
stemlines = art3d.Line3DCollection(
40484048
lines, linestyles=linestyle, colors=linecolor, label='_nolegend_',
40494049
axlim_clip=axlim_clip)
4050-
self.add_collection(stemlines)
4050+
self.add_collection(stemlines, autolim="_datalim_only")
40514051
markerline, = self.plot(x, y, z, markerfmt, label='_nolegend_')
40524052

40534053
stem_container = StemContainer((markerline, stemlines, baseline),

lib/mpl_toolkits/mplot3d/tests/test_art3d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_zordered_error():
5555

5656
fig = plt.figure()
5757
ax = fig.add_subplot(projection="3d")
58-
ax.add_collection(Line3DCollection(lc))
58+
ax.add_collection(Line3DCollection(lc), autolim="_datalim_only")
5959
ax.scatter(*pc, visible=False)
6060
plt.draw()
6161

lib/mpl_toolkits/mplot3d/tests/test_axes3d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1218,7 +1218,7 @@ def _test_proj_draw_axes(M, s=1, *args, **kwargs):
12181218

12191219
fig, ax = plt.subplots(*args, **kwargs)
12201220
linec = LineCollection(lines)
1221-
ax.add_collection(linec)
1221+
ax.add_collection(linec, autolim="_datalim_only")
12221222
for x, y, t in zip(txs, tys, ['o', 'x', 'y', 'z']):
12231223
ax.text(x, y, t)
12241224

lib/mpl_toolkits/mplot3d/tests/test_legend3d.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ def test_linecollection_scaled_dashes():
4747
lc3 = art3d.Line3DCollection(lines3, linestyles=":", lw=.5)
4848

4949
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
50-
ax.add_collection(lc1)
51-
ax.add_collection(lc2)
52-
ax.add_collection(lc3)
50+
ax.add_collection(lc1, autolim="_datalim_only")
51+
ax.add_collection(lc2, autolim="_datalim_only")
52+
ax.add_collection(lc3, autolim="_datalim_only")
5353

5454
leg = ax.legend([lc1, lc2, lc3], ['line1', 'line2', 'line 3'])
5555
h1, h2, h3 = leg.legend_handles

0 commit comments

Comments
 (0)