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

Skip to content

Commit c43c4b5

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

File tree

4 files changed

+219
-17
lines changed

4 files changed

+219
-17
lines changed

examples/subplots_axes_and_figures/zoom_inset_axes.py

Lines changed: 56 additions & 6 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

@@ -42,7 +43,56 @@ def get_demo_image():
4243
axins.set_yticklabels('')
4344

4445
ax.indicate_inset_zoom(axins)
46+
fig.canvas.draw()
47+
plt.show()
4548

49+
#############################################################################
50+
# There is a second interface that closely parallels the interface for
51+
# `~.axes.legend` whereby we specify a location for the inset axes using
52+
# a string code.
53+
54+
fig, ax = plt.subplots(figsize=[5, 4])
55+
56+
ax.imshow(Z2, extent=extent, interpolation="nearest",
57+
origin="lower")
58+
59+
# inset axes....
60+
axins = ax.inset_axes('NE', width=0.5, height=0.5)
61+
62+
axins.imshow(Z2, extent=extent, interpolation="nearest",
63+
origin="lower")
64+
# sub region of the original image
65+
axins.set_xlim(x1, x2)
66+
axins.set_ylim(y1, y2)
67+
axins.set_xticklabels('')
68+
axins.set_yticklabels('')
69+
70+
ax.indicate_inset_zoom(axins)
71+
fig.canvas.draw()
72+
plt.show()
73+
74+
#############################################################################
75+
# Its possible to use either form with a transform in data space instead of
76+
# in the axes-relative co-ordinates:
77+
78+
fig, ax = plt.subplots(figsize=[5, 4])
79+
80+
ax.imshow(Z2, extent=extent, interpolation="nearest",
81+
origin="lower")
82+
83+
# inset axes....
84+
axins = ax.inset_axes([-2.5, 0, 1.6, 1.6], transform=ax.transData)
85+
86+
axins.imshow(Z2, extent=extent, interpolation="nearest",
87+
origin="lower")
88+
# sub region of the original image
89+
axins.set_xlim(x1, x2)
90+
axins.set_ylim(y1, y2)
91+
axins.set_xticklabels('')
92+
axins.set_yticklabels('')
93+
94+
ax.indicate_inset_zoom(axins)
95+
fig.canvas.draw()
4696
plt.show()
4797

4898
#############################################################################

lib/matplotlib/axes/_axes.py

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def _make_inset_locator(bounds, trans, parent):
9090
`.Axes.inset_axes`.
9191
9292
A locator gets used in `Axes.set_aspect` to override the default
93-
locations... It is a function that takes an axes object and
93+
locations. It is a function that takes an axes object and
9494
a renderer and tells `set_aspect` where it is to be placed.
9595
9696
Here *rect* is a rectangle [l, b, w, h] that specifies the
@@ -111,6 +111,80 @@ def inset_locator(ax, renderer):
111111
return inset_locator
112112

113113

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

@@ -419,7 +493,8 @@ def _remove_legend(self, legend):
419493
self.legend_ = None
420494

421495
def inset_axes(self, bounds, *, transform=None, zorder=5,
422-
**kwargs):
496+
borderaxespad=None, width=None, height=None,
497+
bbox_to_anchor=None, **kwargs):
423498
"""
424499
Add a child inset axes to this existing axes.
425500
@@ -431,13 +506,28 @@ def inset_axes(self, bounds, *, transform=None, zorder=5,
431506
Parameters
432507
----------
433508
434-
bounds : [x0, y0, width, height]
435-
Lower-left corner of inset axes, and its width and height.
509+
bounds : [x0, y0, width, height] or string.
510+
If four-tupple: lower-left corner of inset axes, and its width and
511+
height.
512+
513+
If a string, then locations such as "NE", "N", "NW", "W", etc,
514+
or "upper right", "top", "upper left", etc (see `~.axes.legend`)
515+
for codes. (Note we do *not* support the numerical codes).
436516
437517
transform : `.Transform`
438518
Defaults to `ax.transAxes`, i.e. the units of *rect* are in
439519
axes-relative coordinates.
440520
521+
width, height : number
522+
width and height of the inset axes. Only used if ``bounds`` is
523+
a string. Units are set by ``transform``, and default to
524+
axes-relative co-ordinates.
525+
526+
borderaxespad : number
527+
If ``bounds`` is a string, this is the padding between the inset
528+
axes and the parent axes in points. Defaults to half the fontsize
529+
of the tick labels.
530+
441531
zorder : number
442532
Defaults to 5 (same as `.Axes.legend`). Adjust higher or lower
443533
to change whether it is above or below data plotted on the
@@ -470,9 +560,22 @@ def inset_axes(self, bounds, *, transform=None, zorder=5,
470560
transform = self.transAxes
471561
label = kwargs.pop('label', 'inset_axes')
472562

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

477580
inset_ax = Axes(self.figure, bb.bounds, zorder=zorder,
478581
label=label, **kwargs)
@@ -549,6 +652,7 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None,
549652

550653
# to make the axes connectors work, we need to apply the aspect to
551654
# the parent axes.
655+
552656
self.apply_aspect()
553657

554658
if transform is None:
@@ -563,7 +667,7 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None,
563667

564668
if inset_ax is not None:
565669
# want to connect the indicator to the rect....
566-
670+
inset_ax.apply_aspect()
567671
pos = inset_ax.get_position() # this is in fig-fraction.
568672
coordsA = 'axes fraction'
569673
connects = []

lib/matplotlib/backends/backend_qt5agg.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ def paintEvent(self, event):
4848
[[left, self.renderer.height - (top + height * self._dpi_ratio)],
4949
[left + width * self._dpi_ratio, self.renderer.height - top]])
5050
reg = self.copy_from_bbox(bbox)
51-
buf = reg.to_string_argb()
51+
# buf = reg.to_string_argb()
52+
buf = memoryview(reg)
5253
qimage = QtGui.QImage(
53-
buf, width * self._dpi_ratio, height * self._dpi_ratio,
54-
QtGui.QImage.Format_ARGB32)
54+
buf, buf.shape[1], buf.shape[0], QtGui.QImage.Format_RGBA8888)
5555
if hasattr(qimage, 'setDevicePixelRatio'):
5656
# Not available on Qt4 or some older Qt5.
5757
qimage.setDevicePixelRatio(self._dpi_ratio)

lib/matplotlib/tests/test_axes.py

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

0 commit comments

Comments
 (0)