7272
7373 * 'dots' or 'inches': pixels or inches, based on the figure dpi
7474
75- * 'x' or 'y ': *X* or *Y* data units
75+ * 'x', 'y', or 'xy ': *X*, *Y*, or sqrt(X^2+Y^2) data units
7676
7777 The arrows scale differently depending on the units. For
7878 'x' or 'y', the arrows get larger as one zooms in; for other
8181 height of the axes, respectively, when the the window is resized;
8282 for 'dots' or 'inches', resizing does not change the arrows.
8383
84+
8485 *angles*: ['uv' | 'xy' | array]
8586 With the default 'uv', the arrow aspect ratio is 1, so that
8687 if *U*==*V* the angle of the arrow on the plot is 45 degrees
9091 of values in degrees, CCW from the *x*-axis.
9192
9293 *scale*: [ None | float ]
93- data units per arrow unit, e.g. m/s per plot width; a smaller
94+ data units per arrow length unit, e.g. m/s per plot width; a smaller
9495 scale parameter makes the arrow longer. If *None*, a simple
9596 autoscaling algorithm is used, based on the average vector length
96- and the number of vectors.
97+ and the number of vectors. The arrow length unit is given by
98+ the *scale_units* parameter
99+
100+ *scale_units*: None, or any of the *units* options. For example,
101+ if *scale_units* is 'inches', *scale* is 2.0, and (u,v) = (1,0),
102+ then the vector will be 0.5 inches long. If *scale_units* is
103+ 'width', then the vector will be half the width of the axes.
104+ If *scale_units* is 'x' then the vector will be 0.5 x-axis
105+ units. To plot vectors in the x-y plane, with u and v having
106+ the same units as x and y, use
107+ "angles='xy', scale_units='xy', scale=1".
97108
98109 *width*:
99110 shaft width in arrow units; default depends on choice of units,
@@ -390,6 +401,7 @@ def __init__(self, ax, *args, **kw):
390401 self .minshaft = kw .pop ('minshaft' , 1 )
391402 self .minlength = kw .pop ('minlength' , 1 )
392403 self .units = kw .pop ('units' , 'width' )
404+ self .scale_units = kw .pop ('scale_units' , None )
393405 self .angles = kw .pop ('angles' , 'uv' )
394406 self .width = kw .pop ('width' , None )
395407 self .color = kw .pop ('color' , 'k' )
@@ -418,7 +430,8 @@ def on_dpi_change(fig):
418430
419431
420432 def _init (self ):
421- """initialization delayed until first draw;
433+ """
434+ Initialization delayed until first draw;
422435 allow time for axes setup.
423436 """
424437 # It seems that there are not enough event notifications
@@ -429,14 +442,15 @@ def _init(self):
429442 sx , sy = trans .inverted ().transform_point (
430443 (ax .bbox .width , ax .bbox .height ))
431444 self .span = sx
432- sn = max (8 , min (25 , math .sqrt (self .N )))
433445 if self .width is None :
446+ sn = max (8 , min (25 , math .sqrt (self .N )))
434447 self .width = 0.06 * self .span / sn
435448
436449 @allow_rasterization
437450 def draw (self , renderer ):
438451 self ._init ()
439- if self ._new_UV or self .angles == 'xy' :
452+ if (self ._new_UV or self .angles == 'xy'
453+ or self .scale_units in ['x' ,'y' , 'xy' ]):
440454 verts = self ._make_verts (self .U , self .V )
441455 self .set_verts (verts , closed = False )
442456 self ._new_UV = False
@@ -460,27 +474,46 @@ def set_UVC(self, U, V, C=None):
460474 self .set_array (C )
461475 self ._new_UV = True
462476
463- def _set_transform (self ):
477+ def _dots_per_unit (self , units ):
478+ """
479+ Return a scale factor for converting from units to pixels
480+ """
464481 ax = self .ax
465- if self . units in ('x' , 'y' ):
466- if self . units == 'x' :
482+ if units in ('x' , 'y' , 'xy ' ):
483+ if units == 'x' :
467484 dx0 = ax .viewLim .width
468485 dx1 = ax .bbox .width
469- else :
486+ elif units == 'y' :
470487 dx0 = ax .viewLim .height
471488 dx1 = ax .bbox .height
489+ else : # 'xy' is assumed
490+ dxx0 = ax .viewLim .width
491+ dxx1 = ax .bbox .width
492+ dyy0 = ax .viewLim .height
493+ dyy1 = ax .bbox .height
494+ dx1 = np .sqrt (dxx1 * dxx1 + dyy1 * dyy1 )
495+ dx0 = np .sqrt (dxx0 * dxx0 + dyy0 * dyy0 )
472496 dx = dx1 / dx0
473497 else :
474- if self . units == 'width' :
498+ if units == 'width' :
475499 dx = ax .bbox .width
476- elif self . units == 'height' :
500+ elif units == 'height' :
477501 dx = ax .bbox .height
478- elif self . units == 'dots' :
502+ elif units == 'dots' :
479503 dx = 1.0
480- elif self . units == 'inches' :
504+ elif units == 'inches' :
481505 dx = ax .figure .dpi
482506 else :
483507 raise ValueError ('unrecognized units' )
508+ return dx
509+
510+ def _set_transform (self ):
511+ """
512+ Sets the PolygonCollection transform to go
513+ from arrow width units to pixels.
514+ """
515+ dx = self ._dots_per_unit (self .units )
516+ self ._trans_scale = dx # pixels per arrow width unit
484517 trans = transforms .Affine2D ().scale (dx )
485518 self .set_transform (trans )
486519 return trans
@@ -503,8 +536,18 @@ def _make_verts(self, U, V):
503536 else :
504537 amean = a .mean ()
505538 scale = 1.8 * amean * sn / self .span # crude auto-scaling
506- self .scale = scale
507- length = a / (self .scale * self .width )
539+ # scale is typical arrow length as a multiple
540+ # of the arrow width
541+ if self .scale_units is None :
542+ if self .scale is None :
543+ self .scale = scale
544+ widthu_per_lenu = 1.0
545+ else :
546+ dx = self ._dots_per_unit (self .scale_units )
547+ widthu_per_lenu = dx / self ._trans_scale
548+ if self .scale is None :
549+ self .scale = scale * widthu_per_lenu
550+ length = a * (widthu_per_lenu / (self .scale * self .width ))
508551 X , Y = self ._h_arrows (length )
509552 if self .angles == 'xy' :
510553 theta = self ._angles (U , V )
0 commit comments