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

Skip to content

FIX: return the actual ax.get_window_extent #12716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions doc/api/next_api_changes/2018-11-04-JMK.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
get_window_extents changes:
---------------------------

`.matplotlib.axes.Axes.get_window_extent` used to return a bounding box
that was slightly larger than the axes, presumably to take into account
the ticks that may be on a spine. However, it was not scaling the tick sizes
according to the dpi of the canvas, and it did not check if the ticks were
visible, or on the spine.

Now `.matplotlib.axes.Axes.get_window_extent` just returns the axes extent
with no padding for ticks.

This affects `.matplotlib.axes.Axes.get_tightbbox` in cases where there are
outward ticks with no tick labels, and it also removes the (small) pad around
axes in that case.

`.spines.get_window_extent` now takes into account ticks that are on the
spine.
47 changes: 29 additions & 18 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,18 +576,21 @@ def __setstate__(self, state):

def get_window_extent(self, *args, **kwargs):
"""
get the axes bounding box in display space; *args* and
*kwargs* are empty
"""
bbox = self.bbox
x_pad = 0
if self.axison and self.xaxis.get_visible():
x_pad = self.xaxis.get_tick_padding()
y_pad = 0
if self.axison and self.yaxis.get_visible():
y_pad = self.yaxis.get_tick_padding()
return mtransforms.Bbox([[bbox.x0 - x_pad, bbox.y0 - y_pad],
[bbox.x1 + x_pad, bbox.y1 + y_pad]])
Return the axes bounding box in display space; *args* and *kwargs*
are empty.

This bounding box does not include the spines, ticks, ticklables,
or other labels. For a bounding box including these elements use
`~matplotlib.axes.Axes.get_tightbbox`.

See Also
--------
matplotlib.axes.Axes.get_tightbbox
matplotlib.axis.Axis.get_tightbbox
matplotlib.spines.get_window_extent

"""
return self.bbox

def _init_axis(self):
"move this out of __init__ because non-separable axes don't use it"
Expand Down Expand Up @@ -4286,6 +4289,13 @@ def get_tightbbox(self, renderer, call_axes_locator=True,
-------
bbox : `.BboxBase`
bounding box in figure pixel coordinates.

See Also
--------
matplotlib.axis.Axes.get_window_extent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These extra 'See also' sections with non functional API links are breaking more strict docs builds downstream.
https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.axes.Axes.get_tightbbox.html#matplotlib.axes.Axes.get_tightbbox

(We run the docs build in our CI in astropy with the -w option, so any sphinx warning is causing a failure (as our WCSAxes class is inherited from Axes and ultimately from this docstring).

If it's helpful I can open a separate issue for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new issue would get better traction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also run sphinx with -W in our options

SPHINXOPTS = -W --keep-going

so it is odd that astropy is failing but we are not...

I suspect difference is sphinx versions (we are currently pinning to sphinx < 2 due to weird spacing at TOC and some breakage (which may be this)).

It is also odd that this is not linking an the 'previous' link in the top right is the thing we want this to have linked to.

Agree with @QuLogic , this should get it's own issue.

matplotlib.axis.Axis.get_tightbbox
matplotlib.spines.get_window_extent

"""

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

bb_xaxis = self.xaxis.get_tightbbox(renderer)
if bb_xaxis:
bb.append(bb_xaxis)
if self.axison:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change (self.axison), caused a few tests top change. So this is probably a breaking change. OTOH, I think its correct - it doesn't make sense to include the axis in the tight layout if its been turned off.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about if not self.xaxis.get_visible()? (just asking)

bb_xaxis = self.xaxis.get_tightbbox(renderer)
if bb_xaxis:
bb.append(bb_xaxis)

bb_yaxis = self.yaxis.get_tightbbox(renderer)
if bb_yaxis:
bb.append(bb_yaxis)
bb_yaxis = self.yaxis.get_tightbbox(renderer)
if bb_yaxis:
bb.append(bb_yaxis)

