|
From: <ry...@us...> - 2008-07-23 03:08:44
|
Revision: 5819
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=5819&view=rev
Author: ryanmay
Date: 2008-07-23 03:08:41 +0000 (Wed, 23 Jul 2008)
Log Message:
-----------
Added Barbs polygon collection (similar to Quiver) for plotting wind barbs. Added corresponding helpers to Axes and pyplot as well. (examples/pylab_examples/barb_demo.py shows it off.)
Modified Paths:
--------------
trunk/matplotlib/CHANGELOG
trunk/matplotlib/boilerplate.py
trunk/matplotlib/examples/tests/backend_driver.py
trunk/matplotlib/lib/matplotlib/axes.py
trunk/matplotlib/lib/matplotlib/pyplot.py
trunk/matplotlib/lib/matplotlib/quiver.py
Added Paths:
-----------
trunk/matplotlib/examples/pylab_examples/barb_demo.py
Modified: trunk/matplotlib/CHANGELOG
===================================================================
--- trunk/matplotlib/CHANGELOG 2008-07-23 02:44:49 UTC (rev 5818)
+++ trunk/matplotlib/CHANGELOG 2008-07-23 03:08:41 UTC (rev 5819)
@@ -1,3 +1,7 @@
+2008-07-22 Added Barbs polygon collection (similar to Quiver) for plotting
+ wind barbs. Added corresponding helpers to Axes and pyplot as
+ well. (examples/pylab_examples/barb_demo.py shows it off.) - RMM
+
2008-07-21 Added scikits.delaunay as matplotlib.delaunay. Added griddata
function in matplotlib.mlab, with example (griddata_demo.py) in
pylab_examples. griddata function will use mpl_toolkits._natgrid
Modified: trunk/matplotlib/boilerplate.py
===================================================================
--- trunk/matplotlib/boilerplate.py 2008-07-23 02:44:49 UTC (rev 5818)
+++ trunk/matplotlib/boilerplate.py 2008-07-23 03:08:41 UTC (rev 5819)
@@ -86,6 +86,7 @@
'step',
'vlines',
'xcorr',
+ 'barbs',
)
_misccommands = (
Added: trunk/matplotlib/examples/pylab_examples/barb_demo.py
===================================================================
--- trunk/matplotlib/examples/pylab_examples/barb_demo.py (rev 0)
+++ trunk/matplotlib/examples/pylab_examples/barb_demo.py 2008-07-23 03:08:41 UTC (rev 5819)
@@ -0,0 +1,33 @@
+'''
+Demonstration of wind barb plots
+'''
+import matplotlib.pyplot as plt
+import numpy as np
+
+x = np.linspace(-5, 5, 5)
+X,Y = np.meshgrid(x, x)
+U, V = 12*X, 12*Y
+
+data = [(-1.5,.5,-6,-6),
+ (1,-1,-46,46),
+ (-3,-1,11,-11),
+ (1,1.5,80,80)]
+
+#Default parameters for arbitrary set of vectors
+ax = plt.subplot(2,2,1)
+ax.barbs(*zip(*data))
+
+#Default parameters, uniform grid
+ax = plt.subplot(2,2,2)
+ax.barbs(X, Y, U, V)
+
+#Change parameters for arbitrary set of vectors
+ax = plt.subplot(2,2,3)
+ax.barbs(flagcolor='r', barbcolor=['b','g'], barb_increments=dict(half=10,
+ full=20, flag=100), *zip(*data))
+
+#Showing colormapping with uniform grid.
+ax = plt.subplot(2,2,4)
+ax.barbs(X, Y, U, V, np.sqrt(U*U + V*V), fill_empty=True, rounding=False)
+
+plt.show()
Modified: trunk/matplotlib/examples/tests/backend_driver.py
===================================================================
--- trunk/matplotlib/examples/tests/backend_driver.py 2008-07-23 02:44:49 UTC (rev 5818)
+++ trunk/matplotlib/examples/tests/backend_driver.py 2008-07-23 03:08:41 UTC (rev 5819)
@@ -33,6 +33,7 @@
'axhspan_demo.py',
'bar_stacked.py',
'barchart_demo.py',
+ 'barb_demo.py',
'boxplot_demo.py',
'broken_barh.py',
'barh_demo.py',
Modified: trunk/matplotlib/lib/matplotlib/axes.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/axes.py 2008-07-23 02:44:49 UTC (rev 5818)
+++ trunk/matplotlib/lib/matplotlib/axes.py 2008-07-23 03:08:41 UTC (rev 5819)
@@ -5224,6 +5224,15 @@
return q
quiver.__doc__ = mquiver.Quiver.quiver_doc
+ def barbs(self, *args, **kw):
+ if not self._hold: self.cla()
+ b = mquiver.Barbs(self, *args, **kw)
+ self.add_collection(b)
+ self.update_datalim(b.get_offsets())
+ self.autoscale_view()
+ return b
+ barbs.__doc__ = mquiver.Barbs.barbs_doc
+
def fill(self, *args, **kwargs):
"""
call signature::
Modified: trunk/matplotlib/lib/matplotlib/pyplot.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/pyplot.py 2008-07-23 02:44:49 UTC (rev 5818)
+++ trunk/matplotlib/lib/matplotlib/pyplot.py 2008-07-23 03:08:41 UTC (rev 5819)
@@ -2342,6 +2342,28 @@
# This function was autogenerated by boilerplate.py. Do not edit as
# changes will be lost
+def barbs(*args, **kwargs):
+ # allow callers to override the hold state by passing hold=True|False
+ b = ishold()
+ h = kwargs.pop('hold', None)
+ if h is not None:
+ hold(h)
+ try:
+ ret = gca().barbs(*args, **kwargs)
+ draw_if_interactive()
+ except:
+ hold(b)
+ raise
+
+ hold(b)
+ return ret
+if Axes.barbs.__doc__ is not None:
+ barbs.__doc__ = dedent(Axes.barbs.__doc__) + """
+
+Additional kwargs: hold = [True|False] overrides default hold state"""
+
+# This function was autogenerated by boilerplate.py. Do not edit as
+# changes will be lost
def cla(*args, **kwargs):
ret = gca().cla(*args, **kwargs)
Modified: trunk/matplotlib/lib/matplotlib/quiver.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/quiver.py 2008-07-23 02:44:49 UTC (rev 5818)
+++ trunk/matplotlib/lib/matplotlib/quiver.py 2008-07-23 03:08:41 UTC (rev 5819)
@@ -1,9 +1,14 @@
"""
-Support for plotting fields of arrows.
+Support for plotting vector fields.
-Presently this contains a single class, Quiver, but it
-might make sense to consolidate other arrow plotting here.
+Presently this contains Quiver and Barb. Quiver plots an arrow in the
+direction of the vector, with the size of the arrow related to the
+magnitude of the vector.
+Barbs are like quiver in that they point along a vector, but
+the magnitude of the vector is given schematically by the presence of barbs
+or flags on the barb.
+
This will also become a home for things such as standard
deviation ellipses, which can and will be derived very easily from
the Quiver code.
@@ -17,6 +22,8 @@
import matplotlib.text as mtext
import matplotlib.artist as martist
import matplotlib.font_manager as font_manager
+from matplotlib.cbook import delete_masked_points
+from matplotlib.patches import CirclePolygon
import math
@@ -498,3 +505,388 @@
return X, Y
quiver_doc = _quiver_doc
+
+_barbs_doc = """
+Plot a 2-D field of barbs.
+
+call signatures::
+
+ barb(U, V, **kw)
+ barb(U, V, C, **kw)
+ barb(X, Y, U, V, **kw)
+ barb(X, Y, U, V, C, **kw)
+
+Arguments:
+
+ *X*, *Y*:
+ The x and y coordinates of the barb locations
+ (default is head of barb; see *pivot* kwarg)
+
+ *U*, *V*:
+ give the *x* and *y* components of the barb shaft
+
+ *C*:
+ an optional array used to map colors to the barbs
+
+All arguments may be 1-D or 2-D arrays or sequences. If *X* and *Y*
+are absent, they will be generated as a uniform grid. If *U* and *V*
+are 2-D arrays but *X* and *Y* are 1-D, and if len(*X*) and len(*Y*)
+match the column and row dimensions of *U*, then *X* and *Y* will be
+expanded with :func:`numpy.meshgrid`.
+
+*U*, *V*, *C* may be masked arrays, but masked *X*, *Y* are not
+supported at present.
+
+Keyword arguments:
+
+ *length*:
+ Length of the barb in points; the other parts of the barb
+ are scaled against this.
+ Default is 9
+
+ *pivot*: [ 'tip' | 'middle' ]
+ The part of the arrow that is at the grid point; the arrow
+ rotates about this point, hence the name *pivot*.
+ Default is 'tip'
+
+ *barbcolor*: [ color | color sequence ]
+ Specifies the color all parts of the barb except any flags.
+ This parameter is analagous to the *edgecolor* parameter
+ for polygons, which can be used instead. However this parameter
+ will override facecolor.
+
+ *flagcolor*: [ color | color sequence ]
+ Specifies the color of any flags on the barb.
+ This parameter is analagous to the *facecolor* parameter
+ for polygons, which can be used instead. However this parameter
+ will override facecolor. If this is not set (and *C* has not either)
+ then *flagcolor* will be set to match *barbcolor* so that the barb
+ has a uniform color. If *C* has been set, *flagcolor* has no effect.
+
+ *sizes*:
+ A dictionary of coefficients specifying the ratio of a given feature
+ to the length of the barb. Only those values one wishes to override
+ need to be included. These features include:
+ 'spacing' - space between features (flags, full/half barbs)
+ 'height' - height (distance from shaft to top) of a flag or full barb
+ 'width' - width of a flag, twice the width of a full barb
+ 'emptybarb' - radius of the circle used for low magnitudes
+
+ *fill_empty*:
+ A flag on whether the empty barbs (circles) that are drawn should be filled
+ with the flag color. If they are not filled, they will be drawn such that
+ no color is applied to the center.
+ Default is False
+
+ *rounding*:
+ A flag to indicate whether the vector magnitude should be rounded when
+ allocating barb components. If True, the magnitude is rounded to the
+ nearest multiple of the half-barb increment. If False, the magnitude
+ is simply truncated to the next lowest multiple.
+ Default is True
+
+ *barb_increments*:
+ A dictionary of increments specifying values to associate with different
+ parts of the barb. Only those values one wishes to override need to be
+ included.
+ 'half' - half barbs (Default is 5)
+ 'full' - full barbs (Default is 10)
+ 'flag' - flags (default is 50)
+
+Barbs are traditionally used in meteorology as a way to plot the speed
+and direction of wind observations, but can technically be used to plot
+any two dimensional vector quantity. As opposed to arrows, which give
+vector magnitude by the length of the arrow, the barbs give more quantitative
+information about the vector magnitude by putting slanted lines or a triangle
+for various increments in magnitude, as show schematically below:
+
+ /\ \
+ / \ \
+ / \ \ \
+/ \ \ \
+------------------------------
+
+The largest increment is given by a triangle (or "flag"). After those come full
+lines (barbs). The smallest increment is a half line. There is only, of
+course, ever at most 1 half line. If the magnitude is small and only needs a
+single half-line and no full lines or triangles, the half-line is offset from
+the end of the barb so that it can be easily distinguished from barbs with a
+single full line. The magnitude for the barb shown above would nominally be
+65, using the standard increments of 50, 10, and 5.
+
+linewidths and edgecolors can be used to customize the barb.
+Additional :class:`~matplotlib.collections.PolyCollection`
+keyword arguments:
+
+%(PolyCollection)s
+""" % martist.kwdocd
+
+class Barbs(collections.PolyCollection):
+ '''
+ Specialized PolyCollection for barbs.
+
+ The only API method is set_UVC(), which can be used
+ to change the size, orientation, and color of the
+ arrows. Locations are changed using the set_offsets() collection
+ method.Possibly this method will be useful in animations.
+
+ There is one internal function _find_tails() which finds exactly
+ what should be put on the barb given the vector magnitude. From there
+ _make_barbs() is used to find the vertices of the polygon to represent the
+ barb based on this information.
+ '''
+ #This may be an abuse of polygons here to render what is essentially maybe
+ #1 triangle and a series of lines. It works fine as far as I can tell
+ #however.
+ def __init__(self, ax, *args, **kw):
+ self._pivot = kw.pop('pivot', 'tip')
+ self._length = kw.pop('length', 7)
+ barbcolor = kw.pop('barbcolor', None)
+ flagcolor = kw.pop('flagcolor', None)
+ self.sizes = kw.pop('sizes', dict())
+ self.fill_empty = kw.pop('fill_empty', False)
+ self.barb_increments = kw.pop('barb_increments', dict())
+ self.rounding = kw.pop('rounding', True)
+
+ #Flagcolor and and barbcolor provide convenience parameters for setting
+ #the facecolor and edgecolor, respectively, of the barb polygon. We
+ #also work here to make the flag the same color as the rest of the barb
+ #by default
+ if None in (barbcolor, flagcolor):
+ kw['edgecolors'] = 'face'
+ if flagcolor:
+ kw['facecolors'] = flagcolor
+ elif barbcolor:
+ kw['facecolors'] = barbcolor
+ else:
+ #Set to facecolor passed in or default to black
+ kw.setdefault('facecolors', 'k')
+ else:
+ kw['edgecolors'] = barbcolor
+ kw['facecolors'] = flagcolor
+
+ #Parse out the data arrays from the various configurations supported
+ x, y, u, v, c = self._parse_args(*args)
+ self.x = x
+ self.y = y
+ xy = np.hstack((x[:,np.newaxis], y[:,np.newaxis]))
+
+ #Make a collection
+ barb_size = self._length**2 / 4 #Empirically determined
+ collections.PolyCollection.__init__(self, [], (barb_size,), offsets=xy,
+ transOffset=ax.transData, **kw)
+ self.set_transform(transforms.IdentityTransform())
+
+ self.set_UVC(u, v, c)
+
+ __init__.__doc__ = """
+ The constructor takes one required argument, an Axes
+ instance, followed by the args and kwargs described
+ by the following pylab interface documentation:
+ %s""" % _barbs_doc
+
+ def _find_tails(self, mag, rounding=True, half=5, full=10, flag=50):
+ '''Find how many of each of the tail pieces is necessary. Flag
+ specifies the increment for a flag, barb for a full barb, and half for
+ half a barb. Mag should be the magnitude of a vector (ie. >= 0).
+
+ This returns a tuple of:
+ (number of flags, number of barbs, half_flag, empty_flag)
+ half_flag is a boolean whether half of a barb is needed, since there
+ should only ever be one half on a given barb. Empty flag is an array
+ of flags to easily tell if a barb is empty (too low to plot any
+ barbs/flags.'''
+
+ #If rounding, round to the nearest multiple of half, the smallest
+ #increment
+ if rounding:
+ mag = half * (mag / half + 0.5).astype(np.int)
+
+ num_flags = np.floor(mag / flag).astype(np.int)
+ mag = np.mod(mag, flag)
+
+ num_barb = np.floor(mag / full).astype(np.int)
+ mag = np.mod(mag, full)
+
+ half_flag = mag >= half
+ empty_flag = ~(half_flag | (num_flags > 0) | (num_barb > 0))
+
+ return num_flags, num_barb, half_flag, empty_flag
+
+ def _make_barbs(self, u, v, nflags, nbarbs, half_barb, empty_flag, length,
+ pivot, sizes, fill_empty):
+ '''This function actually creates the wind barbs. u and v are
+ components of the vector in the x and y directions, respectively.
+ nflags, nbarbs, and half_barb, empty_flag are, respectively, the number
+ of flags, number of barbs, flag for half a barb, and flag for empty
+ barb, ostensibly obtained from _find_tails. length is the length of
+ the barb staff in points. pivot specifies the point on the barb around
+ which the entire barb should be rotated. Right now valid options are
+ 'head' and 'middle'. sizes is a dictionary of coefficients specifying
+ the ratio of a given feature to the length of the barb. These features
+ include:
+
+ spacing - space between features (flags, full/half barbs)
+ height - height (distance from shaft of top) of a flag or full barb
+ width - width of a flag, twice the width of a full barb
+ emptybarb - radius of the circle used for low magnitudes
+
+ This function returns list of arrays of vertices, defining a polygon for
+ each of the wind barbs. These polygons have been rotated to properly
+ align with the vector direction.'''
+
+ #These control the spacing and size of barb elements relative to the
+ #length of the shaft
+ spacing = length * sizes.get('spacing', 0.125)
+ full_height = length * sizes.get('height', 0.4)
+ full_width = length * sizes.get('width', 0.25)
+ empty_rad = length * sizes.get('emptybarb', 0.15)
+
+ #Controls y point where to pivot the barb.
+ pivot_points = dict(tip=0.0, middle=-length/2.)
+
+ endx = 0.0
+ endy = pivot_points[pivot.lower()]
+
+ #Get the appropriate angle for the vector components. The offset is due
+ #to the way the barb is initially drawn, going down the y-axis. This
+ #makes sense in a meteorological mode of thinking since there 0 degrees
+ #corresponds to north (the y-axis traditionally)
+ angles = -(ma.arctan2(v, u) + np.pi/2)
+
+ #Used for low magnitude. We just get the vertices, so if we make it
+ #out here, it can be reused. The center set here should put the
+ #center of the circle at the location(offset), rather than at the
+ #same point as the barb pivot; this seems more sensible.
+ circ = CirclePolygon((0,0), radius=empty_rad).get_verts()
+ if fill_empty:
+ empty_barb = circ
+ else:
+ #If we don't want the empty one filled, we make a degenerate polygon
+ #that wraps back over itself
+ empty_barb = np.concatenate((circ, circ[::-1]))
+
+ barb_list = []
+ for index, angle in np.ndenumerate(angles):
+ #If the vector magnitude is too weak to draw anything, plot an
+ #empty circle instead
+ if empty_flag[index]:
+ #We can skip the transform since the circle has no preferred
+ #orientation
+ barb_list.append(empty_barb)
+ continue
+
+ poly_verts = [(endx, endy)]
+ offset = length
+
+ #Add vertices for each flag
+ for i in range(nflags[index]):
+ #The spacing that works for the barbs is a little to much for
+ #the flags, but this only occurs when we have more than 1 flag.
+ if offset != length: offset += spacing / 2.
+ poly_verts.extend([[endx, endy + offset],
+ [endx + full_height, endy - full_width/2 + offset],
+ [endx, endy - full_width + offset]])
+
+ offset -= full_width + spacing
+
+ #Add vertices for each barb. These really are lines, but works
+ #great adding 3 vertices that basically pull the polygon out and
+ #back down the line
+ for i in range(nbarbs[index]):
+ poly_verts.extend([(endx, endy + offset),
+ (endx + full_height, endy + offset + full_width/2),
+ (endx, endy + offset)])
+
+ offset -= spacing
+
+ #Add the vertices for half a barb, if needed
+ if half_barb[index]:
+ #If the half barb is the first on the staff, traditionally it is
+ #offset from the end to make it easy to distinguish from a barb
+ #with a full one
+ if offset == length:
+ poly_verts.append((endx, endy + offset))
+ offset -= 1.5 * spacing
+ poly_verts.extend([(endx, endy + offset),
+ (endx + full_height/2, endy + offset + full_width/4),
+ (endx, endy + offset)])
+
+ #Rotate the barb according the angle. Making the barb first and then
+ #rotating it made the math for drawing the barb really easy. Also,
+ #the transform framework makes doing the rotation simple.
+ poly_verts = transforms.Affine2D().rotate(-angle).transform(
+ poly_verts)
+ barb_list.append(poly_verts)
+
+ return barb_list
+
+ #Taken shamelessly from Quiver
+ def _parse_args(self, *args):
+ X, Y, U, V, C = [None]*5
+ args = list(args)
+ if len(args) == 3 or len(args) == 5:
+ C = ma.asarray(args.pop(-1)).ravel()
+ V = ma.asarray(args.pop(-1))
+ U = ma.asarray(args.pop(-1))
+ nn = np.shape(U)
+ nc = nn[0]
+ nr = 1
+ if len(nn) > 1:
+ nr = nn[1]
+ if len(args) == 2: # remaining after removing U,V,C
+ X, Y = [np.array(a).ravel() for a in args]
+ if len(X) == nc and len(Y) == nr:
+ X, Y = [a.ravel() for a in np.meshgrid(X, Y)]
+ else:
+ indexgrid = np.meshgrid(np.arange(nc), np.arange(nr))
+ X, Y = [np.ravel(a) for a in indexgrid]
+ return X, Y, U, V, C
+
+ def set_UVC(self, U, V, C=None):
+ self.u = ma.asarray(U).ravel()
+ self.v = ma.asarray(V).ravel()
+ if C is not None:
+ c = ma.asarray(C).ravel()
+ x,y,u,v,c = delete_masked_points(self.x.ravel(), self.y.ravel(),
+ self.u, self.v, c)
+ else:
+ x,y,u,v = delete_masked_points(self.x.ravel(), self.y.ravel(),
+ self.u, self.v)
+
+ magnitude = np.sqrt(u*u + v*v)
+ flags, barbs, halves, empty = self._find_tails(magnitude,
+ self.rounding, **self.barb_increments)
+
+ #Get the vertices for each of the barbs
+
+ plot_barbs = self._make_barbs(u, v, flags, barbs, halves, empty,
+ self._length, self._pivot, self.sizes, self.fill_empty)
+ self.set_verts(plot_barbs)
+
+ #Set the color array
+ if C is not None:
+ self.set_array(c)
+
+ #Update the offsets in case the masked data changed
+ xy = np.hstack((x[:,np.newaxis], y[:,np.newaxis]))
+ self._offsets = xy
+
+ def set_offsets(self, xy):
+ '''
+ Set the offsets for the barb polygons. This saves the offets passed in
+ and actually sets version masked as appropriate for the existing U/V
+ data. *offsets* should be a sequence.
+
+ ACCEPTS: sequence of pairs of floats
+ '''
+ self.x = xy[:,0]
+ self.y = xy[:,1]
+ x,y,u,v = delete_masked_points(self.x.ravel(), self.y.ravel(), self.u,
+ self.v)
+ xy = np.hstack((x[:,np.newaxis], y[:,np.newaxis]))
+ collections.PolyCollection.set_offsets(self, xy)
+ set_offsets.__doc__ = collections.PolyCollection.set_offsets.__doc__
+
+ barbs_doc = _barbs_doc
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|