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 0abd014

Browse filesBrowse files
committed
ENH: add an inset_axes to the axes class
1 parent 0544616 commit 0abd014
Copy full SHA for 0abd014

File tree

Expand file treeCollapse file tree

6 files changed

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

6 files changed

+372
-0
lines changed

‎.flake8

Copy file name to clipboardExpand all lines: .flake8
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ per-file-ignores =
253253
examples/subplots_axes_and_figures/axes_zoom_effect.py: E402
254254
examples/subplots_axes_and_figures/demo_tight_layout.py: E402
255255
examples/subplots_axes_and_figures/two_scales.py: E402
256+
examples/subplots_axes_and_figures/zoom_inset_axes.py: E402
256257
examples/tests/backend_driver_sgskip.py: E402, E501
257258
examples/text_labels_and_annotations/annotation_demo.py: E501
258259
examples/text_labels_and_annotations/custom_legends.py: E402

‎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_from_bounds
185+
Axes.indicate_inset_bounds
186+
Axes.indicate_inset_zoom
184187

185188

186189
Fields
+61Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
extent = (-3, 4, -4, 3)
21+
return z, extent
22+
23+
fig, ax = plt.subplots(figsize=[5, 4])
24+
25+
# make data
26+
Z, extent = get_demo_image()
27+
Z2 = np.zeros([150, 150], dtype="d")
28+
ny, nx = Z.shape
29+
Z2[30:30 + ny, 30:30 + nx] = Z
30+
31+
ax.imshow(Z2, extent=extent, interpolation="nearest",
32+
origin="lower")
33+
34+
# inset axes....
35+
axins = ax.inset_axes_from_bounds([0.5, 0.5, 0.47, 0.47])
36+
axins.imshow(Z2, extent=extent, interpolation="nearest",
37+
origin="lower")
38+
# sub region of the original image
39+
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
40+
axins.set_xlim(x1, x2)
41+
axins.set_ylim(y1, y2)
42+
axins.set_xticklabels('')
43+
axins.set_yticklabels('')
44+
45+
ax.indicate_inset_zoom(axins)
46+
47+
plt.show()
48+
49+
#############################################################################
50+
#
51+
# ------------
52+
#
53+
# References
54+
# """"""""""
55+
#
56+
# The use of the following functions and methods is shown in this example:
57+
58+
import matplotlib
59+
matplotlib.axes.Axes.inset_axes_from_bounds
60+
matplotlib.axes.Axes.indicate_inset_zoom
61+
matplotlib.axes.Axes.imshow

‎lib/matplotlib/axes/_axes.py

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

8686

87+
def _make_inset_locator(rect, trans, parent):
88+
"""
89+
Helper function to locate inset axes, used in
90+
`.Axes.inset_axes_from_bounds`.
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+
_rect = mtransforms.Bbox.from_bounds(*rect)
101+
_trans = trans
102+
_parent = parent
103+
104+
def inset_locator(ax, renderer):
105+
bb = mtransforms.TransformedBbox(_rect, _trans)
106+
tr = _parent.figure.transFigure.inverted()
107+
bb = mtransforms.TransformedBbox(bb, tr)
108+
return bb
109+
110+
return inset_locator
111+
112+
87113
# The axes module contains all the wrappers to plotting functions.
88114
# All the other methods should go in the _AxesBase class.
89115

116+
90117
class Axes(_AxesBase):
91118
"""
92119
The :class:`Axes` contains most of the figure elements:
@@ -390,6 +417,227 @@ def legend(self, *args, **kwargs):
390417
def _remove_legend(self, legend):
391418
self.legend_ = None
392419

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