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

WIP ENH secondary axes:#11589

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
jklymak wants to merge22 commits intomatplotlib:masterfromjklymak:enh-secondary-axes

Conversation

jklymak
Copy link
Member

@jklymakjklymak commentedJul 6, 2018
edited
Loading

PR Summary

MOVED TO#11859 (sorry for the inconvenience)

New methodsax.secondary_xaxis andax.secondary_yaxis; here the work is all in_secondary_axes.py, and a new method in_axes.py.

Updated 15 Aug.

Feedback needed:

Any other use cases for a secondary axes? If so, let me know so I can test...

Notes

  • the secondary axes below are recalculated at draw time based on their parents, so they are responsive to the xlim changing
  • there is no way to set the limits of the secondary axes independent of the parent axes. i.e. you can't specify xlim in the secondary axes units.
  • this functionality has no knowledge of actualunits as meant by Matplotlib unit support. I'd need to think hard about how that would work, and is kind of waiting for the units MEP.

Closes#10976

Example placement:

importnumpyasnpimportmatplotlib.pyplotaspltconvert= [np.pi/180]fig,axs=plt.subplots(2,2,constrained_layout=True)ax=axs[0,0]ax.plot(np.arange(360)+20)ax.set_xlabel('Raw')ax.set_title('The Title')axsecond=ax.secondary_xaxis('top',conversion=convert)axsecond.set_xlabel('Converted: top')ax=axs[0,1]ax.plot(np.arange(360)+20)ax.set_xlabel('Raw')ax.set_title('The Title')axsecond=ax.secondary_xaxis('bottom',conversion=convert)axsecond.set_color('0.5')axsecond.set_axis_orientation('top')axsecond.set_xlabel('Converted: bottom')ax=axs[1,0]ax.plot(np.arange(360)+20)ax.set_xlabel('Raw')ax.set_title('The Title')axsecond=ax.secondary_xaxis(0.6,conversion=convert)axsecond.set_color('0.5')axsecond.set_xlabel('Converted: y=0.6')ax=axs[1,1]ax.plot(np.arange(360)+20)ax.set_xlabel('Raw')ax.set_title('The Title')axsecond=ax.secondary_xaxis(1.1,conversion=convert)axsecond.set_xlabel('Converted: y=1.1')

yields:

figure_1

Example inverted axes

Note thatconversion='power' andconversion='linear' are also possible...

fig,ax=plt.subplots(constrained_layout=True)x=np.arange(0.02,1,0.02)np.random.seed(19680801)y=np.random.randn(len(x))**2ax.loglog(x,y)ax.set_xlabel('f [Hz]')ax.set_ylabel('PSD')ax.set_title('Random spectrum')secax=ax.secondary_xaxis('top',conversion='inverted',otherargs=1)secax.set_xlabel('period [s]')secax.set_xscale('log')plt.show()

figure_1

Arbitrary transforms...

... these need to have a definable inverse. Here the idea is that we have some data that maps one-to-one into the xaxis of the parent plot, and we'd like that mapping to be the xaxis of the secondary axis:

fig,ax=plt.subplots(constrained_layout=True)ax.plot(np.arange(1,11),np.arange(1,11))classLocalArbitraryInterp(Transform):"""    Return interpolated from data.  Note that both arrays    have to be ascending for this to work in this example.  (Could    have more error checking to do more generally)    """input_dims=1output_dims=1is_separable=Truehas_inverse=Truedef__init__(self,xold,xnew):Transform.__init__(self)self._xold=xoldself._xnew=xnewdeftransform_non_affine(self,values):q=np.interp(values,self._xold,self._xnew, )returnqdefinverted(self):""" we are just our own inverse """returnLocalArbitraryInterp(self._xnew,self._xold)# this is anarbitrary mapping defined by data.  Only issue is that it# should be one-to-one and the vectors need to be ascending for the inverse# mapping to work.xold=np.arange(0,11,0.2)xnew=np.sort(10*np.exp(-xold/4))ax.plot(xold[3:],xnew[3:])ax.set_xlabel('X [m]')secax=ax.secondary_xaxis('top',conversion=LocalArbitraryInterp(xold,xnew))secax.xaxis.set_minor_locator(AutoMinorLocator())secax.set_xlabel('Exponential axes')

figure_3

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)

@jklymakjklymak added this to thev3.1 milestoneJul 6, 2018
@tacaswelltacaswell modified the milestones:v3.1,v3.0Jul 7, 2018
@jklymak
Copy link
MemberAuthor

jklymak commentedJul 8, 2018
edited
Loading

Accepting comments on this. Of course, not for 3.0, so no rush. Tests, examples, etc still to be done.

@jklymak
Copy link
MemberAuthor

