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

Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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.
11 changes: 4 additions & 7 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,10 +268,10 @@ 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: 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
Comment thread
timhoffm marked this conversation as resolved.
Outdated

def have_units(self):
Expand Down
67 changes: 41 additions & 26 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from collections.abc import Iterable, Sequence
from contextlib import ExitStack
import functools
Expand Down Expand Up @@ -2386,10 +2386,21 @@
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
Comment thread
timhoffm marked this conversation as resolved.
Outdated
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 calls to `~.Axes.autoscale_view`, and collections
Comment thread
timhoffm marked this conversation as resolved.
Outdated
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 @@
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 @@
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 @@
"""
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 @@
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