self._update_title_position(renderer)
bb.append(self.get_window_extent(renderer))
Expand Down
52 changes: 51 additions & 1 deletion lib/matplotlib/spines.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,60 @@ def get_patch_transform(self):
return super().get_patch_transform()

def get_window_extent(self, renderer=None):
"""
Return the window extent of the spines in display space, including
padding for ticks (but not their labels)

See Also
--------
matplotlib.axes.Axes.get_tightbbox
matplotlib.axes.Axes.get_window_extent

"""
# make sure the location is updated so that transforms etc are
# correct:
self._adjust_location()
return super().get_window_extent(renderer=renderer)
bb = super().get_window_extent(renderer=renderer)
bboxes = [bb]
tickstocheck = [self.axis.majorTicks[0]]
if len(self.axis.minorTicks) > 1:
# only pad for minor ticks if there are more than one
# of them. There is always one...
tickstocheck.append(self.axis.minorTicks[1])
for tick in tickstocheck:
bb0 = bb.frozen()
tickl = tick._size
tickdir = tick._tickdir
if tickdir == 'out':
padout = 1
padin = 0
elif tickdir == 'in':
padout = 0
padin = 1
else:
padout = 0.5
padin = 0.5
padout = padout * tickl / 72 * self.figure.dpi
padin = padin * tickl / 72 * self.figure.dpi

if tick.tick1line.get_visible():
if self.spine_type in ['left']:
bb0.x0 = bb0.x0 - padout
bb0.x1 = bb0.x1 + padin
elif self.spine_type in ['bottom']:
bb0.y0 = bb0.y0 - padout
bb0.y1 = bb0.y1 + padin

if tick.tick2line.get_visible():
if self.spine_type in ['right']:
bb0.x1 = bb0.x1 + padout
bb0.x0 = bb0.x0 - padin
elif self.spine_type in ['top']:
bb0.y1 = bb0.y1 + padout
bb0.y0 = bb0.y0 - padout
bboxes.append(bb0)

return mtransforms.Bbox.union(bboxes)

def get_path(self):
return self._path
Expand Down
Binary file modified lib/matplotlib/tests/baseline_images/test_axes/secondary_xy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified lib/matplotlib/tests/baseline_images/test_table/table_labels.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified lib/matplotlib/tests/baseline_images/test_table/table_zorder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
201 changes: 199 additions & 2 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
import matplotlib.markers as mmarkers
import matplotlib.patches as mpatches
import matplotlib.colors as mcolors
import matplotlib.transforms as mtransforms
from numpy.testing import (
assert_allclose, assert_array_equal, assert_array_almost_equal)
from matplotlib import rc_context
from matplotlib.cbook import (
IgnoredKeywordWarning, MatplotlibDeprecationWarning)

Expand Down Expand Up @@ -5891,9 +5893,9 @@ def test_tick_padding_tightbbox():
plt.rcParams["xtick.direction"] = "out"
plt.rcParams["ytick.direction"] = "out"
fig, ax = plt.subplots()
bb = ax.get_window_extent(fig.canvas.get_renderer())
bb = ax.get_tightbbox(fig.canvas.get_renderer())
ax.axis('off')
bb2 = ax.get_window_extent(fig.canvas.get_renderer())
bb2 = ax.get_tightbbox(fig.canvas.get_renderer())
assert bb.x0 < bb2.x0
assert bb.y0 < bb2.y0

Expand Down Expand Up @@ -6063,3 +6065,198 @@ def invert(x):
fig.canvas.draw()
fig.set_size_inches((7, 4))
assert_allclose(ax.get_position().extents, [0.125, 0.1, 0.9, 0.9])


def color_boxes(fig, axs):
"""
Helper for the tests below that test the extents of various axes elements
"""
fig.canvas.draw()

renderer = fig.canvas.get_renderer()
bbaxis = []
for nn, axx in enumerate([axs.xaxis, axs.yaxis]):
bb = axx.get_tightbbox(renderer)
if bb:
axisr = plt.Rectangle((bb.x0, bb.y0), width=bb.width,
height=bb.height, linewidth=0.7, edgecolor='y',
facecolor="none", transform=None, zorder=3)
fig.add_artist(axisr)
bbaxis += [bb]

