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

Skip to content

Commit 82b88b7

Browse files
committed
ENH: add an inset_axes to the axes class
1 parent 7544c46 commit 82b88b7

File tree

5 files changed

+373
-0
lines changed

5 files changed

+373
-0
lines changed

doc/api/axes_api.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ Text and Annotations
181181
Axes.text
182182
Axes.table
183183
Axes.arrow
184+
Axes.inset_axes
185+
Axes.indicate_inset
186+
Axes.indicate_inset_zoom
184187

185188

186189
Fields
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""
2+
======================
3+
Zoom region inset axes
4+
======================
5+
6+
Example of an inset axes and a rectangle showing where the zoom is located.
7+
8+
"""
9+
10+
import matplotlib.pyplot as plt
11+
import numpy as np
12+
13+
14+
def get_demo_image():
15+
from matplotlib.cbook import get_sample_data
16+
import numpy as np
17+
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
18+
z = np.load(f)
19+
# z is a numpy array of 15x15
20+
return z, (-3, 4, -4, 3)
21+
22+
fig, ax = plt.subplots(figsize=[5, 4])
23+
24+
# 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+
ax.imshow(Z2, extent=extent, interpolation="nearest",
31+
origin="lower")
32+
33+
# inset axes....
34+
axins = ax.inset_axes([0.5, 0.5, 0.47, 0.47])
35+
axins.imshow(Z2, extent=extent, interpolation="nearest",
36+
origin="lower")
37+
# sub region of the original image
38+
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
39+
axins.set_xlim(x1, x2)
40+
axins.set_ylim(y1, y2)
41+
axins.set_xticklabels('')
42+
axins.set_yticklabels('')
43+
44+
ax.indicate_inset_zoom(axins)
45+
46+
plt.show()
47+
48+
#############################################################################
49+
#
50+
# ------------
51+
#
52+
# References
53+
# """"""""""
54+
#
55+
# The use of the following functions and methods is shown in this example:
56+
57+
import matplotlib
58+
matplotlib.axes.Axes.inset_axes
59+
matplotlib.axes.Axes.indicate_inset_zoom
60+
matplotlib.axes.Axes.imshow

lib/matplotlib/axes/_axes.py

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,37 @@ def _plot_args_replacer(args, data):
7878
"multiple plotting calls instead.")
7979

8080

81+
def _make_inset_locator(bounds, trans, parent):
82+
"""
83+
Helper function to locate inset axes, used in
84+
`.Axes.inset_axes`.
85+
86+
A locator gets used in `Axes.set_aspect` to override the default
87+
locations... It is a function that takes an axes object and
88+
a renderer and tells `set_aspect` where it is to be placed.
89+
90+
Here *rect* is a rectangle [l, b, w, h] that specifies the
91+
location for the axes in the transform given by *trans* on the
92+
*parent*.
93+
"""
94+
_bounds = mtransforms.Bbox.from_bounds(*bounds)
95+
_trans = trans
96+
_parent = parent
97+
98+
def inset_locator(ax, renderer):
99+
bbox = _bounds
100+
bb = mtransforms.TransformedBbox(bbox, _trans)
101+
tr = _parent.figure.transFigure.inverted()
102+
bb = mtransforms.TransformedBbox(bb, tr)
103+
return bb
104+
105+
return inset_locator
106+
107+
81108
# The axes module contains all the wrappers to plotting functions.
82109
# All the other methods should go in the _AxesBase class.
83110

