-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
BUG: Fix relim() to support Collection artists (scatter, etc.) #31530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
07127b6
1d8b229
3da28af
68a6269
bfdb6c9
ee070e3
a1c58ec
f90c147
0161751
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2432,6 +2432,10 @@ def add_collection(self, collection, autolim=True): | |
| ) | ||
| 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) | ||
|
|
||
| self.stale = True | ||
| return collection | ||
|
|
@@ -2638,15 +2642,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. | ||
|
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 | ||
|
|
@@ -2661,6 +2661,28 @@ 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): | ||
| datalim = artist.get_datalim(self.transData) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is on the wrong abstraction level and duplicates code from If you want a narrow fix, please add
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done — extracted _update_collection_limits() following the same pattern as _update_line_limits, _update_patch_limits, and _update_image_limits. Both add_collection() and relim() now call the helper instead of repeating the get_datalim / minpos-concatenation / contains_branch_separately / update_datalim block inline. See commit 68a6269. |
||
| points = datalim.get_points() | ||
| if not np.isinf(datalim.minpos).all(): | ||
| # As in add_collection: include minpos so that | ||
| # self.dataLim updates its own minpos, which ensures | ||
| # log scales see the correct minimum. | ||
| points = np.concatenate([points, | ||
| [datalim.minpos]]) | ||
| # Only update dataLim for x/y if the collection uses | ||
| # transData in that direction. | ||
| x_is_data, y_is_data = ( | ||
| artist.get_transform() | ||
| .contains_branch_separately(self.transData)) | ||
| ox_is_data, oy_is_data = ( | ||
| artist.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 update_datalim(self, xys, updatex=True, updatey=True): | ||
| """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. relim works on |
||
|
|
||
| # 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 | ||
|
|
||
There was a problem hiding this comment.
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_autoscaleflag, 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=Falseas special-cased backward-compatibility. Am I missing a point why we can't do that?