Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Non string projection definitions #694

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 16 commits into from

Conversation

pelson
Copy link
Member

@pelson pelson commented Feb 6, 2012

This is an update to the obsolete pull request #470.

Apart from using __call__, I have implemented the recommendations suggested (by leejjoon)
(if this is not correct, it is not intentional and I would be happy to make any further
changes). The reasons for using _as_mpl_axes as an interface instead
of __call__ are:

  • Implementing the call of a projection object is not obvious that it should
    return a tuple of Axes subclass & kwargs. At least for _as_mpl_axes the
    behaviour is indexable in the documentation and would be easy to look up
    what the agreed interface is.
  • Projection objects needn't be subclasses of Axes, Transforms or any other
    mpl base class, therefore it makes interfacing easier for third party
    packages (their code will be easier to follow and they are free to do whatever
    they desire with the __call__ method.)
  • Internally there is no benefit to calling the projection with () over
    _as_mpl_axes, indeed, it makes it clearer to the reader what is expected of
    the projection.
  • Implementing __call__ closes the potential future symmetry for other types of
    matplotlib interfaces (_as_mpl_path, _as_mpl_patch, _as_mpl_transform etc.).
  • Pre-existing projections may have a better use of __call__ (e.g. pyproj may
    decide to extend its API to create a map from a projection, but it has
    already used the __call__ for transforming coordinates).
  • Provides a consistent interface which similar in style to the __array_interface__ of numpy.

With the above changes, the trivial Polar projection object would then look like:

class Polar(object):
    """
    Represents the polar projection, a Polar instance can be used to initialise
    a polar plot in the standard ways, for example::

        plt.axes(projection=Polar())

    """
    def _as_mpl_axes(self):
        # implement the matplotlib axes interface
        return PolarAxes, {}

Although the whole point of this change is to be able to implement far more interesting (parameterisable) projections.

Many Thanks,

@WeatherGod
Copy link
Member

I am very interested in this Pull and I will be reviewing this further for use with mplot3d. As for the debate about call versus _as_mpl_axes(), I have to agree with _as_mpl_axes(), but I am curious to hear what the rational is for using call().

My recommendation is to get this pull reviewed fairly quickly and -- baring any show-stoppers -- I would like to see this functionality merged into master to give it the proper "baking" time. This is playing around with some core features and I want to make sure all bugs are found and ironed out before the next release.

@leejjoon
Copy link
Contributor

leejjoon commented Feb 8, 2012

From the developer's point of view, especially when one tries to extend a existing class to be acceptable as a projection parameter, use of _as_mpl_axes could be better.

In general, you're not just adding the _as_mpl_axes method. You need to create a customized Axes class. Int the Polar case, matplotlib already implemented the appropriate Axes class (PolarAxes), but this will not be true in general. And I think it is often better to create a new projection class (as you are doing in the above Polar class). I don't see much benefit of extending an existing class optionally making the class quite dependent on Matplotlb. This is quite different from the case of __array_interface__, which does not require numpy to implement.

As a matter of fact, I think the straightforward way is to let the projection parameter to be a tuple of (AxesClass, keyword_parameters), and forget about a callable object or a object with _as_mpl_axes method.

My comment on your original PR was more likely from a point of view from the user. I mean, how do you want your doc. look like?

  1. projection can be any object with _as_mpl_axes method which returns ....
  2. projection can be any callable object which returns ...
  3. projection can be an instance of the ProjectionBase class or its derivatives.
  4. projection can be a tuple of custom Axes class and a dictionary of keyword arguments.

And I don't think there are much strong points for option1 compared to other options.

Anyhow, I don't want this pull request to be pending because of my inclination. If others are okay, please go ahead and merge (of course after code reviews).

@WeatherGod
Copy link
Member

Sorry for the delay, I will likely be looking over this PR within the next couple of days.

@pelson
Copy link
Member Author

pelson commented Feb 16, 2012

Thanks Ben & leejjoon, I have a small change which I was considering adding to this branch which allows a couple of nice features:

  • Enables the transform keyword to contour, contourf and scatter (example code):
