Matplotlib: custom projection for hemisphere / wedge

I am looking at custom projection in matplotlib gallery - I am trying to change it should only plot the southern hemisphere. I adjusted the required [-pi / 2, pi / 2] limits to [-pi / 2.0]. Now I am looking at:

def _gen_axes_patch(self):
    Override this method to define the shape that is used for the
    background of the plot.  It should be a subclass of Patch.

    In this case, it is a Circle (that may be warped by the axes
    transform into an ellipse).  Any data and gridlines will be
    clipped to this shape.
    #return Circle((0.5, 0.5), 0.5)
    return Wedge((0.5,0.5), 0.5, 180, 360)

def _gen_axes_spines(self):
    return {'custom_hammer':mspines.Spine.circular_spine(self,
                                                  (0.5, 0.5), 0.25)}


As you can see, I replaced the Circle patch with a wedge. This is what the projection plot looks like now: projection plot

The spine still follows the circle / ellipse - how can I indicate that I want the spine to follow the wedge border?

I'm not sure what is the best way to change the spine, so any help would be much appreciated!




source to share

1 answer

Just for the record, you are sure to jump straight to the deep end of the pool if you are still new to python. (And you, luckily, right now!)

What you are doing requires a fairly detailed knowledge of the inner workings of matplotlib, which is a fairly complex library.

That being said, this is a good way to learn quickly!

For something like this, you need to understand the internal architecture of how things are structured, not just "public" apis.

For most of this, you need to dig in and "source". For any project, the internal processing documentation is the code itself.

That was said, for a simple case, it's pretty straight forward.

import numpy as np
from matplotlib.projections.geo import HammerAxes
import matplotlib.projections as mprojections
from matplotlib.axes import Axes
from matplotlib.patches import Wedge
import matplotlib.spines as mspines

class LowerHammerAxes(HammerAxes):
    name = 'lower_hammer'
    def cla(self):
        Axes.set_xlim(self, -np.pi, np.pi)
        Axes.set_ylim(self, -np.pi / 2.0, 0)

    def _gen_axes_patch(self):
        return Wedge((0.5, 0.5), 0.5, 180, 360)

    def _gen_axes_spines(self):
        path = Wedge((0, 0), 1.0, 180, 360).get_path()
        spine = mspines.Spine(self, 'circle', path)
        spine.set_patch_circle((0.5, 0.5), 0.5)
        return {'wedge':spine}


if __name__ == '__main__':
    import matplotlib.pyplot as plt
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='lower_hammer')


enter image description here

Dig in the method a bit _get_axes_spines


def _gen_axes_spines(self):
    """Return the spines for the axes."""
    # Make the path for the spines
    # We need the path, rather than the patch, thus the "get_path()"
    # The path is expected to be centered at 0,0, with radius of 1
    # It will be transformed by `Spine` when we initialize it
    path = Wedge((0, 0), 1.0, 180, 360).get_path()

    # We can fake a "wedge" spine without subclassing `Spine` by initializing 
    # it as a circular spine with the wedge path. 
    spine = mspines.Spine(self, 'circle', path)

    # This sets some attributes of the patch object. In this particular 
    # case, what it sets happens to be approriate for our "wedge spine"
    spine.set_patch_circle((0.5, 0.5), 0.5)

    # Spines in matplotlib are handled in a dict (normally, you'd have top,
    # left, right, and bottom, instead of just wedge). The name is arbitrary
    return {'wedge':spine}


Now there are several problems with this:

  • Things are not centered within the axis correctly
  • The axes patch can be scaled a little more to properly tackle the room within the axes.
  • We draw the grid lines for the full globe and then crop them. It would be more efficient to just draw them inside our "bottom" wedge.

However, when we look at the structure HammerAxes

, you will notice that many things (especially centering the axis patch) are effectively hard-coded in the transforms. (As they note in the comments, this means a "toy" example, and, assuming you're always dealing with a full globe, makes the math in transformations much easier.)

If you want to fix this, you will need to configure several different transforms in HammerAxes._set_lim_and_transforms


However, it works well enough as it is, so I'll leave this as an exercise for the reader. :) (Be careful, this part is a little more complicated, as it requires detailed knowledge of matplotlib transforms.)



All Articles