@ImportanceOfBeingErnest you are very familiar w/ the axes_grid API. Any comments here?


def set_color(self, color):
"""
Change the color of the secondary axes and all decorators

Choose a reason for hiding this comment

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

I would add here that this is a convenience wrapper and that you may of course use the usual methods to set the color of the label, tickmarks, ticklabels and spines individually as well, in case this is desired.

jklymak reacted with thumbs up emoji
loc : string or scalar
FIX
The position to put the secondary axis. Strings can be 'top' or
'bottom', scalar can be a float indicating the relative position

Choose a reason for hiding this comment

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

"left" or "right"

jklymak reacted with thumbs up emoji

return rectpatch, connects

def secondary_xaxis(self, loc, conversion, **kwargs):

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 letconversion andloc take a default? Maybe justloc="right" andconversion=1?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Oh, hmm. I'd be OK w/ that....


Returns
-------
ax : `~matplotlib.axes.Axes` (??)

Choose a reason for hiding this comment

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

Why is that axes private? If you include it as a public object you could directly refer to it as the return type.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

It wasn't meant to be private. I think the (??) was just because I wasn't sure that the ref was right... (sorry, this isn't a fully polished PR yet, just wanted to get feedback on the overall API)...

Choose a reason for hiding this comment

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

I'm just wondering if having this as private object only would make it harder to customize, if needed.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I'm not following what makes it private? Its not meant to be private...

Choose a reason for hiding this comment

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

What is returned is amatplotlib.axes._secondary_axes.Secondary_Yaxis. So one would expect to see something like

Returns-------ax : `~matplotlib.axes._secondary_axes.Secondary_Yaxis`

right?

But that may not be possible due to_secondary_axes being "private" and not part of the documentation.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Ah I see what you mean. I find this sort of thing very confusing. Somehow Axes is defined in a private sub module, and I think that’s what should be done here too. But I’m not clear on how the API bubbles up to be public.

@ImportanceOfBeingErnest
Copy link
Member

I like this. It allows to get a second scale for a plot, without creating a totally new twin axes. Up to now people needed to use such twin axes in cases where they only wanted to have a different formatter on the right side or different colors etc.

I'm currently not sure how would this behave in cases with set aspect. I.e. what if the parent has a specific aspect set? And more importantly, what if the user callsset_aspect on thisSecondary_Axis object?
Some tests for those edge cases would be good.

In general I think the documentation should point towards the differences between twinx and secondary_xaxis.

@jklymak
Copy link
MemberAuthor

I'm currently not sure how would this behave in cases with set aspect. I.e. what if the parent has a specific aspect set? And more importantly, what if the user calls set_aspect on this Secondary_Axis object?

Ooh, good point. I'm not sure either. I suspect issues ;-).

I'll make it so the user will not have aset_aspect method available on the secondary axes.

@jklymak
Copy link
MemberAuthor

I'm currently not sure how would this behave in cases with set aspect. I.e. what if the parent has a specific aspect set? And more importantly, what if the user calls set_aspect on this Secondary_Axis object?

OK,set_aspect on parent works.set_aspect on secondary axis has been disabled (with a warning)

One could argue that the secondary axes might be able to set an aspect using its "units", but I think that'd be very hard to make work in general (i.e. w/ non-linear conversion between the axes), and I don't think its unreasonable to ask the user to specify the aspect ratio on the parent axes. The secondary axes is just a decoration, not meant to control the other axes.

Similarly set_xlim doesn't work for the secondary axes...

ImportanceOfBeingErnest reacted with thumbs up emoji

@leejjoon
Copy link
Contributor

I only had a quick look at the code, but it seems to me that it only changes the limit of an secondary axis. Then, I guess the location of ticks will be correct only if the convert function is linear. If this is the case, allowing an arbitrary function may not be a good idea.

@ImportanceOfBeingErnest
Copy link
Member

That's a good point. So I guess the documentation needs to be very very clear about the fact that only because you use some arbitrary function, it does not mean that the axes itself is following this function.

Instead one still needs to set some custom locator and formatter, or use a customScale on that axis.

Or else, it would of course be cool to have this function manage this as well.

In any case, in a next stage there will then need to be some example on how to produce a plot with e.g. frequency and wavelength correctly.

jklymak reacted with thumbs up emoji

@jklymak
Copy link
MemberAuthor

I only had a quick look at the code, but it seems to me that it only changes the limit of an secondary axis. Then, I guess the location of ticks will be correct only if the convert function is linear. If this is the case, allowing an arbitrary function may not be a good idea.

Ooops, yes, thats correct. Shouldn't advertise something I didn't really make work. I imagine its possible to encompass non-linear transformations in the locator, but would have to look at it.

Also, I supposes I"ve made no effort to link the xscale, so if I make the parent a logarithm, the secondary axes should be as well. That should be doable.

@jklymak
Copy link
MemberAuthor

OK, that took a while, and needs a bit of an API discussion.

What we need to do to make ticks be properly spaced is callsec.set_xscale('arbitrary', transform=transform) where we need to define the transform, and the "arbitrary" scale gets registered. So that all works in prototype (not pushed yet).

For an API, I have the following options:

importnumpyasnpimportmatplotlib.pyplotaspltfrommatplotlib.transformsimportTransformfig,ax=plt.subplots()ax.plot(np.arange(2,11),np.arange(2,11))classLocalInverted(Transform):"""    Return a/x    """input_dims=1output_dims=1is_separable=Truehas_inverse=Truedef__init__(self,out_of_bounds='mask'):Transform.__init__(self)self._fac=1.0deftransform_non_affine(self,values):withnp.errstate(divide="ignore",invalid="ignore"):q=self._fac/valuesreturnqdefinverted(self):""" we are just our own inverse """returnLocalInverted(1/self._fac)axsecond=ax.secondary_xaxis(0.2,conversion='power',otherargs=(1.5,1.))axsecond=ax.secondary_xaxis(0.4,conversion='inverted',otherargs=2)axsecond=ax.secondary_xaxis(0.6,conversion=[3.5,1.])axsecond=ax.secondary_xaxis(0.8,conversion=2.5)axsecond=ax.secondary_xaxis(1.0,conversion=LocalInverted())plt.show()

TheArbitraryScale class that gets used by the non-linear transformations just uses the AutoLocator and ScalarFormater. Of course the user could specify these themselves....

figure_1

@jklymakjklymakforce-pushed theenh-secondary-axes branch 2 times, most recently fromb7c5a92 toac33b8aCompareAugust 3, 2018 17:30
@jklymak
Copy link
MemberAuthor

Closing for now so I can sync between machines w/o triggering CI every time. This is close to working, but the transform structure needs some thought...

@Knusper
Copy link

(I hope I comment in the right place, if not please direct me to the right place)

First, I have to say that I was quite excited when I saw this feature arriving in 3.1, because I always felt this was a bit hacky to implement using previous versions of matplotlib. For getting a secondary axis quick it is already nice.
Nevertheless, I encountered two problems with the current implementation:

(a) Why do I need to supply a forward and backward transform? I assume there is a technical reason for it, but from a user perspective this makes not much sense to me. There are cases where I can define easily a forward transform, but not so easily a backward transform. In theses cases I have to define a grid and resort to linear interpolation for the forward and backward transformation, as shown in the"arbitrary transform" example. The question than arises, if the call to the secondary_axis as such could be made in a way, that if a user supplies only one transform, the backward transform is handled internally, e.g. via linear interpolation?

(b) I was not able to get minor_ticks working, e.g. in the deg2rad example from the documentation I tried

secax = ax.secondary_xaxis('top', functions=(deg2rad, rad2deg))secax.set_xlabel('angle [rad]')secax.minorticks_on()

but that didn't work.

@jklymak
Copy link
MemberAuthor

@Knusper, can you open a new issue that outlines your concerns with minor ticks? Hopefully that is something straight forward.

For forward/inverse transformations, you need to be able to go from plot space to data space in order to do the tick labelling, so the inverse is crucial. We appreciate the problem with coming up with an inverse, but hopefully you can appreciate the issue with us automatically making one for you - there is no guarantee that a transform is one-to-one, and hence the inverse may not be unique. So the user should supply the inverse to make the one-to-one mapping clear.

Out of curiousity, what are you trying to map that doesn't have an inverse?

@Knusper
Copy link

Knusper commentedJun 4, 2019
edited
Loading

For example transformations from vacuum wavelengths to air wavelengths. The inverse transformation will always be an approximation, since you require the refractive index as a function of vacuum wavelength. If you want to look at the mess you can check here:http://www.astro.uu.se/valdwiki/Air-to-vacuum%20conversion - maybe thats a very specific case...

@jklymak
Copy link
MemberAuthor

Gotcha, well, you definitely need the inverse, and for now we are probably going to keep it user-supplied. But if there is a PR that implemented doing it on our side, I'm sure it'd be considered.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@ImportanceOfBeingErnestImportanceOfBeingErnestImportanceOfBeingErnest left review comments

Assignees
No one assigned
Projects
None yet
Milestone
v3.1.0
Development

Successfully merging this pull request may close these issues.

ENH: secondary axis for a x or y scale.
5 participants
@jklymak@ImportanceOfBeingErnest@leejjoon@Knusper@tacaswell

[8]ページ先頭

©2009-2025 Movatter.jp