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

Appearance settings

Commit 74f6526

Browse filesBrowse 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
Copy full SHA for 74f6526

File tree

Expand file treeCollapse file tree

9 files changed

+1750
-1697
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+1750
-1697
lines changed

‎lib/matplotlib/axes/_base.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
+77-75Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.