import matplotlib.pyplot as plt
import matplotlib.transforms as mtrans
import numpy

pi = numpy.pi

x = numpy.linspace(0, pi / 2, 50).reshape(1, -1)
y = numpy.linspace(0.1, 1, 30).reshape(-1, 1)
z = (x ** 2 + y ** 2) ** 0.5

scatter_theta, scatter_r = pi/2 * 1.5 , 0.5  

ax = plt.axes(projection='polar')

# rotate the polar transform by one radian
rotated_polar = mtrans.Affine2D().translate(pi, 0) + ax.transData

# contour the data, once with the standard transform, once with the rotated transform  
plt.contourf(x.flatten(), y.flatten(), z)
plt.scatter(scatter_theta, scatter_r, c='b')
plt.contourf(x.flatten(), y.flatten(), z, transform=rotated_polar)
plt.scatter(scatter_theta, scatter_r, c='r', transform=rotated_polar)

plt.show()
  • Enables syntax for an external package, assuming suitable Transform and Axes subclasses exist (which they normally do if your in this domain), to look something like:
import matplotlib.pyplot as plt
plt.axes(projection=HammerProjection())
plt.contourf(lons, lats, data, transform=LonLatProjection())
plt.scatter(mollweide_xs, mollweide_ys, transform=MollweideProjection())
plt.plot(hammer_xs, hammer_ys)
plt.show()

I haven't integrated the (few) changes in to this branch but if you want to check them out (and let me know if you want me to include them in this branch) then you can find them at https://github.com/PhilipElson/matplotlib/compare/class_projections...class_transforms

@rhattersley
Copy link
Contributor

PhilipElson's latest code snippet demonstrates that it'll be extremely useful and elegant to use the same object in projection=... as in transform=.... That alone makes me favour using a specific object rather than a tuple of Axes-subclass and keyword args.

Regarding the question of how this could be documented, perhaps an abstract class (or two - one for each method) could be useful. Then the documentation could state, "projection can be any instance implementing the Projection abstract class".

    class Projection(object):
        __metaclass__ = ABCMeta

        @abstractmethod
        def _as_mpl_axes(self):
            pass

        @abstractmethod
        def _as_mpl_transform(self):
            pass

fig.add_subplot(212, axisbg='r') # add subplot with red background
fig.add_subplot(111, polar=True) # add a polar subplot
fig.add_subplot(sub) # add Subplot instance sub
fig.add_subplot(1,1,1) # equivalent but more general
Copy link
Member

Choose a reason for hiding this comment

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

These lines exceed 75 characters wide. Please follow PEP 8.

@pelson
Copy link
Member Author

pelson commented Feb 20, 2012

Thanks for the review Ben. I think this covers your requested actions. If not please let me know and I will update asap.

