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

Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions doc/api/next_api_changes/behavior/31530-TZ.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
``relim()`` now accounts for Collection artists
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously, `~.axes.Axes.relim` did not recalculate data limits for
`.Collection` artists (e.g. those created by `~.axes.Axes.scatter`).
Calling ``ax.relim()`` followed by ``ax.autoscale_view()`` now correctly
includes scatter plots and other collections in the axes limits.
54 changes: 30 additions & 24 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2410,28 +2410,13 @@ def add_collection(self, collection, autolim=True):
# Make sure viewLim is not stale (mostly to match
# pre-lazy-autoscale behavior, which is not really better).
self._unstale_viewLim()
datalim = collection.get_datalim(self.transData)
points = datalim.get_points()
if not np.isinf(datalim.minpos).all():
# By definition, if minpos (minimum positive value) is set
# (i.e., non-inf), then min(points) <= minpos <= max(points),
# and minpos would be superfluous. However, we add minpos to
# the call so that self.dataLim will update its own minpos.
# This ensures that log scales see the correct minimum.
points = np.concatenate([points, [datalim.minpos]])
# only update the dataLim for x/y if the collection uses transData
# in this direction.
x_is_data, y_is_data = (collection.get_transform()
.contains_branch_separately(self.transData))
ox_is_data, oy_is_data = (collection.get_offset_transform()
.contains_branch_separately(self.transData))
self.update_datalim(
points,
updatex=x_is_data or ox_is_data,
updatey=y_is_data or oy_is_data,
)
self._update_collection_limits(collection)
if autolim != "_datalim_only":
self._request_autoscale_view()
# Mark collection as participating in relim() only when autolim
# is enabled. If autolim=False the caller explicitly opted out,
# so relim() must not pick this collection up later.
collection._set_in_autoscale(True)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we revert the logic here? Historically, Collections did not take part in autoscaling. When introducing the in_autoscale flag, we therefore defaulted it to False on Collections, but True for all other Artists.
For better consistency we may now want Collections defaulting to True as well and explicitly set to False in case of autolim=False as special-cased backward-compatibility. Am I missing a point why we can't do that?


self.stale = True
return collection
Expand Down Expand Up @@ -2598,6 +2583,29 @@ def _update_patch_limits(self, patch):
xys = trf_to_data.transform(vertices)
self.update_datalim(xys, updatex=updatex, updatey=updatey)

def _update_collection_limits(self, collection):
"""Update the data limits for the given collection."""
datalim = collection.get_datalim(self.transData)
points = datalim.get_points()
if not np.isinf(datalim.minpos).all():
# By definition, if minpos (minimum positive value) is set
# (i.e., non-inf), then min(points) <= minpos <= max(points),
# and minpos would be superfluous. However, we add minpos to
# the call so that self.dataLim will update its own minpos.
# This ensures that log scales see the correct minimum.
points = np.concatenate([points, [datalim.minpos]])
# only update the dataLim for x/y if the collection uses transData
# in this direction.
x_is_data, y_is_data = (collection.get_transform()
.contains_branch_separately(self.transData))
ox_is_data, oy_is_data = (collection.get_offset_transform()
.contains_branch_separately(self.transData))
self.update_datalim(
points,
updatex=x_is_data or ox_is_data,
updatey=y_is_data or oy_is_data,
)

def add_table(self, tab):
"""
Add a `.Table` to the Axes; return the table.
Expand Down Expand Up @@ -2638,15 +2646,11 @@ def relim(self, visible_only=False):
"""
Recompute the data limits based on current artists.

At present, `.Collection` instances are not supported.

Parameters
----------
visible_only : bool, default: False
Whether to exclude invisible artists.
"""
# Collections are deliberately not supported (yet); see
# the TODO note in artists.py.
Comment thread
timhoffm marked this conversation as resolved.
self.dataLim.ignore(True)
self.dataLim.set_points(mtransforms.Bbox.null().get_points())
self.ignore_existing_data_limits = True
Expand All @@ -2661,6 +2665,8 @@ def relim(self, visible_only=False):
self._update_patch_limits(artist)
elif isinstance(artist, mimage.AxesImage):
self._update_image_limits(artist)
elif isinstance(artist, mcoll.Collection):
self._update_collection_limits(artist)

def update_datalim(self, xys, updatex=True, updatey=True):
"""
Expand Down
65 changes: 65 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6511,6 +6511,71 @@ def test_relim_visible_only():
assert ax.get_ylim() == y1


def test_relim_collection():
fig, ax = plt.subplots()
sc = ax.scatter([1, 2, 3], [4, 5, 6])
ax.relim()
ax.autoscale_view()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
assert xlim[0] <= 1 and xlim[1] >= 3
assert ylim[0] <= 4 and ylim[1] >= 6
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

relim works on ax.dataLim. Please test its exact values and not the view limits.


# After updating offsets, relim should track the new data.
sc.set_offsets([[10, 20], [30, 40]])
ax.relim()
ax.autoscale_view()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
assert xlim[0] <= 10 and xlim[1] >= 30
assert ylim[0] <= 20 and ylim[1] >= 40

# visible_only=True should ignore hidden collections.
line, = ax.plot([0, 1], [0, 1])
sc.set_visible(False)
ax.relim(visible_only=True)
ax.autoscale_view()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# With scatter hidden, limits should be driven by the line only.
assert xlim[1] < 10
assert ylim[1] < 10


def test_relim_collection_autolim_false():
# GH#30859 - Collection added with autolim=False must not participate
# in relim() later.
import matplotlib.collections as mcollections
fig, ax = plt.subplots()
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
# Build a collection far outside current limits and add it with autolim=False.
sc = mcollections.PathCollection([])
sc.set_offsets([[100, 200], [300, 400]])
ax.add_collection(sc, autolim=False)
ax.relim()
ax.autoscale_view()
# Limits must remain unchanged because autolim=False was requested.
assert ax.get_xlim() == (0, 1)
assert ax.get_ylim() == (0, 1)


def test_relim_collection_log_scale():
# GH#30859 - relim() for Collection on a log-scaled axis should
# correctly pick up minpos so that log scaling works properly.
fig, ax = plt.subplots()
ax.set_xscale('log')
ax.set_yscale('log')
sc = ax.scatter([1e-3, 1e-2, 1e-1], [1e1, 1e2, 1e3])
sc.set_offsets([[1e1, 1e4], [1e2, 1e5]])
ax.relim()
ax.autoscale_view()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
assert xlim[0] <= 1e1 and xlim[1] >= 1e2
assert ylim[0] <= 1e4 and ylim[1] >= 1e5


def test_text_labelsize():
"""
tests for issue #1172
Expand Down
Loading