Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

ENH: inset_axes anchored-artist API#11730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed

Conversation

jklymak
Copy link
Member

@jklymakjklymak commentedJul 21, 2018
edited
Loading

PR Summary

ax.inset_axes is now merged for 3.0 (#11026) with a minimal positioning API. (ax.inset_axes([l, b, w, h])), but folks can specify transforms that allow a pad with the edge of the axes.

This PR expands the API to be similar toax.legend andaxes_grid1.inset_axes and allow a string for the position argument. So now we can doax.inset_axes('NE', width=0.4, height=0.4)

importmatplotlib.pyplotaspltimportnumpyasnpdefget_demo_image():frommatplotlib.cbookimportget_sample_dataimportnumpyasnpf=get_sample_data("axes_grid/bivariate_normal.npy",asfileobj=False)z=np.load(f)Z2=np.zeros([150,150],dtype="d")ny,nx=z.shapeZ2[30:30+ny,30:30+nx]=z# z is a numpy array of 15x15extent= (-3,4,-4,3)returnZ2,extentfig,ax=plt.subplots(figsize=[5,4])# make dataZ2,extent=get_demo_image()ax.imshow(Z2,extent=extent,interpolation="nearest",origin="lower")# inset axes....axins=ax.inset_axes('NE',width=0.4,height=0.4)axins.imshow(Z2,extent=extent,interpolation="nearest",origin="lower")# sub region of the original imagex1,x2,y1,y2=-1.5,-0.9,-2.5,-1.9axins.set_xlim(x1,x2)axins.set_ylim(y1,y2)axins.set_xticklabels('');axins.set_yticklabels('')ax.indicate_inset_zoom(axins)fig.canvas.draw()plt.show()

boo

PR Checklist

  • Has Pytest style unit tests
  • Code is PEP 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@jklymakjklymakforce-pushed theenh-inset-axes-anchored branch frombba682b to5207b60CompareJuly 21, 2018 23:08
@jklymak
Copy link
MemberAuthor

jklymak commentedJul 21, 2018
edited
Loading

ping@ImportanceOfBeingErnest who's concern about the API sparked this, though note the API is notexactly the same...

@jklymakjklymak added this to thev3.1 milestoneJul 21, 2018
@jklymakjklymakforce-pushed theenh-inset-axes-anchored branch from5207b60 to4275e09CompareJuly 21, 2018 23:17
@ImportanceOfBeingErnest
Copy link
Member

Yeah, I guess the options are discussed in#11026 (comment), so if the aim is to truely replaceaxes_grid1.inset_locator, something like the mentionnedadd_inset_complete would be needed. But doing this step by step is of course an option and this PR then seems to do the second step.

`.Axes.inset_axes`.

A locator gets used in `Axes.set_aspect` to override the default
locations... It is a function that takes an axes object and

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

remove ellipsis (...)

jklymak reacted with thumbs up emoji
height.

If a string, then locations such as "NE", "N", "NW", "W", etc,
of "upper right", "top", "upper left", etc (see `~.axes.legend`)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

or "upper right"

jklymak reacted with thumbs up emoji

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

And if "center" is unsopported (see above), one cannot simply link toaxes.legend.


If a string, then locations such as "NE", "N", "NW", "W", etc,
of "upper right", "top", "upper left", etc (see `~.axes.legend`)
for codes. (Note we do *not* support the numerical codes).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

As long as other functions likelegend support the number codes, I would support them everywhere. Once it is decided to deprectate them, I would deprecate them all, everywhere, in one single rush. One might silently allow them here, without mentionning them specifically though. <- my personal opinion (I'm not too inclined to open that discussion again as it will just never get to an end.)

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I hear you, but someone can add that as a followup PR if they really feel strongly about it. Its a bunch of extra code for something we don't really want to support any longer.

@@ -48,10 +48,10 @@ def paintEvent(self, event):
[[left, self.renderer.height - (top + height * self._dpi_ratio)],
[left + width * self._dpi_ratio, self.renderer.height - top]])
reg = self.copy_from_bbox(bbox)
buf = reg.to_string_argb()
# buf = reg.to_string_argb()
buf = memoryview(reg)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm almost certain that this should not be part of this PR.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ha, no...



def test_inset_codes():
"Test that the inset codes put the inset where we want..."

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Single double-quotes as function docs are pretty non-standard. Not sure if it matters for tests though.

jklymak reacted with thumbs up emoji
codes = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
for pos, code in zip(poss, codes):
axin1 = ax.inset_axes(code)
np.testing.assert_allclose(axin1.get_position().get_points(),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Would it make sense to define somebboxes in axes coordinates here and compare to their coordinates instead? (Seems like this test is pretty susceptible to unrelated changes in the subplot positioning.)

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The string-code positioning has a borderpad that is in pixels, not axes co-ordinates, so knowing the bbox a-priori would just mean I was duplicating the code in the method. I guess that'd test if someone changed it ...

'center right': 'E',
'lower center': 'S',
'upper center': 'N',
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What about the center itself? I.e. loc code10.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Done. Thanks for all the help!

@jklymak
Copy link
MemberAuthor

something like the mentionnedadd_inset_complete would be needed.

Ithink this has all the features ofadd_inset_complete. Note thetransform kwarg works with the new API and theborderaxespad parameter gives the padding. You can also specify abbox_to_anchor. The difference is that we need to specifywidth andheight or take the defaults, which you don't need to do for legend because it shrinks to fit.

@jklymakjklymakforce-pushed theenh-inset-axes-anchored branch 2 times, most recently fromc43c4b5 tof41aa99CompareJuly 22, 2018 00:05
@jklymakjklymakforce-pushed theenh-inset-axes-anchored branch fromf41aa99 tod61a940CompareJuly 22, 2018 02:33
@ImportanceOfBeingErnest
Copy link
Member

I went throughthe inset_locator_demo and indeed almost all cases are feasible with the proposed API.

Here is the changed example code
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)axins = ax.inset_axes("NE", width=1.3, height=0.9, transform=fig.dpi_scale_trans)# 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)axins2 = ax.inset_axes("SW", width=0.3, height=.4)# 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)from matplotlib.transforms import blended_transform_factorytrans = blended_transform_factory(ax2.transAxes, fig.dpi_scale_trans)axins3 = ax2.inset_axes("NW", width=0.3, height=1., transform=trans)# 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)axins4 = ax2.inset_axes("SE", width=.2, height=.2, borderaxespad=10)# Turn ticklabels of insets offfor axi in [axins, axins2, axins3, axins4]:    axi.tick_params(labelleft=False, labelbottom=False)plt.show()## =============================================================================##from matplotlib import transforms as mtransfig = 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)# This is hard to reproduce, because padding is applied towards the axes# There is no bounding box.trans = ax.transAxes  + mtrans.ScaledTranslation(+5/72., +5/72, fig.dpi_scale_trans) axins = ax.inset_axes((.2, .4, .6*0.5, .5*0.75), transform=trans)# For visualization purposes we mark the bounding box by a rectangleax.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%")axins2 = ax2.inset_axes("NE", width=.3, height=.5)ax3 = fig.add_subplot(224)trans = ax3.transAxes  + mtrans.ScaledTranslation(-5/72., -5/72, fig.dpi_scale_trans) axins3 = ax3.inset_axes((.7, .5, .3, .5), width="100%", height="100%",                        transform=trans)# For visualization purposes we mark the bounding box by a rectangleax2.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 offfor axi in [axins2, axins3, ax2, ax3]:    axi.tick_params(labelleft=False, labelbottom=False)plt.show()#### =============================================================================####fig = plt.figure(figsize=[5.5, 2.8])ax = fig.add_subplot(131)# 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 = ax.inset_axes((1.05, .6, .5, .4))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)# -----------#This seems impossible, even when using a 4 tuple bounds.# -----------ax2 = fig.add_subplot(133)ax2.set_xscale("log")ax2.axis([1e-6, 1e6, -2, 6])# 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)axins3 = ax2.inset_axes((1e-2, 2, 1e3, 3), transform=ax2.transData)# Create an inset horizontally centered in figure coordinates and vertically# bound to line up with the axes.from matplotlib.transforms import blended_transform_factorytransform = 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)axins4 = ax2.inset_axes((.5-.16/2, 0, .16, .34), transform=transform)plt.show()

While some cases become much more straight forward, e.g.

axins = inset_axes(ax, width="100%", height="100%",                   bbox_to_anchor=(1.05, .6, .5, .4),                   bbox_transform=ax.transAxes, loc=2, borderpad=0)

becomes

axins = ax.inset_axes((1.05, .6, .5, .4))

others are more intricate, e.g.

axins3 = inset_axes(ax2, width="30%", height=1., loc=2)

becomes:

from matplotlib.transforms import blended_transform_factorytrans = blended_transform_factory(ax2.transAxes, fig.dpi_scale_trans)axins3 = ax2.inset_axes("NW", width=0.3, height=1., transform=trans)

Now there is one example, where I did not find any way to reproduce. This should create an 0.5 by 0.4 inch inset at position (0.33, 0.25) in axes coordinates.

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)

