Revision: 5230
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=5230&view=rev
Author: mdboom
Date: 2008-05-23 10:41:36 -0700 (Fri, 23 May 2008)
Log Message:
-----------
Move examples from pylab to api.
Added Paths:
-----------
trunk/matplotlib/examples/api/custom_projection_example.py
trunk/matplotlib/examples/api/custom_scale_example.py
Removed Paths:
-------------
trunk/matplotlib/examples/pylab/custom_projection_example.py
trunk/matplotlib/examples/pylab/custom_scale_example.py
Copied: trunk/matplotlib/examples/api/custom_projection_example.py (from rev
5226, trunk/matplotlib/examples/pylab/custom_projection_example.py)
===================================================================
--- trunk/matplotlib/examples/api/custom_projection_example.py
(rev 0)
+++ trunk/matplotlib/examples/api/custom_projection_example.py 2008-05-23
17:41:36 UTC (rev 5230)
@@ -0,0 +1,475 @@
+from matplotlib.axes import Axes
+from matplotlib import cbook
+from matplotlib.patches import Circle
+from matplotlib.path import Path
+from matplotlib.ticker import Formatter, Locator, NullLocator, FixedLocator,
NullFormatter
+from matplotlib.transforms import Affine2D, Affine2DBase, Bbox, \
+ BboxTransformTo, IdentityTransform, Transform, TransformWrapper
+from matplotlib.projections import register_projection
+
+import numpy as np
+
+# This example projection class is rather long, but it is designed to
+# illustrate many features, not all of which will be used every time.
+# It is also common to factor out a lot of these methods into common
+# code used by a number of projections with similar characteristics
+# (see geo.py).
+
+class HammerAxes(Axes):
+ """
+ A custom class for the Aitoff-Hammer projection, an equal-area map
+ projection.
+
+ http://en.wikipedia.org/wiki/Hammer_projection
+ """
+ # The projection must specify a name. This will be used be the
+ # user to select the projection, i.e. ``subplot(111,
+ # projection='hammer')``.
+ name = 'hammer'
+
+ # The number of interpolation steps when converting from straight
+ # lines to curves. (See ``transform_path``).
+ RESOLUTION = 75
+
+ def __init__(self, *args, **kwargs):
+ Axes.__init__(self, *args, **kwargs)
+ self.set_aspect(0.5, adjustable='box', anchor='C')
+ self.cla()
+
+ def cla(self):
+ """
+ Override to set up some reasonable defaults.
+ """
+ # Don't forget to call the base class
+ Axes.cla(self)
+
+ # Set up a default grid spacing
+ self.set_longitude_grid(30)
+ self.set_latitude_grid(15)
+ self.set_longitude_grid_ends(75)
+
+ # Turn off minor ticking altogether
+ self.xaxis.set_minor_locator(NullLocator())
+ self.yaxis.set_minor_locator(NullLocator())
+
+ # Do not display ticks -- we only want gridlines and text
+ self.xaxis.set_ticks_position('none')
+ self.yaxis.set_ticks_position('none')
+
+ # The limits on this projection are fixed -- they are not to
+ # be changed by the user. This makes the math in the
+ # transformation itself easier, and since this is a toy
+ # example, the easier, the better.
+ Axes.set_xlim(self, -np.pi, np.pi)
+ Axes.set_ylim(self, -np.pi / 2.0, np.pi / 2.0)
+
+ def cla(self):
+ """
+ Initialize the Axes object to reasonable defaults.
+ """
+ Axes.cla(self)
+
+ self.set_longitude_grid(30)
+ self.set_latitude_grid(15)
+ self.set_longitude_grid_ends(75)
+ self.xaxis.set_minor_locator(NullLocator())
+ self.yaxis.set_minor_locator(NullLocator())
+ self.xaxis.set_ticks_position('none')
+ self.yaxis.set_ticks_position('none')
+
+ # self.grid(rcParams['axes.grid'])
+
+ Axes.set_xlim(self, -np.pi, np.pi)
+ Axes.set_ylim(self, -np.pi / 2.0, np.pi / 2.0)
+
+ def _set_lim_and_transforms(self):
+ """
+ This is called once when the plot is created to set up all the
+ transforms for the data, text and grids.
+ """
+ # There are three important coordinate spaces going on here:
+ #
+ # 1. Data space: The space of the data itself
+ #
+ # 2. Axes space: The unit rectangle (0, 0) to (1, 1)
+ # covering the entire plot area.
+ #
+ # 3. Display space: The coordinates of the resulting image,
+ # often in pixels or dpi/inch.
+
+ # This function makes heavy use of the Transform classes in
+ # ``lib/matplotlib/transforms.py.`` For more information, see
+ # the inline documentation there.
+
+ # The goal of the first two transformations is to get from the
+ # data space (in this case longitude and latitude) to axes
+ # space. It is separated into a non-affine and affine part so
+ # that the non-affine part does not have to be recomputed when
+ # a simple affine change to the figure has been made (such as
+ # resizing the window or changing the dpi).
+
+ # 1) The core transformation from data space into
+ # rectilinear space defined in the HammerTransform class.
+ self.transProjection = self.HammerTransform(self.RESOLUTION)
+
+ # 2) The above has an output range that is not in the unit
+ # rectangle, so scale and translate it so it fits correctly
+ # within the axes. The peculiar calculations of xscale and
+ # yscale are specific to a Aitoff-Hammer projection, so don't
+ # worry about them too much.
+ xscale = 2.0 * np.sqrt(2.0) * np.sin(0.5 * np.pi)
+ yscale = np.sqrt(2.0) * np.sin(0.5 * np.pi)
+ self.transAffine = Affine2D() \
+ .scale(0.5 / xscale, 0.5 / yscale) \
+ .translate(0.5, 0.5)
+
+ # 3) This is the transformation from axes space to display
+ # space.
+ self.transAxes = BboxTransformTo(self.bbox)
+
+ # Now put these 3 transforms together -- from data all the way
+ # to display coordinates. Using the '+' operator, these
+ # transforms will be applied "in order". The transforms are
+ # automatically simplified, if possible, by the underlying
+ # transformation framework.
+ self.transData = \
+ self.transProjection + \
+ self.transAffine + \
+ self.transAxes
+
+ # The main data transformation is set up. Now deal with
+ # gridlines and tick labels.
+
+ # Longitude gridlines and ticklabels. The input to these
+ # transforms are in display space in x and axes space in y.
+ # Therefore, the input values will be in range (-xmin, 0),
+ # (xmax, 1). The goal of these transforms is to go from that
+ # space to display space. The tick labels will be offset 4
+ # pixels from the equator.
+ self._xaxis_pretransform = \
+ Affine2D() \
+ .scale(1.0, np.pi) \
+ .translate(0.0, -np.pi)
+ self._xaxis_transform = \
+ self._xaxis_pretransform + \
+ self.transData
+ self._xaxis_text1_transform = \
+ Affine2D().scale(1.0, 0.0) + \
+ self.transData + \
+ Affine2D().translate(0.0, 4.0)
+ self._xaxis_text2_transform = \
+ Affine2D().scale(1.0, 0.0) + \
+ self.transData + \
+ Affine2D().translate(0.0, -4.0)
+
+ # Now set up the transforms for the latitude ticks. The input to
+ # these transforms are in axes space in x and display space in
+ # y. Therefore, the input values will be in range (0, -ymin),
+ # (1, ymax). The goal of these transforms is to go from that
+ # space to display space. The tick labels will be offset 4
+ # pixels from the edge of the axes ellipse.
+ yaxis_stretch = Affine2D().scale(np.pi * 2.0, 1.0).translate(-np.pi,
0.0)
+ yaxis_space = Affine2D().scale(1.0, 1.1)
+ self._yaxis_transform = \
+ yaxis_stretch + \
+ self.transData
+ yaxis_text_base = \
+ yaxis_stretch + \
+ self.transProjection + \
+ (yaxis_space + \
+ self.transAffine + \
+ self.transAxes)
+ self._yaxis_text1_transform = \
+ yaxis_text_base + \
+ Affine2D().translate(-8.0, 0.0)
+ self._yaxis_text2_transform = \
+ yaxis_text_base + \
+ Affine2D().translate(8.0, 0.0)
+
+ def get_xaxis_transform(self):
+ """
+ Override this method to provide a transformation for the
+ x-axis grid and ticks.
+ """
+ return self._xaxis_transform
+
+ def get_xaxis_text1_transform(self, pixelPad):
+ """
+ Override this method to provide a transformation for the
+ x-axis tick labels.
+
+ Returns a tuple of the form (transform, valign, halign)
+ """
+ return self._xaxis_text1_transform, 'bottom', 'center'
+
+ def get_xaxis_text2_transform(self, pixelPad):
+ """
+ Override this method to provide a transformation for the
+ secondary x-axis tick labels.
+
+ Returns a tuple of the form (transform, valign, halign)
+ """
+ return self._xaxis_text2_transform, 'top', 'center'
+
+ def get_yaxis_transform(self):
+ """
+ Override this method to provide a transformation for the
+ y-axis grid and ticks.
+ """
+ return self._yaxis_transform
+
+ def get_yaxis_text1_transform(self, pixelPad):
+ """
+ Override this method to provide a transformation for the
+ y-axis tick labels.
+
+ Returns a tuple of the form (transform, valign, halign)
+ """
+ return self._yaxis_text1_transform, 'center', 'right'
+
+ def get_yaxis_text2_transform(self, pixelPad):
+ """
+ Override this method to provide a transformation for the
+ secondary y-axis tick labels.
+
+ Returns a tuple of the form (transform, valign, halign)
+ """
+ return self._yaxis_text2_transform, 'center', 'left'
+
+ def get_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)
+
+ # Prevent the user from applying scales to one or both of the
+ # axes. In this particular case, scaling the axes wouldn't make
+ # sense, so we don't allow it.
+ def set_xscale(self, *args, **kwargs):
+ if args[0] != 'linear':
+ raise NotImplementedError
+ Axes.set_xscale(self, *args, **kwargs)
+
+ def set_yscale(self, *args, **kwargs):
+ if args[0] != 'linear':
+ raise NotImplementedError
+ Axes.set_yscale(self, *args, **kwargs)
+
+ # Prevent the user from changing the axes limits. In our case, we
+ # want to display the whole sphere all the time, so we override
+ # set_xlim and set_ylim to ignore any input. This also applies to
+ # interactive panning and zooming in the GUI interfaces.
+ def set_xlim(self, *args, **kwargs):
+ Axes.set_xlim(self, -np.pi, np.pi)
+ Axes.set_ylim(self, -np.pi / 2.0, np.pi / 2.0)
+ set_ylim = set_xlim
+
+ def format_coord(self, long, lat):
+ """
+ Override this method to change how the values are displayed in
+ the status bar.
+
+ In this case, we want them to be displayed in degrees N/S/E/W.
+ """
+ long = long * (180.0 / np.pi)
+ lat = lat * (180.0 / np.pi)
+ if lat >= 0.0:
+ ns = 'N'
+ else:
+ ns = 'S'
+ if long >= 0.0:
+ ew = 'E'
+ else:
+ ew = 'W'
+ # \u00b0 : degree symbol
+ return u'%f\u00b0%s, %f\u00b0%s' % (abs(lat), ns, abs(long), ew)
+
+ class DegreeFormatter(Formatter):
+ """
+ This is a custom formatter that converts the native unit of
+ radians into (truncated) degrees and adds a degree symbol.
+ """
+ def __init__(self, round_to=1.0):
+ self._round_to = round_to
+
+ def __call__(self, x, pos=None):
+ degrees = (x / np.pi) * 180.0
+ degrees = round(degrees / self._round_to) * self._round_to
+ # \u00b0 : degree symbol
+ return u"%d\u00b0" % degrees
+
+ def set_longitude_grid(self, degrees):
+ """
+ Set the number of degrees between each longitude grid.
+
+ This is an example method that is specific to this projection
+ class -- it provides a more convenient interface to set the
+ ticking than set_xticks would.
+ """
+ # Set up a FixedLocator at each of the points, evenly spaced
+ # by degrees.
+ number = (360.0 / degrees) + 1
+ self.xaxis.set_major_locator(
+ FixedLocator(
+ np.linspace(-np.pi, np.pi, number, True)[1:-1]))
+ # Set the formatter to display the tick labels in degrees,
+ # rather than radians.
+ self.xaxis.set_major_formatter(self.DegreeFormatter(degrees))
+
+ def set_latitude_grid(self, degrees):
+ """
+ Set the number of degrees between each longitude grid.
+
+ This is an example method that is specific to this projection
+ class -- it provides a more convenient interface than
+ set_yticks would.
+ """
+ # Set up a FixedLocator at each of the points, evenly spaced
+ # by degrees.
+ number = (180.0 / degrees) + 1
+ self.yaxis.set_major_locator(
+ FixedLocator(
+ np.linspace(-np.pi / 2.0, np.pi / 2.0, number, True)[1:-1]))
+ # Set the formatter to display the tick labels in degrees,
+ # rather than radians.
+ self.yaxis.set_major_formatter(self.DegreeFormatter(degrees))
+
+ def set_longitude_grid_ends(self, degrees):
+ """
+ Set the latitude(s) at which to stop drawing the longitude grids.
+
+ Often, in geographic projections, you wouldn't want to draw
+ longitude gridlines near the poles. This allows the user to
+ specify the degree at which to stop drawing longitude grids.
+
+ This is an example method that is specific to this projection
+ class -- it provides an interface to something that has no
+ analogy in the base Axes class.
+ """
+ longitude_cap = degrees * (np.pi / 180.0)
+ # Change the xaxis gridlines transform so that it draws from
+ # -degrees to degrees, rather than -pi to pi.
+ self._xaxis_pretransform \
+ .clear() \
+ .scale(1.0, longitude_cap * 2.0) \
+ .translate(0.0, -longitude_cap)
+
+ def get_data_ratio(self):
+ """
+ Return the aspect ratio of the data itself.
+
+ This method should be overridden by any Axes that have a
+ fixed data ratio.
+ """
+ return 1.0
+
+ # Interactive panning and zooming is not supported with this projection,
+ # so we override all of the following methods to disable it.
+ def can_zoom(self):
+ """
+ Return True if this axes support the zoom box
+ """
+ return False
+ def start_pan(self, x, y, button):
+ pass
+ def end_pan(self):
+ pass
+ def drag_pan(self, button, key, x, y):
+ pass
+
+ # Now, the transforms themselves.
+
+ class HammerTransform(Transform):
+ """
+ The base Hammer transform.
+ """
+ input_dims = 2
+ output_dims = 2
+ is_separable = False
+
+ def __init__(self, resolution):
+ """
+ Create a new Hammer transform. Resolution is the number of steps
+ to interpolate between each input line segment to approximate its
+ path in curved Hammer space.
+ """
+ Transform.__init__(self)
+ self._resolution = resolution
+
+ def transform(self, ll):
+ """
+ Override the transform method to implement the custom transform.
+
+ The input and output are Nx2 numpy arrays.
+ """
+ longitude = ll[:, 0:1]
+ latitude = ll[:, 1:2]
+
+ # Pre-compute some values
+ half_long = longitude / 2.0
+ cos_latitude = np.cos(latitude)
+ sqrt2 = np.sqrt(2.0)
+
+ alpha = 1.0 + cos_latitude * np.cos(half_long)
+ x = (2.0 * sqrt2) * (cos_latitude * np.sin(half_long)) / alpha
+ y = (sqrt2 * np.sin(latitude)) / alpha
+ return np.concatenate((x, y), 1)
+
+ # This is where things get interesting. With this projection,
+ # straight lines in data space become curves in display space.
+ # This is done by interpolating new values between the input
+ # values of the data. Since ``transform`` must not return a
+ # differently-sized array, any transform that requires
+ # changing the length of the data array must happen within
+ # ``transform_path``.
+ def transform_path(self, path):
+ vertices = path.vertices
+ ipath = path.interpolated(self._resolution)
+ return Path(self.transform(ipath.vertices), ipath.codes)
+
+ def inverted(self):
+ return HammerAxes.InvertedHammerTransform(self._resolution)
+ inverted.__doc__ = Transform.inverted.__doc__
+
+ class InvertedHammerTransform(Transform):
+ input_dims = 2
+ output_dims = 2
+ is_separable = False
+
+ def __init__(self, resolution):
+ Transform.__init__(self)
+ self._resolution = resolution
+
+ def transform(self, xy):
+ x = xy[:, 0:1]
+ y = xy[:, 1:2]
+
+ quarter_x = 0.25 * x
+ half_y = 0.5 * y
+ z = np.sqrt(1.0 - quarter_x*quarter_x - half_y*half_y)
+ longitude = 2 * np.arctan((z*x) / (2.0 * (2.0*z*z - 1.0)))
+ latitude = np.arcsin(y*z)
+ return np.concatenate((longitude, latitude), 1)
+ transform.__doc__ = Transform.transform.__doc__
+
+ def inverted(self):
+ # The inverse of the inverse is the original transform... ;)
+ return HammerAxes.HammerTransform(self._resolution)
+ inverted.__doc__ = Transform.inverted.__doc__
+
+# Now register the projection with matplotlib so the user can select
+# it.
+register_projection(HammerAxes)
+
+# Now make a simple example using the custom projection.
+from pylab import *
+
+subplot(111, projection="hammer")
+grid(True)
+
+show()
Copied: trunk/matplotlib/examples/api/custom_scale_example.py (from rev 5226,
trunk/matplotlib/examples/pylab/custom_scale_example.py)
===================================================================
--- trunk/matplotlib/examples/api/custom_scale_example.py
(rev 0)
+++ trunk/matplotlib/examples/api/custom_scale_example.py 2008-05-23
17:41:36 UTC (rev 5230)
@@ -0,0 +1,165 @@
+from matplotlib import scale as mscale
+from matplotlib import transforms as mtransforms
+
+class MercatorLatitudeScale(mscale.ScaleBase):
+ """
+ Scales data in range -pi/2 to pi/2 (-90 to 90 degrees) using
+ the system used to scale latitudes in a Mercator projection.
+
+ The scale function:
+ ln(tan(y) + sec(y))
+
+ The inverse scale function:
+ atan(sinh(y))
+
+ Since the Mercator scale tends to infinity at +/- 90 degrees,
+ there is user-defined threshold, above and below which nothing
+ will be plotted. This defaults to +/- 85 degrees.
+
+ source:
+ http://en.wikipedia.org/wiki/Mercator_projection
+ """
+
+ # The scale class must have a member ``name`` that defines the
+ # string used to select the scale. For example,
+ # ``gca().set_yscale("mercator")`` would be used to select this
+ # scale.
+ name = 'mercator'
+
+
+ def __init__(self, axis, **kwargs):
+ """
+ Any keyword arguments passed to ``set_xscale`` and
+ ``set_yscale`` will be passed along to the scale's
+ constructor.
+
+ thresh: The degree above which to crop the data.
+ """
+ mscale.ScaleBase.__init__(self)
+ thresh = kwargs.pop("thresh", (85 / 180.0) * np.pi)
+ if thresh >= np.pi / 2.0:
+ raise ValueError("thresh must be less than pi/2")
+ self.thresh = thresh
+
+ def get_transform(self):
+ """
+ Override this method to return a new instance that does the
+ actual transformation of the data.
+
+ The MercatorLatitudeTransform class is defined below as a
+ nested class of this one.
+ """
+ return self.MercatorLatitudeTransform(self.thresh)
+
+ def set_default_locators_and_formatters(self, axis):
+ """
+ Override to set up the locators and formatters to use with the
+ scale. This is only required if the scale requires custom
+ locators and formatters. Writing custom locators and
+ formatters is rather outside the scope of this example, but
+ there are many helpful examples in ``ticker.py``.
+
+ In our case, the Mercator example uses a fixed locator from
+ -90 to 90 degrees and a custom formatter class to put convert
+ the radians to degrees and put a degree symbol after the
+ value::
+ """
+ class DegreeFormatter(Formatter):
+ def __call__(self, x, pos=None):
+ # \u00b0 : degree symbol
+ return u"%d\u00b0" % ((x / np.pi) * 180.0)
+
+ deg2rad = np.pi / 180.0
+ axis.set_major_locator(FixedLocator(
+ np.arange(-90, 90, 10) * deg2rad))
+ axis.set_major_formatter(DegreeFormatter())
+ axis.set_minor_formatter(DegreeFormatter())
+
+ def limit_range_for_scale(self, vmin, vmax, minpos):
+ """
+ Override to limit the bounds of the axis to the domain of the
+ transform. In the case of Mercator, the bounds should be
+ limited to the threshold that was passed in. Unlike the
+ autoscaling provided by the tick locators, this range limiting
+ will always be adhered to, whether the axis range is set
+ manually, determined automatically or changed through panning
+ and zooming.
+ """
+ return max(vmin, -self.thresh), min(vmax, self.thresh)
+
+ class MercatorLatitudeTransform(mtransforms.Transform):
+ # There are two value members that must be defined.
+ # ``input_dims`` and ``output_dims`` specify number of input
+ # dimensions and output dimensions to the transformation.
+ # These are used by the transformation framework to do some
+ # error checking and prevent incompatible transformations from
+ # being connected together. When defining transforms for a
+ # scale, which are, by definition, separable and have only one
+ # dimension, these members should always be set to 1.
+ input_dims = 1
+ output_dims = 1
+ is_separable = True
+
+ def __init__(self, thresh):
+ mtransforms.Transform.__init__(self)
+ self.thresh = thresh
+
+ def transform(self, a):
+ """
+ This transform takes an Nx1 ``numpy`` array and returns a
+ transformed copy. Since the range of the Mercator scale
+ is limited by the user-specified threshold, the input
+ array must be masked to contain only valid values.
+ ``matplotlib`` will handle masked arrays and remove the
+ out-of-range data from the plot. Importantly, the
+ ``transform`` method *must* return an array that is the
+ same shape as the input array, since these values need to
+ remain synchronized with values in the other dimension.
+ """
+ masked = ma.masked_where((a < -self.thresh) | (a > self.thresh), a)
+ if masked.mask.any():
+ return ma.log(np.abs(ma.tan(masked) + 1.0 / ma.cos(masked)))
+ else:
+ return np.log(np.abs(np.tan(a) + 1.0 / np.cos(a)))
+
+ def inverted(self):
+ """
+ Override this method so matplotlib knows how to get the
+ inverse transform for this transform.
+ """
+ return
MercatorLatitudeScale.InvertedMercatorLatitudeTransform(self.thresh)
+
+ class InvertedMercatorLatitudeTransform(mtransforms.Transform):
+ input_dims = 1
+ output_dims = 1
+ is_separable = True
+
+ def __init__(self, thresh):
+ mtransforms.Transform.__init__(self)
+ self.thresh = thresh
+
+ def transform(self, a):
+ return np.arctan(np.sinh(a))
+
+ def inverted(self):
+ return MercatorLatitudeScale.MercatorLatitudeTransform(self.thresh)
+
+# Now that the Scale class has been defined, it must be registered so
+# that ``matplotlib`` can find it.
+mscale.register_scale(MercatorLatitudeScale)
+
+from pylab import *
+import numpy as np
+
+t = arange(-180.0, 180.0, 0.1)
+s = t / 360.0 * np.pi
+
+plot(t, s, '-', lw=2)
+gca().set_yscale('mercator')
+
+xlabel('Longitude')
+ylabel('Latitude')
+title('Mercator: Projection of the Oppressor')
+grid(True)
+
+show()
Deleted: trunk/matplotlib/examples/pylab/custom_projection_example.py
===================================================================
--- trunk/matplotlib/examples/pylab/custom_projection_example.py
2008-05-23 17:41:07 UTC (rev 5229)
+++ trunk/matplotlib/examples/pylab/custom_projection_example.py
2008-05-23 17:41:36 UTC (rev 5230)
@@ -1,475 +0,0 @@
-from matplotlib.axes import Axes
-from matplotlib import cbook
-from matplotlib.patches import Circle
-from matplotlib.path import Path
-from matplotlib.ticker import Formatter, Locator, NullLocator, FixedLocator,
NullFormatter
-from matplotlib.transforms import Affine2D, Affine2DBase, Bbox, \
- BboxTransformTo, IdentityTransform, Transform, TransformWrapper
-from matplotlib.projections import register_projection
-
-import numpy as np
-
-# This example projection class is rather long, but it is designed to
-# illustrate many features, not all of which will be used every time.
-# It is also common to factor out a lot of these methods into common
-# code used by a number of projections with similar characteristics
-# (see geo.py).
-
-class HammerAxes(Axes):
- """
- A custom class for the Aitoff-Hammer projection, an equal-area map
- projection.
-
- http://en.wikipedia.org/wiki/Hammer_projection
- """
- # The projection must specify a name. This will be used be the
- # user to select the projection, i.e. ``subplot(111,
- # projection='hammer')``.
- name = 'hammer'
-
- # The number of interpolation steps when converting from straight
- # lines to curves. (See ``transform_path``).
- RESOLUTION = 75
-
- def __init__(self, *args, **kwargs):
- Axes.__init__(self, *args, **kwargs)
- self.set_aspect(0.5, adjustable='box', anchor='C')
- self.cla()
-
- def cla(self):
- """
- Override to set up some reasonable defaults.
- """
- # Don't forget to call the base class
- Axes.cla(self)
-
- # Set up a default grid spacing
- self.set_longitude_grid(30)
- self.set_latitude_grid(15)
- self.set_longitude_grid_ends(75)
-
- # Turn off minor ticking altogether
- self.xaxis.set_minor_locator(NullLocator())
- self.yaxis.set_minor_locator(NullLocator())
-
- # Do not display ticks -- we only want gridlines and text
- self.xaxis.set_ticks_position('none')
- self.yaxis.set_ticks_position('none')
-
- # The limits on this projection are fixed -- they are not to
- # be changed by the user. This makes the math in the
- # transformation itself easier, and since this is a toy
- # example, the easier, the better.
- Axes.set_xlim(self, -np.pi, np.pi)
- Axes.set_ylim(self, -np.pi / 2.0, np.pi / 2.0)
-
- def cla(self):
- """
- Initialize the Axes object to reasonable defaults.
- """
- Axes.cla(self)
-
- self.set_longitude_grid(30)
- self.set_latitude_grid(15)
- self.set_longitude_grid_ends(75)
- self.xaxis.set_minor_locator(NullLocator())
- self.yaxis.set_minor_locator(NullLocator())
- self.xaxis.set_ticks_position('none')
- self.yaxis.set_ticks_position('none')
-
- # self.grid(rcParams['axes.grid'])
-
- Axes.set_xlim(self, -np.pi, np.pi)
- Axes.set_ylim(self, -np.pi / 2.0, np.pi / 2.0)
-
- def _set_lim_and_transforms(self):
- """
- This is called once when the plot is created to set up all the
- transforms for the data, text and grids.
- """
- # There are three important coordinate spaces going on here:
- #
- # 1. Data space: The space of the data itself
- #
- # 2. Axes space: The unit rectangle (0, 0) to (1, 1)
- # covering the entire plot area.
- #
- # 3. Display space: The coordinates of the resulting image,
- # often in pixels or dpi/inch.
-
- # This function makes heavy use of the Transform classes in
- # ``lib/matplotlib/transforms.py.`` For more information, see
- # the inline documentation there.
-
- # The goal of the first two transformations is to get from the
- # data space (in this case longitude and latitude) to axes
- # space. It is separated into a non-affine and affine part so
- # that the non-affine part does not have to be recomputed when
- # a simple affine change to the figure has been made (such as
- # resizing the window or changing the dpi).
-
- # 1) The core transformation from data space into
- # rectilinear space defined in the HammerTransform class.
- self.transProjection = self.HammerTransform(self.RESOLUTION)
-
- # 2) The above has an output range that is not in the unit
- # rectangle, so scale and translate it so it fits correctly
- # within the axes. The peculiar calculations of xscale and
- # yscale are specific to a Aitoff-Hammer projection, so don't
- # worry about them too much.
- xscale = 2.0 * np.sqrt(2.0) * np.sin(0.5 * np.pi)
- yscale = np.sqrt(2.0) * np.sin(0.5 * np.pi)
- self.transAffine = Affine2D() \
- .scale(0.5 / xscale, 0.5 / yscale) \
- .translate(0.5, 0.5)
-
- # 3) This is the transformation from axes space to display
- # space.
- self.transAxes = BboxTransformTo(self.bbox)
-
- # Now put these 3 transforms together -- from data all the way
- # to display coordinates. Using the '+' operator, these
- # transforms will be applied "in order". The transforms are
- # automatically simplified, if possible, by the underlying
- # transformation framework.
- self.transData = \
- self.transProjection + \
- self.transAffine + \
- self.transAxes
-
- # The main data transformation is set up. Now deal with
- # gridlines and tick labels.
-
- # Longitude gridlines and ticklabels. The input to these
- # transforms are in display space in x and axes space in y.
- # Therefore, the input values will be in range (-xmin, 0),
- # (xmax, 1). The goal of these transforms is to go from that
- # space to display space. The tick labels will be offset 4
- # pixels from the equator.
- self._xaxis_pretransform = \
- Affine2D() \
- .scale(1.0, np.pi) \
- .translate(0.0, -np.pi)
- self._xaxis_transform = \
- self._xaxis_pretransform + \
- self.transData
- self._xaxis_text1_transform = \
- Affine2D().scale(1.0, 0.0) + \
- self.transData + \
- Affine2D().translate(0.0, 4.0)
- self._xaxis_text2_transform = \
- Affine2D().scale(1.0, 0.0) + \
- self.transData + \
- Affine2D().translate(0.0, -4.0)
-
- # Now set up the transforms for the latitude ticks. The input to
- # these transforms are in axes space in x and display space in
- # y. Therefore, the input values will be in range (0, -ymin),
- # (1, ymax). The goal of these transforms is to go from that
- # space to display space. The tick labels will be offset 4
- # pixels from the edge of the axes ellipse.
- yaxis_stretch = Affine2D().scale(np.pi * 2.0, 1.0).translate(-np.pi,
0.0)
- yaxis_space = Affine2D().scale(1.0, 1.1)
- self._yaxis_transform = \
- yaxis_stretch + \
- self.transData
- yaxis_text_base = \
- yaxis_stretch + \
- self.transProjection + \
- (yaxis_space + \
- self.transAffine + \
- self.transAxes)
- self._yaxis_text1_transform = \
- yaxis_text_base + \
- Affine2D().translate(-8.0, 0.0)
- self._yaxis_text2_transform = \
- yaxis_text_base + \
- Affine2D().translate(8.0, 0.0)
-
- def get_xaxis_transform(self):
- """
- Override this method to provide a transformation for the
- x-axis grid and ticks.
- """
- return self._xaxis_transform
-
- def get_xaxis_text1_transform(self, pixelPad):
- """
- Override this method to provide a transformation for the
- x-axis tick labels.
-
- Returns a tuple of the form (transform, valign, halign)
- """
- return self._xaxis_text1_transform, 'bottom', 'center'
-
- def get_xaxis_text2_transform(self, pixelPad):
- """
- Override this method to provide a transformation for the
- secondary x-axis tick labels.
-
- Returns a tuple of the form (transform, valign, halign)
- """
- return self._xaxis_text2_transform, 'top', 'center'
-
- def get_yaxis_transform(self):
- """
- Override this method to provide a transformation for the
- y-axis grid and ticks.
- """
- return self._yaxis_transform
-
- def get_yaxis_text1_transform(self, pixelPad):
- """
- Override this method to provide a transformation for the
- y-axis tick labels.
-
- Returns a tuple of the form (transform, valign, halign)
- """
- return self._yaxis_text1_transform, 'center', 'right'
-
- def get_yaxis_text2_transform(self, pixelPad):
- """
- Override this method to provide a transformation for the
- secondary y-axis tick labels.
-
- Returns a tuple of the form (transform, valign, halign)
- """
- return self._yaxis_text2_transform, 'center', 'left'
-
- def get_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)
-
- # Prevent the user from applying scales to one or both of the
- # axes. In this particular case, scaling the axes wouldn't make
- # sense, so we don't allow it.
- def set_xscale(self, *args, **kwargs):
- if args[0] != 'linear':
- raise NotImplementedError
- Axes.set_xscale(self, *args, **kwargs)
-
- def set_yscale(self, *args, **kwargs):
- if args[0] != 'linear':
- raise NotImplementedError
- Axes.set_yscale(self, *args, **kwargs)
-
- # Prevent the user from changing the axes limits. In our case, we
- # want to display the whole sphere all the time, so we override
- # set_xlim and set_ylim to ignore any input. This also applies to
- # interactive panning and zooming in the GUI interfaces.
- def set_xlim(self, *args, **kwargs):
- Axes.set_xlim(self, -np.pi, np.pi)
- Axes.set_ylim(self, -np.pi / 2.0, np.pi / 2.0)
- set_ylim = set_xlim
-
- def format_coord(self, long, lat):
- """
- Override this method to change how the values are displayed in
- the status bar.
-
- In this case, we want them to be displayed in degrees N/S/E/W.
- """
- long = long * (180.0 / np.pi)
- lat = lat * (180.0 / np.pi)
- if lat >= 0.0:
- ns = 'N'
- else:
- ns = 'S'
- if long >= 0.0:
- ew = 'E'
- else:
- ew = 'W'
- # \u00b0 : degree symbol
- return u'%f\u00b0%s, %f\u00b0%s' % (abs(lat), ns, abs(long), ew)
-
- class DegreeFormatter(Formatter):
- """
- This is a custom formatter that converts the native unit of
- radians into (truncated) degrees and adds a degree symbol.
- """
- def __init__(self, round_to=1.0):
- self._round_to = round_to
-
- def __call__(self, x, pos=None):
- degrees = (x / np.pi) * 180.0
- degrees = round(degrees / self._round_to) * self._round_to
- # \u00b0 : degree symbol
- return u"%d\u00b0" % degrees
-
- def set_longitude_grid(self, degrees):
- """
- Set the number of degrees between each longitude grid.
-
- This is an example method that is specific to this projection
- class -- it provides a more convenient interface to set the
- ticking than set_xticks would.
- """
- # Set up a FixedLocator at each of the points, evenly spaced
- # by degrees.
- number = (360.0 / degrees) + 1
- self.xaxis.set_major_locator(
- FixedLocator(
- np.linspace(-np.pi, np.pi, number, True)[1:-1]))
- # Set the formatter to display the tick labels in degrees,
- # rather than radians.
- self.xaxis.set_major_formatter(self.DegreeFormatter(degrees))
-
- def set_latitude_grid(self, degrees):
- """
- Set the number of degrees between each longitude grid.
-
- This is an example method that is specific to this projection
- class -- it provides a more convenient interface than
- set_yticks would.
- """
- # Set up a FixedLocator at each of the points, evenly spaced
- # by degrees.
- number = (180.0 / degrees) + 1
- self.yaxis.set_major_locator(
- FixedLocator(
- np.linspace(-np.pi / 2.0, np.pi / 2.0, number, True)[1:-1]))
- # Set the formatter to display the tick labels in degrees,
- # rather than radians.
- self.yaxis.set_major_formatter(self.DegreeFormatter(degrees))
-
- def set_longitude_grid_ends(self, degrees):
- """
- Set the latitude(s) at which to stop drawing the longitude grids.
-
- Often, in geographic projections, you wouldn't want to draw
- longitude gridlines near the poles. This allows the user to
- specify the degree at which to stop drawing longitude grids.
-
- This is an example method that is specific to this projection
- class -- it provides an interface to something that has no
- analogy in the base Axes class.
- """
- longitude_cap = degrees * (np.pi / 180.0)
- # Change the xaxis gridlines transform so that it draws from
- # -degrees to degrees, rather than -pi to pi.
- self._xaxis_pretransform \
- .clear() \
- .scale(1.0, longitude_cap * 2.0) \
- .translate(0.0, -longitude_cap)
-
- def get_data_ratio(self):
- """
- Return the aspect ratio of the data itself.
-
- This method should be overridden by any Axes that have a
- fixed data ratio.
- """
- return 1.0
-
- # Interactive panning and zooming is not supported with this projection,
- # so we override all of the following methods to disable it.
- def can_zoom(self):
- """
- Return True if this axes support the zoom box
- """
- return False
- def start_pan(self, x, y, button):
- pass
- def end_pan(self):
- pass
- def drag_pan(self, button, key, x, y):
- pass
-
- # Now, the transforms themselves.
-
- class HammerTransform(Transform):
- """
- The base Hammer transform.
- """
- input_dims = 2
- output_dims = 2
- is_separable = False
-
- def __init__(self, resolution):
- """
- Create a new Hammer transform. Resolution is the number of steps
- to interpolate between each input line segment to approximate its
- path in curved Hammer space.
- """
- Transform.__init__(self)
- self._resolution = resolution
-
- def transform(self, ll):
- """
- Override the transform method to implement the custom transform.
-
- The input and output are Nx2 numpy arrays.
- """
- longitude = ll[:, 0:1]
- latitude = ll[:, 1:2]
-
- # Pre-compute some values
- half_long = longitude / 2.0
- cos_latitude = np.cos(latitude)
- sqrt2 = np.sqrt(2.0)
-
- alpha = 1.0 + cos_latitude * np.cos(half_long)
- x = (2.0 * sqrt2) * (cos_latitude * np.sin(half_long)) / alpha
- y = (sqrt2 * np.sin(latitude)) / alpha
- return np.concatenate((x, y), 1)
-
- # This is where things get interesting. With this projection,
- # straight lines in data space become curves in display space.
- # This is done by interpolating new values between the input
- # values of the data. Since ``transform`` must not return a
- # differently-sized array, any transform that requires
- # changing the length of the data array must happen within
- # ``transform_path``.
- def transform_path(self, path):
- vertices = path.vertices
- ipath = path.interpolated(self._resolution)
- return Path(self.transform(ipath.vertices), ipath.codes)
-
- def inverted(self):
- return HammerAxes.InvertedHammerTransform(self._resolution)
- inverted.__doc__ = Transform.inverted.__doc__
-
- class InvertedHammerTransform(Transform):
- input_dims = 2
- output_dims = 2
- is_separable = False
-
- def __init__(self, resolution):
- Transform.__init__(self)
- self._resolution = resolution
-
- def transform(self, xy):
- x = xy[:, 0:1]
- y = xy[:, 1:2]
-
- quarter_x = 0.25 * x
- half_y = 0.5 * y
- z = np.sqrt(1.0 - quarter_x*quarter_x - half_y*half_y)
- longitude = 2 * np.arctan((z*x) / (2.0 * (2.0*z*z - 1.0)))
- latitude = np.arcsin(y*z)
- return np.concatenate((longitude, latitude), 1)
- transform.__doc__ = Transform.transform.__doc__
-
- def inverted(self):
- # The inverse of the inverse is the original transform... ;)
- return HammerAxes.HammerTransform(self._resolution)
- inverted.__doc__ = Transform.inverted.__doc__
-
-# Now register the projection with matplotlib so the user can select
-# it.
-register_projection(HammerAxes)
-
-# Now make a simple example using the custom projection.
-from pylab import *
-
-subplot(111, projection="hammer")
-grid(True)
-
-show()
Deleted: trunk/matplotlib/examples/pylab/custom_scale_example.py
===================================================================
--- trunk/matplotlib/examples/pylab/custom_scale_example.py 2008-05-23
17:41:07 UTC (rev 5229)
+++ trunk/matplotlib/examples/pylab/custom_scale_example.py 2008-05-23
17:41:36 UTC (rev 5230)
@@ -1,165 +0,0 @@
-from matplotlib import scale as mscale
-from matplotlib import transforms as mtransforms
-
-class MercatorLatitudeScale(mscale.ScaleBase):
- """
- Scales data in range -pi/2 to pi/2 (-90 to 90 degrees) using
- the system used to scale latitudes in a Mercator projection.
-
- The scale function:
- ln(tan(y) + sec(y))
-
- The inverse scale function:
- atan(sinh(y))
-
- Since the Mercator scale tends to infinity at +/- 90 degrees,
- there is user-defined threshold, above and below which nothing
- will be plotted. This defaults to +/- 85 degrees.
-
- source:
- http://en.wikipedia.org/wiki/Mercator_projection
- """
-
- # The scale class must have a member ``name`` that defines the
- # string used to select the scale. For example,
- # ``gca().set_yscale("mercator")`` would be used to select this
- # scale.
- name = 'mercator'
-
-
- def __init__(self, axis, **kwargs):
- """
- Any keyword arguments passed to ``set_xscale`` and
- ``set_yscale`` will be passed along to the scale's
- constructor.
-
- thresh: The degree above which to crop the data.
- """
- mscale.ScaleBase.__init__(self)
- thresh = kwargs.pop("thresh", (85 / 180.0) * np.pi)
- if thresh >= np.pi / 2.0:
- raise ValueError("thresh must be less than pi/2")
- self.thresh = thresh
-
- def get_transform(self):
- """
- Override this method to return a new instance that does the
- actual transformation of the data.
-
- The MercatorLatitudeTransform class is defined below as a
- nested class of this one.
- """
- return self.MercatorLatitudeTransform(self.thresh)
-
- def set_default_locators_and_formatters(self, axis):
- """
- Override to set up the locators and formatters to use with the
- scale. This is only required if the scale requires custom
- locators and formatters. Writing custom locators and
- formatters is rather outside the scope of this example, but
- there are many helpful examples in ``ticker.py``.
-
- In our case, the Mercator example uses a fixed locator from
- -90 to 90 degrees and a custom formatter class to put convert
- the radians to degrees and put a degree symbol after the
- value::
- """
- class DegreeFormatter(Formatter):
- def __call__(self, x, pos=None):
- # \u00b0 : degree symbol
- return u"%d\u00b0" % ((x / np.pi) * 180.0)
-
- deg2rad = np.pi / 180.0
- axis.set_major_locator(FixedLocator(
- np.arange(-90, 90, 10) * deg2rad))
- axis.set_major_formatter(DegreeFormatter())
- axis.set_minor_formatter(DegreeFormatter())
-
- def limit_range_for_scale(self, vmin, vmax, minpos):
- """
- Override to limit the bounds of the axis to the domain of the
- transform. In the case of Mercator, the bounds should be
- limited to the threshold that was passed in. Unlike the
- autoscaling provided by the tick locators, this range limiting
- will always be adhered to, whether the axis range is set
- manually, determined automatically or changed through panning
- and zooming.
- """
- return max(vmin, -self.thresh), min(vmax, self.thresh)
-
- class MercatorLatitudeTransform(mtransforms.Transform):
- # There are two value members that must be defined.
- # ``input_dims`` and ``output_dims`` specify number of input
- # dimensions and output dimensions to the transformation.
- # These are used by the transformation framework to do some
- # error checking and prevent incompatible transformations from
- # being connected together. When defining transforms for a
- # scale, which are, by definition, separable and have only one
- # dimension, these members should always be set to 1.
- input_dims = 1
- output_dims = 1
- is_separable = True
-
- def __init__(self, thresh):
- mtransforms.Transform.__init__(self)
- self.thresh = thresh
-
- def transform(self, a):
- """
- This transform takes an Nx1 ``numpy`` array and returns a
- transformed copy. Since the range of the Mercator scale
- is limited by the user-specified threshold, the input
- array must be masked to contain only valid values.
- ``matplotlib`` will handle masked arrays and remove the
- out-of-range data from the plot. Importantly, the
- ``transform`` method *must* return an array that is the
- same shape as the input array, since these values need to
- remain synchronized with values in the other dimension.
- """
- masked = ma.masked_where((a < -self.thresh) | (a > self.thresh), a)
- if masked.mask.any():
- return ma.log(np.abs(ma.tan(masked) + 1.0 / ma.cos(masked)))
- else:
- return np.log(np.abs(np.tan(a) + 1.0 / np.cos(a)))
-
- def inverted(self):
- """
- Override this method so matplotlib knows how to get the
- inverse transform for this transform.
- """
- return
MercatorLatitudeScale.InvertedMercatorLatitudeTransform(self.thresh)
-
- class InvertedMercatorLatitudeTransform(mtransforms.Transform):
- input_dims = 1
- output_dims = 1
- is_separable = True
-
- def __init__(self, thresh):
- mtransforms.Transform.__init__(self)
- self.thresh = thresh
-
- def transform(self, a):
- return np.arctan(np.sinh(a))
-
- def inverted(self):
- return MercatorLatitudeScale.MercatorLatitudeTransform(self.thresh)
-
-# Now that the Scale class has been defined, it must be registered so
-# that ``matplotlib`` can find it.
-mscale.register_scale(MercatorLatitudeScale)
-
-from pylab import *
-import numpy as np
-
-t = arange(-180.0, 180.0, 0.1)
-s = t / 360.0 * np.pi
-
-plot(t, s, '-', lw=2)
-gca().set_yscale('mercator')
-
-xlabel('Longitude')
-ylabel('Latitude')
-title('Mercator: Projection of the Oppressor')
-grid(True)
-
-show()
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins