quiver.py 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253
  1. """
  2. Support for plotting vector fields.
  3. Presently this contains Quiver and Barb. Quiver plots an arrow in the
  4. direction of the vector, with the size of the arrow related to the
  5. magnitude of the vector.
  6. Barbs are like quiver in that they point along a vector, but
  7. the magnitude of the vector is given schematically by the presence of barbs
  8. or flags on the barb.
  9. This will also become a home for things such as standard
  10. deviation ellipses, which can and will be derived very easily from
  11. the Quiver code.
  12. """
  13. import math
  14. import weakref
  15. import numpy as np
  16. from numpy import ma
  17. from matplotlib import cbook, docstring, font_manager
  18. import matplotlib.artist as martist
  19. import matplotlib.collections as mcollections
  20. from matplotlib.patches import CirclePolygon
  21. import matplotlib.text as mtext
  22. import matplotlib.transforms as transforms
  23. _quiver_doc = """
  24. Plot a 2D field of arrows.
  25. Call signature::
  26. quiver([X, Y], U, V, [C], **kw)
  27. Where *X*, *Y* define the arrow locations, *U*, *V* define the arrow
  28. directions, and *C* optionally sets the color.
  29. **Arrow size**
  30. The default settings auto-scales the length of the arrows to a reasonable size.
  31. To change this behavior see the *scale* and *scale_units* parameters.
  32. **Arrow shape**
  33. The defaults give a slightly swept-back arrow; to make the head a
  34. triangle, make *headaxislength* the same as *headlength*. To make the
  35. arrow more pointed, reduce *headwidth* or increase *headlength* and
  36. *headaxislength*. To make the head smaller relative to the shaft,
  37. scale down all the head parameters. You will probably do best to leave
  38. minshaft alone.
  39. **Arrow outline**
  40. *linewidths* and *edgecolors* can be used to customize the arrow
  41. outlines.
  42. Parameters
  43. ----------
  44. X, Y : 1D or 2D array-like, optional
  45. The x and y coordinates of the arrow locations.
  46. If not given, they will be generated as a uniform integer meshgrid based
  47. on the dimensions of *U* and *V*.
  48. If *X* and *Y* are 1D but *U*, *V* are 2D, *X*, *Y* are expanded to 2D
  49. using ``X, Y = np.meshgrid(X, Y)``. In this case ``len(X)`` and ``len(Y)``
  50. must match the column and row dimensions of *U* and *V*.
  51. U, V : 1D or 2D array-like
  52. The x and y direction components of the arrow vectors.
  53. They must have the same number of elements, matching the number of arrow
  54. locations. *U* and *V* may be masked. Only locations unmasked in
  55. *U*, *V*, and *C* will be drawn.
  56. C : 1D or 2D array-like, optional
  57. Numeric data that defines the arrow colors by colormapping via *norm* and
  58. *cmap*.
  59. This does not support explicit colors. If you want to set colors directly,
  60. use *color* instead. The size of *C* must match the number of arrow
  61. locations.
  62. units : {'width', 'height', 'dots', 'inches', 'x', 'y' 'xy'}, default: 'width'
  63. The arrow dimensions (except for *length*) are measured in multiples of
  64. this unit.
  65. The following values are supported:
  66. - 'width', 'height': The width or height of the axis.
  67. - 'dots', 'inches': Pixels or inches based on the figure dpi.
  68. - 'x', 'y', 'xy': *X*, *Y* or :math:`\\sqrt{X^2 + Y^2}` in data units.
  69. The arrows scale differently depending on the units. For
  70. 'x' or 'y', the arrows get larger as one zooms in; for other
  71. units, the arrow size is independent of the zoom state. For
  72. 'width or 'height', the arrow size increases with the width and
  73. height of the axes, respectively, when the window is resized;
  74. for 'dots' or 'inches', resizing does not change the arrows.
  75. angles : {'uv', 'xy'} or array-like, optional, default: 'uv'
  76. Method for determining the angle of the arrows.
  77. - 'uv': The arrow axis aspect ratio is 1 so that
  78. if *U* == *V* the orientation of the arrow on the plot is 45 degrees
  79. counter-clockwise from the horizontal axis (positive to the right).
  80. Use this if the arrows symbolize a quantity that is not based on
  81. *X*, *Y* data coordinates.
  82. - 'xy': Arrows point from (x, y) to (x+u, y+v).
  83. Use this for plotting a gradient field, for example.
  84. - Alternatively, arbitrary angles may be specified explicitly as an array
  85. of values in degrees, counter-clockwise from the horizontal axis.
  86. In this case *U*, *V* is only used to determine the length of the
  87. arrows.
  88. Note: inverting a data axis will correspondingly invert the
  89. arrows only with ``angles='xy'``.
  90. scale : float, optional
  91. Number of data units per arrow length unit, e.g., m/s per plot width; a
  92. smaller scale parameter makes the arrow longer. Default is *None*.
  93. If *None*, a simple autoscaling algorithm is used, based on the average
  94. vector length and the number of vectors. The arrow length unit is given by
  95. the *scale_units* parameter.
  96. scale_units : {'width', 'height', 'dots', 'inches', 'x', 'y', 'xy'}, optional
  97. If the *scale* kwarg is *None*, the arrow length unit. Default is *None*.
  98. e.g. *scale_units* is 'inches', *scale* is 2.0, and ``(u, v) = (1, 0)``,
  99. then the vector will be 0.5 inches long.
  100. If *scale_units* is 'width' or 'height', then the vector will be half the
  101. width/height of the axes.
  102. If *scale_units* is 'x' then the vector will be 0.5 x-axis
  103. units. To plot vectors in the x-y plane, with u and v having
  104. the same units as x and y, use
  105. ``angles='xy', scale_units='xy', scale=1``.
  106. width : float, optional
  107. Shaft width in arrow units; default depends on choice of units,
  108. above, and number of vectors; a typical starting value is about
  109. 0.005 times the width of the plot.
  110. headwidth : float, optional, default: 3
  111. Head width as multiple of shaft width.
  112. headlength : float, optional, default: 5
  113. Head length as multiple of shaft width.
  114. headaxislength : float, optional, default: 4.5
  115. Head length at shaft intersection.
  116. minshaft : float, optional, default: 1
  117. Length below which arrow scales, in units of head length. Do not
  118. set this to less than 1, or small arrows will look terrible!
  119. minlength : float, optional, default: 1
  120. Minimum length as a multiple of shaft width; if an arrow length
  121. is less than this, plot a dot (hexagon) of this diameter instead.
  122. pivot : {'tail', 'mid', 'middle', 'tip'}, optional, default: 'tail'
  123. The part of the arrow that is anchored to the *X*, *Y* grid. The arrow
  124. rotates about this point.
  125. 'mid' is a synonym for 'middle'.
  126. color : color or color sequence, optional
  127. Explicit color(s) for the arrows. If *C* has been set, *color* has no
  128. effect.
  129. This is a synonym for the `~.PolyCollection` *facecolor* parameter.
  130. Other Parameters
  131. ----------------
  132. **kwargs : `~matplotlib.collections.PolyCollection` properties, optional
  133. All other keyword arguments are passed on to `.PolyCollection`:
  134. %(PolyCollection)s
  135. See Also
  136. --------
  137. quiverkey : Add a key to a quiver plot.
  138. """ % docstring.interpd.params
  139. class QuiverKey(martist.Artist):
  140. """Labelled arrow for use as a quiver plot scale key."""
  141. halign = {'N': 'center', 'S': 'center', 'E': 'left', 'W': 'right'}
  142. valign = {'N': 'bottom', 'S': 'top', 'E': 'center', 'W': 'center'}
  143. pivot = {'N': 'middle', 'S': 'middle', 'E': 'tip', 'W': 'tail'}
  144. def __init__(self, Q, X, Y, U, label,
  145. *, angle=0, coordinates='axes', color=None, labelsep=0.1,
  146. labelpos='N', labelcolor=None, fontproperties=None,
  147. **kw):
  148. """
  149. Add a key to a quiver plot.
  150. The positioning of the key depends on *X*, *Y*, *coordinates*, and
  151. *labelpos*. If *labelpos* is 'N' or 'S', *X*, *Y* give the position of
  152. the middle of the key arrow. If *labelpos* is 'E', *X*, *Y* positions
  153. the head, and if *labelpos* is 'W', *X*, *Y* positions the tail; in
  154. either of these two cases, *X*, *Y* is somewhere in the middle of the
  155. arrow+label key object.
  156. Parameters
  157. ----------
  158. Q : `matplotlib.quiver.Quiver`
  159. A `.Quiver` object as returned by a call to `~.Axes.quiver()`.
  160. X, Y : float
  161. The location of the key.
  162. U : float
  163. The length of the key.
  164. label : str
  165. The key label (e.g., length and units of the key).
  166. angle : float, default: 0
  167. The angle of the key arrow, in degrees anti-clockwise from the
  168. x-axis.
  169. coordinates : {'axes', 'figure', 'data', 'inches'}, default: 'axes'
  170. Coordinate system and units for *X*, *Y*: 'axes' and 'figure' are
  171. normalized coordinate systems with (0, 0) in the lower left and
  172. (1, 1) in the upper right; 'data' are the axes data coordinates
  173. (used for the locations of the vectors in the quiver plot itself);
  174. 'inches' is position in the figure in inches, with (0, 0) at the
  175. lower left corner.
  176. color : color
  177. Overrides face and edge colors from *Q*.
  178. labelpos : {'N', 'S', 'E', 'W'}
  179. Position the label above, below, to the right, to the left of the
  180. arrow, respectively.
  181. labelsep : float, default: 0.1
  182. Distance in inches between the arrow and the label.
  183. labelcolor : color, default: :rc:`text.color`
  184. Label color.
  185. fontproperties : dict, optional
  186. A dictionary with keyword arguments accepted by the
  187. `~matplotlib.font_manager.FontProperties` initializer:
  188. *family*, *style*, *variant*, *size*, *weight*.
  189. **kwargs
  190. Any additional keyword arguments are used to override vector
  191. properties taken from *Q*.
  192. """
  193. martist.Artist.__init__(self)
  194. self.Q = Q
  195. self.X = X
  196. self.Y = Y
  197. self.U = U
  198. self.angle = angle
  199. self.coord = coordinates
  200. self.color = color
  201. self.label = label
  202. self._labelsep_inches = labelsep
  203. self.labelsep = (self._labelsep_inches * Q.ax.figure.dpi)
  204. # try to prevent closure over the real self
  205. weak_self = weakref.ref(self)
  206. def on_dpi_change(fig):
  207. self_weakref = weak_self()
  208. if self_weakref is not None:
  209. self_weakref.labelsep = self_weakref._labelsep_inches * fig.dpi
  210. # simple brute force update works because _init is called at
  211. # the start of draw.
  212. self_weakref._initialized = False
  213. self._cid = Q.ax.figure.callbacks.connect('dpi_changed',
  214. on_dpi_change)
  215. self.labelpos = labelpos
  216. self.labelcolor = labelcolor
  217. self.fontproperties = fontproperties or dict()
  218. self.kw = kw
  219. _fp = self.fontproperties
  220. # boxprops = dict(facecolor='red')
  221. self.text = mtext.Text(
  222. text=label, # bbox=boxprops,
  223. horizontalalignment=self.halign[self.labelpos],
  224. verticalalignment=self.valign[self.labelpos],
  225. fontproperties=font_manager.FontProperties(**_fp))
  226. if self.labelcolor is not None:
  227. self.text.set_color(self.labelcolor)
  228. self._initialized = False
  229. self.zorder = Q.zorder + 0.1
  230. def remove(self):
  231. """
  232. Overload the remove method
  233. """
  234. self.Q.ax.figure.callbacks.disconnect(self._cid)
  235. self._cid = None
  236. # pass the remove call up the stack
  237. martist.Artist.remove(self)
  238. def _init(self):
  239. if True: # not self._initialized:
  240. if not self.Q._initialized:
  241. self.Q._init()
  242. self._set_transform()
  243. _pivot = self.Q.pivot
  244. self.Q.pivot = self.pivot[self.labelpos]
  245. # Hack: save and restore the Umask
  246. _mask = self.Q.Umask
  247. self.Q.Umask = ma.nomask
  248. u = self.U * np.cos(np.radians(self.angle))
  249. v = self.U * np.sin(np.radians(self.angle))
  250. angle = self.Q.angles if isinstance(self.Q.angles, str) else 'uv'
  251. self.verts = self.Q._make_verts(
  252. np.array([u]), np.array([v]), angle)
  253. self.Q.Umask = _mask
  254. self.Q.pivot = _pivot
  255. kw = self.Q.polykw
  256. kw.update(self.kw)
  257. self.vector = mcollections.PolyCollection(
  258. self.verts,
  259. offsets=[(self.X, self.Y)],
  260. transOffset=self.get_transform(),
  261. **kw)
  262. if self.color is not None:
  263. self.vector.set_color(self.color)
  264. self.vector.set_transform(self.Q.get_transform())
  265. self.vector.set_figure(self.get_figure())
  266. self._initialized = True
  267. def _text_x(self, x):
  268. if self.labelpos == 'E':
  269. return x + self.labelsep
  270. elif self.labelpos == 'W':
  271. return x - self.labelsep
  272. else:
  273. return x
  274. def _text_y(self, y):
  275. if self.labelpos == 'N':
  276. return y + self.labelsep
  277. elif self.labelpos == 'S':
  278. return y - self.labelsep
  279. else:
  280. return y
  281. @martist.allow_rasterization
  282. def draw(self, renderer):
  283. self._init()
  284. self.vector.draw(renderer)
  285. x, y = self.get_transform().transform((self.X, self.Y))
  286. self.text.set_x(self._text_x(x))
  287. self.text.set_y(self._text_y(y))
  288. self.text.draw(renderer)
  289. self.stale = False
  290. def _set_transform(self):
  291. if self.coord == 'data':
  292. self.set_transform(self.Q.ax.transData)
  293. elif self.coord == 'axes':
  294. self.set_transform(self.Q.ax.transAxes)
  295. elif self.coord == 'figure':
  296. self.set_transform(self.Q.ax.figure.transFigure)
  297. elif self.coord == 'inches':
  298. self.set_transform(self.Q.ax.figure.dpi_scale_trans)
  299. else:
  300. raise ValueError('unrecognized coordinates')
  301. def set_figure(self, fig):
  302. martist.Artist.set_figure(self, fig)
  303. self.text.set_figure(fig)
  304. def contains(self, mouseevent):
  305. inside, info = self._default_contains(mouseevent)
  306. if inside is not None:
  307. return inside, info
  308. # Maybe the dictionary should allow one to
  309. # distinguish between a text hit and a vector hit.
  310. if (self.text.contains(mouseevent)[0] or
  311. self.vector.contains(mouseevent)[0]):
  312. return True, {}
  313. return False, {}
  314. @cbook.deprecated("3.2")
  315. @property
  316. def quiverkey_doc(self):
  317. return self.__init__.__doc__
  318. def _parse_args(*args, caller_name='function'):
  319. """
  320. Helper function to parse positional parameters for colored vector plots.
  321. This is currently used for Quiver and Barbs.
  322. Parameters
  323. ----------
  324. *args : list
  325. list of 2-5 arguments. Depending on their number they are parsed to::
  326. U, V
  327. U, V, C
  328. X, Y, U, V
  329. X, Y, U, V, C
  330. caller_name : str
  331. Name of the calling method (used in error messages).
  332. """
  333. X = Y = C = None
  334. len_args = len(args)
  335. if len_args == 2:
  336. # The use of atleast_1d allows for handling scalar arguments while also
  337. # keeping masked arrays
  338. U, V = np.atleast_1d(*args)
  339. elif len_args == 3:
  340. U, V, C = np.atleast_1d(*args)
  341. elif len_args == 4:
  342. X, Y, U, V = np.atleast_1d(*args)
  343. elif len_args == 5:
  344. X, Y, U, V, C = np.atleast_1d(*args)
  345. else:
  346. raise TypeError(f'{caller_name} takes 2-5 positional arguments but '
  347. f'{len_args} were given')
  348. nr, nc = (1, U.shape[0]) if U.ndim == 1 else U.shape
  349. if X is not None:
  350. X = X.ravel()
  351. Y = Y.ravel()
  352. if len(X) == nc and len(Y) == nr:
  353. X, Y = [a.ravel() for a in np.meshgrid(X, Y)]
  354. elif len(X) != len(Y):
  355. raise ValueError('X and Y must be the same size, but '
  356. f'X.size is {X.size} and Y.size is {Y.size}.')
  357. else:
  358. indexgrid = np.meshgrid(np.arange(nc), np.arange(nr))
  359. X, Y = [np.ravel(a) for a in indexgrid]
  360. # Size validation for U, V, C is left to the set_UVC method.
  361. return X, Y, U, V, C
  362. def _check_consistent_shapes(*arrays):
  363. all_shapes = {a.shape for a in arrays}
  364. if len(all_shapes) != 1:
  365. raise ValueError('The shapes of the passed in arrays do not match')
  366. class Quiver(mcollections.PolyCollection):
  367. """
  368. Specialized PolyCollection for arrows.
  369. The only API method is set_UVC(), which can be used
  370. to change the size, orientation, and color of the
  371. arrows; their locations are fixed when the class is
  372. instantiated. Possibly this method will be useful
  373. in animations.
  374. Much of the work in this class is done in the draw()
  375. method so that as much information as possible is available
  376. about the plot. In subsequent draw() calls, recalculation
  377. is limited to things that might have changed, so there
  378. should be no performance penalty from putting the calculations
  379. in the draw() method.
  380. """
  381. _PIVOT_VALS = ('tail', 'middle', 'tip')
  382. @docstring.Substitution(_quiver_doc)
  383. def __init__(self, ax, *args,
  384. scale=None, headwidth=3, headlength=5, headaxislength=4.5,
  385. minshaft=1, minlength=1, units='width', scale_units=None,
  386. angles='uv', width=None, color='k', pivot='tail', **kw):
  387. """
  388. The constructor takes one required argument, an Axes
  389. instance, followed by the args and kwargs described
  390. by the following pyplot interface documentation:
  391. %s
  392. """
  393. self.ax = ax
  394. X, Y, U, V, C = _parse_args(*args, caller_name='quiver()')
  395. self.X = X
  396. self.Y = Y
  397. self.XY = np.column_stack((X, Y))
  398. self.N = len(X)
  399. self.scale = scale
  400. self.headwidth = headwidth
  401. self.headlength = float(headlength)
  402. self.headaxislength = headaxislength
  403. self.minshaft = minshaft
  404. self.minlength = minlength
  405. self.units = units
  406. self.scale_units = scale_units
  407. self.angles = angles
  408. self.width = width
  409. if pivot.lower() == 'mid':
  410. pivot = 'middle'
  411. self.pivot = pivot.lower()
  412. cbook._check_in_list(self._PIVOT_VALS, pivot=self.pivot)
  413. self.transform = kw.pop('transform', ax.transData)
  414. kw.setdefault('facecolors', color)
  415. kw.setdefault('linewidths', (0,))
  416. mcollections.PolyCollection.__init__(self, [], offsets=self.XY,
  417. transOffset=self.transform,
  418. closed=False,
  419. **kw)
  420. self.polykw = kw
  421. self.set_UVC(U, V, C)
  422. self._initialized = False
  423. # try to prevent closure over the real self
  424. weak_self = weakref.ref(self)
  425. def on_dpi_change(fig):
  426. self_weakref = weak_self()
  427. if self_weakref is not None:
  428. # vertices depend on width, span which in turn depend on dpi
  429. self_weakref._new_UV = True
  430. # simple brute force update works because _init is called at
  431. # the start of draw.
  432. self_weakref._initialized = False
  433. self._cid = self.ax.figure.callbacks.connect('dpi_changed',
  434. on_dpi_change)
  435. @cbook.deprecated("3.1", alternative="get_facecolor()")
  436. @property
  437. def color(self):
  438. return self.get_facecolor()
  439. @cbook.deprecated("3.1")
  440. @property
  441. def keyvec(self):
  442. return None
  443. @cbook.deprecated("3.1")
  444. @property
  445. def keytext(self):
  446. return None
  447. def remove(self):
  448. """
  449. Overload the remove method
  450. """
  451. # disconnect the call back
  452. self.ax.figure.callbacks.disconnect(self._cid)
  453. self._cid = None
  454. # pass the remove call up the stack
  455. mcollections.PolyCollection.remove(self)
  456. def _init(self):
  457. """
  458. Initialization delayed until first draw;
  459. allow time for axes setup.
  460. """
  461. # It seems that there are not enough event notifications
  462. # available to have this work on an as-needed basis at present.
  463. if True: # not self._initialized:
  464. trans = self._set_transform()
  465. ax = self.ax
  466. self.span = trans.inverted().transform_bbox(ax.bbox).width
  467. if self.width is None:
  468. sn = np.clip(math.sqrt(self.N), 8, 25)
  469. self.width = 0.06 * self.span / sn
  470. # _make_verts sets self.scale if not already specified
  471. if not self._initialized and self.scale is None:
  472. self._make_verts(self.U, self.V, self.angles)
  473. self._initialized = True
  474. def get_datalim(self, transData):
  475. trans = self.get_transform()
  476. transOffset = self.get_offset_transform()
  477. full_transform = (trans - transData) + (transOffset - transData)
  478. XY = full_transform.transform(self.XY)
  479. bbox = transforms.Bbox.null()
  480. bbox.update_from_data_xy(XY, ignore=True)
  481. return bbox
  482. @martist.allow_rasterization
  483. def draw(self, renderer):
  484. self._init()
  485. verts = self._make_verts(self.U, self.V, self.angles)
  486. self.set_verts(verts, closed=False)
  487. self._new_UV = False
  488. mcollections.PolyCollection.draw(self, renderer)
  489. self.stale = False
  490. def set_UVC(self, U, V, C=None):
  491. # We need to ensure we have a copy, not a reference
  492. # to an array that might change before draw().
  493. U = ma.masked_invalid(U, copy=True).ravel()
  494. V = ma.masked_invalid(V, copy=True).ravel()
  495. if C is not None:
  496. C = ma.masked_invalid(C, copy=True).ravel()
  497. for name, var in zip(('U', 'V', 'C'), (U, V, C)):
  498. if not (var is None or var.size == self.N or var.size == 1):
  499. raise ValueError(f'Argument {name} has a size {var.size}'
  500. f' which does not match {self.N},'
  501. ' the number of arrow positions')
  502. mask = ma.mask_or(U.mask, V.mask, copy=False, shrink=True)
  503. if C is not None:
  504. mask = ma.mask_or(mask, C.mask, copy=False, shrink=True)
  505. if mask is ma.nomask:
  506. C = C.filled()
  507. else:
  508. C = ma.array(C, mask=mask, copy=False)
  509. self.U = U.filled(1)
  510. self.V = V.filled(1)
  511. self.Umask = mask
  512. if C is not None:
  513. self.set_array(C)
  514. self._new_UV = True
  515. self.stale = True
  516. def _dots_per_unit(self, units):
  517. """
  518. Return a scale factor for converting from units to pixels
  519. """
  520. ax = self.ax
  521. if units in ('x', 'y', 'xy'):
  522. if units == 'x':
  523. dx0 = ax.viewLim.width
  524. dx1 = ax.bbox.width
  525. elif units == 'y':
  526. dx0 = ax.viewLim.height
  527. dx1 = ax.bbox.height
  528. else: # 'xy' is assumed
  529. dxx0 = ax.viewLim.width
  530. dxx1 = ax.bbox.width
  531. dyy0 = ax.viewLim.height
  532. dyy1 = ax.bbox.height
  533. dx1 = np.hypot(dxx1, dyy1)
  534. dx0 = np.hypot(dxx0, dyy0)
  535. dx = dx1 / dx0
  536. else:
  537. if units == 'width':
  538. dx = ax.bbox.width
  539. elif units == 'height':
  540. dx = ax.bbox.height
  541. elif units == 'dots':
  542. dx = 1.0
  543. elif units == 'inches':
  544. dx = ax.figure.dpi
  545. else:
  546. raise ValueError('unrecognized units')
  547. return dx
  548. def _set_transform(self):
  549. """
  550. Sets the PolygonCollection transform to go
  551. from arrow width units to pixels.
  552. """
  553. dx = self._dots_per_unit(self.units)
  554. self._trans_scale = dx # pixels per arrow width unit
  555. trans = transforms.Affine2D().scale(dx)
  556. self.set_transform(trans)
  557. return trans
  558. def _angles_lengths(self, U, V, eps=1):
  559. xy = self.ax.transData.transform(self.XY)
  560. uv = np.column_stack((U, V))
  561. xyp = self.ax.transData.transform(self.XY + eps * uv)
  562. dxy = xyp - xy
  563. angles = np.arctan2(dxy[:, 1], dxy[:, 0])
  564. lengths = np.hypot(*dxy.T) / eps
  565. return angles, lengths
  566. def _make_verts(self, U, V, angles):
  567. uv = (U + V * 1j)
  568. str_angles = angles if isinstance(angles, str) else ''
  569. if str_angles == 'xy' and self.scale_units == 'xy':
  570. # Here eps is 1 so that if we get U, V by diffing
  571. # the X, Y arrays, the vectors will connect the
  572. # points, regardless of the axis scaling (including log).
  573. angles, lengths = self._angles_lengths(U, V, eps=1)
  574. elif str_angles == 'xy' or self.scale_units == 'xy':
  575. # Calculate eps based on the extents of the plot
  576. # so that we don't end up with roundoff error from
  577. # adding a small number to a large.
  578. eps = np.abs(self.ax.dataLim.extents).max() * 0.001
  579. angles, lengths = self._angles_lengths(U, V, eps=eps)
  580. if str_angles and self.scale_units == 'xy':
  581. a = lengths
  582. else:
  583. a = np.abs(uv)
  584. if self.scale is None:
  585. sn = max(10, math.sqrt(self.N))
  586. if self.Umask is not ma.nomask:
  587. amean = a[~self.Umask].mean()
  588. else:
  589. amean = a.mean()
  590. # crude auto-scaling
  591. # scale is typical arrow length as a multiple of the arrow width
  592. scale = 1.8 * amean * sn / self.span
  593. if self.scale_units is None:
  594. if self.scale is None:
  595. self.scale = scale
  596. widthu_per_lenu = 1.0
  597. else:
  598. if self.scale_units == 'xy':
  599. dx = 1
  600. else:
  601. dx = self._dots_per_unit(self.scale_units)
  602. widthu_per_lenu = dx / self._trans_scale
  603. if self.scale is None:
  604. self.scale = scale * widthu_per_lenu
  605. length = a * (widthu_per_lenu / (self.scale * self.width))
  606. X, Y = self._h_arrows(length)
  607. if str_angles == 'xy':
  608. theta = angles
  609. elif str_angles == 'uv':
  610. theta = np.angle(uv)
  611. else:
  612. theta = ma.masked_invalid(np.deg2rad(angles)).filled(0)
  613. theta = theta.reshape((-1, 1)) # for broadcasting
  614. xy = (X + Y * 1j) * np.exp(1j * theta) * self.width
  615. XY = np.stack((xy.real, xy.imag), axis=2)
  616. if self.Umask is not ma.nomask:
  617. XY = ma.array(XY)
  618. XY[self.Umask] = ma.masked
  619. # This might be handled more efficiently with nans, given
  620. # that nans will end up in the paths anyway.
  621. return XY
  622. def _h_arrows(self, length):
  623. """Length is in arrow width units."""
  624. # It might be possible to streamline the code
  625. # and speed it up a bit by using complex (x, y)
  626. # instead of separate arrays; but any gain would be slight.
  627. minsh = self.minshaft * self.headlength
  628. N = len(length)
  629. length = length.reshape(N, 1)
  630. # This number is chosen based on when pixel values overflow in Agg
  631. # causing rendering errors
  632. # length = np.minimum(length, 2 ** 16)
  633. np.clip(length, 0, 2 ** 16, out=length)
  634. # x, y: normal horizontal arrow
  635. x = np.array([0, -self.headaxislength,
  636. -self.headlength, 0],
  637. np.float64)
  638. x = x + np.array([0, 1, 1, 1]) * length
  639. y = 0.5 * np.array([1, 1, self.headwidth, 0], np.float64)
  640. y = np.repeat(y[np.newaxis, :], N, axis=0)
  641. # x0, y0: arrow without shaft, for short vectors
  642. x0 = np.array([0, minsh - self.headaxislength,
  643. minsh - self.headlength, minsh], np.float64)
  644. y0 = 0.5 * np.array([1, 1, self.headwidth, 0], np.float64)
  645. ii = [0, 1, 2, 3, 2, 1, 0, 0]
  646. X = x[:, ii]
  647. Y = y[:, ii]
  648. Y[:, 3:-1] *= -1
  649. X0 = x0[ii]
  650. Y0 = y0[ii]
  651. Y0[3:-1] *= -1
  652. shrink = length / minsh if minsh != 0. else 0.
  653. X0 = shrink * X0[np.newaxis, :]
  654. Y0 = shrink * Y0[np.newaxis, :]
  655. short = np.repeat(length < minsh, 8, axis=1)
  656. # Now select X0, Y0 if short, otherwise X, Y
  657. np.copyto(X, X0, where=short)
  658. np.copyto(Y, Y0, where=short)
  659. if self.pivot == 'middle':
  660. X -= 0.5 * X[:, 3, np.newaxis]
  661. elif self.pivot == 'tip':
  662. # numpy bug? using -= does not work here unless we multiply by a
  663. # float first, as with 'mid'.
  664. X = X - X[:, 3, np.newaxis]
  665. elif self.pivot != 'tail':
  666. cbook._check_in_list(["middle", "tip", "tail"], pivot=self.pivot)
  667. tooshort = length < self.minlength
  668. if tooshort.any():
  669. # Use a heptagonal dot:
  670. th = np.arange(0, 8, 1, np.float64) * (np.pi / 3.0)
  671. x1 = np.cos(th) * self.minlength * 0.5
  672. y1 = np.sin(th) * self.minlength * 0.5
  673. X1 = np.repeat(x1[np.newaxis, :], N, axis=0)
  674. Y1 = np.repeat(y1[np.newaxis, :], N, axis=0)
  675. tooshort = np.repeat(tooshort, 8, 1)
  676. np.copyto(X, X1, where=tooshort)
  677. np.copyto(Y, Y1, where=tooshort)
  678. # Mask handling is deferred to the caller, _make_verts.
  679. return X, Y
  680. quiver_doc = _quiver_doc
  681. _barbs_doc = r"""
  682. Plot a 2D field of barbs.
  683. Call signature::
  684. barbs([X, Y], U, V, [C], **kw)
  685. Where *X*, *Y* define the barb locations, *U*, *V* define the barb
  686. directions, and *C* optionally sets the color.
  687. All arguments may be 1D or 2D. *U*, *V*, *C* may be masked arrays, but masked
  688. *X*, *Y* are not supported at present.
  689. Barbs are traditionally used in meteorology as a way to plot the speed
  690. and direction of wind observations, but can technically be used to
  691. plot any two dimensional vector quantity. As opposed to arrows, which
  692. give vector magnitude by the length of the arrow, the barbs give more
  693. quantitative information about the vector magnitude by putting slanted
  694. lines or a triangle for various increments in magnitude, as show
  695. schematically below::
  696. : /\ \
  697. : / \ \
  698. : / \ \ \
  699. : / \ \ \
  700. : ------------------------------
  701. The largest increment is given by a triangle (or "flag"). After those
  702. come full lines (barbs). The smallest increment is a half line. There
  703. is only, of course, ever at most 1 half line. If the magnitude is
  704. small and only needs a single half-line and no full lines or
  705. triangles, the half-line is offset from the end of the barb so that it
  706. can be easily distinguished from barbs with a single full line. The
  707. magnitude for the barb shown above would nominally be 65, using the
  708. standard increments of 50, 10, and 5.
  709. See also https://en.wikipedia.org/wiki/Wind_barb.
  710. Parameters
  711. ----------
  712. X, Y : 1D or 2D array-like, optional
  713. The x and y coordinates of the barb locations. See *pivot* for how the
  714. barbs are drawn to the x, y positions.
  715. If not given, they will be generated as a uniform integer meshgrid based
  716. on the dimensions of *U* and *V*.
  717. If *X* and *Y* are 1D but *U*, *V* are 2D, *X*, *Y* are expanded to 2D
  718. using ``X, Y = np.meshgrid(X, Y)``. In this case ``len(X)`` and ``len(Y)``
  719. must match the column and row dimensions of *U* and *V*.
  720. U, V : 1D or 2D array-like
  721. The x and y components of the barb shaft.
  722. C : 1D or 2D array-like, optional
  723. Numeric data that defines the barb colors by colormapping via *norm* and
  724. *cmap*.
  725. This does not support explicit colors. If you want to set colors directly,
  726. use *barbcolor* instead.
  727. length : float, default: 7
  728. Length of the barb in points; the other parts of the barb
  729. are scaled against this.
  730. pivot : {'tip', 'middle'} or float, default: 'tip'
  731. The part of the arrow that is anchored to the *X*, *Y* grid. The barb
  732. rotates about this point. This can also be a number, which shifts the
  733. start of the barb that many points away from grid point.
  734. barbcolor : color or color sequence
  735. Specifies the color of all parts of the barb except for the flags. This
  736. parameter is analogous to the *edgecolor* parameter for polygons,
  737. which can be used instead. However this parameter will override
  738. facecolor.
  739. flagcolor : color or color sequence
  740. Specifies the color of any flags on the barb. This parameter is
  741. analogous to the *facecolor* parameter for polygons, which can be
  742. used instead. However, this parameter will override facecolor. If
  743. this is not set (and *C* has not either) then *flagcolor* will be
  744. set to match *barbcolor* so that the barb has a uniform color. If
  745. *C* has been set, *flagcolor* has no effect.
  746. sizes : dict, optional
  747. A dictionary of coefficients specifying the ratio of a given
  748. feature to the length of the barb. Only those values one wishes to
  749. override need to be included. These features include:
  750. - 'spacing' - space between features (flags, full/half barbs)
  751. - 'height' - height (distance from shaft to top) of a flag or full barb
  752. - 'width' - width of a flag, twice the width of a full barb
  753. - 'emptybarb' - radius of the circle used for low magnitudes
  754. fill_empty : bool, default: False
  755. Whether the empty barbs (circles) that are drawn should be filled with
  756. the flag color. If they are not filled, the center is transparent.
  757. rounding : bool, default: True
  758. Whether the vector magnitude should be rounded when allocating barb
  759. components. If True, the magnitude is rounded to the nearest multiple
  760. of the half-barb increment. If False, the magnitude is simply truncated
  761. to the next lowest multiple.
  762. barb_increments : dict, optional
  763. A dictionary of increments specifying values to associate with
  764. different parts of the barb. Only those values one wishes to
  765. override need to be included.
  766. - 'half' - half barbs (Default is 5)
  767. - 'full' - full barbs (Default is 10)
  768. - 'flag' - flags (default is 50)
  769. flip_barb : bool or array-like of bool, default: False
  770. Whether the lines and flags should point opposite to normal.
  771. Normal behavior is for the barbs and lines to point right (comes from wind
  772. barbs having these features point towards low pressure in the Northern
  773. Hemisphere).
  774. A single value is applied to all barbs. Individual barbs can be flipped by
  775. passing a bool array of the same size as *U* and *V*.
  776. Returns
  777. -------
  778. barbs : `~matplotlib.quiver.Barbs`
  779. Other Parameters
  780. ----------------
  781. **kwargs
  782. The barbs can further be customized using `.PolyCollection` keyword
  783. arguments:
  784. %(PolyCollection)s
  785. """ % docstring.interpd.params
  786. docstring.interpd.update(barbs_doc=_barbs_doc)
  787. class Barbs(mcollections.PolyCollection):
  788. '''
  789. Specialized PolyCollection for barbs.
  790. The only API method is :meth:`set_UVC`, which can be used to
  791. change the size, orientation, and color of the arrows. Locations
  792. are changed using the :meth:`set_offsets` collection method.
  793. Possibly this method will be useful in animations.
  794. There is one internal function :meth:`_find_tails` which finds
  795. exactly what should be put on the barb given the vector magnitude.
  796. From there :meth:`_make_barbs` is used to find the vertices of the
  797. polygon to represent the barb based on this information.
  798. '''
  799. # This may be an abuse of polygons here to render what is essentially maybe
  800. # 1 triangle and a series of lines. It works fine as far as I can tell
  801. # however.
  802. @docstring.interpd
  803. def __init__(self, ax, *args,
  804. pivot='tip', length=7, barbcolor=None, flagcolor=None,
  805. sizes=None, fill_empty=False, barb_increments=None,
  806. rounding=True, flip_barb=False, **kw):
  807. """
  808. The constructor takes one required argument, an Axes
  809. instance, followed by the args and kwargs described
  810. by the following pyplot interface documentation:
  811. %(barbs_doc)s
  812. """
  813. self.sizes = sizes or dict()
  814. self.fill_empty = fill_empty
  815. self.barb_increments = barb_increments or dict()
  816. self.rounding = rounding
  817. self.flip = np.atleast_1d(flip_barb)
  818. transform = kw.pop('transform', ax.transData)
  819. self._pivot = pivot
  820. self._length = length
  821. barbcolor = barbcolor
  822. flagcolor = flagcolor
  823. # Flagcolor and barbcolor provide convenience parameters for
  824. # setting the facecolor and edgecolor, respectively, of the barb
  825. # polygon. We also work here to make the flag the same color as the
  826. # rest of the barb by default
  827. if None in (barbcolor, flagcolor):
  828. kw['edgecolors'] = 'face'
  829. if flagcolor:
  830. kw['facecolors'] = flagcolor
  831. elif barbcolor:
  832. kw['facecolors'] = barbcolor
  833. else:
  834. # Set to facecolor passed in or default to black
  835. kw.setdefault('facecolors', 'k')
  836. else:
  837. kw['edgecolors'] = barbcolor
  838. kw['facecolors'] = flagcolor
  839. # Explicitly set a line width if we're not given one, otherwise
  840. # polygons are not outlined and we get no barbs
  841. if 'linewidth' not in kw and 'lw' not in kw:
  842. kw['linewidth'] = 1
  843. # Parse out the data arrays from the various configurations supported
  844. x, y, u, v, c = _parse_args(*args, caller_name='barbs()')
  845. self.x = x
  846. self.y = y
  847. xy = np.column_stack((x, y))
  848. # Make a collection
  849. barb_size = self._length ** 2 / 4 # Empirically determined
  850. mcollections.PolyCollection.__init__(self, [], (barb_size,),
  851. offsets=xy,
  852. transOffset=transform, **kw)
  853. self.set_transform(transforms.IdentityTransform())
  854. self.set_UVC(u, v, c)
  855. def _find_tails(self, mag, rounding=True, half=5, full=10, flag=50):
  856. '''
  857. Find how many of each of the tail pieces is necessary. Flag
  858. specifies the increment for a flag, barb for a full barb, and half for
  859. half a barb. Mag should be the magnitude of a vector (i.e., >= 0).
  860. This returns a tuple of:
  861. (*number of flags*, *number of barbs*, *half_flag*, *empty_flag*)
  862. *half_flag* is a boolean whether half of a barb is needed,
  863. since there should only ever be one half on a given
  864. barb. *empty_flag* flag is an array of flags to easily tell if
  865. a barb is empty (too low to plot any barbs/flags.
  866. '''
  867. # If rounding, round to the nearest multiple of half, the smallest
  868. # increment
  869. if rounding:
  870. mag = half * (mag / half + 0.5).astype(int)
  871. num_flags = np.floor(mag / flag).astype(int)
  872. mag = mag % flag
  873. num_barb = np.floor(mag / full).astype(int)
  874. mag = mag % full
  875. half_flag = mag >= half
  876. empty_flag = ~(half_flag | (num_flags > 0) | (num_barb > 0))
  877. return num_flags, num_barb, half_flag, empty_flag
  878. def _make_barbs(self, u, v, nflags, nbarbs, half_barb, empty_flag, length,
  879. pivot, sizes, fill_empty, flip):
  880. '''
  881. This function actually creates the wind barbs. *u* and *v*
  882. are components of the vector in the *x* and *y* directions,
  883. respectively.
  884. *nflags*, *nbarbs*, and *half_barb*, empty_flag* are,
  885. *respectively, the number of flags, number of barbs, flag for
  886. *half a barb, and flag for empty barb, ostensibly obtained
  887. *from :meth:`_find_tails`.
  888. *length* is the length of the barb staff in points.
  889. *pivot* specifies the point on the barb around which the
  890. entire barb should be rotated. Right now, valid options are
  891. 'tip' and 'middle'. Can also be a number, which shifts the start
  892. of the barb that many points from the origin.
  893. *sizes* is a dictionary of coefficients specifying the ratio
  894. of a given feature to the length of the barb. These features
  895. include:
  896. - *spacing*: space between features (flags, full/half
  897. barbs)
  898. - *height*: distance from shaft of top of a flag or full
  899. barb
  900. - *width* - width of a flag, twice the width of a full barb
  901. - *emptybarb* - radius of the circle used for low
  902. magnitudes
  903. *fill_empty* specifies whether the circle representing an
  904. empty barb should be filled or not (this changes the drawing
  905. of the polygon).
  906. *flip* is a flag indicating whether the features should be flipped to
  907. the other side of the barb (useful for winds in the southern
  908. hemisphere).
  909. This function returns list of arrays of vertices, defining a polygon
  910. for each of the wind barbs. These polygons have been rotated to
  911. properly align with the vector direction.
  912. '''
  913. # These control the spacing and size of barb elements relative to the
  914. # length of the shaft
  915. spacing = length * sizes.get('spacing', 0.125)
  916. full_height = length * sizes.get('height', 0.4)
  917. full_width = length * sizes.get('width', 0.25)
  918. empty_rad = length * sizes.get('emptybarb', 0.15)
  919. # Controls y point where to pivot the barb.
  920. pivot_points = dict(tip=0.0, middle=-length / 2.)
  921. endx = 0.0
  922. try:
  923. endy = float(pivot)
  924. except ValueError:
  925. endy = pivot_points[pivot.lower()]
  926. # Get the appropriate angle for the vector components. The offset is
  927. # due to the way the barb is initially drawn, going down the y-axis.
  928. # This makes sense in a meteorological mode of thinking since there 0
  929. # degrees corresponds to north (the y-axis traditionally)
  930. angles = -(ma.arctan2(v, u) + np.pi / 2)
  931. # Used for low magnitude. We just get the vertices, so if we make it
  932. # out here, it can be reused. The center set here should put the
  933. # center of the circle at the location(offset), rather than at the
  934. # same point as the barb pivot; this seems more sensible.
  935. circ = CirclePolygon((0, 0), radius=empty_rad).get_verts()
  936. if fill_empty:
  937. empty_barb = circ
  938. else:
  939. # If we don't want the empty one filled, we make a degenerate
  940. # polygon that wraps back over itself
  941. empty_barb = np.concatenate((circ, circ[::-1]))
  942. barb_list = []
  943. for index, angle in np.ndenumerate(angles):
  944. # If the vector magnitude is too weak to draw anything, plot an
  945. # empty circle instead
  946. if empty_flag[index]:
  947. # We can skip the transform since the circle has no preferred
  948. # orientation
  949. barb_list.append(empty_barb)
  950. continue
  951. poly_verts = [(endx, endy)]
  952. offset = length
  953. # Handle if this barb should be flipped
  954. barb_height = -full_height if flip[index] else full_height
  955. # Add vertices for each flag
  956. for i in range(nflags[index]):
  957. # The spacing that works for the barbs is a little to much for
  958. # the flags, but this only occurs when we have more than 1
  959. # flag.
  960. if offset != length:
  961. offset += spacing / 2.
  962. poly_verts.extend(
  963. [[endx, endy + offset],
  964. [endx + barb_height, endy - full_width / 2 + offset],
  965. [endx, endy - full_width + offset]])
  966. offset -= full_width + spacing
  967. # Add vertices for each barb. These really are lines, but works
  968. # great adding 3 vertices that basically pull the polygon out and
  969. # back down the line
  970. for i in range(nbarbs[index]):
  971. poly_verts.extend(
  972. [(endx, endy + offset),
  973. (endx + barb_height, endy + offset + full_width / 2),
  974. (endx, endy + offset)])
  975. offset -= spacing
  976. # Add the vertices for half a barb, if needed
  977. if half_barb[index]:
  978. # If the half barb is the first on the staff, traditionally it
  979. # is offset from the end to make it easy to distinguish from a
  980. # barb with a full one
  981. if offset == length:
  982. poly_verts.append((endx, endy + offset))
  983. offset -= 1.5 * spacing
  984. poly_verts.extend(
  985. [(endx, endy + offset),
  986. (endx + barb_height / 2, endy + offset + full_width / 4),
  987. (endx, endy + offset)])
  988. # Rotate the barb according the angle. Making the barb first and
  989. # then rotating it made the math for drawing the barb really easy.
  990. # Also, the transform framework makes doing the rotation simple.
  991. poly_verts = transforms.Affine2D().rotate(-angle).transform(
  992. poly_verts)
  993. barb_list.append(poly_verts)
  994. return barb_list
  995. def set_UVC(self, U, V, C=None):
  996. self.u = ma.masked_invalid(U, copy=False).ravel()
  997. self.v = ma.masked_invalid(V, copy=False).ravel()
  998. # Flip needs to have the same number of entries as everything else.
  999. # Use broadcast_to to avoid a bloated array of identical values.
  1000. # (can't rely on actual broadcasting)
  1001. if len(self.flip) == 1:
  1002. flip = np.broadcast_to(self.flip, self.u.shape)
  1003. else:
  1004. flip = self.flip
  1005. if C is not None:
  1006. c = ma.masked_invalid(C, copy=False).ravel()
  1007. x, y, u, v, c, flip = cbook.delete_masked_points(
  1008. self.x.ravel(), self.y.ravel(), self.u, self.v, c,
  1009. flip.ravel())
  1010. _check_consistent_shapes(x, y, u, v, c, flip)
  1011. else:
  1012. x, y, u, v, flip = cbook.delete_masked_points(
  1013. self.x.ravel(), self.y.ravel(), self.u, self.v, flip.ravel())
  1014. _check_consistent_shapes(x, y, u, v, flip)
  1015. magnitude = np.hypot(u, v)
  1016. flags, barbs, halves, empty = self._find_tails(magnitude,
  1017. self.rounding,
  1018. **self.barb_increments)
  1019. # Get the vertices for each of the barbs
  1020. plot_barbs = self._make_barbs(u, v, flags, barbs, halves, empty,
  1021. self._length, self._pivot, self.sizes,
  1022. self.fill_empty, flip)
  1023. self.set_verts(plot_barbs)
  1024. # Set the color array
  1025. if C is not None:
  1026. self.set_array(c)
  1027. # Update the offsets in case the masked data changed
  1028. xy = np.column_stack((x, y))
  1029. self._offsets = xy
  1030. self.stale = True
  1031. def set_offsets(self, xy):
  1032. """
  1033. Set the offsets for the barb polygons. This saves the offsets passed
  1034. in and masks them as appropriate for the existing U/V data.
  1035. Parameters
  1036. ----------
  1037. xy : sequence of pairs of floats
  1038. """
  1039. self.x = xy[:, 0]
  1040. self.y = xy[:, 1]
  1041. x, y, u, v = cbook.delete_masked_points(
  1042. self.x.ravel(), self.y.ravel(), self.u, self.v)
  1043. _check_consistent_shapes(x, y, u, v)
  1044. xy = np.column_stack((x, y))
  1045. mcollections.PolyCollection.set_offsets(self, xy)
  1046. self.stale = True
  1047. barbs_doc = _barbs_doc