Skip to content

Commit 5144212

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 011d12f commit 5144212

File tree

17 files changed

+61
-62
lines changed

17 files changed

+61
-62
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/lines_bars_and_markers/multicolored_line.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def colored_line(x, y, c, ax=None, **lc_kwargs):
7272
# Plot the line collection to the axes
7373
ax = ax or plt.gca()
7474
ax.add_collection(lc)
75-
ax.autoscale_view()
7675

7776
return lc
7877

galleries/examples/shapes_and_collections/collections.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
2-
=========================================================
3-
Line, Poly and RegularPoly Collection with autoscaling
4-
=========================================================
2+
=====================================
3+
Line, Poly and RegularPoly Collection
4+
=====================================
55
66
For the first two subplots, we will use spirals. Their size will be set in
77
plot units, not data units. Their positions will be set in data units by using
@@ -38,7 +38,7 @@
3838
# Make some offsets
3939
xyo = rs.randn(npts, 2)
4040

41-
# Make a list of colors cycling through the default series.
41+
# Make a list of colors from the default color cycle.
4242
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
4343

4444
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
@@ -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)')
@@ -136,6 +131,5 @@
136131
# - `matplotlib.collections.LineCollection`
137132
# - `matplotlib.collections.RegularPolyCollection`
138133
# - `matplotlib.axes.Axes.add_collection`
139-
# - `matplotlib.axes.Axes.autoscale_view`
140134
# - `matplotlib.transforms.Affine2D`
141135
# - `matplotlib.transforms.Affine2D.scale`

galleries/examples/shapes_and_collections/ellipse_collection.py

Lines changed: 0 additions & 2 deletions
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)
@@ -47,5 +46,4 @@
4746
# - `matplotlib.collections`
4847
# - `matplotlib.collections.EllipseCollection`
4948
# - `matplotlib.axes.Axes.add_collection`
50-
# - `matplotlib.axes.Axes.autoscale_view`
5149
# - `matplotlib.cm.ScalarMappable.set_array`

galleries/users_explain/axes/autoscale.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import matplotlib.pyplot as plt
1919
import numpy as np
2020

21-
import matplotlib as mpl
2221

2322
x = np.linspace(-2 * np.pi, 2 * np.pi, 100)
2423
y = np.sinc(x)
@@ -159,22 +158,3 @@
159158
ax.autoscale(enable=None, axis="x", tight=True)
160159

161160
print(ax.margins())
162-
163-
# %%
164-
# Working with collections
165-
# ------------------------
166-
#
167-
# Autoscale works out of the box for all lines, patches, and images added to
168-
# the Axes. One of the artists that it won't work with is a `.Collection`.
169-
# After adding a collection to the Axes, one has to manually trigger the
170-
# `~matplotlib.axes.Axes.autoscale_view()` to recalculate
171-
# axes limits.
172-
173-
fig, ax = plt.subplots()
174-
collection = mpl.collections.StarPolygonCollection(
175-
5, rotation=0, sizes=(250,), # five point star, zero angle, size 250px
176-
offsets=np.column_stack([x, y]), # Set the positions
177-
offset_transform=ax.transData, # Propagate transformations of the Axes
178-
)
179-
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
@@ -3028,7 +3028,6 @@ def broken_barh(self, xranges, yrange, **kwargs):
30283028

30293029
col = mcoll.PolyCollection(np.array(vertices), **kwargs)
30303030
self.add_collection(col, autolim=True)
3031-
self._request_autoscale_view()
30323031

30333032
return col
30343033

@@ -5337,7 +5336,6 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None,
53375336
self.set_ymargin(0.05)
53385337

53395338
self.add_collection(collection)
5340-
self._request_autoscale_view()
53415339

53425340
return collection
53435341

@@ -5808,7 +5806,6 @@ def quiver(self, *args, **kwargs):
58085806
args = self._quiver_units(args, kwargs)
58095807
q = mquiver.Quiver(self, *args, **kwargs)
58105808
self.add_collection(q, autolim=True)
5811-
self._request_autoscale_view()
58125809
return q
58135810

58145811
# args can be some combination of X, Y, U, V, C and all should be replaced
@@ -5820,7 +5817,6 @@ def barbs(self, *args, **kwargs):
58205817
args = self._quiver_units(args, kwargs)
58215818
b = mquiver.Barbs(self, *args, **kwargs)
58225819
self.add_collection(b, autolim=True)
5823-
self._request_autoscale_view()
58245820
return b
58255821

58265822
# Uses a custom implementation of data-kwarg handling in
@@ -5980,7 +5976,6 @@ def _fill_between_x_or_y(
59805976
where=where, interpolate=interpolate, step=step, **kwargs)
59815977

59825978
self.add_collection(collection)
5983-
self._request_autoscale_view()
59845979
return collection
59855980

59865981
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
@@ -2336,6 +2336,23 @@ def add_child_axes(self, ax):
23362336
def add_collection(self, collection, autolim=True):
23372337
"""
23382338
Add a `.Collection` to the Axes; return the collection.
2339+
2340+
Parameters
2341+
----------
2342+
collection : `.Collection`
2343+
The collection to add.
2344+
autolim : bool
2345+
Whether to update data and view limits.
2346+
2347+
.. versionchanged:: 3.11
2348+
2349+
This now also updates the view limits, making explicit
2350+
calls to `~.Axes.autoscale_view` unnecessary.
2351+
2352+
As an implementation detail, the value "_datalim_only" is
2353+
supported to smooth the internal transition from pre-3.11
2354+
behavior. This is not a public interface and will be removed
2355+
again in the future.
23392356
"""
23402357
_api.check_isinstance(mcoll.Collection, collection=collection)
23412358
if not collection.get_label():
@@ -2371,6 +2388,8 @@ def add_collection(self, collection, autolim=True):
23712388
updatex=x_is_data or ox_is_data,
23722389
updatey=y_is_data or oy_is_data,
23732390
)
2391+
if autolim != "_datalim_only":
2392+
self._request_autoscale_view()
23742393

23752394
self.stale = True
23762395
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["_datalim_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
@@ -373,7 +373,7 @@ def __init__(
373373
colors=[mpl.rcParams['axes.edgecolor']],
374374
linewidths=[0.5 * mpl.rcParams['axes.linewidth']],
375375
clip_on=False)
376-
self.ax.add_collection(self.dividers)
376+
self.ax.add_collection(self.dividers, autolim=False)
377377

378378
self._locator = None
379379
self._minorlocator = None
@@ -807,7 +807,7 @@ def add_lines(self, *args, **kwargs):
807807
xy = self.ax.transAxes.inverted().transform(inches.transform(xy))
808808
col.set_clip_path(mpath.Path(xy, closed=True),
809809
self.ax.transAxes)
810-
self.ax.add_collection(col)
810+
self.ax.add_collection(col, autolim=False)
811811
self.stale = True
812812

813813
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

0 commit comments

Comments
 (0)