What was your opinion on including the transform capabilities at the same time? (https://github.com/PhilipElson/matplotlib/compare/class_projections...class_transforms)

Many Thanks,

@pelson
Copy link
Member Author

pelson commented Feb 20, 2012

There was an error being raised in one of the tests which was highlighting a bug in my changes. That was fixed in the last commit.

@WeatherGod
Copy link
Member

Let's keep this piece-meal. So far, things are looking good. The last few commits that refactored the code in figure.py deserves extra scrutiny given how central it is in the rest of mpl. I commend your fortitude to attempt to tackle it. Myself and others will likely be making further comments on this pull.

Any other kwargs are passed along to the specific projection
constructor being used.
"""
def process_projection_requirements(figure, *args, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

I think you double-tabbed the body of this method.

@WeatherGod
Copy link
Member

Probably my only remaining question is this. In mplot3d, the 3d axes look best when the rect=(0.0, 0.0, 1.0, 1.0), but this is not the default for SubplotAxes (which is the usual method of adding axes to a figure). Do you see a way with your PR (or future addendums) to allow mplot3d to override this default when "projection='3d'"? Currently, I see rect treated explicitly and is not a possible kwarg.

@pelson
Copy link
Member Author

pelson commented Mar 16, 2012

Good question. If we accept that subplots have their rects defined by the subplot specification, and therefore cannot be changed, I think your question can be boiled down to the following:

  • Why should plt.figure().add_axes() not create an axes (with a default rect defined by Axes)?
  • Why does plt.gca() create an AxesSubplot rather than an Axes?

I don't have enough background pyplot knowledge to answer those questions fully. And I fear such a discussion in this pull request is only going to confuse matters. Feel free to quote me if you want to spark a discussion on the devel mailing list.

@pelson
Copy link
Member Author

pelson commented Mar 16, 2012

Think that is the final piece to the jigsaw. Let me know if there is anything else.

@WeatherGod
Copy link
Member

I think the questions about add_axes() and gca() are a bit in the wrong direction. A subplot axes is just the part that gets graphed, and does not include the axes labels and tick labels and titles. It is the white part. So, whether or not the axes is the only one in the figure or a part of multiple subplots, it still has to make room for those labels. This works with most of the 2d graphs mpl uses just fine.

The issue with mplot3d is that the tick and axes labels are drawn within the white space, and so there is a lot of wasted space surrounding the 3d plot, regardless of whether we have a single plot or multiple plots within the figure.

It is my understanding that the SubplotAxes wrapper class just simply became the de-facto Axes object because it was just simply a thin wrapper and made it much easier to have an arbitrary number of axes in the figure. Due to its ubiquitous use, it became the injection point for loading up different kinds of axes into the figure (through the register_projections mechanism. The gca() and add_axes() idioms creating a subplot axes is merely because it is impossible to know if the user will want to add more later.

@pelson
Copy link
Member Author

pelson commented Apr 20, 2012

@WeatherGod: do you think we should open the discussion re a default rect up to the mailing list? Its an interesting topic but would require a break in api (in particular figure.add_axes() needs to be possible in order to allow figure.add_axes(projection='3d')) which it would be nice to be clear about before doing any actual development.

As far as I can see looking through all of the other comments, I think this PR is complete. Is there anything else which I should do before this can be merged?

Thanks,

@WeatherGod
Copy link
Member

Sorry for the delay. I think you are ready for this to be pulled in (I
will give it one last look-over). When we pull it in, we probably should
post an email to the devel list explaining the changes.

Just realized that we need to add an entry to doc/devel/index.rst for your
new doc.

@pelson
Copy link
Member Author

pelson commented Apr 24, 2012

| Just realized that we need to add an entry to doc/devel/index.rst for your new doc.

Sorry Ben, I'm not following, both api_change & add_new_projection are already included in the docs.

@WeatherGod
Copy link
Member

My bad, for some reason, I was thinking that add_new_projection.rst was new.

@pelson
Copy link
Member Author

pelson commented May 21, 2012

Does this branch need rebasing to a newer version of master before it can be merged or does it just merge cleanly?

@mdboom
Copy link
Member

mdboom commented Jun 1, 2012

Sorry this has languished. I think I'm probably too close to the original code here to understand what necessitates this change. Is there a short summary of what this allows that can't be done in the current system and why it's an improvement? Also, rebasing against current master at this point might make things easier to compare. Thanks.

@pelson
Copy link
Member Author

pelson commented Jun 6, 2012

@mdboom: The linked pull request (#470) has the following description:

I would like the ability to setup a plot projection in MPL that can be defined by various parameters and does not need to be serialised as a string and registered with matplotlib.projections.register_projection. For example, an extension of /examples/api/custom_projection_example.py might be to add the ability to define the central meridian to an arbitrary value rather than the current value of 0 - in this case I would expect to define the projection by creating an object which can then be turned into a matplotlib axes:

hammer_proj = Hammer(central_meridian=45)
ax = plt.subplot(111, projection=hammer_proj)

@pelson
Copy link
Member Author

pelson commented Jun 6, 2012

Rebasing was non-trivial, so I did a squashed merge on a new branch (PR #923).

@WeatherGod
Copy link
Member

Thanks for resubmitting the PR. I will close this PR.

@WeatherGod WeatherGod closed this Jun 6, 2012
pelson pushed a commit to pelson/matplotlib that referenced this pull request Jun 11, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants