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

Skip to content

Commit 99de892

Browse files
authored
Merge pull request #21442 from anntzer/xyzlim
Factor out common limits handling for x/y/z axes.
2 parents 60d0c33 + abd1f1a commit 99de892

File tree

7 files changed

+114
-333
lines changed

7 files changed

+114
-333
lines changed

lib/matplotlib/axes/_base.py

Lines changed: 10 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -3654,9 +3654,8 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False,
36543654
False turns off, None leaves unchanged.
36553655
36563656
xmin, xmax : float, optional
3657-
They are equivalent to left and right respectively,
3658-
and it is an error to pass both *xmin* and *left* or
3659-
*xmax* and *right*.
3657+
They are equivalent to left and right respectively, and it is an
3658+
error to pass both *xmin* and *left* or *xmax* and *right*.
36603659
36613660
Returns
36623661
-------
@@ -3691,76 +3690,18 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False,
36913690
present is on the right.
36923691
36933692
>>> set_xlim(5000, 0)
3694-
36953693
"""
36963694
if right is None and np.iterable(left):
36973695
left, right = left
36983696
if xmin is not None:
36993697
if left is not None:
3700-
raise TypeError('Cannot pass both `xmin` and `left`')
3698+
raise TypeError("Cannot pass both 'left' and 'xmin'")
37013699
left = xmin
37023700
if xmax is not None:
37033701
if right is not None:
3704-
raise TypeError('Cannot pass both `xmax` and `right`')
3702+
raise TypeError("Cannot pass both 'right' and 'xmax'")
37053703
right = xmax
3706-
3707-
self._process_unit_info([("x", (left, right))], convert=False)
3708-
left = self._validate_converted_limits(left, self.convert_xunits)
3709-
right = self._validate_converted_limits(right, self.convert_xunits)
3710-
3711-
if left is None or right is None:
3712-
# Axes init calls set_xlim(0, 1) before get_xlim() can be called,
3713-
# so only grab the limits if we really need them.
3714-
old_left, old_right = self.get_xlim()
3715-
if left is None:
3716-
left = old_left
3717-
if right is None:
3718-
right = old_right
3719-
3720-
if self.get_xscale() == 'log' and (left <= 0 or right <= 0):
3721-
# Axes init calls set_xlim(0, 1) before get_xlim() can be called,
3722-
# so only grab the limits if we really need them.
3723-
old_left, old_right = self.get_xlim()
3724-
if left <= 0:
3725-
_api.warn_external(
3726-
'Attempted to set non-positive left xlim on a '
3727-
'log-scaled axis.\n'
3728-
'Invalid limit will be ignored.')
3729-
left = old_left
3730-
if right <= 0:
3731-
_api.warn_external(
3732-
'Attempted to set non-positive right xlim on a '
3733-
'log-scaled axis.\n'
3734-
'Invalid limit will be ignored.')
3735-
right = old_right
3736-
if left == right:
3737-
_api.warn_external(
3738-
f"Attempting to set identical left == right == {left} results "
3739-
f"in singular transformations; automatically expanding.")
3740-
reverse = left > right
3741-
left, right = self.xaxis.get_major_locator().nonsingular(left, right)
3742-
left, right = self.xaxis.limit_range_for_scale(left, right)
3743-
# cast to bool to avoid bad interaction between python 3.8 and np.bool_
3744-
left, right = sorted([left, right], reverse=bool(reverse))
3745-
3746-
self._viewLim.intervalx = (left, right)
3747-
# Mark viewlims as no longer stale without triggering an autoscale.
3748-
for ax in self._shared_axes["x"].get_siblings(self):
3749-
ax._stale_viewlims["x"] = False
3750-
if auto is not None:
3751-
self._autoscaleXon = bool(auto)
3752-
3753-
if emit:
3754-
self.callbacks.process('xlim_changed', self)
3755-
# Call all of the other x-axes that are shared with this one
3756-
for other in self._shared_axes["x"].get_siblings(self):
3757-
if other is not self:
3758-
other.set_xlim(self.viewLim.intervalx,
3759-
emit=False, auto=auto)
3760-
if other.figure != self.figure:
3761-
other.figure.canvas.draw_idle()
3762-
self.stale = True
3763-
return left, right
3704+
return self.xaxis._set_lim(left, right, emit=emit, auto=auto)
37643705

37653706
get_xscale = _axis_method_wrapper("xaxis", "get_scale")
37663707

@@ -3985,9 +3926,8 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
39853926
*False* turns off, *None* leaves unchanged.
39863927
39873928
ymin, ymax : float, optional
3988-
They are equivalent to bottom and top respectively,
3989-
and it is an error to pass both *ymin* and *bottom* or
3990-
*ymax* and *top*.
3929+
They are equivalent to bottom and top respectively, and it is an
3930+
error to pass both *ymin* and *bottom* or *ymax* and *top*.
39913931
39923932
Returns
39933933
-------
@@ -4027,71 +3967,13 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
40273967
bottom, top = bottom
40283968
if ymin is not None:
40293969
if bottom is not None:
4030-
raise TypeError('Cannot pass both `ymin` and `bottom`')
3970+
raise TypeError("Cannot pass both 'bottom' and 'ymin'")
40313971
bottom = ymin
40323972
if ymax is not None:
40333973
if top is not None:
4034-
raise TypeError('Cannot pass both `ymax` and `top`')
3974+
raise TypeError("Cannot pass both 'top' and 'ymax'")
40353975
top = ymax
4036-
4037-
self._process_unit_info([("y", (bottom, top))], convert=False)
4038-
bottom = self._validate_converted_limits(bottom, self.convert_yunits)
4039-
top = self._validate_converted_limits(top, self.convert_yunits)
4040-
4041-
if bottom is None or top is None:
4042-
# Axes init calls set_ylim(0, 1) before get_ylim() can be called,
4043-
# so only grab the limits if we really need them.
4044-
old_bottom, old_top = self.get_ylim()
4045-
if bottom is None:
4046-
bottom = old_bottom
4047-
if top is None:
4048-
top = old_top
4049-
4050-
if self.get_yscale() == 'log' and (bottom <= 0 or top <= 0):
4051-
# Axes init calls set_xlim(0, 1) before get_xlim() can be called,
4052-
# so only grab the limits if we really need them.
4053-
old_bottom, old_top = self.get_ylim()
4054-
if bottom <= 0:
4055-
_api.warn_external(
4056-
'Attempted to set non-positive bottom ylim on a '
4057-
'log-scaled axis.\n'
4058-
'Invalid limit will be ignored.')
4059-
bottom = old_bottom
4060-
if top <= 0:
4061-
_api.warn_external(
4062-
'Attempted to set non-positive top ylim on a '
4063-
'log-scaled axis.\n'
4064-
'Invalid limit will be ignored.')
4065-
top = old_top
4066-
if bottom == top:
4067-
_api.warn_external(
4068-
f"Attempting to set identical bottom == top == {bottom} "
4069-
f"results in singular transformations; automatically "
4070-
f"expanding.")
4071-
reverse = bottom > top
4072-
bottom, top = self.yaxis.get_major_locator().nonsingular(bottom, top)
4073-
bottom, top = self.yaxis.limit_range_for_scale(bottom, top)
4074-
# cast to bool to avoid bad interaction between python 3.8 and np.bool_
4075-
bottom, top = sorted([bottom, top], reverse=bool(reverse))
4076-
4077-
self._viewLim.intervaly = (bottom, top)
4078-
# Mark viewlims as no longer stale without triggering an autoscale.
4079-
for ax in self._shared_axes["y"].get_siblings(self):
4080-
ax._stale_viewlims["y"] = False
4081-
if auto is not None:
4082-
self._autoscaleYon = bool(auto)
4083-
4084-
if emit:
4085-
self.callbacks.process('ylim_changed', self)
4086-
# Call all of the other y-axes that are shared with this one
4087-
for other in self._shared_axes["y"].get_siblings(self):
4088-
if other is not self:
4089-
other.set_ylim(self.viewLim.intervaly,
4090-
emit=False, auto=auto)
4091-
if other.figure != self.figure:
4092-
other.figure.canvas.draw_idle()
4093-
self.stale = True
4094-
return bottom, top
3976+
return self.yaxis._set_lim(bottom, top, emit=emit, auto=auto)
40953977

40963978
get_yscale = _axis_method_wrapper("yaxis", "get_scale")
40973979

lib/matplotlib/axis.py

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,10 +1004,9 @@ def set_inverted(self, inverted):
10041004
the top for the y-axis; the "inverse" direction is increasing to the
10051005
left for the x-axis and to the bottom for the y-axis.
10061006
"""
1007-
# Currently, must be implemented in subclasses using set_xlim/set_ylim
1008-
# rather than generically using set_view_interval, so that shared
1009-
# axes get updated as well.
1010-
raise NotImplementedError('Derived must override')
1007+
a, b = self.get_view_interval()
1008+
# cast to bool to avoid bad interaction between python 3.8 and np.bool_
1009+
self._set_lim(*sorted((a, b), reverse=bool(inverted)), auto=None)
10111010

