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

Skip to content

Commit d12b8f4

Browse files
authored
Merge pull request #12716 from jklymak/fix-proper-extents-axes
FIX: return the actual ax.get_window_extent
2 parents eb92f62 + c676f45 commit d12b8f4

File tree

14 files changed

+306
-29
lines changed

14 files changed

+306
-29
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
get_window_extents changes:
2+
---------------------------
3+
4+
`.matplotlib.axes.Axes.get_window_extent` used to return a bounding box
5+
that was slightly larger than the axes, presumably to take into account
6+
the ticks that may be on a spine. However, it was not scaling the tick sizes
7+
according to the dpi of the canvas, and it did not check if the ticks were
8+
visible, or on the spine.
9+
10+
Now `.matplotlib.axes.Axes.get_window_extent` just returns the axes extent
11+
with no padding for ticks.
12+
13+
This affects `.matplotlib.axes.Axes.get_tightbbox` in cases where there are
14+
outward ticks with no tick labels, and it also removes the (small) pad around
15+
axes in that case.
16+
17+
`.spines.get_window_extent` now takes into account ticks that are on the
18+
spine.

lib/matplotlib/axes/_base.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -576,18 +576,21 @@ def __setstate__(self, state):
576576

577577
def get_window_extent(self, *args, **kwargs):
578578
"""
579-
get the axes bounding box in display space; *args* and
580-
*kwargs* are empty
581-
"""
582-
bbox = self.bbox
583-
x_pad = 0
584-
if self.axison and self.xaxis.get_visible():
585-
x_pad = self.xaxis.get_tick_padding()
586-
y_pad = 0
587-
if self.axison and self.yaxis.get_visible():
588-
y_pad = self.yaxis.get_tick_padding()
589-
return mtransforms.Bbox([[bbox.x0 - x_pad, bbox.y0 - y_pad],
590-
[bbox.x1 + x_pad, bbox.y1 + y_pad]])
579+
Return the axes bounding box in display space; *args* and *kwargs*
580+
are empty.
581+
582+
This bounding box does not include the spines, ticks, ticklables,
583+
or other labels. For a bounding box including these elements use
584+
`~matplotlib.axes.Axes.get_tightbbox`.
585+
586+
See Also
587+
--------
588+
matplotlib.axes.Axes.get_tightbbox
589+
matplotlib.axis.Axis.get_tightbbox
590+
matplotlib.spines.get_window_extent
591+
592+
"""
593+
return self.bbox
591594

592595
def _init_axis(self):
593596
"move this out of __init__ because non-separable axes don't use it"
@@ -4286,6 +4289,13 @@ def get_tightbbox(self, renderer, call_axes_locator=True,
42864289
-------
42874290
bbox : `.BboxBase`
42884291
bounding box in figure pixel coordinates.
4292+
4293+
See Also
4294+
--------
4295+
matplotlib.axis.Axes.get_window_extent
4296+
matplotlib.axis.Axis.get_tightbbox
4297+
matplotlib.spines.get_window_extent
4298+
42894299
"""
42904300

