Skip to content

API : tighten validation on pivot in Quiver #3955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 30, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/api/api_changes/2014-12-28_quiver.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Tighted input validation on 'pivot' kwarg to quiver
```````````````````````````````````````````````````

Tightened validation so that only {'tip', 'tail', 'mid', and 'middle'}
(but any capitalization) are valid values for the 'pivot' kwarg in
the `Quiver.__init__` (and hence `Axes.quiver` and
`plt.quiver` which both fully delegate to `Quiver`). Previously any
input matching 'mid.*' would be interpreted as 'middle', 'tip.*' as
'tip' and any string not matching one of those patterns as 'tail'.

The value of `Quiver.pivot` is normalized to be in the set {'tip',
'tail', 'middle'} in `Quiver.__init__`.
78 changes: 47 additions & 31 deletions lib/matplotlib/quiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
is less than this, plot a dot (hexagon) of this diameter instead.
Default is 1.

*pivot*: [ 'tail' | 'middle' | 'tip' ]
*pivot*: [ 'tail' | 'mid' | 'middle' | 'tip' ]
The part of the arrow that is at the grid point; the arrow rotates
about this point, hence the name *pivot*.

Expand Down Expand Up @@ -247,8 +247,9 @@ def on_dpi_change(fig):
if self_weakref is not None:
self_weakref.labelsep = (self_weakref._labelsep_inches*fig.dpi)
self_weakref._initialized = False # simple brute force update
# works because _init is called
# at the start of draw.
# works because _init is
# called at the start of
# draw.

self._cid = Q.ax.figure.callbacks.connect('dpi_changed',
on_dpi_change)
Expand All @@ -258,7 +259,7 @@ def on_dpi_change(fig):
self.fontproperties = kw.pop('fontproperties', dict())
self.kw = kw
_fp = self.fontproperties
#boxprops = dict(facecolor='red')
# boxprops = dict(facecolor='red')
self.text = mtext.Text(
text=label, # bbox=boxprops,
horizontalalignment=self.halign[self.labelpos],
Expand Down Expand Up @@ -405,6 +406,8 @@ class Quiver(mcollections.PolyCollection):
in the draw() method.
"""

_PIVOT_VALS = ('tail', 'mid', 'middle', 'tip')

@docstring.Substitution(_quiver_doc)
def __init__(self, ax, *args, **kw):
"""
Expand All @@ -430,7 +433,18 @@ def __init__(self, ax, *args, **kw):
self.angles = kw.pop('angles', 'uv')
self.width = kw.pop('width', None)
self.color = kw.pop('color', 'k')
self.pivot = kw.pop('pivot', 'tail')

pivot = kw.pop('pivot', 'tail').lower()
# validate pivot
if pivot not in self._PIVOT_VALS:
raise ValueError(
'pivot must be one of {keys}, you passed {inp}'.format(
keys=self._PIVOT_VALS, inp=pivot))
# normalize to 'middle'
if pivot == 'mid':
pivot = 'middle'
self.pivot = pivot

self.transform = kw.pop('transform', ax.transData)
kw.setdefault('facecolors', self.color)
kw.setdefault('linewidths', (0,))
Expand All @@ -452,10 +466,11 @@ def on_dpi_change(fig):
self_weakref = weak_self()
if self_weakref is not None:
self_weakref._new_UV = True # vertices depend on width, span
# which in turn depend on dpi
# which in turn depend on dpi
self_weakref._initialized = False # simple brute force update
# works because _init is called
# at the start of draw.
# works because _init is
# called at the start of
# draw.

self._cid = self.ax.figure.callbacks.connect('dpi_changed',
on_dpi_change)
Expand Down Expand Up @@ -654,7 +669,7 @@ def _h_arrows(self, length):
length = length.reshape(N, 1)
# This number is chosen based on when pixel values overflow in Agg
# causing rendering errors
#length = np.minimum(length, 2 ** 16)
# length = np.minimum(length, 2 ** 16)
np.clip(length, 0, 2 ** 16, out=length)
# x, y: normal horizontal arrow
x = np.array([0, -self.headaxislength,
Expand All @@ -681,9 +696,9 @@ def _h_arrows(self, length):
# Now select X0, Y0 if short, otherwise X, Y
cbook._putmask(X, short, X0)
cbook._putmask(Y, short, Y0)
if self.pivot[:3] == 'mid':
if self.pivot == 'middle':
X -= 0.5 * X[:, 3, np.newaxis]
elif self.pivot[:3] == 'tip':
elif self.pivot == 'tip':
X = X - X[:, 3, np.newaxis] # numpy bug? using -= does not
# work here unless we multiply
# by a float first, as with 'mid'.
Expand Down Expand Up @@ -857,9 +872,9 @@ class Barbs(mcollections.PolyCollection):
From there :meth:`_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.
# 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.
@docstring.interpd
def __init__(self, ax, *args, **kw):
"""
Expand All @@ -879,30 +894,31 @@ def __init__(self, ax, *args, **kw):
self.flip = kw.pop('flip_barb', False)
transform = kw.pop('transform', ax.transData)

#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
# 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
# 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
# Parse out the data arrays from the various configurations supported
x, y, u, v, c = _parse_args(*args)
self.x = x
self.y = y
xy = np.hstack((x[:, np.newaxis], y[:, np.newaxis]))

#Make a collection
# Make a collection
barb_size = self._length ** 2 / 4 # Empirically determined
mcollections.PolyCollection.__init__(self, [], (barb_size,),
offsets=xy,
Expand All @@ -927,8 +943,8 @@ def _find_tails(self, mag, rounding=True, half=5, full=10, flag=50):
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, round to the nearest multiple of half, the smallest
# increment
if rounding:
mag = half * (mag / half + 0.5).astype(np.int)

Expand Down Expand Up @@ -989,17 +1005,17 @@ def _make_barbs(self, u, v, nflags, nbarbs, half_barb, empty_flag, length,
properly align with the vector direction.
'''

#These control the spacing and size of barb elements relative to the
#length of the shaft
# 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.
# Controls y point where to pivot the barb.
pivot_points = dict(tip=0.0, middle=-length / 2.)

#Check for flip
# Check for flip
if flip:
full_height = -full_height

Expand All @@ -1026,11 +1042,11 @@ def _make_barbs(self, u, v, nflags, nbarbs, half_barb, empty_flag, length,

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 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
# We can skip the transform since the circle has no preferred
# orientation
barb_list.append(empty_barb)
continue

Expand Down