10121011
def set_default_intervals(self):
10131012
"""
@@ -1023,6 +1022,81 @@ def set_default_intervals(self):
10231022
# attribute, and the derived code below will check for that
10241023
# and use it if it's available (else just use 0..1)
10251024

1025+
def _set_lim(self, v0, v1, *, emit=True, auto):
1026+
"""
1027+
Set view limits.
1028+
1029+
This method is a helper for the Axes ``set_xlim``, ``set_ylim``, and
1030+
``set_zlim`` methods.
1031+
1032+
Parameters
1033+
----------
1034+
v0, v1 : float
1035+
The view limits. (Passing *v0* as a (low, high) pair is not
1036+
supported; normalization must occur in the Axes setters.)
1037+
emit : bool, default: True
1038+
Whether to notify observers of limit change.
1039+
auto : bool or None, default: False
1040+
Whether to turn on autoscaling of the x-axis. True turns on, False
1041+
turns off, None leaves unchanged.
1042+
"""
1043+
name, = [name for name, axis in self.axes._get_axis_map().items()
1044+
if axis is self] # The axis name.
1045+
1046+
self.axes._process_unit_info([(name, (v0, v1))], convert=False)
1047+
v0 = self.axes._validate_converted_limits(v0, self.convert_units)
1048+
v1 = self.axes._validate_converted_limits(v1, self.convert_units)
1049+
1050+
if v0 is None or v1 is None:
1051+
# Axes init calls set_xlim(0, 1) before get_xlim() can be called,
1052+
# so only grab the limits if we really need them.
1053+
old0, old1 = self.get_view_interval()
1054+
if v0 is None:
1055+
v0 = old0
1056+
if v1 is None:
1057+
v1 = old1
1058+
1059+
if self.get_scale() == 'log' and (v0 <= 0 or v1 <= 0):
1060+
# Axes init calls set_xlim(0, 1) before get_xlim() can be called,
1061+
# so only grab the limits if we really need them.
1062+
old0, old1 = self.get_view_interval()
1063+
if v0 <= 0:
1064+
_api.warn_external(f"Attempt to set non-positive {name}lim on "
1065+
f"a log-scaled axis will be ignored.")
1066+
v0 = old0
1067+
if v1 <= 0:
1068+
_api.warn_external(f"Attempt to set non-positive {name}lim on "
1069+
f"a log-scaled axis will be ignored.")
1070+
v1 = old1
1071+
if v0 == v1:
1072+
_api.warn_external(
1073+
f"Attempting to set identical low and high {name}lims "
1074+
f"makes transformation singular; automatically expanding.")
1075+
reverse = bool(v0 > v1) # explicit cast needed for python3.8+np.bool_.
1076+
v0, v1 = self.get_major_locator().nonsingular(v0, v1)
1077+
v0, v1 = self.limit_range_for_scale(v0, v1)
1078+
v0, v1 = sorted([v0, v1], reverse=bool(reverse))
1079+
1080+
self.set_view_interval(v0, v1, ignore=True)
1081+
# Mark viewlims as no longer stale without triggering an autoscale.
1082+
for ax in self.axes._shared_axes[name].get_siblings(self.axes):
1083+
ax._stale_viewlims[name] = False
1084+
if auto is not None:
1085+
setattr(self.axes, f"_autoscale{name.upper()}on", bool(auto))
1086+
1087+
if emit:
1088+
self.axes.callbacks.process(f"{name}lim_changed", self.axes)
1089+
# Call all of the other axes that are shared with this one
1090+
for other in self.axes._shared_axes[name].get_siblings(self.axes):
1091+
if other is not self.axes:
1092+
other._get_axis_map()[name]._set_lim(
1093+
v0, v1, emit=False, auto=auto)
1094+
if other.figure != self.figure:
1095+
other.figure.canvas.draw_idle()
1096+
1097+
self.stale = True
1098+
return v0, v1
1099+
10261100
def _set_artist_props(self, a):
10271101
if a is None:
10281102
return
@@ -2242,12 +2316,6 @@ def get_ticks_position(self):
22422316
def get_minpos(self):
22432317
return self.axes.dataLim.minposx
22442318

2245-
def set_inverted(self, inverted):
2246-
# docstring inherited
2247-
a, b = self.get_view_interval()
2248-
# cast to bool to avoid bad interaction between python 3.8 and np.bool_
2249-
self.axes.set_xlim(sorted((a, b), reverse=bool(inverted)), auto=None)
2250-
22512319
def set_default_intervals(self):
22522320
# docstring inherited
22532321
# only change view if dataLim has not changed and user has
@@ -2500,12 +2568,6 @@ def get_ticks_position(self):
25002568
def get_minpos(self):
25012569
return self.axes.dataLim.minposy
25022570

2503-
def set_inverted(self, inverted):
2504-
# docstring inherited
2505-
a, b = self.get_view_interval()
2506-
# cast to bool to avoid bad interaction between python 3.8 and np.bool_
2507-
self.axes.set_ylim(sorted((a, b), reverse=bool(inverted)), auto=None)
2508-
25092571
def set_default_intervals(self):
25102572
# docstring inherited
25112573
# only change view if dataLim has not changed and user has

lib/matplotlib/projections/polar.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,7 +1194,7 @@ def set_rlim(self, bottom=None, top=None, emit=True, auto=False, **kwargs):
11941194
def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
11951195
*, ymin=None, ymax=None):
11961196
"""
1197-
Set the data limits for the radial axis.
1197+
Set the view limits for the radial axis.
11981198
11991199
Parameters
12001200
----------
@@ -1227,21 +1227,7 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
12271227
bottom, top : (float, float)
12281228
The new y-axis limits in data coordinates.
12291229
"""
1230-
if ymin is not None:
1231-
if bottom is not None:
1232-
raise ValueError('Cannot supply both positional "bottom" '
1233-
'argument and kwarg "ymin"')
1234-
else:
1235-
bottom = ymin
1236-
if ymax is not None:
1237-
if top is not None:
1238-
raise ValueError('Cannot supply both positional "top" '
1239-
'argument and kwarg "ymax"')
1240-
else:
1241-
top = ymax
1242-
if top is None and np.iterable(bottom):
1243-
bottom, top = bottom[0], bottom[1]
1244-
return super().set_ylim(bottom=bottom, top=top, emit=emit, auto=auto)
1230+
return super().set_ylim(bottom, top, emit, auto, ymin=ymin, ymax=ymax)
12451231

12461232
def get_rlabel_position(self):
12471233
"""

