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

Skip to content

Commit 5207b60

Browse files
committed
ENH: inset_axes anchored-artist API
1 parent 4bc1ca9 commit 5207b60

File tree

3 files changed

+208
-15
lines changed

3 files changed

+208
-15
lines changed

examples/subplots_axes_and_figures/zoom_inset_axes.py

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@ def get_demo_image():
1616
import numpy as np
1717
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
1818
z = np.load(f)
19+
Z2 = np.zeros([150, 150], dtype="d")
20+
ny, nx = z.shape
21+
Z2[30:30 + ny, 30:30 + nx] = z
22+
1923
# z is a numpy array of 15x15
20-
return z, (-3, 4, -4, 3)
24+
extent = (-3, 4, -4, 3)
25+
return Z2, extent
2126

2227
fig, ax = plt.subplots(figsize=[5, 4])
2328

2429
# make data
25-
Z, extent = get_demo_image()
26-
Z2 = np.zeros([150, 150], dtype="d")
27-
ny, nx = Z.shape
28-
Z2[30:30 + ny, 30:30 + nx] = Z
29-
30+
Z2, extent = get_demo_image()
3031
ax.imshow(Z2, extent=extent, interpolation="nearest",
3132
origin="lower")
3233

@@ -38,13 +39,61 @@ def get_demo_image():
3839
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
3940
axins.set_xlim(x1, x2)
4041
axins.set_ylim(y1, y2)
41-
axins.set_xticklabels('')
42-
axins.set_yticklabels('')
42+
axins.set_xticklabels(''); axins.set_yticklabels('')
4343

4444
ax.indicate_inset_zoom(axins)
45+
fig.canvas.draw()
46+
plt.show()
47+
48+
#############################################################################
49+
# There is a second interface that closely parallels the interface for
50+
# `~.axes.legend` whereby we specify a location for the inset axes using
51+
# a string code.
52+
53+
fig, ax = plt.subplots(figsize=[5, 4])
54+
55+
ax.imshow(Z2, extent=extent, interpolation="nearest",
56+
origin="lower")
4557

58+
# inset axes....
59+
axins = ax.inset_axes('NE', width=0.5, height=0.5)
60+
61+
axins.imshow(Z2, extent=extent, interpolation="nearest",
62+
origin="lower")
63+
# sub region of the original image
64+
axins.set_xlim(x1, x2)
65+
axins.set_ylim(y1, y2)
66+
axins.set_xticklabels('') ;axins.set_yticklabels('')
67+
68+
ax.indicate_inset_zoom(axins)
69+
fig.canvas.draw()
4670
plt.show()
4771

72+
#############################################################################
73+
# Its possible to use either form with a transform in data space instead of
74+
# in the axes-relative co-ordinates:
75+
76+
fig, ax = plt.subplots(figsize=[5, 4])
77+
78+
ax.imshow(Z2, extent=extent, interpolation="nearest",
79+
origin="lower")
80+
81+
# inset axes....
82+
axins = ax.inset_axes([-2.5, 0, 1.6, 1.6], transform=ax.transData)
83+
84+
axins.imshow(Z2, extent=extent, interpolation="nearest",
85+
origin="lower")
86+
# sub region of the original image
87+
axins.set_xlim(x1, x2)
88+
axins.set_ylim(y1, y2)
89+
axins.set_xticklabels(''); axins.set_yticklabels('')
90+
91+
ax.indicate_inset_zoom(axins)
92+
fig.canvas.draw()
93+
plt.show()
94+
95+
96+
4897
#############################################################################
4998
#
5099
# ------------

lib/matplotlib/axes/_axes.py

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,77 @@ def inset_locator(ax, renderer):
110110

111111
return inset_locator
112112

