def _compute_projection_pick(artist, path, xy):
"""Project *xy* on *path* to obtain a `Selection` for *artist*.
*path* is first transformed to screen coordinates using the artist
transform, and the target of the returned `Selection` is transformed
back to data coordinates using the artist *axes* inverse transform. The
`Selection` `index` is returned as a float. This function returns ``None``
for degenerate inputs.
The caller is responsible for converting the index to the proper class if
needed.
"""
transform = artist.get_transform().frozen()
tpath = (path.cleaned(transform) if transform.is_affine
# `cleaned` only handles affine transforms.
else transform.transform_path(path).cleaned())
# `cleaned` should return a path where the first element is `MOVETO`, the
# following are `LINETO` or `CLOSEPOLY`, and the last one is `STOP`, i.e.
# codes = path.codes
# assert (codes[0], codes[-1]) == (path.MOVETO, path.STOP)
# assert np.in1d(codes[1:-1], [path.LINETO, path.CLOSEPOLY]).all()
vertices = tpath.vertices[:-1]
codes = tpath.codes[:-1]
vertices[codes == tpath.CLOSEPOLY] = vertices[0]
# Unit vectors for each segment.
us = vertices[1:] - vertices[:-1]
ls = np.hypot(*us.T)
with np.errstate(invalid="ignore"):
# Results in 0/0 for repeated consecutive points.
us /= ls[:, None]
# Vectors from each vertex to the event (overwritten below).
vs = xy - vertices[:-1]
# Clipped dot products -- `einsum` cannot be done in place, `clip` can.
dot = np.clip(np.einsum("ij,ij->i", vs, us), 0, ls, out=vs[:, 0])
# Projections.
projs = vertices[:-1] + dot[:, None] * us
ds = np.hypot(*(xy - projs).T, out=vs[:, 1])
try:
argmin = np.nanargmin(ds)
dmin = ds[argmin]
except (ValueError, IndexError): # See above re: exceptions caught.
return
else:
target = AttrArray(
artist.axes.transData.inverted().transform_point(projs[argmin]))
target.index = (
(argmin + dot[argmin] / ls[argmin])
/ (path._interpolation_steps / tpath._interpolation_steps))
return Selection(artist, target, dmin, None, None)
评论列表
文章目录