111+
84112
class Axes(_AxesBase):
85113
"""
86114
The :class:`Axes` contains most of the figure elements:
@@ -384,6 +412,227 @@ def legend(self, *args, **kwargs):
384412
def _remove_legend(self, legend):
385413
self.legend_ = None
386414

415+
def inset_axes(self, bounds, *, transform=None, zorder=5,
416+
**kwargs):
417+
"""
418+
Add a child inset axes to this existing axes.
419+
420+
Warnings
421+
--------
422+
423+
This method is experimental as of 3.0, and the API may change.
424+
425+
Parameters
426+
----------
427+
428+
bounds : [x0, y0, width, height]
429+
Lower-left corner of inset axes, and its width and height.
430+
431+
transform : `.Transform`
432+
Defaults to `ax.transAxes`, i.e. the units of *rect* are in
433+
axes-relative coordinates.
434+
435+
zorder : number
436+
Defaults to 5 (same as `.Axes.legend`). Adjust higher or lower
437+
to change whether it is above or below data plotted on the
438+
parent axes.
439+
440+
**kwargs
441+
442+
Other *kwargs* are passed on to the `axes.Axes` child axes.
443+
444+
Returns
445+
-------
446+
447+
Axes
448+
The created `.axes.Axes` instance.
449+
450+
Examples
451+
--------
452+
453+
This example makes two inset axes, the first is in axes-relative
454+
coordinates, and the second in data-coordinates::
455+
456+
fig, ax = plt.suplots()
457+
ax.plot(range(10))
458+
axin1 = ax.inset_axes([0.8, 0.1, 0.15, 0.15])
459+
axin2 = ax.inset_axes(
460+
[5, 7, 2.3, 2.3], transform=ax.transData)
461+
462+
"""
463+
if transform is None:
464+
transform = self.transAxes
465+
label = kwargs.pop('label', 'inset_axes')
466+
467+
# This puts the rectangle into figure-relative coordinates.
468+
inset_locator = _make_inset_locator(bounds, transform, self)
469+
bb = inset_locator(None, None)
470+
471+
inset_ax = Axes(self.figure, bb.bounds, zorder=zorder,
472+
label=label, **kwargs)
473+
474+
# this locator lets the axes move if in data coordinates.
475+
# it gets called in `ax.apply_aspect() (of all places)
476+
inset_ax.set_axes_locator(inset_locator)
477+
478+
self.add_child_axes(inset_ax)
479+
480+
return inset_ax
481+
482+
def indicate_inset(self, bounds, inset_ax=None, *, transform=None,
483+
facecolor='none', edgecolor='0.5', alpha=0.5,
484+
zorder=4.99, **kwargs):
485+
"""
486+
Add an inset indicator to the axes. This is a rectangle on the plot
487+
at the position indicated by *bounds* that optionally has lines that
488+
connect the rectangle to an inset axes
489+
(`.Axes.inset_axes`).
490+
491+
Warnings
492+
--------
493+
494+
This method is experimental as of 3.0, and the API may change.
495+
496+
497+
Parameters
498+
----------
499+
500+
bounds : [x0, y0, width, height]
501+
Lower-left corner of rectangle to be marked, and its width
502+
and height.
503+
504+
inset_ax : `.Axes`
505+
An optional inset axes to draw connecting lines to. Two lines are
506+
drawn connecting the indicator box to the inset axes on corners
507+
chosen so as to not overlap with the indicator box.
508+
509+
transform : `.Transform`
510+
Transform for the rectangle co-ordinates. Defaults to
511+
`ax.transAxes`, i.e. the units of *rect* are in axes-relative
512+
coordinates.
513+
514+
facecolor : Matplotlib color
515+
Facecolor of the rectangle (default 'none').
516+
517+
edgecolor : Matplotlib color
518+
Color of the rectangle and color of the connecting lines. Default
519+
is '0.5'.
520+
521+
alpha : number
522+
Transparency of the rectangle and connector lines. Default is 0.5.
523+
524+
zorder : number
525+
Drawing order of the rectangle and connector lines. Default is 4.99
526+
(just below the default level of inset axes).
527+
528+
**kwargs
529+
Other *kwargs* are passed on to the rectangle patch.
530+
531+
Returns
532+
-------
533+
534+
rectangle_patch: `.Patches.Rectangle`
535+
Rectangle artist.
536+
537+
connector_lines: 4-tuple of `.Patches.ConnectionPatch`
538+
One for each of four connector lines. Two are set with visibility
539+
to *False*, but the user can set the visibility to True if the
540+
automatic choice is not deemed correct.
541+
542+
"""
543+
544+
# to make the axes connectors work, we need to apply the aspect to
545+
# the parent axes.
546+
self.apply_aspect()
547+
548+
if transform is None:
549+
transform = self.transData
550+
label = kwargs.pop('label', 'indicate_inset')
551+
552+
xy = (bounds[0], bounds[1])
553+
rectpatch = mpatches.Rectangle(xy, bounds[2], bounds[3],
554+
facecolor=facecolor, edgecolor=edgecolor, alpha=alpha,
555+
zorder=zorder, label=label, transform=transform, **kwargs)
556+
self.add_patch(rectpatch)
557+
558+
if inset_ax is not None:
559+
# want to connect the indicator to the rect....
560+
561+
pos = inset_ax.get_position() # this is in fig-fraction.
562+
coordsA = 'axes fraction'
563+
connects = []
564+
xr = [bounds[0], bounds[0]+bounds[2]]
565+
yr = [bounds[1], bounds[1]+bounds[3]]
566+
for xc in range(2):
567+
for yc in range(2):
568+
xyA = (xc, yc)
569+
xyB = (xr[xc], yr[yc])
570+
connects += [mpatches.ConnectionPatch(xyA, xyB,
571+
'axes fraction', 'data',
572+
axesA=inset_ax, axesB=self, arrowstyle="-",
573+
zorder=zorder, edgecolor=edgecolor, alpha=alpha)]
574+
self.add_patch(connects[-1])
575+
# decide which two of the lines to keep visible....
576+
pos = inset_ax.get_position()
577+
bboxins = pos.transformed(self.figure.transFigure)
578+
rectbbox = mtransforms.Bbox.from_bounds(
579+
*bounds).transformed(transform)
580+
if rectbbox.x0 < bboxins.x0:
581+
sig = 1
582+
else:
583+
sig = -1
584+
if sig*rectbbox.y0 < sig*bboxins.y0:
585+
connects[0].set_visible(False)
586+
connects[3].set_visible(False)
587+
else:
588+
connects[1].set_visible(False)
589+
connects[2].set_visible(False)
590+
591+
return rectpatch, connects
592+
593+
def indicate_inset_zoom(self, inset_ax, **kwargs):
594+
"""
595+
Add an inset indicator rectangle to the axes based on the axis
596+
limits for an *inset_ax* and draw connectors between *inset_ax*
597+
and the rectangle.
598+
599+
Warnings
600+
--------
601+
602+
This method is experimental as of 3.0, and the API may change.
603+
604+
Parameters
605+
----------
606+
607+
inset_ax : `.Axes`
608+
Inset axes to draw connecting lines to. Two lines are
609+
drawn connecting the indicator box to the inset axes on corners
610+
chosen so as to not overlap with the indicator box.
611+
612+
**kwargs
613+
Other *kwargs* are passed on to `.Axes.inset_rectangle`
614+
615+
Returns
616+
-------
617+
618+
rectangle_patch: `.Patches.Rectangle`
619+
Rectangle artist.
620+
621+
connector_lines: 4-tuple of `.Patches.ConnectionPatch`
622+
One for each of four connector lines. Two are set with visibility
623+
to *False*, but the user can set the visibility to True if the
624+
automatic choice is not deemed correct.
625+
626+
"""
627+
628+
xlim = inset_ax.get_xlim()
629+
ylim = inset_ax.get_ylim()
630+
rect = [xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0]]
631+
rectpatch, connects = self.indicate_inset(
632+
rect, inset_ax, **kwargs)
633+
634+
return rectpatch, connects
635+
387636
def text(self, x, y, s, fontdict=None, withdash=False, **kwargs):
388637
"""
389638
Add text to the axes.