113+
def _make_inset_locator_anchored(loc, borderpad, width, height,
114+
bbox_to_anchor, transform, parent):
115+
"""
116+
Helper function to locate inset axes, used in
117+
`.Axes.inset_axes`.
118+
119+
A locator gets used in `Axes.set_aspect` to override the default
120+
locations... It is a function that takes an axes object and
121+
a renderer and tells `set_aspect` where it is to be placed.
122+
123+
Here *rect* is a rectangle [l, b, w, h] that specifies the
124+
location for the axes in the transform given by *trans* on the
125+
*parent*.
126+
"""
127+
codes = {'upper right': 'NE',
128+
'upper left': 'NW',
129+
'lower left': 'SW',
130+
'lower right': 'SE',
131+
'right': 'E',
132+
'left': 'W',
133+
'bottom': 'S',
134+
'top': 'N',
135+
'center left': 'W',
136+
'center right': 'E',
137+
'lower center': 'S',
138+
'upper center': 'N',
139+
}
140+
141+
if loc in codes:
142+
loc = codes[loc]
143+
if loc not in ['N', 'S', 'E', 'W', 'NE', 'NW', 'SE', 'SW']:
144+
warnings.warn(f'inset_axes location "{loc}" not recognized; '
145+
'Set to "NE".')
146+
loc = 'NE'
147+
_loc = loc
148+
_parent = parent
149+
_borderpadder = borderpad
150+
if _borderpadder is None:
151+
_borderpadder = _parent.xaxis.get_ticklabels()[0].get_size() * 0.5
152+
153+
_basebox = mtransforms.Bbox.from_bounds(0, 0, width, height)
154+
155+
_transform = transform
156+
if _transform is None:
157+
_transform = BboxTransformTo(self.parent.bbox)
158+
159+
_bbox_to_anchor = bbox_to_anchor
160+
if not isinstance(_bbox_to_anchor, mtransforms.BboxBase):
161+
try:
162+
l = len(_bbox_to_anchor)
163+
except TypeError:
164+
raise ValueError("Invalid argument for bbox : %s" % str(bbox))
165+
if l == 2:
166+
_bbox_to_anchor = [_bbox_to_anchor[0], _bbox_to_anchor[1],
167+
width, height]
168+
_bbox_to_anchor = mtransforms.Bbox.from_bounds(*_bbox_to_anchor)
169+
_bbox_to_anchor = mtransforms.TransformedBbox(_bbox_to_anchor,
170+
_transform)
171+
172+
def inset_locator(ax, renderer):
173+
bbox = mtransforms.TransformedBbox(_basebox, _transform)
174+
borderpad = renderer.points_to_pixels(_borderpadder)
175+
anchored_box = bbox.anchored(loc,
176+
container=_bbox_to_anchor.padded(-borderpad))
177+
tr = _parent.figure.transFigure.inverted()
178+
anchored_box = mtransforms.TransformedBbox(anchored_box, tr)
179+
180+
return anchored_box
181+
182+
return inset_locator
183+
113184

114185
# The axes module contains all the wrappers to plotting functions.
115186
# All the other methods should go in the _AxesBase class.
@@ -419,7 +490,8 @@ def _remove_legend(self, legend):
419490
self.legend_ = None
420491

421492
def inset_axes(self, bounds, *, transform=None, zorder=5,
422-
**kwargs):
493+
borderaxespad=None, width=None, height=None,
494+
bbox_to_anchor=None, **kwargs):
423495
"""
424496
Add a child inset axes to this existing axes.
425497
@@ -431,13 +503,28 @@ def inset_axes(self, bounds, *, transform=None, zorder=5,
431503
Parameters
432504
----------
433505
434-
bounds : [x0, y0, width, height]
435-
Lower-left corner of inset axes, and its width and height.
506+
bounds : [x0, y0, width, height] or string.
507+
If four-tupple: lower-left corner of inset axes, and its width and
508+
height.
509+
510+
If a string, then locations such as "NE", "N", "NW", "W", etc,
511+
of "upper right", "top", "upper left", etc (see `~.axes.legend`)
512+
for codes. (Note we do *not* support the numerical codes).
436513
437514
transform : `.Transform`
438515
Defaults to `ax.transAxes`, i.e. the units of *rect* are in
439516
axes-relative coordinates.
440517
518+
width, height : number
519+
width and height of the inset axes. Only used if ``bounds`` is
520+
a string. Units are set by ``transform``, and default to
521+
axes-relative co-ordinates.
522+
523+
borderaxespad : number
524+
If ``bounds`` is a string, this is the padding between the inset
525+
axes and the parent axes in points. Defaults to half the fontsize
526+
of the tick labels.
527+
441528
zorder : number
442529
Defaults to 5 (same as `.Axes.legend`). Adjust higher or lower
443530
to change whether it is above or below data plotted on the
@@ -470,9 +557,22 @@ def inset_axes(self, bounds, *, transform=None, zorder=5,
470557
transform = self.transAxes
471558
label = kwargs.pop('label', 'inset_axes')
472559

473-
# This puts the rectangle into figure-relative coordinates.
474-
inset_locator = _make_inset_locator(bounds, transform, self)
475-
bb = inset_locator(None, None)
560+
if isinstance(bounds, str):
561+
# i.e. NE, S, etc
562+
if width is None:
563+
width = 0.25
564+
if height is None:
565+
height = 0.25
566+
if bbox_to_anchor is None:
567+
bbox_to_anchor = self.bbox
568+
inset_locator = _make_inset_locator_anchored(bounds,
569+
borderaxespad, width, height, bbox_to_anchor,
570+
transform, self)
571+
else:
572+
# This puts the rectangle into figure-relative coordinates.
573+
inset_locator = _make_inset_locator(bounds, transform, self)
574+
575+
bb = inset_locator(None, self.figure.canvas.get_renderer())
476576

477577
inset_ax = Axes(self.figure, bb.bounds, zorder=zorder,
478578
label=label, **kwargs)
@@ -549,6 +649,7 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None,
549649

550650
# to make the axes connectors work, we need to apply the aspect to
551651
# the parent axes.
652+
552653
self.apply_aspect()
553654

554655
if transform is None:
@@ -563,7 +664,7 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None,
563664

564665
if inset_ax is not None:
565666
# want to connect the indicator to the rect....
566-
667+
inset_ax.apply_aspect()
567668
pos = inset_ax.get_position() # this is in fig-fraction.
568669
coordsA = 'axes fraction'
569670
connects = []

lib/matplotlib/tests/test_axes.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5782,3 +5782,46 @@ def test_zoom_inset():
57825782
[0.8425, 0.907692]])
57835783
np.testing.assert_allclose(axin1.get_position().get_points(),
57845784
xx, rtol=1e-4)
5785+
5786+
5787+
def test_inset_codes():
5788+
"Test that the inset codes put the inset where we want..."
5789+
fig, ax = plt.subplots()
5790+
poss = [[[0.415625, 0.686111],
5791+
[0.609375, 0.886111]],
5792+
[[0.695833, 0.686111],
5793+
[0.889583, 0.886111]],
5794+
[[0.695833, 0.4 ],
5795+
[0.889583, 0.6 ]],
5796+
[[0.695833, 0.113889],
5797+
[0.889583, 0.313889]],
5798+
[[0.415625, 0.113889],
5799+
[0.609375, 0.313889]],
5800+
[[0.135417, 0.113889],
5801+
[0.329167, 0.313889]],
5802+
[[0.135417, 0.4 ],
5803+
[0.329167, 0.6 ]],
5804+
[[0.135417, 0.686111],
5805+
[0.329167, 0.886111]]]
5806+
codes = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
5807+
for pos, code in zip(poss, codes):
5808+
axin1 = ax.inset_axes(code)
5809+
np.testing.assert_allclose(axin1.get_position().get_points(),
5810+
pos, rtol=1e-4)
5811+
del axin1
5812+
5813+
# test synonyms
5814+
syns = ['top', 'upper right', 'center right', 'lower right', 'bottom',
5815+
'lower left', 'center left', 'upper left']
5816+
codes = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
5817+
for syn, code in zip(syns, codes):
5818+
axin1 = ax.inset_axes(code)
5819+
axin2 = ax.inset_axes(syn)
5820+
np.testing.assert_allclose(axin1.get_position().get_points(),
5821+
axin2.get_position().get_points(), rtol=1e-4)
5822+
5823+
# test the borderaxespad
5824+
axin1 = ax.inset_axes('NE', borderaxespad=20)
5825+
pos = [[0.671528, 0.653704], [0.865278, 0.853704]]
5826+
np.testing.assert_allclose(axin1.get_position().get_points(),
5827+
pos, rtol=1e-4)

0 commit comments

Comments
 (0)