42914301
bb = []
@@ -4300,13 +4310,14 @@ def get_tightbbox(self, renderer, call_axes_locator=True,
43004310
else:
43014311
self.apply_aspect()
43024312

4303-
bb_xaxis = self.xaxis.get_tightbbox(renderer)
4304-
if bb_xaxis:
4305-
bb.append(bb_xaxis)
4313+
if self.axison:
4314+
bb_xaxis = self.xaxis.get_tightbbox(renderer)
4315+
if bb_xaxis:
4316+
bb.append(bb_xaxis)
43064317

4307-
bb_yaxis = self.yaxis.get_tightbbox(renderer)
4308-
if bb_yaxis:
4309-
bb.append(bb_yaxis)
4318+
bb_yaxis = self.yaxis.get_tightbbox(renderer)
4319+
if bb_yaxis:
4320+
bb.append(bb_yaxis)
43104321

43114322
self._update_title_position(renderer)
43124323
bb.append(self.get_window_extent(renderer))

lib/matplotlib/spines.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,60 @@ def get_patch_transform(self):
145145
return super().get_patch_transform()
146146

147147
def get_window_extent(self, renderer=None):
148+
"""
149+
Return the window extent of the spines in display space, including
150+
padding for ticks (but not their labels)
151+
152+
See Also
153+
--------
154+
matplotlib.axes.Axes.get_tightbbox
155+
matplotlib.axes.Axes.get_window_extent
156+
157+
"""
148158
# make sure the location is updated so that transforms etc are
149159
# correct:
150160
self._adjust_location()
151-
return super().get_window_extent(renderer=renderer)
161+
bb = super().get_window_extent(renderer=renderer)
162+
bboxes = [bb]
163+
tickstocheck = [self.axis.majorTicks[0]]
164+
if len(self.axis.minorTicks) > 1:
165+
# only pad for minor ticks if there are more than one
166+
# of them. There is always one...
167+
tickstocheck.append(self.axis.minorTicks[1])
168+
for tick in tickstocheck:
169+
bb0 = bb.frozen()
170+
tickl = tick._size
171+
tickdir = tick._tickdir
172+
if tickdir == 'out':
173+
padout = 1
174+
padin = 0
175+
elif tickdir == 'in':
176+
padout = 0
177+
padin = 1
178+
else:
179+
padout = 0.5
180+
padin = 0.5
181+
padout = padout * tickl / 72 * self.figure.dpi
182+
padin = padin * tickl / 72 * self.figure.dpi
183+
184+
if tick.tick1line.get_visible():
185+
if self.spine_type in ['left']:
186+
bb0.x0 = bb0.x0 - padout
187+
bb0.x1 = bb0.x1 + padin
188+
elif self.spine_type in ['bottom']:
189+
bb0.y0 = bb0.y0 - padout
190+
bb0.y1 = bb0.y1 + padin
191+
192+
if tick.tick2line.get_visible():
193+
if self.spine_type in ['right']:
194+
bb0.x1 = bb0.x1 + padout
195+
bb0.x0 = bb0.x0 - padin
196+
elif self.spine_type in ['top']:
197+
bb0.y1 = bb0.y1 + padout
198+
bb0.y0 = bb0.y0 - padout
199+
bboxes.append(bb0)
200+
201+
return mtransforms.Bbox.union(bboxes)
152202

153203
def get_path(self):
154204
return self._path
Binary file not shown.

lib/matplotlib/tests/test_axes.py

Lines changed: 199 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import matplotlib.markers as mmarkers
2424
import matplotlib.patches as mpatches
2525
import matplotlib.colors as mcolors
26+
import matplotlib.transforms as mtransforms
2627
from numpy.testing import (
2728
assert_allclose, assert_array_equal, assert_array_almost_equal)
29+
from matplotlib import rc_context
2830
from matplotlib.cbook import (
2931
IgnoredKeywordWarning, MatplotlibDeprecationWarning)
3032

@@ -5902,9 +5904,9 @@ def test_tick_padding_tightbbox():
59025904
plt.rcParams["xtick.direction"] = "out"
59035905
plt.rcParams["ytick.direction"] = "out"
59045906
fig, ax = plt.subplots()
5905-
bb = ax.get_window_extent(fig.canvas.get_renderer())
5907+
bb = ax.get_tightbbox(fig.canvas.get_renderer())
59065908
ax.axis('off')
5907-
bb2 = ax.get_window_extent(fig.canvas.get_renderer())
5909+
bb2 = ax.get_tightbbox(fig.canvas.get_renderer())
59085910
assert bb.x0 < bb2.x0
59095911
assert bb.y0 < bb2.y0
59105912

@@ -6074,3 +6076,198 @@ def invert(x):
60746076
fig.canvas.draw()
60756077
fig.set_size_inches((7, 4))
60766078
assert_allclose(ax.get_position().extents, [0.125, 0.1, 0.9, 0.9])
6079+
6080+
6081+
def color_boxes(fig, axs):
6082+
"""
6083+
Helper for the tests below that test the extents of various axes elements
6084+
"""
6085+
fig.canvas.draw()
6086+
6087+
renderer = fig.canvas.get_renderer()
6088+
bbaxis = []
6089+
for nn, axx in enumerate([axs.xaxis, axs.yaxis]):
6090+
bb = axx.get_tightbbox(renderer)
6091+
if bb:
6092+
axisr = plt.Rectangle((bb.x0, bb.y0), width=bb.width,
6093+
height=bb.height, linewidth=0.7, edgecolor='y',
6094+
facecolor="none", transform=None, zorder=3)
6095+
fig.add_artist(axisr)
6096+
bbaxis += [bb]
6097+
6098+
bbspines = []
6099+
for nn, a in enumerate(['bottom', 'top', 'left', 'right']):
6100+
bb = axs.spines[a].get_window_extent(renderer)
6101+
spiner = plt.Rectangle((bb.x0, bb.y0), width=bb.width,
6102+
height=bb.height, linewidth=0.7,
6103+
edgecolor="green", facecolor="none",
6104+
transform=None, zorder=3)
6105+
fig.add_artist(spiner)
6106+
bbspines += [bb]
6107+
6108+
bb = axs.get_window_extent()
6109+
rect2 = plt.Rectangle((bb.x0, bb.y0), width=bb.width, height=bb.height,
6110+
linewidth=1.5, edgecolor="magenta",
6111+
facecolor="none", transform=None, zorder=2)
6112+
fig.add_artist(rect2)
6113+
bbax = bb
6114+
6115+
bb2 = axs.get_tightbbox(renderer)
6116+
rect2 = plt.Rectangle((bb2.x0, bb2.y0), width=bb2.width,
6117+
height=bb2.height, linewidth=3, edgecolor="red",
6118+
facecolor="none", transform=None, zorder=1)
6119+
fig.add_artist(rect2)
6120+
bbtb = bb2
6121+
return bbaxis, bbspines, bbax, bbtb
6122+
6123+
6124+
def test_normal_axes():
6125+
with rc_context({'_internal.classic_mode': False}):
6126+
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
6127+
fig.canvas.draw()
6128+
plt.close(fig)
6129+
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
6130+
6131+
# test the axis bboxes
6132+
target = [
6133+
[123.375, 75.88888888888886, 983.25, 33.0],
6134+
[85.51388888888889, 99.99999999999997, 53.375, 993.0]
6135+
]
6136+
for nn, b in enumerate(bbaxis):
6137+
targetbb = mtransforms.Bbox.from_bounds(*target[nn])
6138+
assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=2)
6139+
6140+
target = [
6141+
[150.0, 119.999, 930.0, 11.111],
6142+
[150.0, 1080.0, 930.0, 0.0],
6143+
[150.0, 119.9999, 11.111, 960.0],
6144+
[1068.8888, 119.9999, 11.111, 960.0]
6145+
]
6146+
for nn, b in enumerate(bbspines):
6147+
targetbb = mtransforms.Bbox.from_bounds(*target[nn])
6148+
assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=2)
6149+
6150+
target = [150.0, 119.99999999999997, 930.0, 960.0]
6151+
targetbb = mtransforms.Bbox.from_bounds(*target)
6152+
assert_array_almost_equal(bbax.bounds, targetbb.bounds, decimal=2)
6153+
6154+
target = [85.5138, 75.88888, 1021.11, 1017.11]
6155+
targetbb = mtransforms.Bbox.from_bounds(*target)
6156+
assert_array_almost_equal(bbtb.bounds, targetbb.bounds, decimal=2)
6157+
6158+
# test that get_position roundtrips to get_window_extent
6159+
axbb = ax.get_position().transformed(fig.transFigure).bounds
6160+
assert_array_almost_equal(axbb, ax.get_window_extent().bounds, decimal=2)
6161+
6162+
6163+
def test_nodecorator():
6164+
with rc_context({'_internal.classic_mode': False}):
6165+
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
6166+
fig.canvas.draw()
6167+
ax.set(xticklabels=[], yticklabels=[])
6168+
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
6169+
6170+
# test the axis bboxes
6171+
target = [
6172+
None,
6173+
None
6174+
]
6175+
for nn, b in enumerate(bbaxis):
6176+
assert b is None
6177+
6178+
target = [
6179+
[150.0, 119.999, 930.0, 11.111],
6180+
[150.0, 1080.0, 930.0, 0.0],
6181+
[150.0, 119.9999, 11.111, 960.0],
6182+
[1068.8888, 119.9999, 11.111, 960.0]
6183+
]
6184+
for nn, b in enumerate(bbspines):
6185+
targetbb = mtransforms.Bbox.from_bounds(*target[nn])
6186+
assert_allclose(b.bounds, targetbb.bounds, atol=1e-2)
6187+
6188+
target = [150.0, 119.99999999999997, 930.0, 960.0]
6189+
targetbb = mtransforms.Bbox.from_bounds(*target)
6190+
assert_allclose(bbax.bounds, targetbb.bounds, atol=1e-2)
6191+
6192+
target = [150., 120., 930., 960.]
6193+
targetbb = mtransforms.Bbox.from_bounds(*target)
6194+
assert_allclose(bbtb.bounds, targetbb.bounds, atol=1e-2)
6195+
6196+
6197+
def test_displaced_spine():
6198+
with rc_context({'_internal.classic_mode': False}):
6199+
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
6200+
ax.set(xticklabels=[], yticklabels=[])
6201+
ax.spines['bottom'].set_position(('axes', -0.1))
6202+
fig.canvas.draw()
6203+
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
6204+
6205+
target = [
6206+
[150., 24., 930., 11.111111],
6207+
[150.0, 1080.0, 930.0, 0.0],
6208+
[150.0, 119.9999, 11.111, 960.0],
6209+
[1068.8888, 119.9999, 11.111, 960.0]
6210+
]
6211+
for nn, b in enumerate(bbspines):
6212+
targetbb = mtransforms.Bbox.from_bounds(*target[nn])
6213+
6214+
target = [150.0, 119.99999999999997, 930.0, 960.0]
6215+
targetbb = mtransforms.Bbox.from_bounds(*target)
6216+
assert_allclose(bbax.bounds, targetbb.bounds, atol=1e-2)
6217+
6218+
target = [150., 24., 930., 1056.]
6219+
targetbb = mtransforms.Bbox.from_bounds(*target)
6220+
assert_allclose(bbtb.bounds, targetbb.bounds, atol=1e-2)
6221+
6222+
6223+
def test_tickdirs():
6224+
"""
6225+
Switch the tickdirs and make sure the bboxes switch with them
6226+
"""
6227+
targets = [[[150.0, 120.0, 930.0, 11.1111],
6228+
[150.0, 120.0, 11.111, 960.0]],
6229+
[[150.0, 108.8889, 930.0, 11.111111111111114],
6230+
[138.889, 120, 11.111, 960.0]],
6231+
[[150.0, 114.44444444444441, 930.0, 11.111111111111114],
6232+
[144.44444444444446, 119.999, 11.111, 960.0]]]
6233+
for dnum, dirs in enumerate(['in', 'out', 'inout']):
6234+
with rc_context({'_internal.classic_mode': False}):
6235+
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
6236+
ax.tick_params(direction=dirs)
6237+
fig.canvas.draw()
6238+
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
6239+
for nn, num in enumerate([0, 2]):
6240+
targetbb = mtransforms.Bbox.from_bounds(*targets[dnum][nn])
6241+
assert_allclose(bbspines[num].bounds, targetbb.bounds,
6242+
atol=1e-2)
6243+
6244+
6245+
def test_minor_accountedfor():
6246+
with rc_context({'_internal.classic_mode': False}):
6247+
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
6248+
fig.canvas.draw()
6249+
ax.tick_params(which='both', direction='out')
6250+
6251+
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
6252+
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
6253+
targets = [[150.0, 108.88888888888886, 930.0, 11.111111111111114],
6254+
[138.8889, 119.9999, 11.1111, 960.0]]
6255+
for n in range(2):
6256+
targetbb = mtransforms.Bbox.from_bounds(*targets[n])
6257+
assert_allclose(bbspines[n * 2].bounds, targetbb.bounds,
6258+
atol=1e-2)
6259+
6260+
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
6261+
fig.canvas.draw()
6262+
ax.tick_params(which='both', direction='out')
6263+
ax.minorticks_on()
6264+
ax.tick_params(axis='both', which='minor', length=30)
6265+
fig.canvas.draw()
6266+
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
6267+
targets = [[150.0, 36.66666666666663, 930.0, 83.33333333333334],
6268+
[66.6667, 120.0, 83.3333, 960.0]]
6269+
6270+
for n in range(2):
6271+
targetbb = mtransforms.Bbox.from_bounds(*targets[n])
6272+
assert_allclose(bbspines[n * 2].bounds, targetbb.bounds,
6273+
atol=1e-2)