lib/matplotlib/axes/_base.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,7 @@ def cla(self):
10311031
self.artists = []
10321032
self.images = []
10331033
self._mouseover_set = _OrderedSet()
1034+
self.child_axes = []
10341035
self._current_image = None # strictly for pyplot via _sci, _gci
10351036
self.legend_ = None
10361037
self.collections = [] # collection.Collection instances
@@ -1807,6 +1808,27 @@ def add_artist(self, a):
18071808
self.stale = True
18081809
return a
18091810

1811+
def add_child_axes(self, ax):
1812+
"""
1813+
Add a :class:`~matplotlib.axes.Axesbase` instance
1814+
as a child to the axes.
1815+
1816+
Returns the added axes.
1817+
1818+
This is the lowlevel version. See `.axes.Axes.inset_axes`
1819+
"""
1820+
1821+
# normally axes have themselves as the axes, but these need to have
1822+
# their parent...
1823+
# Need to bypass the getter...
1824+
ax._axes = self
1825+
ax.stale_callback = martist._stale_axes_callback
1826+
1827+
self.child_axes.append(ax)
1828+
ax._remove_method = self.child_axes.remove
1829+
self.stale = True
1830+
return ax
1831+
18101832
def add_collection(self, collection, autolim=True):
18111833
"""
18121834
Add a :class:`~matplotlib.collections.Collection` instance
@@ -4073,9 +4095,12 @@ def get_children(self):
40734095
children.append(self._right_title)
40744096
children.extend(self.tables)
40754097
children.extend(self.images)
4098+
children.extend(self.child_axes)
4099+
40764100
if self.legend_ is not None:
40774101
children.append(self.legend_)
40784102
children.append(self.patch)
4103+
40794104
return children
40804105

40814106
def contains(self, mouseevent):
@@ -4160,6 +4185,8 @@ def get_tightbbox(self, renderer, call_axes_locator=True):
41604185
bb.append(child.get_window_extent(renderer))
41614186
elif isinstance(child, Legend) and child.get_visible():
41624187
bb.append(child._legend_box.get_window_extent(renderer))
4188+
elif isinstance(child, _AxesBase) and child.get_visible():
4189+
bb.append(child.get_tightbbox(renderer))
41634190

41644191
_bbox = mtransforms.Bbox.union(
41654192
[b for b in bb if b.width != 0 or b.height != 0])

0 commit comments

Comments
 (0)