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

Skip to content
Merged
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
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.
8 changes: 0 additions & 8 deletions lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,6 @@ def remove(self):
with `.FigureCanvasBase.draw_idle`. Call `~.axes.Axes.relim` to
update the Axes limits if desired.

Note: `~.axes.Axes.relim` will not see collections even if the
collection was added to the Axes with *autolim* = True.

Note: there is no support for removing the artist's legend entry.
"""

Expand Down Expand Up @@ -271,11 +268,6 @@ def remove(self):

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

def have_units(self):
"""Return whether units are set on any axis."""
Expand Down
67 changes: 41 additions & 26 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2386,10 +2386,21 @@ def add_collection(self, collection, autolim=True):
autolim : bool
Whether to update data and view limits.

If *False*, the collection does not take part in any limit
operations.

.. versionchanged:: 3.11

This now also updates the view limits, making explicit
calls to `~.Axes.autoscale_view` unnecessary.
Since 3.11 ``autolim=True`` matches the standard behavior
of other ``add_[artist]`` methods: Axes data and view limits
are both updated in the method, and the collection will
be considered in future data limit updates through
`.relim`.

Prior to matplotlib 3.11 this was only a one-time update
of the data limits. Updating view limits required an
explicit call to `~.Axes.autoscale_view`, and collections
did not take part in `.relim`.

As an implementation detail, the value "_datalim_only" is
supported to smooth the internal transition from pre-3.11
Expand All @@ -2407,29 +2418,12 @@ def add_collection(self, collection, autolim=True):
collection.set_clip_path(self.patch)

if autolim:
if autolim != "_datalim_only":
collection._set_in_autoscale(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()

Expand Down Expand Up @@ -2598,6 +2592,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 +2655,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 +2674,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
73 changes: 73 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6511,6 +6511,79 @@ 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()
expected = sc.get_datalim(ax.transData)
assert_allclose(ax.dataLim.get_points(), expected.get_points())
assert_allclose(ax.dataLim.minpos, expected.minpos)

# After updating offsets, relim should track the new data.
sc.set_offsets([[10, 20], [30, 40]])
ax.relim()
expected = sc.get_datalim(ax.transData)
assert_allclose(ax.dataLim.get_points(), expected.get_points())
assert_allclose(ax.dataLim.minpos, expected.minpos)

# visible_only=True should ignore hidden collections.
line, = ax.plot([0, 1], [0, 1])
sc.set_visible(False)
ax.relim(visible_only=True)
# With scatter hidden, limits should be driven by the line only.
assert_allclose(ax.dataLim.get_points(), [[0, 0], [1, 1]])
# minpos is the minimum *positive* value; line data [0, 1] gives 1.0.
assert_array_equal(ax.dataLim.minpos, [1., 1.])


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.plot([0, 1], [0, 1])
ax.relim()
expected = ax.dataLim.frozen()
# 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()
# dataLim must remain unchanged because autolim=False was requested.
assert_allclose(ax.dataLim.get_points(), expected.get_points())
assert_allclose(ax.dataLim.minpos, expected.minpos)


def test_relim_collection_log_scale():
# GH#30859 - relim() for Collection on a log-scaled axis should
# correctly propagate minpos into dataLim.
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()
expected = sc.get_datalim(ax.transData)
assert_allclose(ax.dataLim.get_points(), expected.get_points())
assert_allclose(ax.dataLim.minpos, expected.minpos)


def test_relim_collection_autoscale_view():
# GH#30859 - end-to-end: after set_offsets(), relim() + autoscale_view()
# must update the visible axis limits, not just dataLim.
fig, ax = plt.subplots()
sc = ax.scatter([], [])
xs = np.linspace(0, 10, 50)
sc.set_offsets(np.column_stack((xs, np.sin(xs))))
ax.relim()
ax.autoscale_view()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# autoscale_view adds a margin, so limits should comfortably contain data
assert xlim[0] <= 0 and xlim[1] >= 10, f"xlim should contain [0, 10], got {xlim}"
assert ylim[0] <= -1 and ylim[1] >= 1, f"ylim should contain [-1, 1], got {ylim}"


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