Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitae58960

Browse files
committed
Prevent reuse of certain Locators and Formatters over multiple Axises.
The call to _wrap_locator_formatter was deleted from ThetaAxis.gca()because it is redundant with the call in ThetaAxis._set_scale (whichgca() calls), and would cause double wrapping with _AxisWrapper, causingissues when checking for axis equality.
1 parentac07e81 commitae58960

File tree

5 files changed

+103
-14
lines changed

5 files changed

+103
-14
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Axis-dependent Locators and Formatters explicitly error out when used over multiple Axis
2+
````````````````````````````````````````````````````````````````````````````````````````
3+
4+
Certain Locators and Formatters (e.g. the default `AutoLocator` and
5+
`ScalarFormatter`) can only be used meaningfully on one Axis object at a time
6+
(i.e., attempting to use a single `AutoLocator` instance on the x and the y
7+
axis of an Axes, or the x axis of two different Axes, would result in
8+
nonsensical results).
9+
10+
Such "double-use" is now detected and raises a RuntimeError *at canvas draw
11+
time*. The exception is not raised when the second Axis is registered in order
12+
to avoid incorrectly raising exceptions for the Locators and Formatters that
13+
*can* be used on multiple Axis objects simultaneously (e.g. `NullLocator` and
14+
`FuncFormatter`).
15+
16+
In case a Locator or a Formatter really needs to be reassigned from one axis to
17+
another, first set its axis to None to bypass this protection.

‎lib/matplotlib/figure.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,11 +1589,19 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False,
15891589
returnaxarr
15901590

15911591
def_remove_ax(self,ax):
1592-
def_reset_loc_form(axis):
1593-
axis.set_major_formatter(axis.get_major_formatter())
1594-
axis.set_major_locator(axis.get_major_locator())
1595-
axis.set_minor_formatter(axis.get_minor_formatter())
1596-
axis.set_minor_locator(axis.get_minor_locator())
1592+
def_reset_tickers(axis):
1593+
major_formatter=axis.get_major_formatter()
1594+
major_formatter.set_axis(None)# Bypass prevention of axis reset.
1595+
axis.set_major_formatter(major_formatter)
1596+
major_locator=axis.get_major_locator()
1597+
major_locator.set_axis(None)
1598+
axis.set_major_locator(major_locator)
1599+
minor_formatter=axis.get_minor_formatter()
1600+
minor_formatter.set_axis(None)
1601+
axis.set_minor_formatter(minor_formatter)
1602+
minor_locator=axis.get_minor_locator()
1603+
minor_locator.set_axis(None)
1604+
axis.set_minor_locator(minor_locator)
15971605

15981606
def_break_share_link(ax,grouper):
15991607
siblings=grouper.get_siblings(ax)
@@ -1607,11 +1615,11 @@ def _break_share_link(ax, grouper):
16071615
self.delaxes(ax)
16081616
last_ax=_break_share_link(ax,ax._shared_y_axes)
16091617
iflast_axisnotNone:
1610-
_reset_loc_form(last_ax.yaxis)
1618+
_reset_tickers(last_ax.yaxis)
16111619

16121620
last_ax=_break_share_link(ax,ax._shared_x_axes)
16131621
iflast_axisnotNone:
1614-
_reset_loc_form(last_ax.xaxis)
1622+
_reset_tickers(last_ax.xaxis)
16151623

16161624
defclf(self,keep_observers=False):
16171625
"""

‎lib/matplotlib/projections/polar.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,14 @@ class _AxisWrapper(object):
198198
def__init__(self,axis):
199199
self._axis=axis
200200

201+
def__eq__(self,other):
202+
# Needed so that assignment, as the locator.axis attribute, of another
203+
# _AxisWrapper wrapping the same axis works.
204+
returnself._axis==other._axis
205+
206+
def__hash__(self):
207+
returnhash((type(self),*sorted(vars(self).items())))
208+
201209
defget_view_interval(self):
202210
returnnp.rad2deg(self._axis.get_view_interval())
203211

@@ -227,10 +235,11 @@ class ThetaLocator(mticker.Locator):
227235
"""
228236
def__init__(self,base):
229237
self.base=base
230-
self.axis=self.base.axis=_AxisWrapper(self.base.axis)
238+
self.set_axis(self.base.axis)
231239