Despite this I would still approve this, as the API is in general much simplified in contrast to the axes_grid1 solution. Still, the above shows that the axes_grid1 method has a lot of benefits and is still more generally applicable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Despite some minor drawbacks this solution greatly simplifies the usual inset creation process. See comment above for details.

@jklymak
Copy link
MemberAuthor

@ImportanceOfBeingErnest thanks for looking at this so carefully.

There is no huge rush on this, so I will look at your cases and see if an API can be worked out that can cover those as well. Moving back to WIP...

@QuLogicQuLogic modified the milestones:v3.4.0,v3.5.0Jan 21, 2021
@QuLogicQuLogic modified the milestones:v3.5.0,v3.6.0Aug 21, 2021
@timhoffmtimhoffm modified the milestones:v3.6.0,unassignedApr 30, 2022
@story645story645 modified the milestones:unassigned,needs sortingOct 6, 2022
@jklymakjklymak closed thisFeb 5, 2023
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@ImportanceOfBeingErnestImportanceOfBeingErnestImportanceOfBeingErnest approved these changes

Assignees
No one assigned
Projects
None yet
Milestone
future releases
Development

Successfully merging this pull request may close these issues.

6 participants
@jklymak@ImportanceOfBeingErnest@tacaswell@QuLogic@story645@timhoffm

[8]ページ先頭

©2009-2025 Movatter.jp