bbspines = []
for nn, a in enumerate(['bottom', 'top', 'left', 'right']):
bb = axs.spines[a].get_window_extent(renderer)
spiner = plt.Rectangle((bb.x0, bb.y0), width=bb.width,
height=bb.height, linewidth=0.7,
edgecolor="green", facecolor="none",
transform=None, zorder=3)
fig.add_artist(spiner)
bbspines += [bb]

bb = axs.get_window_extent()
rect2 = plt.Rectangle((bb.x0, bb.y0), width=bb.width, height=bb.height,
linewidth=1.5, edgecolor="magenta",
facecolor="none", transform=None, zorder=2)
fig.add_artist(rect2)
bbax = bb

bb2 = axs.get_tightbbox(renderer)
rect2 = plt.Rectangle((bb2.x0, bb2.y0), width=bb2.width,
height=bb2.height, linewidth=3, edgecolor="red",
facecolor="none", transform=None, zorder=1)
fig.add_artist(rect2)
bbtb = bb2
return bbaxis, bbspines, bbax, bbtb


def test_normal_axes():
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
fig.canvas.draw()
plt.close(fig)
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)

# test the axis bboxes
target = [
[123.375, 75.88888888888886, 983.25, 33.0],
[85.51388888888889, 99.99999999999997, 53.375, 993.0]
]
for nn, b in enumerate(bbaxis):
targetbb = mtransforms.Bbox.from_bounds(*target[nn])
assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=2)

target = [
[150.0, 119.999, 930.0, 11.111],
[150.0, 1080.0, 930.0, 0.0],
[150.0, 119.9999, 11.111, 960.0],
[1068.8888, 119.9999, 11.111, 960.0]
]
for nn, b in enumerate(bbspines):
targetbb = mtransforms.Bbox.from_bounds(*target[nn])
assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=2)

target = [150.0, 119.99999999999997, 930.0, 960.0]
targetbb = mtransforms.Bbox.from_bounds(*target)
assert_array_almost_equal(bbax.bounds, targetbb.bounds, decimal=2)

target = [85.5138, 75.88888, 1021.11, 1017.11]
targetbb = mtransforms.Bbox.from_bounds(*target)
assert_array_almost_equal(bbtb.bounds, targetbb.bounds, decimal=2)

# test that get_position roundtrips to get_window_extent
axbb = ax.get_position().transformed(fig.transFigure).bounds
assert_array_almost_equal(axbb, ax.get_window_extent().bounds, decimal=2)


def test_nodecorator():
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
fig.canvas.draw()
ax.set(xticklabels=[], yticklabels=[])
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)

# test the axis bboxes
target = [
None,
None
]
for nn, b in enumerate(bbaxis):
assert b is None

target = [
[150.0, 119.999, 930.0, 11.111],
[150.0, 1080.0, 930.0, 0.0],
[150.0, 119.9999, 11.111, 960.0],
[1068.8888, 119.9999, 11.111, 960.0]
]
for nn, b in enumerate(bbspines):
targetbb = mtransforms.Bbox.from_bounds(*target[nn])
assert_allclose(b.bounds, targetbb.bounds, atol=1e-2)

target = [150.0, 119.99999999999997, 930.0, 960.0]
targetbb = mtransforms.Bbox.from_bounds(*target)
assert_allclose(bbax.bounds, targetbb.bounds, atol=1e-2)

target = [150., 120., 930., 960.]
targetbb = mtransforms.Bbox.from_bounds(*target)
assert_allclose(bbtb.bounds, targetbb.bounds, atol=1e-2)


def test_displaced_spine():
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
ax.set(xticklabels=[], yticklabels=[])
ax.spines['bottom'].set_position(('axes', -0.1))
fig.canvas.draw()
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)

target = [
[150., 24., 930., 11.111111],
[150.0, 1080.0, 930.0, 0.0],
[150.0, 119.9999, 11.111, 960.0],
[1068.8888, 119.9999, 11.111, 960.0]
]
for nn, b in enumerate(bbspines):
targetbb = mtransforms.Bbox.from_bounds(*target[nn])

target = [150.0, 119.99999999999997, 930.0, 960.0]
targetbb = mtransforms.Bbox.from_bounds(*target)
assert_allclose(bbax.bounds, targetbb.bounds, atol=1e-2)

target = [150., 24., 930., 1056.]
targetbb = mtransforms.Bbox.from_bounds(*target)
assert_allclose(bbtb.bounds, targetbb.bounds, atol=1e-2)


def test_tickdirs():
"""
Switch the tickdirs and make sure the bboxes switch with them
"""
targets = [[[150.0, 120.0, 930.0, 11.1111],
[150.0, 120.0, 11.111, 960.0]],
[[150.0, 108.8889, 930.0, 11.111111111111114],
[138.889, 120, 11.111, 960.0]],
[[150.0, 114.44444444444441, 930.0, 11.111111111111114],
[144.44444444444446, 119.999, 11.111, 960.0]]]
for dnum, dirs in enumerate(['in', 'out', 'inout']):
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
ax.tick_params(direction=dirs)
fig.canvas.draw()
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
for nn, num in enumerate([0, 2]):
targetbb = mtransforms.Bbox.from_bounds(*targets[dnum][nn])
assert_allclose(bbspines[num].bounds, targetbb.bounds,
atol=1e-2)


def test_minor_accountedfor():
with rc_context({'_internal.classic_mode': False}):
fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
fig.canvas.draw()
ax.tick_params(which='both', direction='out')

bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
targets = [[150.0, 108.88888888888886, 930.0, 11.111111111111114],
[138.8889, 119.9999, 11.1111, 960.0]]
for n in range(2):
targetbb = mtransforms.Bbox.from_bounds(*targets[n])
assert_allclose(bbspines[n * 2].bounds, targetbb.bounds,
atol=1e-2)

fig, ax = plt.subplots(dpi=200, figsize=(6, 6))
fig.canvas.draw()
ax.tick_params(which='both', direction='out')
ax.minorticks_on()
ax.tick_params(axis='both', which='minor', length=30)
fig.canvas.draw()
bbaxis, bbspines, bbax, bbtb = color_boxes(fig, ax)
targets = [[150.0, 36.66666666666663, 930.0, 83.33333333333334],
[66.6667, 120.0, 83.3333, 960.0]]

for n in range(2):
targetbb = mtransforms.Bbox.from_bounds(*targets[n])
assert_allclose(bbspines[n * 2].bounds, targetbb.bounds,
atol=1e-2)
17 changes: 9 additions & 8 deletions lib/matplotlib/tests/test_tightlayout.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,15 @@ def test_outward_ticks():
ax.xaxis.set_tick_params(tickdir='out', length=32, width=3)
ax.yaxis.set_tick_params(tickdir='out', length=32, width=3)
plt.tight_layout()
assert_array_equal(
np.round([ax.get_position().get_points() for ax in fig.axes], 3),
# These values were obtained after visual checking that they correspond
# to a tight layouting that did take the ticks into account.
[[[0.091, 0.590], [0.437, 0.903]],
[[0.581, 0.590], [0.927, 0.903]],
[[0.091, 0.140], [0.437, 0.454]],
[[0.581, 0.140], [0.927, 0.454]]])
# These values were obtained after visual checking that they correspond
# to a tight layouting that did take the ticks into account.
ans = [[[0.091, 0.607], [0.433, 0.933]],
[[0.579, 0.607], [0.922, 0.933]],
[[0.091, 0.140], [0.433, 0.466]],
[[0.579, 0.140], [0.922, 0.466]]]
for nn, ax in enumerate(fig.axes):
assert_array_equal(np.round(ax.get_position().get_points(), 3),
ans[nn])


def add_offsetboxes(ax, size=10, margin=.1, color='black'):
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.