Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 35c29c2

Browse filesBrowse files
committed
ENH: add an inset_axes to the axes class
1 parent 691fb7f commit 35c29c2
Copy full SHA for 35c29c2

File tree

Expand file treeCollapse file tree

5 files changed

+371
-0
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+371
-0
lines changed

‎doc/api/axes_api.rst

Copy file name to clipboardExpand all lines: doc/api/axes_api.rst
+3Lines 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
+60Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_axes.py
+249Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,37 @@ def _plot_args_replacer(args, data):
8484
"multiple plotting calls instead.")
8585

8686

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

117+
90118
class Axes(_AxesBase):
91119
"""
92120
The :class:`Axes` contains most of the figure elements:
@@ -390,6 +418,227 @@ def legend(self, *args, **kwargs):
390418
def _remove_legend(self, legend):
391419
self.legend_ = None
392420

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

‎lib/matplotlib/axes/_base.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
+25Lines changed: 25 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):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.