Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1068992

Browse files
authored
Merge pull request matplotlib#31530 from tinezivic/fix-relim-scatter-offsets
BUG: Fix relim() to support Collection artists (scatter, etc.)
2 parents b07211f + 0161751 commit 1068992

4 files changed

Lines changed: 120 additions & 34 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
``relim()`` now accounts for Collection artists
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Previously, `~.axes.Axes.relim` did not recalculate data limits for
4+
`.Collection` artists (e.g. those created by `~.axes.Axes.scatter`).
5+
Calling ``ax.relim()`` followed by ``ax.autoscale_view()`` now correctly
6+
includes scatter plots and other collections in the axes limits.

lib/matplotlib/artist.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,6 @@ def remove(self):
240240
with `.FigureCanvasBase.draw_idle`. Call `~.axes.Axes.relim` to
241241
update the Axes limits if desired.
242242
243-
Note: `~.axes.Axes.relim` will not see collections even if the
244-
collection was added to the Axes with *autolim* = True.
245-
246243
Note: there is no support for removing the artist's legend entry.
247244
"""
248245

@@ -271,11 +268,6 @@ def remove(self):
271268

272269
else:
273270
raise NotImplementedError('cannot remove artist')
274-
# TODO: the fix for the collections relim problem is to move the
275-
# limits calculation into the artist itself, including the property of
276-
# whether or not the artist should affect the limits. Then there will
277-
# be no distinction between axes.add_line, axes.add_patch, etc.
278-
# TODO: add legend support
279271

280272
def have_units(self):
281273
"""Return whether units are set on any axis."""

lib/matplotlib/axes/_base.py

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2386,10 +2386,21 @@ def add_collection(self, collection, autolim=True):
23862386
autolim : bool
23872387
Whether to update data and view limits.
23882388
2389+
If *False*, the collection does not take part in any limit
2390+
operations.
2391+
23892392
.. versionchanged:: 3.11
23902393
2391-
This now also updates the view limits, making explicit
2392-
calls to `~.Axes.autoscale_view` unnecessary.
2394+
Since 3.11 ``autolim=True`` matches the standard behavior
2395+
of other ``add_[artist]`` methods: Axes data and view limits
2396+
are both updated in the method, and the collection will
2397+
be considered in future data limit updates through
2398+
`.relim`.
2399+
2400+
Prior to matplotlib 3.11 this was only a one-time update
2401+
of the data limits. Updating view limits required an
2402+
explicit call to `~.Axes.autoscale_view`, and collections
2403+
did not take part in `.relim`.
23932404
23942405
As an implementation detail, the value "_datalim_only" is
23952406
supported to smooth the internal transition from pre-3.11
@@ -2407,29 +2418,12 @@ def add_collection(self, collection, autolim=True):
24072418
collection.set_clip_path(self.patch)
24082419

24092420
if autolim:
2421+
if autolim != "_datalim_only":
2422+
collection._set_in_autoscale(True)
24102423
# Make sure viewLim is not stale (mostly to match
24112424
# pre-lazy-autoscale behavior, which is not really better).
24122425
self._unstale_viewLim()
2413-
datalim = collection.get_datalim(self.transData)
2414-
points = datalim.get_points()
2415-
if not np.isinf(datalim.minpos).all():
2416-
# By definition, if minpos (minimum positive value) is set
2417-
# (i.e., non-inf), then min(points) <= minpos <= max(points),
2418-
# and minpos would be superfluous. However, we add minpos to
2419-
# the call so that self.dataLim will update its own minpos.
2420-
# This ensures that log scales see the correct minimum.
2421-
points = np.concatenate([points, [datalim.minpos]])
2422-
# only update the dataLim for x/y if the collection uses transData
2423-
# in this direction.
2424-
x_is_data, y_is_data = (collection.get_transform()
2425-
.contains_branch_separately(self.transData))
2426-
ox_is_data, oy_is_data = (collection.get_offset_transform()
2427-
.contains_branch_separately(self.transData))
2428-
self.update_datalim(
2429-
points,
2430-
updatex=x_is_data or ox_is_data,
2431-
updatey=y_is_data or oy_is_data,
2432-
)
2426+
self._update_collection_limits(collection)
24332427
if autolim != "_datalim_only":
24342428
self._request_autoscale_view()
24352429

@@ -2598,6 +2592,29 @@ def _update_patch_limits(self, patch):
25982592
xys = trf_to_data.transform(vertices)
25992593
self.update_datalim(xys, updatex=updatex, updatey=updatey)
26002594

2595+
def _update_collection_limits(self, collection):
2596+
"""Update the data limits for the given collection."""
2597+
datalim = collection.get_datalim(self.transData)
2598+
points = datalim.get_points()
2599+
if not np.isinf(datalim.minpos).all():
2600+
# By definition, if minpos (minimum positive value) is set
2601+
# (i.e., non-inf), then min(points) <= minpos <= max(points),
2602+
# and minpos would be superfluous. However, we add minpos to
2603+
# the call so that self.dataLim will update its own minpos.
2604+
# This ensures that log scales see the correct minimum.
2605+
points = np.concatenate([points, [datalim.minpos]])
2606+
# only update the dataLim for x/y if the collection uses transData
2607+
# in this direction.
2608+
x_is_data, y_is_data = (collection.get_transform()
2609+
.contains_branch_separately(self.transData))
2610+
ox_is_data, oy_is_data = (collection.get_offset_transform()
2611+
.contains_branch_separately(self.transData))
2612+
self.update_datalim(
2613+
points,
2614+
updatex=x_is_data or ox_is_data,
2615+
updatey=y_is_data or oy_is_data,
2616+
)
2617+
26012618
def add_table(self, tab):
26022619
"""
26032620
Add a `.Table` to the Axes; return the table.
@@ -2638,15 +2655,11 @@ def relim(self, visible_only=False):
26382655
"""
26392656
Recompute the data limits based on current artists.
26402657
2641-
At present, `.Collection` instances are not supported.
2642-
26432658
Parameters
26442659
----------
26452660
visible_only : bool, default: False
26462661
Whether to exclude invisible artists.
26472662
"""
2648-
# Collections are deliberately not supported (yet); see
2649-
# the TODO note in artists.py.
26502663
self.dataLim.ignore(True)
26512664
self.dataLim.set_points(mtransforms.Bbox.null().get_points())
26522665
self.ignore_existing_data_limits = True
@@ -2661,6 +2674,8 @@ def relim(self, visible_only=False):
26612674
self._update_patch_limits(artist)
26622675
elif isinstance(artist, mimage.AxesImage):
26632676
self._update_image_limits(artist)
2677+
elif isinstance(artist, mcoll.Collection):
2678+
self._update_collection_limits(artist)
26642679

