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

Skip to content

Commit 74f6526

Browse files
committed
Simplify axes and axis clearing, sharing, and aspect ratio control
- Most of the unnecessary calls to Axis.cla() have been removed. - Axis sharing works across figures. - The "box-forced" adjustable is no longer needed. - Sharing both axes requires the use of "box", not "datalim". - A new "share" kwarg triggers synchronized setting of aspect ratio and adjustable in Axes within shared axis groups. - Added a test for axis sharing with aspect ratio setting. - Fixed and updated skew_rects test.
1 parent 166a144 commit 74f6526

File tree

9 files changed

+1750
-1697
lines changed

9 files changed

+1750
-1697
lines changed

lib/matplotlib/axes/_base.py

Lines changed: 77 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -488,25 +488,15 @@ def __init__(self, fig, rect,
488488
self._originalPosition = self._position.frozen()
489489
# self.set_axes(self)
490490
self.axes = self
491-
self.set_aspect('auto')
491+
self._aspect = 'auto'
492492
self._adjustable = 'box'
493-
self.set_anchor('C')
493+
self._anchor = 'C'
494494
self._sharex = sharex
495495
self._sharey = sharey
496496
if sharex is not None:
497497
self._shared_x_axes.join(self, sharex)
498-
if sharex._adjustable == 'box':
499-
sharex._adjustable = 'datalim'
500-
# warnings.warn(
501-
# 'shared axes: "adjustable" is being changed to "datalim"')
502-
self._adjustable = 'datalim'
503498
if sharey is not None:
504499
self._shared_y_axes.join(self, sharey)
505-
if sharey._adjustable == 'box':
506-
sharey._adjustable = 'datalim'
507-
# warnings.warn(
508-
# 'shared axes: "adjustable" is being changed to "datalim"')
509-
self._adjustable = 'datalim'
510500
self.set_label(label)
511501
self.set_figure(fig)
512502

@@ -537,7 +527,11 @@ def __init__(self, fig, rect,
537527
self._hold = True
538528

539529
self._connected = {} # a dict from events to (id, func)
540-
self.cla()
530+
try:
531+
self.cla(clear_axis=False) # new xaxis and yaxis are already cleared
532+
except TypeError:
533+
self.cla() # For Axes subclasses lacking clear_axis argument.
534+
541535
# funcs used to format x and y - fall back on major formatters
542536
self.fmt_xdata = None
543537
self.fmt_ydata = None
@@ -607,11 +601,11 @@ def get_window_extent(self, *args, **kwargs):
607601
def _init_axis(self):
608602
"move this out of __init__ because non-separable axes don't use it"
609603
self.xaxis = maxis.XAxis(self)
610-
self.spines['bottom'].register_axis(self.xaxis)
611-
self.spines['top'].register_axis(self.xaxis)
604+
self.spines['bottom'].register_axis(self.xaxis, clear_axis=False)
605+
self.spines['top'].register_axis(self.xaxis, clear_axis=False)
612606
self.yaxis = maxis.YAxis(self)
613-
self.spines['left'].register_axis(self.yaxis)
614-
self.spines['right'].register_axis(self.yaxis)
607+
self.spines['left'].register_axis(self.yaxis, clear_axis=False)
608+
self.spines['right'].register_axis(self.yaxis, clear_axis=False)
615609
self._update_transScale()
616610

617611
def set_figure(self, fig):
@@ -634,8 +628,7 @@ def set_figure(self, fig):
634628

635629
def _set_lim_and_transforms(self):
636630
"""
637-
set the *dataLim* and *viewLim*
638-
:class:`~matplotlib.transforms.Bbox` attributes and the
631+
set the *_xaxis_transform*, *_yaxis_transform*,
639632
*transScale*, *transData*, *transLimits* and *transAxes*
640633
transformations.
641634
@@ -952,7 +945,7 @@ def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'):
952945
('bottom', mspines.Spine.linear_spine(self, 'bottom')),
953946
('top', mspines.Spine.linear_spine(self, 'top'))])
954947

955-
def cla(self):
948+
def cla(self, clear_axis=True):
956949
"""Clear the current axes."""
957950
# Note: this is called by Axes.__init__()
958951

@@ -965,51 +958,46 @@ def cla(self):
965958
xaxis_visible = self.xaxis.get_visible()
966959
yaxis_visible = self.yaxis.get_visible()
967960

968-
self.xaxis.cla()
969-
self.yaxis.cla()
961+
# The axis cla() sets the scale and default locators and
962+
# formatters. It needs to know if the axis is shared
963+
# so that it can preserve the shared scale.
964+
shared_x = self._sharex.xaxis if self._sharex else None
965+
shared_y = self._sharey.yaxis if self._sharey else None
966+
967+
if clear_axis:
968+
self.xaxis.cla(shared_x)
969+
self.yaxis.cla(shared_y) #
970+
970971
for name, spine in six.iteritems(self.spines):
971-
spine.cla()
972+
spine.cla(clear_axis=False) # Clears only the position.
972973

973974
self.ignore_existing_data_limits = True
974975
self.callbacks = cbook.CallbackRegistry()
975976

976-
if self._sharex is not None:
977-
# major and minor are class instances with
977+
if shared_x is not None:
978+
# major and minor are axis.Ticker class instances with
978979
# locator and formatter attributes
979-
self.xaxis.major = self._sharex.xaxis.major
980-
self.xaxis.minor = self._sharex.xaxis.minor
980+
self.xaxis.major = shared_x.major
981+
self.xaxis.minor = shared_x.minor
981982
x0, x1 = self._sharex.get_xlim()
982983
self.set_xlim(x0, x1, emit=False, auto=None)
983-
self.xaxis._scale = mscale.scale_factory(
984-
self._sharex.xaxis.get_scale(), self.xaxis)
985984
else:
986-
self.xaxis._set_scale('linear')
987985
try:
988986
self.set_xlim(0, 1)
989987
except TypeError:
990988
pass
991989

992-
if self._sharey is not None:
993-
self.yaxis.major = self._sharey.yaxis.major
994-
self.yaxis.minor = self._sharey.yaxis.minor
990+
if shared_y is not None:
991+
self.yaxis.major = shared_y.major
992+
self.yaxis.minor = shared_y.minor
995993
y0, y1 = self._sharey.get_ylim()
996994
self.set_ylim(y0, y1, emit=False, auto=None)
997-
self.yaxis._scale = mscale.scale_factory(
998-
self._sharey.yaxis.get_scale(), self.yaxis)
999995
else:
1000-
self.yaxis._set_scale('linear')
1001996
try:
1002997
self.set_ylim(0, 1)
1003998
except TypeError:
1004999
pass
10051000

1006-
# update the minor locator for x and y axis based on rcParams
1007-
if (rcParams['xtick.minor.visible']):
1008-
self.xaxis.set_minor_locator(mticker.AutoMinorLocator())
1009-
1010-
if (rcParams['ytick.minor.visible']):
1011-
self.yaxis.set_minor_locator(mticker.AutoMinorLocator())
1012-
10131001
self._autoscaleXon = True
10141002
self._autoscaleYon = True
10151003
self._xmargin = rcParams['axes.xmargin']
@@ -1093,6 +1081,14 @@ def cla(self):
10931081
self.yaxis.set_visible(yaxis_visible)
10941082
self.patch.set_visible(patch_visible)
10951083

1084+
# It is not clear to me (EF) why this reset is needed here, but
1085+
# it does seem to be needed somewhere in this vicinity. Otherwise,
1086+
# setting tick rotation via set_params doesn't work until the
1087+
# first draw has occurred.
1088+
self.xaxis.reset_ticks()
1089+
self.yaxis.reset_ticks()
1090+
1091+
10961092
self.stale = True
10971093

10981094
@cbook.deprecated("2.1", alternative="Axes.patch")
@@ -1224,7 +1220,7 @@ def hold(self, b=None):
12241220
def get_aspect(self):
12251221
return self._aspect
12261222

1227-
def set_aspect(self, aspect, adjustable=None, anchor=None):
1223+
def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
12281224
"""
12291225
*aspect*
12301226
@@ -1245,12 +1241,9 @@ def set_aspect(self, aspect, adjustable=None, anchor=None):
12451241
============ =====================================
12461242
'box' change physical size of axes
12471243
'datalim' change xlim or ylim
1248-
'box-forced' same as 'box', but axes can be shared
12491244
============ =====================================
12501245
1251-
'box' does not allow axes sharing, as this can cause
1252-
unintended side effect. For cases when sharing axes is
1253-
fine, use 'box-forced'.
1246+
When both axes are shared, only 'box' is allowable.
12541247
12551248
*anchor*
12561249
@@ -1270,24 +1263,40 @@ def set_aspect(self, aspect, adjustable=None, anchor=None):
12701263
else:
12711264
self._aspect = float(aspect) # raise ValueError if necessary
12721265

1273-
if adjustable is not None:
1274-
self.set_adjustable(adjustable)
1266+
if share and self in self._shared_x_axes:
1267+
for ax in self._shared_x_axes.get_siblings(self):
1268+
ax._aspect = aspect
1269+
if share and self in self._shared_y_axes:
1270+
for ax in self._shared_y_axes.get_siblings(self):
1271+
ax._aspect = aspect
1272+
1273+
if adjustable is None:
1274+
adjustable = self._adjustable
1275+
self.set_adjustable(adjustable, share=share) # Always call this to handle sharing.
1276+
12751277
if anchor is not None:
12761278
self.set_anchor(anchor)
12771279
self.stale = True
12781280

12791281
def get_adjustable(self):
12801282
return self._adjustable
12811283

1282-
def set_adjustable(self, adjustable):
1284+
def set_adjustable(self, adjustable, share=False):
12831285
"""
1284-
ACCEPTS: [ 'box' | 'datalim' | 'box-forced']
1286+
ACCEPTS: [ 'box' | 'datalim']
12851287
"""
1288+
# FIXME: add box-forced deprecation
12861289
if adjustable in ('box', 'datalim', 'box-forced'):
1287-
if self in self._shared_x_axes or self in self._shared_y_axes:
1288-
if adjustable == 'box':
1290+
if self in self._shared_x_axes and self in self._shared_y_axes:
1291+
if adjustable == 'datalim':
12891292
raise ValueError(
1290-
'adjustable must be "datalim" for shared axes')
1293+
'adjustable must be "box" when both axes are shared')
1294+
if share and self in self._shared_x_axes:
1295+
for ax in self._shared_x_axes.get_siblings(self):
1296+
ax._adjustable = adjustable
1297+
if share and self in self._shared_y_axes:
1298+
for ax in self._shared_y_axes.get_siblings(self):
1299+
ax._adjustable = adjustable
12911300
self._adjustable = adjustable
12921301
else:
12931302
raise ValueError('argument must be "box", or "datalim"')
@@ -1387,14 +1396,6 @@ def apply_aspect(self, position=None):
13871396
else:
13881397
A = aspect
13891398

1390-
# Ensure at drawing time that any Axes involved in axis-sharing
1391-
# does not have its position changed.
1392-
if self in self._shared_x_axes or self in self._shared_y_axes:
1393-
if self._adjustable == 'box':
1394-
self._adjustable = 'datalim'
1395-
warnings.warn(
1396-
'shared axes: "adjustable" is being changed to "datalim"')
1397-
13981399
figW, figH = self.get_figure().get_size_inches()
13991400
fig_aspect = figH / figW
14001401
if self._adjustable in ['box', 'box-forced']:
@@ -1452,23 +1453,24 @@ def apply_aspect(self, position=None):
14521453
xm = 0
14531454
ym = 0
14541455

1455-
changex = (self in self._shared_y_axes and
1456-
self not in self._shared_x_axes)
1457-
changey = (self in self._shared_x_axes and
1458-
self not in self._shared_y_axes)
1459-
if changex and changey:
1460-
warnings.warn("adjustable='datalim' cannot work with shared "
1461-
"x and y axes")
1462-
return
1463-
if changex:
1456+
shared_x = self in self._shared_x_axes
1457+
shared_y = self in self._shared_y_axes
1458+
# Not sure whether we need this check:
1459+
if shared_x and shared_y:
1460+
raise RuntimeError("adjustable='datalim' is not allowed when both"
1461+
" axes are shared.")
1462+
1463+
# If y is shared, then we are only allowed to change x, etc.
1464+
if shared_y:
14641465
adjust_y = False
14651466
else:
14661467
if xmarg > xm and ymarg > ym:
14671468
adjy = ((Ymarg > 0 and y_expander < 0) or
14681469
(Xmarg < 0 and y_expander > 0))
14691470
else:
14701471
adjy = y_expander > 0
1471-
adjust_y = changey or adjy # (Ymarg > xmarg)
1472+
adjust_y = shared_x or adjy # (Ymarg > xmarg)
1473+
14721474
if adjust_y:
14731475
yc = 0.5 * (ymin + ymax)
14741476
y0 = yc - Ysize / 2.0
@@ -3977,9 +3979,9 @@ def twiny(self):
39773979
return ax2
39783980

39793981
def get_shared_x_axes(self):
3980-
'Return a copy of the shared axes Grouper object for x axes'
3982+
'Return a reference to the shared axes Grouper object for x axes'
39813983
return self._shared_x_axes
39823984

39833985
def get_shared_y_axes(self):
3984-
'Return a copy of the shared axes Grouper object for y axes'
3986+
'Return a reference to the shared axes Grouper object for y axes'
39853987
return self._shared_y_axes

0 commit comments

Comments
 (0)