lib/matplotlib/tests/test_axes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2591,10 +2591,10 @@ def test_log_scales_no_data():
25912591
def test_log_scales_invalid():
25922592
fig, ax = plt.subplots()
25932593
ax.set_xscale('log')
2594-
with pytest.warns(UserWarning, match='Attempted to set non-positive'):
2594+
with pytest.warns(UserWarning, match='Attempt to set non-positive'):
25952595
ax.set_xlim(-1, 10)
25962596
ax.set_yscale('log')
2597-
with pytest.warns(UserWarning, match='Attempted to set non-positive'):
2597+
with pytest.warns(UserWarning, match='Attempt to set non-positive'):
25982598
ax.set_ylim(-1, 10)
25992599

26002600

lib/matplotlib/tests/test_dates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ def test_too_many_date_ticks(caplog):
182182
with pytest.warns(UserWarning) as rec:
183183
ax.set_xlim((t0, tf), auto=True)
184184
assert len(rec) == 1
185-
assert \
186-
'Attempting to set identical left == right' in str(rec[0].message)
185+
assert ('Attempting to set identical low and high xlims'
186+
in str(rec[0].message))
187187
ax.plot([], [])
188188
ax.xaxis.set_major_locator(mdates.DayLocator())
189189
v = ax.xaxis.get_major_locator()()

lib/matplotlib/tests/test_image.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ def test_imshow_bignumbers_real():
977977
def test_empty_imshow(make_norm):
978978
fig, ax = plt.subplots()
979979
with pytest.warns(UserWarning,
980-
match="Attempting to set identical left == right"):
980+
match="Attempting to set identical low and high xlims"):
981981
im = ax.imshow([[]], norm=make_norm())
982982
im.set_extent([-5, 5, -5, 5])
983983
fig.canvas.draw()

0 commit comments

Comments
 (0)