232240
defset_axis(self,axis):
233-
self.axis=_AxisWrapper(axis)
241+
super().set_axis(_AxisWrapper(axis))
242+
self.base.set_axis(None)# Bypass prevention of axis resetting.
234243
self.base.set_axis(self.axis)
235244

236245
def__call__(self):
@@ -383,7 +392,6 @@ def _wrap_locator_formatter(self):
383392
defcla(self):
384393
super().cla()
385394
self.set_ticks_position('none')
386-
self._wrap_locator_formatter()
387395

388396
def_set_scale(self,value,**kwargs):
389397
super()._set_scale(value,**kwargs)

‎lib/matplotlib/tests/test_ticker.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,3 +897,29 @@ def minorticksubplot(xminor, yminor, i):
897897
minorticksubplot(True,False,2)
898898
minorticksubplot(False,True,3)
899899
minorticksubplot(True,True,4)
900+
901+
902+
deftest_multiple_assignment():
903+
fig=plt.figure()
904+
905+
ax=fig.subplots()
906+
fmt=mticker.NullFormatter()
907+
ax.xaxis.set_major_formatter(fmt)
908+
ax.yaxis.set_major_formatter(fmt)
909+
fig.canvas.draw()# No error.
910+
fig.clf()
911+
912+
ax=fig.subplots()
913+
fmt=mticker.ScalarFormatter()
914+
ax.xaxis.set_major_formatter(fmt)
915+
ax.xaxis.set_minor_formatter(fmt)
916+
fig.canvas.draw()# No error.
917+
fig.clf()
918+
919+
ax=fig.subplots()
920+
fmt=mticker.ScalarFormatter()
921+
ax.xaxis.set_major_formatter(fmt)
922+
ax.yaxis.set_major_formatter(fmt)
923+
withpytest.raises(RuntimeError):
924+
fig.canvas.draw()
925+
fig.clf()

‎lib/matplotlib/ticker.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,41 @@ def get_tick_space(self):
217217
return9
218218

219219

220-
classTickHelper(object):
221-
axis=None
220+
classTickHelper:
221+
# TickHelpers that access their axis attribute can only be assigned to
222+
# one Axis at a time, but we don't know a priori whether they will (e.g.,
223+
# NullFormatter doesn't, but ScalarFormatter does). So keep track of all
224+
# Axises that a TickHelper is assigned to (in a set: a TickHelper could be
225+
# assigned both as major and minor helper on a single axis), but only error
226+
# out after multiple assignment when the attribute is accessed.
222227

223-
defset_axis(self,axis):
224-
self.axis=axis
228+
# As an escape hatch, allow resetting the axis by first setting it to None.
229+
230+
@property
231+
defaxis(self):
232+
# We can't set the '_set_axises' attribute in TickHelper.__init__
233+
# (without a deprecation period) because subclasses didn't have to call
234+
# super().__init__ so far so they likely didn't.
235+
set_axises=getattr(self,"_set_axises",set())
236+
iflen(set_axises)==0:
237+
returnNone
238+
eliflen(set_axises)==1:
239+
axis,=set_axises
240+
returnaxis
241+
else:
242+
raiseRuntimeError(
243+
f"The 'axis' attribute of this{type(self).__name__} object "
244+
f"has been set multiple times, but a{type(self).__name__} "
245+
f"can only be used for one Axis at a time")
246+
247+
@axis.setter
248+
defaxis(self,axis):
249+
ifnothasattr(self,"_set_axises")oraxisisNone:
250+
self._set_axises=set()
251+
ifaxisisnotNone:
252+
self._set_axises.add(axis)
253+
254+
set_axis=axis.fset
225255

226256
defcreate_dummy_axis(self,**kwargs):
227257
ifself.axisisNone:

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp