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 b712c7b

Browse filesBrowse files
committed
ENH add an inset_axes to the axes class
1 parent 7544c46 commit b712c7b
Copy full SHA for b712c7b

File tree

Expand file treeCollapse file tree

5 files changed

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

5 files changed

+373
-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_from_bounds
185+
Axes.indicate_inset_bounds
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_from_bounds([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_from_bounds
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
@@ -78,9 +78,37 @@ def _plot_args_replacer(args, data):
7878
"multiple plotting calls instead.")
7979

8080

81+
def _make_inset_locator(rect, trans, parent):
82+
"""
83+
Helper function to locate inset axes, used in
84+
`.Axes.inset_axes_from_bounds`.
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+
_rect = mtransforms.Bbox.from_bounds(*rect)
95+
_trans = trans
96+
_parent = parent
97+
98+
def inset_locator(ax, renderer):
99+
bbox = _rect
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_from_bounds(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_from_bounds([0.8, 0.1, 0.15, 0.15])
459+
axin2 = ax.inset_axes_from_bounds(
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_from_bounds')
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_bounds(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_from_bounds`).
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_bounds')
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_bounds(
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

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
+27Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.