From: Phil E. <phi...@ho...> - 2012-02-03 16:40:23
|
Some time back I asked about initialising a projection in MPL using generic objects rather than by class name. I created a pull request associated with this which was responded to fantastically by leejjoon which (after several months) I have finally got around to implementing. My changes have been added to the original pull request, which will eventually be obsoleted, but that doesn't seem to have notified the devel mailing list, therefore I would like to draw the list's attention to https://github.com/matplotlib/matplotlib/pull/470#issuecomment-3743543 on which I would greatly appreciate feedback & ultimately get onto the mpl master. The pull request in question would pave the way for non string projections so I thought I would play with how one might go about specifying the location of theta_0 in a polar plot (i.e. should it be due east or due north etc.). I have branched my changeset mentioned in the pull request above and implemented a couple of ideas, although I am not proposing that these changes go any further at this stage (I would be happy if someone wants to run with them though): Currently, one can set the theta_0 of a polar plot with: ax = plt.axes(projection='polar') ax.set_theta_offset(np.pi/2) ax.plot(np.arange(100)*0.15, np.arange(100)) But internally there are some nasties going on (theta_0 is an attribute on the axes, the transform is instantiated from within the axes and is given the axes that is instantiating it, which is all a bit circular). I have made a branch (https://github.com/PhilipElson/matplotlib/compare/master...polar_fun) which alleviates the axes attribute issue and would allow something like: polar_trans = mpl.transforms.Polar.PolarTransform(theta_offset=np.pi/2) ax = plt.axes(projection=polar_trans) ax.plot(np.arange(100)*0.15, np.arange(100)) Or, I have added a helper class which also demonstrates the proposed: non-string change: ax = plt.axes(projection=Polar(theta0=90)) ax.plot(np.arange(100)*0.15, np.arange(100)) As I said, I am not proposing these changes to the way Polar works at this stage, but thought it was worth sharing to show what can be done once something similar to the proposed change gets on to mpl master. Hope that makes sense. Many Thanks, |
From: Michael D. <md...@st...> - 2012-02-03 18:09:15
|
Thanks for doing this work. On 02/03/2012 11:40 AM, Phil Elson wrote: > Currently, one can set the theta_0 of a polar plot with: > > ax = plt.axes(projection='polar') > ax.set_theta_offset(np.pi/2) > ax.plot(np.arange(100)*0.15, np.arange(100)) > > But internally there are some nasties going on (theta_0 is an attribute on the > axes, the transform is instantiated from within the axes and is given the axes > that is instantiating it, which is all a bit circular). I have made a branch > (https://github.com/PhilipElson/matplotlib/compare/master...polar_fun) which > alleviates the axes attribute issue and would allow something like: > > polar_trans = mpl.transforms.Polar.PolarTransform(theta_offset=np.pi/2) > ax = plt.axes(projection=polar_trans) > ax.plot(np.arange(100)*0.15, np.arange(100)) I agree that the canonical copy of theta_offset should probably live in the transform and not the PolarAxes. However, an important feature of the current system that seems to be lost in your branch is that the user deals with Projections (Axes subclasses) which bring together not only the transformation of points from one space to another, but the axes shape and tick placement etc., and they also allow for changing everything after the fact. The Transformation classes, as they stand now, are intended to be an implementation detail hidden from the user. I'm not quite sure what the above lines are meant to do. matplotlib.transforms doesn't have a Polar member -- matplotlib.projections.polar.Polar does not have a PolarTransform member (on master or your polar_fun branch). Even given that, I think the user should be specifying a projection, not a transformation, to create a new axes. There is potential for confusion that some transformations will allow getting a projection out and some won't (for some it doesn't even really make sense). > > Or, I have added a helper class which also demonstrates the proposed: > > non-string change: > ax = plt.axes(projection=Polar(theta0=90)) > ax.plot(np.arange(100)*0.15, np.arange(100)) > > As I said, I am not proposing these changes to the way Polar works at this > stage, but thought it was worth sharing to show what can be done once > something similar to the proposed change gets on to mpl master. > This makes more sense to me. It doesn't appear to allow for setting the theta0 after the fact since Polar doesn't propagate changes along to the PolarAxes object that it created and set_theta_offset has been removed from PolarAxes. Cheers, Mike |
From: Phil E. <phi...@ho...> - 2012-02-04 17:13:58
|
Thanks Mike. > I'm not quite sure what the above lines are meant to do. > matplotlib.transforms doesn't have a Polar member -- > matplotlib.projections.polar.Polar does not have a PolarTransform member > (on master or your polar_fun branch). Even given that, I think the user > should be specifying a projection, not a transformation, to create a new > axes. There is potential for confusion that some transformations will > allow getting a projection out and some won't (for some it doesn't even > really make sense). That was meant to be matplotlib.projections.polar.PolarAxes.PolarTransform but your right, defining the "projection" in the transform could lead to confusion, yet initialising an Axes as a projection seems like unnecessary complexity. This suggests that defining a "projection" class which is neither Transform nor Axes might make the most sense (note, what follows is pseudo code and does not exist in the branch): >>> polar_proj = Polar(theta0=np.pi/2) >>> ax = plt.axes(projection=polar_proj) >>> print ax.projection Polar(theta0=1.57) The PolarAxes would be initialised with the Projection instance, and the PolarAxes can initialise the PolarTransform with a reference to that projection. Thus changing the theta0 of the projection in the Axes would also change the projection which is used in the Transform instance, i.e.: ax.projection.theta0 = 3*np.pi/2 Would change the way that the overall axes looked. Interestingly, the work that I have been doing which requires the aforementioned pull request is doing precisely this - I have projection classes, one for each type of projection, but where each type of projection is parameterised, which, when passed through plt.axes(projection=<my projection class>) instantiate a generic "GenericProjectionAxes", which itself instantiates a generic "GenericProjectionTransform" (names for illustration purposes only) all the while the original projection is mutable via the MultiProjectionAxes.projection attribute. Did you have any feelings on the pull request? Thanks again for your time, Phil |