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. |