26652680
def update_datalim(self, xys, updatex=True, updatey=True):
26662681
"""

lib/matplotlib/tests/test_axes.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6518,6 +6518,79 @@ def test_relim_visible_only():
65186518
assert ax.get_ylim() == y1
65196519

65206520

6521+
def test_relim_collection():
6522+
fig, ax = plt.subplots()
6523+
sc = ax.scatter([1, 2, 3], [4, 5, 6])
6524+
ax.relim()
6525+
expected = sc.get_datalim(ax.transData)
6526+
assert_allclose(ax.dataLim.get_points(), expected.get_points())
6527+
assert_allclose(ax.dataLim.minpos, expected.minpos)
6528+
6529+
# After updating offsets, relim should track the new data.
6530+
sc.set_offsets([[10, 20], [30, 40]])
6531+
ax.relim()
6532+
expected = sc.get_datalim(ax.transData)
6533+
assert_allclose(ax.dataLim.get_points(), expected.get_points())
6534+
assert_allclose(ax.dataLim.minpos, expected.minpos)
6535+
6536+
# visible_only=True should ignore hidden collections.
6537+
line, = ax.plot([0, 1], [0, 1])
6538+
sc.set_visible(False)
6539+
ax.relim(visible_only=True)
6540+
# With scatter hidden, limits should be driven by the line only.
6541+
assert_allclose(ax.dataLim.get_points(), [[0, 0], [1, 1]])
6542+
# minpos is the minimum *positive* value; line data [0, 1] gives 1.0.
6543+
assert_array_equal(ax.dataLim.minpos, [1., 1.])
6544+
6545+
6546+
def test_relim_collection_autolim_false():
6547+
# GH#30859 - Collection added with autolim=False must not participate
6548+
# in relim() later.
6549+
import matplotlib.collections as mcollections
6550+
fig, ax = plt.subplots()
6551+
ax.plot([0, 1], [0, 1])
6552+
ax.relim()
6553+
expected = ax.dataLim.frozen()
6554+
# Build a collection far outside current limits and add it with autolim=False.
6555+
sc = mcollections.PathCollection([])
6556+
sc.set_offsets([[100, 200], [300, 400]])
6557+
ax.add_collection(sc, autolim=False)
6558+
ax.relim()
6559+
# dataLim must remain unchanged because autolim=False was requested.
6560+
assert_allclose(ax.dataLim.get_points(), expected.get_points())
6561+
assert_allclose(ax.dataLim.minpos, expected.minpos)
6562+
6563+
6564+
def test_relim_collection_log_scale():
6565+
# GH#30859 - relim() for Collection on a log-scaled axis should
6566+
# correctly propagate minpos into dataLim.
6567+
fig, ax = plt.subplots()
6568+
ax.set_xscale('log')
6569+
ax.set_yscale('log')
6570+
sc = ax.scatter([1e-3, 1e-2, 1e-1], [1e1, 1e2, 1e3])
6571+
sc.set_offsets([[1e1, 1e4], [1e2, 1e5]])
6572+
ax.relim()
6573+
expected = sc.get_datalim(ax.transData)
6574+
assert_allclose(ax.dataLim.get_points(), expected.get_points())
6575+
assert_allclose(ax.dataLim.minpos, expected.minpos)
6576+
6577+
6578+
def test_relim_collection_autoscale_view():
6579+
# GH#30859 - end-to-end: after set_offsets(), relim() + autoscale_view()
6580+
# must update the visible axis limits, not just dataLim.
6581+
fig, ax = plt.subplots()
6582+
sc = ax.scatter([], [])
6583+
xs = np.linspace(0, 10, 50)
6584+
sc.set_offsets(np.column_stack((xs, np.sin(xs))))
6585+
ax.relim()
6586+
ax.autoscale_view()
6587+
xlim = ax.get_xlim()
6588+
ylim = ax.get_ylim()
6589+
# autoscale_view adds a margin, so limits should comfortably contain data
6590+
assert xlim[0] <= 0 and xlim[1] >= 10, f"xlim should contain [0, 10], got {xlim}"
6591+
assert ylim[0] <= -1 and ylim[1] >= 1, f"ylim should contain [-1, 1], got {ylim}"
6592+
6593+
65216594
def test_text_labelsize():
65226595
"""
65236596
tests for issue #1172

0 commit comments

Comments
 (0)