Non string projections

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,

Thanks for doing this work.

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

ยทยทยท

On 02/03/2012 11:40 AM, Phil Elson wrote:

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