lib/matplotlib/tests/test_tightlayout.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,15 @@ def test_outward_ticks():
184184
ax.xaxis.set_tick_params(tickdir='out', length=32, width=3)
185185
ax.yaxis.set_tick_params(tickdir='out', length=32, width=3)
186186
plt.tight_layout()
187-
assert_array_equal(
188-
np.round([ax.get_position().get_points() for ax in fig.axes], 3),
189-
# These values were obtained after visual checking that they correspond
190-
# to a tight layouting that did take the ticks into account.
191-
[[[0.091, 0.590], [0.437, 0.903]],
192-
[[0.581, 0.590], [0.927, 0.903]],
193-
[[0.091, 0.140], [0.437, 0.454]],
194-
[[0.581, 0.140], [0.927, 0.454]]])
187+
# These values were obtained after visual checking that they correspond
188+
# to a tight layouting that did take the ticks into account.
189+
ans = [[[0.091, 0.607], [0.433, 0.933]],
190+
[[0.579, 0.607], [0.922, 0.933]],
191+
[[0.091, 0.140], [0.433, 0.466]],
192+
[[0.579, 0.140], [0.922, 0.466]]]
193+
for nn, ax in enumerate(fig.axes):
194+
assert_array_equal(np.round(ax.get_position().get_points(), 3),
195+
ans[nn])
195196

196197

197198
def add_offsetboxes(ax, size=10, margin=.1, color='black'):

0 commit comments

Comments
 (0)