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

Skip to content

Commit 0abd014

Browse files
committed
ENH: add an inset_axes to the axes class
1 parent 0544616 commit 0abd014

File tree

6 files changed

+372
-0
lines changed

6 files changed

+372
-0
lines changed

.flake8

Lines 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

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

185188

186189
Fields
Lines 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

Lines 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

Lines 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)