From 60e9ec01c86b3060f6bffd4802502d31029d9b9b Mon Sep 17 00:00:00 2001 From: ImportanceOfBeingErnest Date: Thu, 12 Apr 2018 01:32:19 +0200 Subject: [PATCH] Fix inset_axes + doc --- examples/axes_grid1/inset_locator_demo.py | 152 +++++++++++++++---- examples/axes_grid1/inset_locator_demo2.py | 64 ++++++-- lib/mpl_toolkits/axes_grid1/inset_locator.py | 90 +++++++++-- lib/mpl_toolkits/tests/test_axes_grid1.py | 63 ++++++-- 4 files changed, 294 insertions(+), 75 deletions(-) diff --git a/examples/axes_grid1/inset_locator_demo.py b/examples/axes_grid1/inset_locator_demo.py index 0dfd611ef0d2..878d3a5b1b04 100644 --- a/examples/axes_grid1/inset_locator_demo.py +++ b/examples/axes_grid1/inset_locator_demo.py @@ -4,49 +4,141 @@ ================== """ + +############################################################################### +# The `.inset_locator`'s `~.inset_axes` allows to easily place insets in the +# corners of the axes by specifying a width and height and optionally +# a location (loc) which accepts locations as codes, similar to +# `~matplotlib.axes.Axes.legend`. +# By default, the inset is offset by some points from the axes - this is +# controlled via the `borderpad` parameter. + import matplotlib.pyplot as plt +from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + +fig, (ax, ax2) = plt.subplots(1, 2, figsize=[5.5, 2.8]) + +# Create inset of width 1.3 inches and height 0.9 inches +# at the default upper right location +axins = inset_axes(ax, width=1.3, height=0.9) + +# Create inset of width 30% and height 40% of the parent axes' bounding box +# at the lower left corner (loc=3) +axins2 = inset_axes(ax, width="30%", height="40%", loc=3) + +# Create inset of mixed specifications in the second subplot; +# width is 30% of parent axes' bounding box and +# height is 1 inch at the upper left corner (loc=2) +axins3 = inset_axes(ax2, width="30%", height=1., loc=2) + +# Create an inset in the lower right corner (loc=4) with borderpad=1, i.e. +# 10 points padding (as 10pt is the default fontsize) to the parent axes +axins4 = inset_axes(ax2, width="20%", height="20%", loc=4, borderpad=1) + +# Turn ticklabels of insets off +for axi in [axins, axins2, axins3, axins4]: + axi.tick_params(labelleft=False, labelbottom=False) + +plt.show() -from mpl_toolkits.axes_grid1.inset_locator import inset_axes, zoomed_inset_axes -from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar +############################################################################### +# The arguments `bbox_to_anchor` and `bbox_transfrom` can be used for a more +# fine grained control over the inset position and size or even to position +# the inset at completely arbitrary positions. +# The `bbox_to_anchor` sets the bounding box in coordinates according to the +# `bbox_transform`. +# -def add_sizebar(ax, size): - asb = AnchoredSizeBar(ax.transData, - size, - str(size), - loc='lower center', - pad=0.1, borderpad=0.5, sep=5, - frameon=False) - ax.add_artist(asb) +fig = plt.figure(figsize=[5.5, 2.8]) +ax = fig.add_subplot(121) +# We use the axes transform as bbox_transform. Therefore the bounding box +# needs to be specified in axes coordinates ((0,0) is the lower left corner +# of the axes, (1,1) is the upper right corner). +# The bounding box (.2, .4, .6, .5) starts at (.2,.4) and ranges to (.8,.9) +# in those coordinates. +# Inside of this bounding box an inset of half the bounding box' width and +# three quarters of the bounding box' height is created. The lower left corner +# of the inset is aligned to the lower left corner of the bounding box (loc=3). +# The inset is then offset by the default 0.5 in units of the font size. + +axins = inset_axes(ax, width="50%", height="75%", + bbox_to_anchor=(.2, .4, .6, .5), + bbox_transform=ax.transAxes, loc=3) + +# For visualization purposes we mark the bounding box by a rectangle +ax.add_patch(plt.Rectangle((.2, .4), .6, .5, ls="--", ec="c", fc="None", + transform=ax.transAxes)) + +# We set the axis limits to something other than the default, in order to not +# distract from the fact that axes coodinates are used here. +ax.axis([0, 10, 0, 10]) + + +# Note how the two following insets are created at the same positions, one by +# use of the default parent axes' bbox and the other via a bbox in axes +# coordinates and the respective transform. +ax2 = fig.add_subplot(222) +axins2 = inset_axes(ax2, width="30%", height="50%") + +ax3 = fig.add_subplot(224) +axins3 = inset_axes(ax3, width="100%", height="100%", + bbox_to_anchor=(.7, .5, .3, .5), + bbox_transform=ax3.transAxes) + +# For visualization purposes we mark the bounding box by a rectangle +ax2.add_patch(plt.Rectangle((0, 0), 1, 1, ls="--", lw=2, ec="c", fc="None")) +ax3.add_patch(plt.Rectangle((.7, .5), .3, .5, ls="--", lw=2, + ec="c", fc="None")) + +# Turn ticklabels off +for axi in [axins2, axins3, ax2, ax3]: + axi.tick_params(labelleft=False, labelbottom=False) + +plt.show() -fig, (ax, ax2) = plt.subplots(1, 2, figsize=[5.5, 3]) -# first subplot -ax.set_aspect(1) +############################################################################### +# In the above the axes transform together with 4-tuple bounding boxes has been +# used as it mostly is useful to specify an inset relative to the axes it is +# an inset to. However other use cases are equally possible. The following +# example examines some of those. +# -axins = inset_axes(ax, - width="30%", # width = 30% of parent_bbox - height=1., # height : 1 inch - loc='lower left') +fig = plt.figure(figsize=[5.5, 2.8]) +ax = fig.add_subplot(131) -plt.xticks(visible=False) -plt.yticks(visible=False) +# Create an inset outside the axes +axins = inset_axes(ax, width="100%", height="100%", + bbox_to_anchor=(1.05, .6, .5, .4), + bbox_transform=ax.transAxes, loc=2, borderpad=0) +axins.tick_params(left=False, right=True, labelleft=False, labelright=True) +# Create an inset with a 2-tuple bounding box. Note that this creates a +# bbox without extent. This hence only makes sense when specifying +# width and height in absolute units (inches). +axins2 = inset_axes(ax, width=0.5, height=0.4, + bbox_to_anchor=(0.33, 0.25), + bbox_transform=ax.transAxes, loc=3, borderpad=0) -# second subplot -ax2.set_aspect(1) -axins = zoomed_inset_axes(ax2, zoom=0.5, loc='upper right') -# fix the number of ticks on the inset axes -axins.yaxis.get_major_locator().set_params(nbins=7) -axins.xaxis.get_major_locator().set_params(nbins=7) +ax2 = fig.add_subplot(133) +ax2.set_xscale("log") +ax2.axis([1e-6, 1e6, -2, 6]) -plt.xticks(visible=False) -plt.yticks(visible=False) +# Create inset in data coordinates using ax.transData as transform +axins3 = inset_axes(ax2, width="100%", height="100%", + bbox_to_anchor=(1e-2, 2, 1e3, 3), + bbox_transform=ax2.transData, loc=2, borderpad=0) -add_sizebar(ax2, 0.5) -add_sizebar(axins, 0.5) +# Create an inset horizontally centered in figure coordinates and vertically +# bound to line up with the axes. +from matplotlib.transforms import blended_transform_factory +transform = blended_transform_factory(fig.transFigure, ax2.transAxes) +axins4 = inset_axes(ax2, width="16%", height="34%", + bbox_to_anchor=(0, 0, 1, 1), + bbox_transform=transform, loc=8, borderpad=0) -plt.draw() plt.show() diff --git a/examples/axes_grid1/inset_locator_demo2.py b/examples/axes_grid1/inset_locator_demo2.py index 4e50dd5855c5..509413d3bf83 100644 --- a/examples/axes_grid1/inset_locator_demo2.py +++ b/examples/axes_grid1/inset_locator_demo2.py @@ -3,11 +3,16 @@ Inset Locator Demo2 =================== +This Demo shows how to create a zoomed inset via `~.zoomed_inset_axes`. +In the first subplot an `~.AnchoredSizeBar` shows the zoom effect. +In the second subplot a connection to the region of interest is +created via `~.mark_inset`. """ + import matplotlib.pyplot as plt -from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes -from mpl_toolkits.axes_grid1.inset_locator import mark_inset +from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes, mark_inset +from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar import numpy as np @@ -20,36 +25,63 @@ def get_demo_image(): # z is a numpy array of 15x15 return z, (-3, 4, -4, 3) -fig, ax = plt.subplots(figsize=[5, 4]) +fig, (ax, ax2) = plt.subplots(ncols=2, figsize=[6, 3]) + + +# First subplot, showing an inset with a size bar. +ax.set_aspect(1) + +axins = zoomed_inset_axes(ax, zoom=0.5, loc='upper right') +# fix the number of ticks on the inset axes +axins.yaxis.get_major_locator().set_params(nbins=7) +axins.xaxis.get_major_locator().set_params(nbins=7) + +plt.setp(axins.get_xticklabels(), visible=False) +plt.setp(axins.get_yticklabels(), visible=False) -# prepare the demo image + +def add_sizebar(ax, size): + asb = AnchoredSizeBar(ax.transData, + size, + str(size), + loc=8, + pad=0.1, borderpad=0.5, sep=5, + frameon=False) + ax.add_artist(asb) + +add_sizebar(ax, 0.5) +add_sizebar(axins, 0.5) + + +# Second subplot, showing an image with an inset zoom +# and a marked inset Z, extent = get_demo_image() Z2 = np.zeros([150, 150], dtype="d") ny, nx = Z.shape Z2[30:30 + ny, 30:30 + nx] = Z # extent = [-3, 4, -4, 3] -ax.imshow(Z2, extent=extent, interpolation="nearest", +ax2.imshow(Z2, extent=extent, interpolation="nearest", origin="lower") -axins = zoomed_inset_axes(ax, zoom=6, loc='upper right') -axins.imshow(Z2, extent=extent, interpolation="nearest", - origin="lower") + +axins2 = zoomed_inset_axes(ax2, 6, loc=1) # zoom = 6 +axins2.imshow(Z2, extent=extent, interpolation="nearest", + origin="lower") # sub region of the original image x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9 -axins.set_xlim(x1, x2) -axins.set_ylim(y1, y2) +axins2.set_xlim(x1, x2) +axins2.set_ylim(y1, y2) # fix the number of ticks on the inset axes -axins.yaxis.get_major_locator().set_params(nbins=7) -axins.xaxis.get_major_locator().set_params(nbins=7) +axins2.yaxis.get_major_locator().set_params(nbins=7) +axins2.xaxis.get_major_locator().set_params(nbins=7) -plt.xticks(visible=False) -plt.yticks(visible=False) +plt.setp(axins2.get_xticklabels(), visible=False) +plt.setp(axins2.get_yticklabels(), visible=False) # draw a bbox of the region of the inset axes in the parent axes and # connecting lines between the bbox and the inset axes area -mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5") +mark_inset(ax2, axins2, loc1=2, loc2=4, fc="none", ec="0.5") -plt.draw() plt.show() diff --git a/lib/mpl_toolkits/axes_grid1/inset_locator.py b/lib/mpl_toolkits/axes_grid1/inset_locator.py index 75a6a90d7bb7..dfdb1a67a105 100644 --- a/lib/mpl_toolkits/axes_grid1/inset_locator.py +++ b/lib/mpl_toolkits/axes_grid1/inset_locator.py @@ -1,6 +1,8 @@ """ A collection of functions and objects for creating or placing inset axes. """ + +import warnings from matplotlib import docstring import six from matplotlib.offsetbox import AnchoredOffsetbox @@ -391,8 +393,16 @@ def inset_axes(parent_axes, width, height, loc='upper right', """ Create an inset axes with a given width and height. - Both sizes used can be specified either in inches or percentage of the - parent axes. + Both sizes used can be specified either in inches or percentage. + For example,:: + + inset_axes(parent_axes, width='40%%', height='30%%', loc=3) + + creates in inset axes in the lower left corner of *parent_axes* which spans + over 30%% in height and 40%% in width of the *parent_axes*. Since the usage + of `.inset_axes` may become slightly tricky when exceeding such standard + cases, it is recommended to read + :ref:`the examples `. Parameters ---------- @@ -400,7 +410,12 @@ def inset_axes(parent_axes, width, height, loc='upper right', Axes to place the inset axes. width, height : float or str - Size of the inset axes to create. + Size of the inset axes to create. If a float is provided, it is + the size in inches, e.g. *width=1.3*. If a string is provided, it is + the size in relative units, e.g. *width='40%%'*. By default, i.e. if + neither *bbox_to_anchor* nor *bbox_transform* are specified, those + are relative to the parent_axes. Otherwise they are to be understood + relative to the bounding box provided via *bbox_to_anchor*. loc : int or string, optional, default to 1 Location to place the inset axes. The valid locations are:: @@ -417,14 +432,29 @@ def inset_axes(parent_axes, width, height, loc='upper right', 'center' : 10 bbox_to_anchor : tuple or `matplotlib.transforms.BboxBase`, optional - Bbox that the inset axes will be anchored. Can be a tuple of - [left, bottom, width, height], or a tuple of [left, bottom]. + Bbox that the inset axes will be anchored to. If None, + *parent_axes.bbox* is used. If a tuple, can be either + [left, bottom, width, height], or [left, bottom]. + If the kwargs *width* and/or *height* are specified in relative units, + the 2-tuple [left, bottom] cannot be used. Note that + the units of the bounding box are determined through the transform + in use. When using *bbox_to_anchor* it almost always makes sense to + also specify a *bbox_transform*. This might often be the axes transform + *parent_axes.transAxes*. bbox_transform : `matplotlib.transforms.Transform`, optional - Transformation for the bbox. if None, `parent_axes.transAxes` is used. + Transformation for the bbox that contains the inset axes. + If None, a `.transforms.IdentityTransform` is used (i.e. pixel + coordinates). This is useful when not providing any argument to + *bbox_to_anchor*. When using *bbox_to_anchor* it almost always makes + sense to also specify a *bbox_transform*. This might often be the + axes transform *parent_axes.transAxes*. Inversely, when specifying + the axes- or figure-transform here, be aware that not specifying + *bbox_to_anchor* will use *parent_axes.bbox*, the units of which are + in display (pixel) coordinates. axes_class : `matplotlib.axes.Axes` type, optional - If specified, the inset axes created with be created with this class's + If specified, the inset axes created will be created with this class's constructor. axes_kwargs : dict, optional @@ -434,6 +464,8 @@ def inset_axes(parent_axes, width, height, loc='upper right', borderpad : float, optional Padding between inset axes and the bbox_to_anchor. Defaults to 0.5. + The units are axes font size, i.e. for a default font size of 10 points + *borderpad = 0.5* is equivalent to a padding of 5 points. Returns ------- @@ -450,11 +482,23 @@ def inset_axes(parent_axes, width, height, loc='upper right', inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(), **axes_kwargs) + if bbox_transform in [parent_axes.transAxes, + parent_axes.figure.transFigure]: + if bbox_to_anchor is None: + warnings.warn("Using the axes or figure transform requires a " + "bounding box in the respective coordinates. " + "Using bbox_to_anchor=(0,0,1,1) now.") + bbox_to_anchor = (0, 0, 1, 1) + if bbox_to_anchor is None: bbox_to_anchor = parent_axes.bbox - if bbox_transform is None: - bbox_transform = parent_axes.transAxes + if isinstance(bbox_to_anchor, tuple) and \ + (isinstance(width, str) or isinstance(height, str)): + if len(bbox_to_anchor) != 4: + raise ValueError("Using relative units for width or height " + "requires to provide a 4-tuple or a " + "`BBox` instance to `bbox_to_anchor.") axes_locator = AnchoredSizeLocator(bbox_to_anchor, width, height, @@ -476,7 +520,8 @@ def zoomed_inset_axes(parent_axes, zoom, loc='upper right', axes_kwargs=None, borderpad=0.5): """ - Create an anchored inset axes by scaling a parent axes. + Create an anchored inset axes by scaling a parent axes. For usage, also see + :ref:`the examples `. Parameters ---------- @@ -503,14 +548,29 @@ def zoomed_inset_axes(parent_axes, zoom, loc='upper right', 'center' : 10 bbox_to_anchor : tuple or `matplotlib.transforms.BboxBase`, optional - Bbox that the inset axes will be anchored. Can be a tuple of - [left, bottom, width, height], or a tuple of [left, bottom]. + Bbox that the inset axes will be anchored to. If None, + *parent_axes.bbox* is used. If a tuple, can be either + [left, bottom, width, height], or [left, bottom]. + If the kwargs *width* and/or *height* are specified in relative units, + the 2-tuple [left, bottom] cannot be used. Note that + the units of the bounding box are determined through the transform + in use. When using *bbox_to_anchor* it almost always makes sense to + also specify a *bbox_transform*. This might often be the axes transform + *parent_axes.transAxes*. bbox_transform : `matplotlib.transforms.Transform`, optional - Transformation for the bbox. if None, `parent_axes.transAxes` is used. + Transformation for the bbox that contains the inset axes. + If None, a `.transforms.IdentityTransform` is used (i.e. pixel + coordinates). This is useful when not providing any argument to + *bbox_to_anchor*. When using *bbox_to_anchor* it almost always makes + sense to also specify a *bbox_transform*. This might often be the + axes transform *parent_axes.transAxes*. Inversely, when specifying + the axes- or figure-transform here, be aware that not specifying + *bbox_to_anchor* will use *parent_axes.bbox*, the units of which are + in display (pixel) coordinates. axes_class : `matplotlib.axes.Axes` type, optional - If specified, the inset axes created with be created with this class's + If specified, the inset axes created will be created with this class's constructor. axes_kwargs : dict, optional @@ -520,6 +580,8 @@ def zoomed_inset_axes(parent_axes, zoom, loc='upper right', borderpad : float, optional Padding between inset axes and the bbox_to_anchor. Defaults to 0.5. + The units are axes font size, i.e. for a default font size of 10 points + *borderpad = 0.5* is equivalent to a padding of 5 points. Returns ------- diff --git a/lib/mpl_toolkits/tests/test_axes_grid1.py b/lib/mpl_toolkits/tests/test_axes_grid1.py index 685f103930b9..6205e3ed4360 100644 --- a/lib/mpl_toolkits/tests/test_axes_grid1.py +++ b/lib/mpl_toolkits/tests/test_axes_grid1.py @@ -22,7 +22,10 @@ blended_transform_factory from itertools import product +import pytest + import numpy as np +from numpy.testing import assert_array_equal, assert_array_almost_equal @image_comparison(baseline_images=['divider_append_axes']) @@ -186,8 +189,9 @@ def get_demo_image(): ax.imshow(Z2, extent=extent, interpolation="nearest", origin="lower") - # creating our inset axes without a bbox_transform parameter - axins = inset_axes(ax, width=1., height=1., bbox_to_anchor=(1, 1)) + # creating our inset axes with a bbox_transform parameter + axins = inset_axes(ax, width=1., height=1., bbox_to_anchor=(1, 1), + bbox_transform=ax.transAxes) axins.imshow(Z2, extent=extent, interpolation="nearest", origin="lower") @@ -214,19 +218,48 @@ def get_demo_image(): ax.add_artist(asb) -def test_inset_axes_without_transform_should_use_parent_axes(): - # creating our figure - fig = plt.figure(dpi=150) - - # gca method gets current axes of the figure - ax = plt.gca() - ax.plot([0.0, 0.25, 0.50, 1.0], [0.1, 0.2, 0.4, 0.9], color='b') - - # creating our inset_axes. without a bbox_transform parameter - ax_ins = inset_axes(ax, width=1., height=1., bbox_to_anchor=(1, 1)) - ax_ins.plot([0.0, 0.25, 0.50, 1.0], [0.9, 0.4, 0.2, 0.1], color='r') - - assert ax.transAxes == ax_ins.transAxes +def test_inset_axes_complete(): + dpi = 100 + figsize = (6, 5) + fig, ax = plt.subplots(figsize=figsize, dpi=dpi) + fig.subplots_adjust(.1, .1, .9, .9) + + ins = inset_axes(ax, width=2., height=2., borderpad=0) + fig.canvas.draw() + assert_array_almost_equal( + ins.get_position().extents, + np.array(((0.9*figsize[0]-2.)/figsize[0], + (0.9*figsize[1]-2.)/figsize[1], 0.9, 0.9))) + + ins = inset_axes(ax, width="40%", height="30%", borderpad=0) + fig.canvas.draw() + assert_array_almost_equal( + ins.get_position().extents, + np.array((.9-.8*.4, .9-.8*.3, 0.9, 0.9))) + + ins = inset_axes(ax, width=1., height=1.2, bbox_to_anchor=(200, 100), + loc=3, borderpad=0) + fig.canvas.draw() + assert_array_almost_equal( + ins.get_position().extents, + np.array((200./dpi/figsize[0], 100./dpi/figsize[1], + (200./dpi+1)/figsize[0], (100./dpi+1.2)/figsize[1]))) + + ins1 = inset_axes(ax, width="35%", height="60%", loc=3, borderpad=1) + ins2 = inset_axes(ax, width="100%", height="100%", + bbox_to_anchor=(0, 0, .35, .60), + bbox_transform=ax.transAxes, loc=3, borderpad=1) + fig.canvas.draw() + assert_array_equal(ins1.get_position().extents, + ins2.get_position().extents) + + with pytest.raises(ValueError): + ins = inset_axes(ax, width="40%", height="30%", + bbox_to_anchor=(0.4, 0.5)) + + with pytest.warns(UserWarning): + ins = inset_axes(ax, width="40%", height="30%", + bbox_transform=ax.transAxes) @image_comparison( pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy